From d2130ff3697f93772339fc9f03463746ba934fad Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 10 Apr 2026 06:04:33 -0400 Subject: [PATCH 01/34] Fix CallToolResult handling across all SDKs (#1049) * Fix CallToolResult handling across all SDKs When a tool handler returns an MCP CallToolResult object ({ content: [...], isError?: bool }), all four SDKs were JSON-serializing it instead of converting it to ToolResultObject. This caused the LLM to see raw JSON instead of actual tool output. Add detection and conversion of CallToolResult in Node.js, Python, Go, and .NET. The .NET SDK additionally handles Microsoft.Extensions.AI content types (TextContent, DataContent, and unknown subtypes via AIJsonUtilities serialization). Fixes #937 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix formatting and lint issues Run prettier on Node.js files, ruff format on Python files, and remove unused ToolResultObject import from test file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unused _convert_call_tool_result import Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review feedback: add type guards in Python, fix Go comment typo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python import sorting in test_tools_unit.py Sort imports in copilot.tools import block to satisfy ruff I001 rule. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove isCallToolResult and convertCallToolResult from public exports These are internal implementation details used by session.ts and client.ts. Go and Python already keep them private (lowercase/underscore-prefixed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * TypeScript formatting Co-Authored-By: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> * Address review feedback: explicit MCP conversion, shared .NET helper, consistent guards - Remove implicit duck-typing of MCP CallToolResult from all SDKs - Add explicit public conversion: convertMcpCallToolResult (TS), ConvertMCPCallToolResult (Go), convert_mcp_call_tool_result (Python) - Extract shared ConvertFromInvocationResult helper in .NET - Remove isCallToolResult type guard (TS) and _is_call_tool_result (Python) - Rename types/functions to include 'Mcp' prefix across all languages - Make McpCallToolResult type non-exported in TS (structural typing) - Skip image blocks with empty data consistently across TS/Go/Python - Update all tests to use explicit conversion functions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI: prettier formatting and ruff import sorting - Fix prettier line-length violation in nodejs/src/types.ts (long if condition) - Fix ruff I001 import sorting in python/e2e/test_tools_unit.py (_normalize_result before convert_mcp_call_tool_result) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Steve Sanderson Co-authored-by: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com> --- dotnet/src/Client.cs | 8 +- dotnet/src/Session.cs | 8 +- dotnet/src/Types.cs | 89 +++++++++++++ go/definetool.go | 102 ++++++++++++++- go/definetool_test.go | 180 +++++++++++++++++++++++++++ nodejs/src/index.ts | 7 +- nodejs/src/types.ts | 95 ++++++++++++++ nodejs/test/call-tool-result.test.ts | 161 ++++++++++++++++++++++++ python/copilot/__init__.py | 3 +- python/copilot/tools.py | 51 ++++++++ python/e2e/test_tools_unit.py | 103 ++++++++++++++- 11 files changed, 789 insertions(+), 18 deletions(-) create mode 100644 nodejs/test/call-tool-result.test.ts diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index aad44e4eb..cbceeede2 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1549,13 +1549,7 @@ public async Task OnToolCallV2(string sessionId, var result = await tool.InvokeAsync(aiFunctionArgs); - var toolResultObject = result is ToolResultAIContent trac ? trac.Result : new ToolResultObject - { - ResultType = "success", - TextResultForLlm = result is JsonElement { ValueKind: JsonValueKind.String } je - ? je.GetString()! - : JsonSerializer.Serialize(result, tool.JsonSerializerOptions.GetTypeInfo(typeof(object))), - }; + var toolResultObject = ToolResultObject.ConvertFromInvocationResult(result, tool.JsonSerializerOptions); return new ToolCallResponseV2(toolResultObject); } catch (Exception ex) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 4e5142cb8..189cdfaff 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -568,13 +568,7 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName, var result = await tool.InvokeAsync(aiFunctionArgs); - var toolResultObject = result is ToolResultAIContent trac ? trac.Result : new ToolResultObject - { - ResultType = "success", - TextResultForLlm = result is JsonElement { ValueKind: JsonValueKind.String } je - ? je.GetString()! - : JsonSerializer.Serialize(result, tool.JsonSerializerOptions.GetTypeInfo(typeof(object))), - }; + var toolResultObject = ToolResultObject.ConvertFromInvocationResult(result, tool.JsonSerializerOptions); await Rpc.Tools.HandlePendingToolCallAsync(requestId, toolResultObject, error: null); } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index d8262e140..1645b1eb0 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -324,6 +324,95 @@ public class ToolResultObject /// [JsonPropertyName("toolTelemetry")] public Dictionary? ToolTelemetry { get; set; } + + /// + /// Converts the result of an invocation into a + /// . Handles , + /// , and falls back to JSON serialization. + /// + internal static ToolResultObject ConvertFromInvocationResult(object? result, JsonSerializerOptions jsonOptions) + { + if (result is ToolResultAIContent trac) + { + return trac.Result; + } + + if (TryConvertFromAIContent(result) is { } aiConverted) + { + return aiConverted; + } + + return new ToolResultObject + { + ResultType = "success", + TextResultForLlm = result is JsonElement { ValueKind: JsonValueKind.String } je + ? je.GetString()! + : JsonSerializer.Serialize(result, jsonOptions.GetTypeInfo(typeof(object))), + }; + } + + /// + /// Attempts to convert a result from an invocation into a + /// . Handles , + /// , and collections of . + /// Returns if the value is not a recognized type. + /// + internal static ToolResultObject? TryConvertFromAIContent(object? result) + { + if (result is AIContent singleContent) + { + return ConvertAIContents([singleContent]); + } + + if (result is IEnumerable contentList) + { + return ConvertAIContents(contentList); + } + + return null; + } + + private static ToolResultObject ConvertAIContents(IEnumerable contents) + { + List? textParts = null; + List? binaryResults = null; + + foreach (var content in contents) + { + switch (content) + { + case TextContent textContent: + if (textContent.Text is { } text) + { + (textParts ??= []).Add(text); + } + break; + + case DataContent dataContent: + (binaryResults ??= []).Add(new ToolBinaryResult + { + Data = dataContent.Base64Data.ToString(), + MimeType = dataContent.MediaType ?? "application/octet-stream", + Type = dataContent.HasTopLevelMediaType("image") ? "image" : "resource", + }); + break; + + default: + (textParts ??= []).Add(SerializeAIContent(content)); + break; + } + } + + return new ToolResultObject + { + TextResultForLlm = textParts is not null ? string.Join("\n", textParts) : "", + ResultType = "success", + BinaryResultsForLlm = binaryResults, + }; + } + + private static string SerializeAIContent(AIContent content) => + JsonSerializer.Serialize(content, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AIContent))); } /// diff --git a/go/definetool.go b/go/definetool.go index 406a8c0b8..ccaa69a58 100644 --- a/go/definetool.go +++ b/go/definetool.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "github.com/google/jsonschema-go/jsonschema" ) @@ -65,7 +66,8 @@ func createTypedHandler[T any, U any](handler func(T, ToolInvocation) (U, error) } // normalizeResult converts any value to a ToolResult. -// Strings pass through directly, ToolResult passes through, other types are JSON-serialized. +// Strings pass through directly, ToolResult passes through, and other types +// are JSON-serialized. func normalizeResult(result any) (ToolResult, error) { if result == nil { return ToolResult{ @@ -99,6 +101,104 @@ func normalizeResult(result any) (ToolResult, error) { }, nil } +// ConvertMCPCallToolResult converts an MCP CallToolResult value (a map or struct +// with a "content" array and optional "isError" bool) into a ToolResult. +// Returns the converted ToolResult and true if the value matched the expected +// shape, or a zero ToolResult and false otherwise. +func ConvertMCPCallToolResult(value any) (ToolResult, bool) { + m, ok := value.(map[string]any) + if !ok { + jsonBytes, err := json.Marshal(value) + if err != nil { + return ToolResult{}, false + } + + if err := json.Unmarshal(jsonBytes, &m); err != nil { + return ToolResult{}, false + } + } + + contentRaw, exists := m["content"] + if !exists { + return ToolResult{}, false + } + + contentSlice, ok := contentRaw.([]any) + if !ok { + return ToolResult{}, false + } + + // Verify every element has a string "type" field + for _, item := range contentSlice { + block, ok := item.(map[string]any) + if !ok { + return ToolResult{}, false + } + if _, ok := block["type"].(string); !ok { + return ToolResult{}, false + } + } + + var textParts []string + var binaryResults []ToolBinaryResult + + for _, item := range contentSlice { + block := item.(map[string]any) + blockType := block["type"].(string) + + switch blockType { + case "text": + if text, ok := block["text"].(string); ok { + textParts = append(textParts, text) + } + case "image": + data, _ := block["data"].(string) + mimeType, _ := block["mimeType"].(string) + if data == "" { + continue + } + binaryResults = append(binaryResults, ToolBinaryResult{ + Data: data, + MimeType: mimeType, + Type: "image", + }) + case "resource": + if resRaw, ok := block["resource"].(map[string]any); ok { + if text, ok := resRaw["text"].(string); ok && text != "" { + textParts = append(textParts, text) + } + if blob, ok := resRaw["blob"].(string); ok && blob != "" { + mimeType, _ := resRaw["mimeType"].(string) + if mimeType == "" { + mimeType = "application/octet-stream" + } + uri, _ := resRaw["uri"].(string) + binaryResults = append(binaryResults, ToolBinaryResult{ + Data: blob, + MimeType: mimeType, + Type: "resource", + Description: uri, + }) + } + } + } + } + + resultType := "success" + if isErr, ok := m["isError"].(bool); ok && isErr { + resultType = "failure" + } + + tr := ToolResult{ + TextResultForLLM: strings.Join(textParts, "\n"), + ResultType: resultType, + } + if len(binaryResults) > 0 { + tr.BinaryResultsForLLM = binaryResults + } + return tr, true +} + // generateSchemaForType generates a JSON schema map from a Go type using reflection. // Panics if schema generation fails, as this indicates a programming error. func generateSchemaForType(t reflect.Type) map[string]any { diff --git a/go/definetool_test.go b/go/definetool_test.go index af620b180..cc9fecb2c 100644 --- a/go/definetool_test.go +++ b/go/definetool_test.go @@ -253,6 +253,186 @@ func TestNormalizeResult(t *testing.T) { }) } +func TestConvertMCPCallToolResult(t *testing.T) { + t.Run("typed CallToolResult struct is converted", func(t *testing.T) { + type Resource struct { + URI string `json:"uri"` + Text string `json:"text"` + } + type ContentBlock struct { + Type string `json:"type"` + Resource *Resource `json:"resource,omitempty"` + } + type CallToolResult struct { + Content []ContentBlock `json:"content"` + } + + input := CallToolResult{ + Content: []ContentBlock{ + { + Type: "resource", + Resource: &Resource{URI: "file:///report.txt", Text: "details"}, + }, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.TextResultForLLM != "details" { + t.Errorf("Expected 'details', got %q", result.TextResultForLLM) + } + if result.ResultType != "success" { + t.Errorf("Expected 'success', got %q", result.ResultType) + } + }) + + t.Run("text-only CallToolResult is converted", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{"type": "text", "text": "hello"}, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.TextResultForLLM != "hello" { + t.Errorf("Expected 'hello', got %q", result.TextResultForLLM) + } + if result.ResultType != "success" { + t.Errorf("Expected 'success', got %q", result.ResultType) + } + }) + + t.Run("multiple text blocks are joined with newline", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{"type": "text", "text": "line 1"}, + map[string]any{"type": "text", "text": "line 2"}, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.TextResultForLLM != "line 1\nline 2" { + t.Errorf("Expected 'line 1\\nline 2', got %q", result.TextResultForLLM) + } + }) + + t.Run("isError maps to failure resultType", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{"type": "text", "text": "oops"}, + }, + "isError": true, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.ResultType != "failure" { + t.Errorf("Expected 'failure', got %q", result.ResultType) + } + }) + + t.Run("image content becomes binaryResultsForLLM", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{"type": "image", "data": "base64data", "mimeType": "image/png"}, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if len(result.BinaryResultsForLLM) != 1 { + t.Fatalf("Expected 1 binary result, got %d", len(result.BinaryResultsForLLM)) + } + if result.BinaryResultsForLLM[0].Data != "base64data" { + t.Errorf("Expected data 'base64data', got %q", result.BinaryResultsForLLM[0].Data) + } + if result.BinaryResultsForLLM[0].MimeType != "image/png" { + t.Errorf("Expected mimeType 'image/png', got %q", result.BinaryResultsForLLM[0].MimeType) + } + }) + + t.Run("resource text goes to textResultForLLM", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{ + "type": "resource", + "resource": map[string]any{"uri": "file:///tmp/data.txt", "text": "file contents"}, + }, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.TextResultForLLM != "file contents" { + t.Errorf("Expected 'file contents', got %q", result.TextResultForLLM) + } + }) + + t.Run("resource blob goes to binaryResultsForLLM", func(t *testing.T) { + input := map[string]any{ + "content": []any{ + map[string]any{ + "type": "resource", + "resource": map[string]any{"uri": "file:///img.png", "blob": "blobdata", "mimeType": "image/png"}, + }, + }, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if len(result.BinaryResultsForLLM) != 1 { + t.Fatalf("Expected 1 binary result, got %d", len(result.BinaryResultsForLLM)) + } + if result.BinaryResultsForLLM[0].Description != "file:///img.png" { + t.Errorf("Expected description 'file:///img.png', got %q", result.BinaryResultsForLLM[0].Description) + } + }) + + t.Run("non-CallToolResult map returns false", func(t *testing.T) { + input := map[string]any{ + "key": "value", + } + + _, ok := ConvertMCPCallToolResult(input) + if ok { + t.Error("Expected ConvertMCPCallToolResult to return false for non-CallToolResult map") + } + }) + + t.Run("empty content array is converted", func(t *testing.T) { + input := map[string]any{ + "content": []any{}, + } + + result, ok := ConvertMCPCallToolResult(input) + if !ok { + t.Fatal("Expected ConvertMCPCallToolResult to succeed") + } + if result.TextResultForLLM != "" { + t.Errorf("Expected empty text, got %q", result.TextResultForLLM) + } + if result.ResultType != "success" { + t.Errorf("Expected 'success', got %q", result.ResultType) + } + }) +} + func TestGenerateSchemaForType(t *testing.T) { t.Run("generates schema for simple struct", func(t *testing.T) { type Simple struct { diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 3fab122db..13e0670fb 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -10,7 +10,12 @@ export { CopilotClient } from "./client.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; -export { defineTool, approveAll, SYSTEM_PROMPT_SECTIONS } from "./types.js"; +export { + defineTool, + approveAll, + convertMcpCallToolResult, + SYSTEM_PROMPT_SECTIONS, +} from "./types.js"; export type { CommandContext, CommandDefinition, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index c2d095234..c8a27009d 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -207,6 +207,101 @@ export type ToolResultObject = { export type ToolResult = string | ToolResultObject; +// ============================================================================ +// MCP CallToolResult support +// ============================================================================ + +/** + * Content block types within an MCP CallToolResult. + */ +type McpCallToolResultTextContent = { + type: "text"; + text: string; +}; + +type McpCallToolResultImageContent = { + type: "image"; + data: string; + mimeType: string; +}; + +type McpCallToolResultResourceContent = { + type: "resource"; + resource: { + uri: string; + mimeType?: string; + text?: string; + blob?: string; + }; +}; + +type McpCallToolResultContent = + | McpCallToolResultTextContent + | McpCallToolResultImageContent + | McpCallToolResultResourceContent; + +/** + * MCP-compatible CallToolResult type. Can be passed to + * {@link convertMcpCallToolResult} to produce a {@link ToolResultObject}. + */ +type McpCallToolResult = { + content: McpCallToolResultContent[]; + isError?: boolean; +}; + +/** + * Converts an MCP CallToolResult into the SDK's ToolResultObject format. + */ +export function convertMcpCallToolResult(callResult: McpCallToolResult): ToolResultObject { + const textParts: string[] = []; + const binaryResults: ToolBinaryResult[] = []; + + for (const block of callResult.content) { + switch (block.type) { + case "text": + // Guard against malformed input where text field is missing at runtime + if (typeof block.text === "string") { + textParts.push(block.text); + } + break; + case "image": + if ( + typeof block.data === "string" && + block.data && + typeof block.mimeType === "string" + ) { + binaryResults.push({ + data: block.data, + mimeType: block.mimeType, + type: "image", + }); + } + break; + case "resource": { + // Use optional chaining: resource field may be absent in malformed input + if (block.resource?.text) { + textParts.push(block.resource.text); + } + if (block.resource?.blob) { + binaryResults.push({ + data: block.resource.blob, + mimeType: block.resource.mimeType ?? "application/octet-stream", + type: "resource", + description: block.resource.uri, + }); + } + break; + } + } + } + + return { + textResultForLlm: textParts.join("\n"), + resultType: callResult.isError ? "failure" : "success", + ...(binaryResults.length > 0 ? { binaryResultsForLlm: binaryResults } : {}), + }; +} + export interface ToolInvocation { sessionId: string; toolCallId: string; diff --git a/nodejs/test/call-tool-result.test.ts b/nodejs/test/call-tool-result.test.ts new file mode 100644 index 000000000..132e482bd --- /dev/null +++ b/nodejs/test/call-tool-result.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, it } from "vitest"; +import { convertMcpCallToolResult } from "../src/types.js"; + +type McpCallToolResult = Parameters[0]; + +describe("convertMcpCallToolResult", () => { + it("extracts text from text content blocks", () => { + const input: McpCallToolResult = { + content: [ + { type: "text", text: "line 1" }, + { type: "text", text: "line 2" }, + ], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe("line 1\nline 2"); + expect(result.resultType).toBe("success"); + expect(result.binaryResultsForLlm).toBeUndefined(); + }); + + it("maps isError to failure resultType", () => { + const input: McpCallToolResult = { + content: [{ type: "text", text: "error occurred" }], + isError: true, + }; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe("error occurred"); + expect(result.resultType).toBe("failure"); + }); + + it("maps isError: false to success", () => { + const input: McpCallToolResult = { + content: [{ type: "text", text: "ok" }], + isError: false, + }; + + expect(convertMcpCallToolResult(input).resultType).toBe("success"); + }); + + it("converts image content to binaryResultsForLlm", () => { + const input: McpCallToolResult = { + content: [{ type: "image", data: "base64data", mimeType: "image/png" }], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe(""); + expect(result.binaryResultsForLlm).toHaveLength(1); + expect(result.binaryResultsForLlm![0]).toEqual({ + data: "base64data", + mimeType: "image/png", + type: "image", + }); + }); + + it("converts resource with text to textResultForLlm", () => { + const input: McpCallToolResult = { + content: [ + { + type: "resource", + resource: { uri: "file:///tmp/data.txt", text: "file contents" }, + }, + ], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe("file contents"); + }); + + it("converts resource with blob to binaryResultsForLlm", () => { + const input: McpCallToolResult = { + content: [ + { + type: "resource", + resource: { + uri: "file:///tmp/image.png", + mimeType: "image/png", + blob: "blobdata", + }, + }, + ], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.binaryResultsForLlm).toHaveLength(1); + expect(result.binaryResultsForLlm![0]).toEqual({ + data: "blobdata", + mimeType: "image/png", + type: "resource", + description: "file:///tmp/image.png", + }); + }); + + it("handles mixed content types", () => { + const input: McpCallToolResult = { + content: [ + { type: "text", text: "Analysis complete" }, + { type: "image", data: "chartdata", mimeType: "image/svg+xml" }, + { + type: "resource", + resource: { uri: "file:///report.txt", text: "Report details" }, + }, + ], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe("Analysis complete\nReport details"); + expect(result.binaryResultsForLlm).toHaveLength(1); + expect(result.binaryResultsForLlm![0]!.mimeType).toBe("image/svg+xml"); + }); + + it("handles empty content array", () => { + const result = convertMcpCallToolResult({ content: [] }); + + expect(result.textResultForLlm).toBe(""); + expect(result.resultType).toBe("success"); + expect(result.binaryResultsForLlm).toBeUndefined(); + }); + + it("defaults resource blob mimeType to application/octet-stream", () => { + const input: McpCallToolResult = { + content: [ + { + type: "resource", + resource: { uri: "file:///data.bin", blob: "binarydata" }, + }, + ], + }; + + const result = convertMcpCallToolResult(input); + + expect(result.binaryResultsForLlm![0]!.mimeType).toBe("application/octet-stream"); + }); + + it("handles text block with missing text field without corrupting output", () => { + // The input type uses structural typing, so type-specific fields might be absent + // at runtime. convertMcpCallToolResult must be defensive. + const input = { content: [{ type: "text" }] } as unknown as McpCallToolResult; + + const result = convertMcpCallToolResult(input); + + expect(result.textResultForLlm).toBe(""); + expect(result.textResultForLlm).not.toBe("undefined"); + }); + + it("handles resource block with missing resource field without crashing", () => { + // A resource content item missing the resource field would crash with an + // unguarded block.resource.text access. Optional chaining must be used. + const input = { content: [{ type: "resource" }] } as unknown as McpCallToolResult; + + expect(() => convertMcpCallToolResult(input)).not.toThrow(); + const result = convertMcpCallToolResult(input); + expect(result.textResultForLlm).toBe(""); + }); +}); diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 702d35035..6333aea51 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -29,7 +29,7 @@ SessionUiApi, SessionUiCapabilities, ) -from .tools import define_tool +from .tools import convert_mcp_call_tool_result, define_tool __version__ = "0.1.0" @@ -55,5 +55,6 @@ "SessionUiApi", "SessionUiCapabilities", "SubprocessConfig", + "convert_mcp_call_tool_result", "define_tool", ] diff --git a/python/copilot/tools.py b/python/copilot/tools.py index 66c660536..c94c396e9 100644 --- a/python/copilot/tools.py +++ b/python/copilot/tools.py @@ -274,3 +274,54 @@ def default(obj: Any) -> Any: text_result_for_llm=json_str, result_type="success", ) + + +def convert_mcp_call_tool_result(call_result: dict[str, Any]) -> ToolResult: + """Convert an MCP CallToolResult dict into a ToolResult.""" + text_parts: list[str] = [] + binary_results: list[ToolBinaryResult] = [] + + for block in call_result["content"]: + block_type = block.get("type") + if block_type == "text": + text = block.get("text", "") + if isinstance(text, str): + text_parts.append(text) + elif block_type == "image": + data = block.get("data", "") + mime_type = block.get("mimeType", "") + if isinstance(data, str) and data and isinstance(mime_type, str): + binary_results.append( + ToolBinaryResult( + data=data, + mime_type=mime_type, + type="image", + ) + ) + elif block_type == "resource": + resource = block.get("resource", {}) + if not isinstance(resource, dict): + continue + text = resource.get("text") + if isinstance(text, str) and text: + text_parts.append(text) + blob = resource.get("blob") + if isinstance(blob, str) and blob: + mime_type = resource.get("mimeType", "application/octet-stream") + uri = resource.get("uri", "") + binary_results.append( + ToolBinaryResult( + data=blob, + mime_type=mime_type + if isinstance(mime_type, str) + else "application/octet-stream", + type="resource", + description=uri if isinstance(uri, str) else "", + ) + ) + + return ToolResult( + text_result_for_llm="\n".join(text_parts), + result_type="failure" if call_result.get("isError") is True else "success", + binary_results_for_llm=binary_results if binary_results else None, + ) diff --git a/python/e2e/test_tools_unit.py b/python/e2e/test_tools_unit.py index c9c996f0e..bbbe2190f 100644 --- a/python/e2e/test_tools_unit.py +++ b/python/e2e/test_tools_unit.py @@ -6,7 +6,12 @@ from pydantic import BaseModel, Field from copilot import define_tool -from copilot.tools import ToolInvocation, ToolResult, _normalize_result +from copilot.tools import ( + ToolInvocation, + ToolResult, + _normalize_result, + convert_mcp_call_tool_result, +) class TestDefineTool: @@ -284,3 +289,99 @@ def test_raises_for_unserializable_value(self): # Functions cannot be JSON serialized with pytest.raises(TypeError, match="Failed to serialize"): _normalize_result(lambda x: x) + + +class TestConvertMcpCallToolResult: + def test_text_only_call_tool_result(self): + result = convert_mcp_call_tool_result( + { + "content": [{"type": "text", "text": "hello"}], + } + ) + assert result.text_result_for_llm == "hello" + assert result.result_type == "success" + + def test_multiple_text_blocks(self): + result = convert_mcp_call_tool_result( + { + "content": [ + {"type": "text", "text": "line 1"}, + {"type": "text", "text": "line 2"}, + ], + } + ) + assert result.text_result_for_llm == "line 1\nline 2" + + def test_is_error_maps_to_failure(self): + result = convert_mcp_call_tool_result( + { + "content": [{"type": "text", "text": "oops"}], + "isError": True, + } + ) + assert result.result_type == "failure" + + def test_is_error_false_maps_to_success(self): + result = convert_mcp_call_tool_result( + { + "content": [{"type": "text", "text": "ok"}], + "isError": False, + } + ) + assert result.result_type == "success" + + def test_image_content_to_binary(self): + result = convert_mcp_call_tool_result( + { + "content": [{"type": "image", "data": "base64data", "mimeType": "image/png"}], + } + ) + assert result.binary_results_for_llm is not None + assert len(result.binary_results_for_llm) == 1 + assert result.binary_results_for_llm[0].data == "base64data" + assert result.binary_results_for_llm[0].mime_type == "image/png" + assert result.binary_results_for_llm[0].type == "image" + + def test_resource_text_to_text_result(self): + result = convert_mcp_call_tool_result( + { + "content": [ + { + "type": "resource", + "resource": {"uri": "file:///data.txt", "text": "file contents"}, + }, + ], + } + ) + assert result.text_result_for_llm == "file contents" + + def test_resource_blob_to_binary(self): + result = convert_mcp_call_tool_result( + { + "content": [ + { + "type": "resource", + "resource": { + "uri": "file:///img.png", + "blob": "blobdata", + "mimeType": "image/png", + }, + }, + ], + } + ) + assert result.binary_results_for_llm is not None + assert len(result.binary_results_for_llm) == 1 + assert result.binary_results_for_llm[0].data == "blobdata" + assert result.binary_results_for_llm[0].description == "file:///img.png" + + def test_empty_content_array(self): + result = convert_mcp_call_tool_result({"content": []}) + assert result.text_result_for_llm == "" + assert result.result_type == "success" + + def test_call_tool_result_dict_is_json_serialized_by_normalize(self): + """_normalize_result does NOT auto-detect MCP results; it JSON-serializes them.""" + result = _normalize_result({"content": [{"type": "text", "text": "hello"}]}) + parsed = json.loads(result.text_result_for_llm) + assert parsed == {"content": [{"type": "text", "text": "hello"}]} From 322667679d11f64e9198e9dc734567d530a9b0b7 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 10 Apr 2026 06:06:29 -0400 Subject: [PATCH 02/34] Refactor MCP server config types across all SDK languages (#1051) * Refactor MCP server config types across all SDK languages Introduce an abstract McpServerConfig base class in C# with a private protected constructor and sealed derived types McpStdioServerConfig and McpHttpServerConfig. Shared properties (Tools, Type, Timeout) are deduplicated into the base class. The Type property uses the JsonPolymorphic discriminator pattern consistent with SessionEvent. All Dictionary McpServers properties are now strongly typed as Dictionary. Rename Local/Remote to Stdio/Http across all four SDK languages to match MCP protocol terminology: - C#: McpStdioServerConfig / McpHttpServerConfig - TypeScript: MCPStdioServerConfig / MCPHttpServerConfig - Go: MCPStdioServerConfig / MCPHTTPServerConfig - Python: MCPStdioServerConfig / MCPHttpServerConfig Update documentation examples accordingly. Fixes #245 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use idiomatic HTTP casing in Python and TypeScript type names Rename MCPHttpServerConfig to MCPHTTPServerConfig in Python (matching stdlib convention: HTTPServer, HTTPError) and TypeScript (matching the all-caps treatment of MCP already in use). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update mcp-servers C# scenario to use strongly-typed McpServerConfig API Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Make Go MCPServerConfig type-safe with interface + marker method Change MCPServerConfig from map[string]any to an interface with a private marker method, matching the type-safety approach used for C#. MCPStdioServerConfig and MCPHTTPServerConfig implement the interface and use MarshalJSON to auto-inject the type discriminator. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix consistency gaps: update JSDoc and drop stale type field in Python tests - Update MCPServerConfigBase JSDoc to reference stdio/http instead of local/remote - Remove explicit type: local from Python E2E tests (omitted type defaults to stdio, matching Go/C# pattern) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/mcp.md | 15 ++--- docs/troubleshooting/mcp-debugging.md | 26 +++---- dotnet/src/Client.cs | 4 +- dotnet/src/Types.cs | 66 +++++++++--------- dotnet/test/CloneTests.cs | 14 ++-- dotnet/test/McpAndAgentsTests.cs | 33 ++++----- go/internal/e2e/mcp_and_agents_test.go | 67 +++++++++---------- go/types.go | 46 ++++++++++--- nodejs/src/index.ts | 4 +- nodejs/src/types.ts | 10 +-- nodejs/test/e2e/mcp_and_agents.test.ts | 16 ++--- python/copilot/session.py | 6 +- python/e2e/test_mcp_and_agents.py | 4 -- .../tools/mcp-servers/csharp/Program.cs | 10 +-- test/scenarios/tools/mcp-servers/go/main.go | 8 +-- 15 files changed, 166 insertions(+), 163 deletions(-) diff --git a/docs/features/mcp.md b/docs/features/mcp.md index d16666501..d8af04533 100644 --- a/docs/features/mcp.md +++ b/docs/features/mcp.md @@ -113,15 +113,13 @@ func main() { } defer client.Stop() - // MCPServerConfig is map[string]any for flexibility session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Model: "gpt-5", MCPServers: map[string]copilot.MCPServerConfig{ - "my-local-server": { - "type": "local", - "command": "node", - "args": []string{"./mcp-server.js"}, - "tools": []string{"*"}, + "my-local-server": copilot.MCPStdioServerConfig{ + Command: "node", + Args: []string{"./mcp-server.js"}, + Tools: []string{"*"}, }, }, }) @@ -143,11 +141,10 @@ await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", - McpServers = new Dictionary + McpServers = new Dictionary { - ["my-local-server"] = new McpLocalServerConfig + ["my-local-server"] = new McpStdioServerConfig { - Type = "local", Command = "node", Args = new List { "./mcp-server.js" }, Tools = new List { "*" }, diff --git a/docs/troubleshooting/mcp-debugging.md b/docs/troubleshooting/mcp-debugging.md index 30e05fd3e..d7b455ecf 100644 --- a/docs/troubleshooting/mcp-debugging.md +++ b/docs/troubleshooting/mcp-debugging.md @@ -250,19 +250,17 @@ public static class McpDotnetConfigExample { public static void Main() { - var servers = new Dictionary + var servers = new Dictionary { - ["my-dotnet-server"] = new McpLocalServerConfig + ["my-dotnet-server"] = new McpStdioServerConfig { - Type = "local", Command = @"C:\Tools\MyServer\MyServer.exe", Args = new List(), Cwd = @"C:\Tools\MyServer", Tools = new List { "*" }, }, - ["my-dotnet-tool"] = new McpLocalServerConfig + ["my-dotnet-tool"] = new McpStdioServerConfig { - Type = "local", Command = "dotnet", Args = new List { @"C:\Tools\MyTool\MyTool.dll" }, Cwd = @"C:\Tools\MyTool", @@ -275,9 +273,8 @@ public static class McpDotnetConfigExample ```csharp // Correct configuration for .NET exe -["my-dotnet-server"] = new McpLocalServerConfig +["my-dotnet-server"] = new McpStdioServerConfig { - Type = "local", Command = @"C:\Tools\MyServer\MyServer.exe", // Full path with .exe Args = new List(), Cwd = @"C:\Tools\MyServer", // Set working directory @@ -285,9 +282,8 @@ public static class McpDotnetConfigExample } // For dotnet tool (DLL) -["my-dotnet-tool"] = new McpLocalServerConfig +["my-dotnet-tool"] = new McpStdioServerConfig { - Type = "local", Command = "dotnet", Args = new List { @"C:\Tools\MyTool\MyTool.dll" }, Cwd = @"C:\Tools\MyTool", @@ -305,11 +301,10 @@ public static class McpNpxConfigExample { public static void Main() { - var servers = new Dictionary + var servers = new Dictionary { - ["filesystem"] = new McpLocalServerConfig + ["filesystem"] = new McpStdioServerConfig { - Type = "local", Command = "cmd", Args = new List { "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\allowed\\path" }, Tools = new List { "*" }, @@ -321,9 +316,8 @@ public static class McpNpxConfigExample ```csharp // Windows needs cmd /c for npx -["filesystem"] = new McpLocalServerConfig +["filesystem"] = new McpStdioServerConfig { - Type = "local", Command = "cmd", Args = new List { "/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\allowed\\path" }, Tools = new List { "*" }, @@ -357,9 +351,9 @@ xattr -d com.apple.quarantine /path/to/mcp-server ```typescript -import { MCPLocalServerConfig } from "@github/copilot-sdk"; +import { MCPStdioServerConfig } from "@github/copilot-sdk"; -const mcpServers: Record = { +const mcpServers: Record = { "my-server": { command: "/opt/homebrew/bin/node", args: ["/path/to/server.js"], diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index cbceeede2..732c15447 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1631,7 +1631,7 @@ internal record CreateSessionRequest( bool? Hooks, string? WorkingDirectory, bool? Streaming, - Dictionary? McpServers, + Dictionary? McpServers, string? EnvValueMode, List? CustomAgents, string? Agent, @@ -1686,7 +1686,7 @@ internal record ResumeSessionRequest( bool? EnableConfigDiscovery, bool? DisableResume, bool? Streaming, - Dictionary? McpServers, + Dictionary? McpServers, string? EnvValueMode, List? CustomAgents, string? Agent, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 1645b1eb0..8ee146dee 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1502,10 +1502,17 @@ public class AzureOptions // ============================================================================ /// -/// Configuration for a local/stdio MCP server. +/// Abstract base class for MCP server configurations. /// -public class McpLocalServerConfig +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + IgnoreUnrecognizedTypeDiscriminators = true)] +[JsonDerivedType(typeof(McpStdioServerConfig), "stdio")] +[JsonDerivedType(typeof(McpHttpServerConfig), "http")] +public abstract class McpServerConfig { + private protected McpServerConfig() { } + /// /// List of tools to include from this server. Empty list means none. Use "*" for all. /// @@ -1513,16 +1520,26 @@ public class McpLocalServerConfig public List Tools { get; set; } = []; /// - /// Server type. Defaults to "local". + /// The server type discriminator. /// - [JsonPropertyName("type")] - public string? Type { get; set; } + [JsonIgnore] + public virtual string Type => "unknown"; /// /// Optional timeout in milliseconds for tool calls to this server. /// [JsonPropertyName("timeout")] public int? Timeout { get; set; } +} + +/// +/// Configuration for a local/stdio MCP server. +/// +public sealed class McpStdioServerConfig : McpServerConfig +{ + /// + [JsonIgnore] + public override string Type => "stdio"; /// /// Command to run the MCP server. @@ -1552,25 +1569,11 @@ public class McpLocalServerConfig /// /// Configuration for a remote MCP server (HTTP or SSE). /// -public class McpRemoteServerConfig +public sealed class McpHttpServerConfig : McpServerConfig { - /// - /// List of tools to include from this server. Empty list means none. Use "*" for all. - /// - [JsonPropertyName("tools")] - public List Tools { get; set; } = []; - - /// - /// Server type. Must be "http" or "sse". - /// - [JsonPropertyName("type")] - public string Type { get; set; } = "http"; - - /// - /// Optional timeout in milliseconds for tool calls to this server. - /// - [JsonPropertyName("timeout")] - public int? Timeout { get; set; } + /// + [JsonIgnore] + public override string Type => "http"; /// /// URL of the remote server. @@ -1628,7 +1631,7 @@ public class CustomAgentConfig /// MCP servers specific to this agent. /// [JsonPropertyName("mcpServers")] - public Dictionary? McpServers { get; set; } + public Dictionary? McpServers { get; set; } /// /// Whether the agent should be available for model inference. @@ -1697,7 +1700,7 @@ protected SessionConfig(SessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? new Dictionary(other.McpServers, other.McpServers.Comparer) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1829,9 +1832,9 @@ protected SessionConfig(SessionConfig? other) /// /// MCP server configurations for the session. - /// Keys are server names, values are server configurations (McpLocalServerConfig or McpRemoteServerConfig). + /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public Dictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. @@ -1925,7 +1928,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? new Dictionary(other.McpServers, other.McpServers.Comparer) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -2061,9 +2064,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// /// MCP server configurations for the session. - /// Keys are server names, values are server configurations (McpLocalServerConfig or McpRemoteServerConfig). + /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public Dictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. @@ -2608,8 +2611,7 @@ public class SystemMessageTransformRpcResponse [JsonSerializable(typeof(GetForegroundSessionResponse))] [JsonSerializable(typeof(GetModelsResponse))] [JsonSerializable(typeof(GetStatusResponse))] -[JsonSerializable(typeof(McpLocalServerConfig))] -[JsonSerializable(typeof(McpRemoteServerConfig))] +[JsonSerializable(typeof(McpServerConfig))] [JsonSerializable(typeof(MessageOptions))] [JsonSerializable(typeof(ModelBilling))] [JsonSerializable(typeof(ModelCapabilities))] diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index a0051ffbc..dcde71f99 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -86,7 +86,7 @@ public void SessionConfig_Clone_CopiesAllProperties() ExcludedTools = ["tool3"], WorkingDirectory = "/workspace", Streaming = true, - McpServers = new Dictionary { ["server1"] = new object() }, + McpServers = new Dictionary { ["server1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "agent1" }], Agent = "agent1", SkillDirectories = ["/skills"], @@ -118,7 +118,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() { AvailableTools = ["tool1"], ExcludedTools = ["tool2"], - McpServers = new Dictionary { ["s1"] = new object() }, + McpServers = new Dictionary { ["s1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "a1" }], SkillDirectories = ["/skills"], DisabledSkills = ["skill1"], @@ -129,7 +129,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() // Mutate clone collections clone.AvailableTools!.Add("tool99"); clone.ExcludedTools!.Add("tool99"); - clone.McpServers!["s2"] = new object(); + clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" }; clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" }); clone.SkillDirectories!.Add("/more"); clone.DisabledSkills!.Add("skill99"); @@ -146,7 +146,7 @@ public void SessionConfig_Clone_CollectionsAreIndependent() [Fact] public void SessionConfig_Clone_PreservesMcpServersComparer() { - var servers = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["server"] = new object() }; + var servers = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["server"] = new McpStdioServerConfig { Command = "echo" } }; var original = new SessionConfig { McpServers = servers }; var clone = original.Clone(); @@ -161,7 +161,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() { AvailableTools = ["tool1"], ExcludedTools = ["tool2"], - McpServers = new Dictionary { ["s1"] = new object() }, + McpServers = new Dictionary { ["s1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "a1" }], SkillDirectories = ["/skills"], DisabledSkills = ["skill1"], @@ -172,7 +172,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() // Mutate clone collections clone.AvailableTools!.Add("tool99"); clone.ExcludedTools!.Add("tool99"); - clone.McpServers!["s2"] = new object(); + clone.McpServers!["s2"] = new McpStdioServerConfig { Command = "echo" }; clone.CustomAgents!.Add(new CustomAgentConfig { Name = "a2" }); clone.SkillDirectories!.Add("/more"); clone.DisabledSkills!.Add("skill99"); @@ -189,7 +189,7 @@ public void ResumeSessionConfig_Clone_CollectionsAreIndependent() [Fact] public void ResumeSessionConfig_Clone_PreservesMcpServersComparer() { - var servers = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["server"] = new object() }; + var servers = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["server"] = new McpStdioServerConfig { Command = "echo" } }; var original = new ResumeSessionConfig { McpServers = servers }; var clone = original.Clone(); diff --git a/dotnet/test/McpAndAgentsTests.cs b/dotnet/test/McpAndAgentsTests.cs index 1d35ffda4..782b01123 100644 --- a/dotnet/test/McpAndAgentsTests.cs +++ b/dotnet/test/McpAndAgentsTests.cs @@ -13,11 +13,10 @@ public class McpAndAgentsTests(E2ETestFixture fixture, ITestOutputHelper output) [Fact] public async Task Should_Accept_MCP_Server_Configuration_On_Session_Create() { - var mcpServers = new Dictionary + var mcpServers = new Dictionary { - ["test-server"] = new McpLocalServerConfig + ["test-server"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["hello"], Tools = ["*"] @@ -50,11 +49,10 @@ public async Task Should_Accept_MCP_Server_Configuration_On_Session_Resume() await session1.SendAndWaitAsync(new MessageOptions { Prompt = "What is 1+1?" }); // Resume with MCP servers - var mcpServers = new Dictionary + var mcpServers = new Dictionary { - ["test-server"] = new McpLocalServerConfig + ["test-server"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["hello"], Tools = ["*"] @@ -78,18 +76,16 @@ public async Task Should_Accept_MCP_Server_Configuration_On_Session_Resume() [Fact] public async Task Should_Handle_Multiple_MCP_Servers() { - var mcpServers = new Dictionary + var mcpServers = new Dictionary { - ["server1"] = new McpLocalServerConfig + ["server1"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["server1"], Tools = ["*"] }, - ["server2"] = new McpLocalServerConfig + ["server2"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["server2"], Tools = ["*"] @@ -207,11 +203,10 @@ public async Task Should_Handle_Custom_Agent_With_MCP_Servers() DisplayName = "MCP Agent", Description = "An agent with its own MCP servers", Prompt = "You are an agent with MCP servers.", - McpServers = new Dictionary + McpServers = new Dictionary { - ["agent-server"] = new McpLocalServerConfig + ["agent-server"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["agent-mcp"], Tools = ["*"] @@ -264,11 +259,10 @@ public async Task Should_Handle_Multiple_Custom_Agents() public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess() { var testHarnessDir = FindTestHarnessDir(); - var mcpServers = new Dictionary + var mcpServers = new Dictionary { - ["env-echo"] = new McpLocalServerConfig + ["env-echo"] = new McpStdioServerConfig { - Type = "local", Command = "node", Args = [Path.Combine(testHarnessDir, "test-mcp-server.mjs")], Env = new Dictionary { ["TEST_SECRET"] = "hunter2" }, @@ -299,11 +293,10 @@ public async Task Should_Pass_Literal_Env_Values_To_Mcp_Server_Subprocess() [Fact] public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents() { - var mcpServers = new Dictionary + var mcpServers = new Dictionary { - ["shared-server"] = new McpLocalServerConfig + ["shared-server"] = new McpStdioServerConfig { - Type = "local", Command = "echo", Args = ["shared"], Tools = ["*"] diff --git a/go/internal/e2e/mcp_and_agents_test.go b/go/internal/e2e/mcp_and_agents_test.go index 7b7d4d037..e05f44585 100644 --- a/go/internal/e2e/mcp_and_agents_test.go +++ b/go/internal/e2e/mcp_and_agents_test.go @@ -18,11 +18,10 @@ func TestMCPServers(t *testing.T) { ctx.ConfigureForTest(t) mcpServers := map[string]copilot.MCPServerConfig{ - "test-server": { - "type": "local", - "command": "echo", - "args": []string{"hello"}, - "tools": []string{"*"}, + "test-server": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"hello"}, + Tools: []string{"*"}, }, } @@ -75,11 +74,10 @@ func TestMCPServers(t *testing.T) { // Resume with MCP servers mcpServers := map[string]copilot.MCPServerConfig{ - "test-server": { - "type": "local", - "command": "echo", - "args": []string{"hello"}, - "tools": []string{"*"}, + "test-server": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"hello"}, + Tools: []string{"*"}, }, } @@ -117,13 +115,12 @@ func TestMCPServers(t *testing.T) { mcpServerDir := filepath.Dir(mcpServerPath) mcpServers := map[string]copilot.MCPServerConfig{ - "env-echo": { - "type": "local", - "command": "node", - "args": []string{mcpServerPath}, - "tools": []string{"*"}, - "env": map[string]string{"TEST_SECRET": "hunter2"}, - "cwd": mcpServerDir, + "env-echo": copilot.MCPStdioServerConfig{ + Command: "node", + Args: []string{mcpServerPath}, + Tools: []string{"*"}, + Env: map[string]string{"TEST_SECRET": "hunter2"}, + Cwd: mcpServerDir, }, } @@ -157,17 +154,15 @@ func TestMCPServers(t *testing.T) { ctx.ConfigureForTest(t) mcpServers := map[string]copilot.MCPServerConfig{ - "server1": { - "type": "local", - "command": "echo", - "args": []string{"server1"}, - "tools": []string{"*"}, + "server1": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"server1"}, + Tools: []string{"*"}, }, - "server2": { - "type": "local", - "command": "echo", - "args": []string{"server2"}, - "tools": []string{"*"}, + "server2": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"server2"}, + Tools: []string{"*"}, }, } @@ -327,11 +322,10 @@ func TestCustomAgents(t *testing.T) { Description: "An agent with its own MCP servers", Prompt: "You are an agent with MCP servers.", MCPServers: map[string]copilot.MCPServerConfig{ - "agent-server": { - "type": "local", - "command": "echo", - "args": []string{"agent-mcp"}, - "tools": []string{"*"}, + "agent-server": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"agent-mcp"}, + Tools: []string{"*"}, }, }, }, @@ -399,11 +393,10 @@ func TestCombinedConfiguration(t *testing.T) { ctx.ConfigureForTest(t) mcpServers := map[string]copilot.MCPServerConfig{ - "shared-server": { - "type": "local", - "command": "echo", - "args": []string{"shared"}, - "tools": []string{"*"}, + "shared-server": copilot.MCPStdioServerConfig{ + Command: "echo", + Args: []string{"shared"}, + Tools: []string{"*"}, }, } diff --git a/go/types.go b/go/types.go index c26f075e3..568bcc1b9 100644 --- a/go/types.go +++ b/go/types.go @@ -382,10 +382,15 @@ type SessionHooks struct { OnErrorOccurred ErrorOccurredHandler } -// MCPLocalServerConfig configures a local/stdio MCP server -type MCPLocalServerConfig struct { +// MCPServerConfig is implemented by MCP server configuration types. +// Only MCPStdioServerConfig and MCPHTTPServerConfig implement this interface. +type MCPServerConfig interface { + mcpServerConfig() +} + +// MCPStdioServerConfig configures a local/stdio MCP server. +type MCPStdioServerConfig struct { Tools []string `json:"tools"` - Type string `json:"type,omitempty"` // "local" or "stdio" Timeout int `json:"timeout,omitempty"` Command string `json:"command"` Args []string `json:"args"` @@ -393,18 +398,41 @@ type MCPLocalServerConfig struct { Cwd string `json:"cwd,omitempty"` } -// MCPRemoteServerConfig configures a remote MCP server (HTTP or SSE) -type MCPRemoteServerConfig struct { +func (MCPStdioServerConfig) mcpServerConfig() {} + +// MarshalJSON implements json.Marshaler, injecting the "type" discriminator. +func (c MCPStdioServerConfig) MarshalJSON() ([]byte, error) { + type alias MCPStdioServerConfig + return json.Marshal(struct { + Type string `json:"type"` + alias + }{ + Type: "stdio", + alias: alias(c), + }) +} + +// MCPHTTPServerConfig configures a remote MCP server (HTTP or SSE). +type MCPHTTPServerConfig struct { Tools []string `json:"tools"` - Type string `json:"type"` // "http" or "sse" Timeout int `json:"timeout,omitempty"` URL string `json:"url"` Headers map[string]string `json:"headers,omitempty"` } -// MCPServerConfig can be either MCPLocalServerConfig or MCPRemoteServerConfig -// Use a map[string]any for flexibility, or create separate configs -type MCPServerConfig map[string]any +func (MCPHTTPServerConfig) mcpServerConfig() {} + +// MarshalJSON implements json.Marshaler, injecting the "type" discriminator. +func (c MCPHTTPServerConfig) MarshalJSON() ([]byte, error) { + type alias MCPHTTPServerConfig + return json.Marshal(struct { + Type string `json:"type"` + alias + }{ + Type: "http", + alias: alias(c), + }) +} // CustomAgentConfig configures a custom agent type CustomAgentConfig struct { diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 13e0670fb..c4e02a396 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -35,8 +35,8 @@ export type { GetStatusResponse, InfiniteSessionConfig, InputOptions, - MCPLocalServerConfig, - MCPRemoteServerConfig, + MCPStdioServerConfig, + MCPHTTPServerConfig, MCPServerConfig, MessageOptions, ModelBilling, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index c8a27009d..ada046436 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1019,8 +1019,8 @@ interface MCPServerConfigBase { */ tools: string[]; /** - * Indicates "remote" or "local" server type. - * If not specified, defaults to "local". + * Indicates the server type: "stdio" for local/subprocess servers, "http"/"sse" for remote servers. + * If not specified, defaults to "stdio". */ type?: string; /** @@ -1032,7 +1032,7 @@ interface MCPServerConfigBase { /** * Configuration for a local/stdio MCP server. */ -export interface MCPLocalServerConfig extends MCPServerConfigBase { +export interface MCPStdioServerConfig extends MCPServerConfigBase { type?: "local" | "stdio"; command: string; args: string[]; @@ -1046,7 +1046,7 @@ export interface MCPLocalServerConfig extends MCPServerConfigBase { /** * Configuration for a remote MCP server (HTTP or SSE). */ -export interface MCPRemoteServerConfig extends MCPServerConfigBase { +export interface MCPHTTPServerConfig extends MCPServerConfigBase { type: "http" | "sse"; /** * URL of the remote server. @@ -1061,7 +1061,7 @@ export interface MCPRemoteServerConfig extends MCPServerConfigBase { /** * Union type for MCP server configurations. */ -export type MCPServerConfig = MCPLocalServerConfig | MCPRemoteServerConfig; +export type MCPServerConfig = MCPStdioServerConfig | MCPHTTPServerConfig; // ============================================================================ // Custom Agent Configuration Types diff --git a/nodejs/test/e2e/mcp_and_agents.test.ts b/nodejs/test/e2e/mcp_and_agents.test.ts index 28ebf28b5..59e6d498b 100644 --- a/nodejs/test/e2e/mcp_and_agents.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.test.ts @@ -5,7 +5,7 @@ import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; import { describe, expect, it } from "vitest"; -import type { CustomAgentConfig, MCPLocalServerConfig, MCPServerConfig } from "../../src/index.js"; +import type { CustomAgentConfig, MCPStdioServerConfig, MCPServerConfig } from "../../src/index.js"; import { approveAll } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -24,7 +24,7 @@ describe("MCP Servers and Custom Agents", async () => { command: "echo", args: ["hello"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }; const session = await client.createSession({ @@ -56,7 +56,7 @@ describe("MCP Servers and Custom Agents", async () => { command: "echo", args: ["hello"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }; const session2 = await client.resumeSession(sessionId, { @@ -81,13 +81,13 @@ describe("MCP Servers and Custom Agents", async () => { command: "echo", args: ["server1"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, server2: { type: "local", command: "echo", args: ["server2"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }; const session = await client.createSession({ @@ -107,7 +107,7 @@ describe("MCP Servers and Custom Agents", async () => { args: [TEST_MCP_SERVER], tools: ["*"], env: { TEST_SECRET: "hunter2" }, - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }; const session = await client.createSession({ @@ -219,7 +219,7 @@ describe("MCP Servers and Custom Agents", async () => { command: "echo", args: ["agent-mcp"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }, }, ]; @@ -268,7 +268,7 @@ describe("MCP Servers and Custom Agents", async () => { command: "echo", args: ["shared"], tools: ["*"], - } as MCPLocalServerConfig, + } as MCPStdioServerConfig, }; const customAgents: CustomAgentConfig[] = [ diff --git a/python/copilot/session.py b/python/copilot/session.py index b3f62789d..45e8826b7 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -725,7 +725,7 @@ class SessionHooks(TypedDict, total=False): # ============================================================================ -class MCPLocalServerConfig(TypedDict, total=False): +class MCPStdioServerConfig(TypedDict, total=False): """Configuration for a local/stdio MCP server.""" tools: list[str] # List of tools to include. [] means none. "*" means all. @@ -737,7 +737,7 @@ class MCPLocalServerConfig(TypedDict, total=False): cwd: NotRequired[str] # Working directory -class MCPRemoteServerConfig(TypedDict, total=False): +class MCPHTTPServerConfig(TypedDict, total=False): """Configuration for a remote MCP server (HTTP or SSE).""" tools: list[str] # List of tools to include. [] means none. "*" means all. @@ -747,7 +747,7 @@ class MCPRemoteServerConfig(TypedDict, total=False): headers: NotRequired[dict[str, str]] # HTTP headers -MCPServerConfig = MCPLocalServerConfig | MCPRemoteServerConfig +MCPServerConfig = MCPStdioServerConfig | MCPHTTPServerConfig # ============================================================================ # Custom Agent Configuration Types diff --git a/python/e2e/test_mcp_and_agents.py b/python/e2e/test_mcp_and_agents.py index c6a590d6c..f93ba432d 100644 --- a/python/e2e/test_mcp_and_agents.py +++ b/python/e2e/test_mcp_and_agents.py @@ -25,7 +25,6 @@ async def test_should_accept_mcp_server_configuration_on_session_create( """Test that MCP server configuration is accepted on session create""" mcp_servers: dict[str, MCPServerConfig] = { "test-server": { - "type": "local", "command": "echo", "args": ["hello"], "tools": ["*"], @@ -59,7 +58,6 @@ async def test_should_accept_mcp_server_configuration_on_session_resume( # Resume with MCP servers mcp_servers: dict[str, MCPServerConfig] = { "test-server": { - "type": "local", "command": "echo", "args": ["hello"], "tools": ["*"], @@ -86,7 +84,6 @@ async def test_should_pass_literal_env_values_to_mcp_server_subprocess( """Test that env values are passed as literals to MCP server subprocess""" mcp_servers: dict[str, MCPServerConfig] = { "env-echo": { - "type": "local", "command": "node", "args": [TEST_MCP_SERVER], "tools": ["*"], @@ -180,7 +177,6 @@ async def test_should_accept_both_mcp_servers_and_custom_agents(self, ctx: E2ETe """Test that both MCP servers and custom agents can be configured together""" mcp_servers: dict[str, MCPServerConfig] = { "shared-server": { - "type": "local", "command": "echo", "args": ["shared"], "tools": ["*"], diff --git a/test/scenarios/tools/mcp-servers/csharp/Program.cs b/test/scenarios/tools/mcp-servers/csharp/Program.cs index 2ee25aacd..e3c1ed428 100644 --- a/test/scenarios/tools/mcp-servers/csharp/Program.cs +++ b/test/scenarios/tools/mcp-servers/csharp/Program.cs @@ -10,16 +10,16 @@ try { - var mcpServers = new Dictionary(); + var mcpServers = new Dictionary(); var mcpServerCmd = Environment.GetEnvironmentVariable("MCP_SERVER_CMD"); if (!string.IsNullOrEmpty(mcpServerCmd)) { var mcpArgs = Environment.GetEnvironmentVariable("MCP_SERVER_ARGS"); - mcpServers["example"] = new Dictionary + mcpServers["example"] = new McpStdioServerConfig { - { "type", "stdio" }, - { "command", mcpServerCmd }, - { "args", string.IsNullOrEmpty(mcpArgs) ? Array.Empty() : mcpArgs.Split(' ') }, + Command = mcpServerCmd, + Args = string.IsNullOrEmpty(mcpArgs) ? [] : [.. mcpArgs.Split(' ')], + Tools = ["*"], }; } diff --git a/test/scenarios/tools/mcp-servers/go/main.go b/test/scenarios/tools/mcp-servers/go/main.go index d2ae5ab86..72cbdc067 100644 --- a/test/scenarios/tools/mcp-servers/go/main.go +++ b/test/scenarios/tools/mcp-servers/go/main.go @@ -30,10 +30,10 @@ func main() { if argsStr := os.Getenv("MCP_SERVER_ARGS"); argsStr != "" { args = strings.Split(argsStr, " ") } - mcpServers["example"] = copilot.MCPServerConfig{ - "type": "stdio", - "command": cmd, - "args": args, + mcpServers["example"] = copilot.MCPStdioServerConfig{ + Command: cmd, + Args: args, + Tools: []string{"*"}, } } From 9a3894510440c3ec1f828f131a446b47cf79bc47 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:20:56 +0100 Subject: [PATCH 03/34] Add changelog for v0.2.2 (#1060) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b1ba317f..369c599be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to the Copilot SDK are documented in this file. This changelog is automatically generated by an AI agent when stable releases are published. See [GitHub Releases](https://github.com/github/copilot-sdk/releases) for the full list. +## [v0.2.2](https://github.com/github/copilot-sdk/releases/tag/v0.2.2) (2026-04-10) + +### Feature: `enableConfigDiscovery` for automatic MCP and skill config loading + +Set `enableConfigDiscovery: true` when creating a session to let the runtime automatically discover MCP server configurations (`.mcp.json`, `.vscode/mcp.json`) and skill directories from the working directory. Discovered settings are merged with any explicitly provided values; explicit values take precedence on name collision. ([#1044](https://github.com/github/copilot-sdk/pull/1044)) + +```ts +const session = await client.createSession({ + enableConfigDiscovery: true, +}); +``` + +```cs +var session = await client.CreateSessionAsync(new SessionConfig { + EnableConfigDiscovery = true, +}); +``` + +- Python: `await client.create_session(enable_config_discovery=True)` +- Go: `client.CreateSession(ctx, &copilot.SessionConfig{EnableConfigDiscovery: ptr(true)})` + ## [v0.2.1](https://github.com/github/copilot-sdk/releases/tag/v0.2.1) (2026-04-03) ### Feature: commands and UI elicitation across all four SDKs From 7fc03d2717e5571968a54435efdc7dfdce258ed0 Mon Sep 17 00:00:00 2001 From: Matthew Rayermann Date: Fri, 10 Apr 2026 03:27:30 -0700 Subject: [PATCH 04/34] [Node] Set `requestPermission` False If Using Default Permission Handler (#1056) * [Node] Set requestPermission False If Using Default Permission Handler * Add joinSession permission tests * Format joinSession changes --- nodejs/src/client.ts | 4 ++- nodejs/src/extension.ts | 10 +++---- nodejs/src/types.ts | 5 ++++ nodejs/test/client.test.ts | 55 +++++++++++++++++++++++++++++++++++ nodejs/test/extension.test.ts | 2 ++ 5 files changed, 70 insertions(+), 6 deletions(-) diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 6941598b8..0780ba6ea 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -55,6 +55,7 @@ import type { TraceContextProvider, TypedSessionLifecycleHandler, } from "./types.js"; +import { defaultJoinSessionPermissionHandler } from "./types.js"; /** * Minimum protocol version this SDK can communicate with. @@ -868,7 +869,8 @@ export class CopilotClient { })), provider: config.provider, modelCapabilities: config.modelCapabilities, - requestPermission: true, + requestPermission: + config.onPermissionRequest !== defaultJoinSessionPermissionHandler, requestUserInput: !!config.onUserInputRequest, requestElicitation: !!config.onElicitationRequest, hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), diff --git a/nodejs/src/extension.ts b/nodejs/src/extension.ts index b7c2da3a8..bd35c0997 100644 --- a/nodejs/src/extension.ts +++ b/nodejs/src/extension.ts @@ -4,11 +4,11 @@ import { CopilotClient } from "./client.js"; import type { CopilotSession } from "./session.js"; -import type { PermissionHandler, PermissionRequestResult, ResumeSessionConfig } from "./types.js"; - -const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ - kind: "no-result", -}); +import { + defaultJoinSessionPermissionHandler, + type PermissionHandler, + type ResumeSessionConfig, +} from "./types.js"; export type JoinSessionConfig = Omit & { onPermissionRequest?: PermissionHandler; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index ada046436..cb8dd7ad2 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -757,6 +757,11 @@ export type PermissionHandler = ( export const approveAll: PermissionHandler = () => ({ kind: "approved" }); +export const defaultJoinSessionPermissionHandler: PermissionHandler = + (): PermissionRequestResult => ({ + kind: "no-result", + }); + // ============================================================================ // User Input Request Types // ============================================================================ diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index c3f0770cd..0c0611df8 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, expect, it, onTestFinished, vi } from "vitest"; import { approveAll, CopilotClient, type ModelInfo } from "../src/index.js"; +import { defaultJoinSessionPermissionHandler } from "../src/types.js"; // This file is for unit tests. Where relevant, prefer to add e2e tests in e2e/*.test.ts instead @@ -97,6 +98,60 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("does not request permissions on session.resume when using the default joinSession handler", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession(session.sessionId, { + onPermissionRequest: defaultJoinSessionPermissionHandler, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + sessionId: session.sessionId, + requestPermission: false, + }) + ); + spy.mockRestore(); + }); + + it("requests permissions on session.resume when using an explicit handler", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + sessionId: session.sessionId, + requestPermission: true, + }) + ); + spy.mockRestore(); + }); + it("sends session.model.switchTo RPC with correct params", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/extension.test.ts b/nodejs/test/extension.test.ts index d9fcf8dfd..1e1f11c88 100644 --- a/nodejs/test/extension.test.ts +++ b/nodejs/test/extension.test.ts @@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { CopilotClient } from "../src/client.js"; import { approveAll } from "../src/index.js"; import { joinSession } from "../src/extension.js"; +import { defaultJoinSessionPermissionHandler } from "../src/types.js"; describe("joinSession", () => { const originalSessionId = process.env.SESSION_ID; @@ -25,6 +26,7 @@ describe("joinSession", () => { const [, config] = resumeSession.mock.calls[0]!; expect(config.onPermissionRequest).toBeDefined(); + expect(config.onPermissionRequest).toBe(defaultJoinSessionPermissionHandler); const result = await Promise.resolve( config.onPermissionRequest!({ kind: "write" }, { sessionId: "session-123" }) ); From c2a68358831a4c8beec24c7fa99e06a61a35ca5b Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 10 Apr 2026 06:27:46 -0400 Subject: [PATCH 05/34] Export ProviderConfig from Node.js and Python SDKs (#1048) ProviderConfig was defined in both SDKs but not re-exported from their public API entry points. Consumers had to duplicate the type locally to use it for Responses API configuration (wireApi: 'responses'). - Node.js: add ProviderConfig to the type re-exports in src/index.ts - Python: import ProviderConfig in copilot/__init__.py and add to __all__ Fixes github/copilot-sdk-partners#7 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/index.ts | 1 + python/copilot/__init__.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index c4e02a396..e2942998a 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -47,6 +47,7 @@ export type { PermissionHandler, PermissionRequest, PermissionRequestResult, + ProviderConfig, ResumeSessionConfig, SectionOverride, SectionOverrideAction, diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 6333aea51..190c058a0 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -23,6 +23,7 @@ ElicitationParams, ElicitationResult, InputOptions, + ProviderConfig, SessionCapabilities, SessionFsConfig, SessionFsHandler, @@ -49,6 +50,7 @@ "ModelLimitsOverride", "ModelSupportsOverride", "ModelVisionLimitsOverride", + "ProviderConfig", "SessionCapabilities", "SessionFsConfig", "SessionFsHandler", From 9fa8b67be893498fe909df8e4830e7ae23abb2cf Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 10 Apr 2026 12:24:43 +0100 Subject: [PATCH 06/34] Fix release notes agent (#1061) * Add instructions to create a full clone, not shallow one Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> * Refresh other aw output Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/aw/actions-lock.json | 29 +- .github/workflows/handle-bug.lock.yml | 429 +++++++------ .../workflows/handle-documentation.lock.yml | 429 +++++++------ .github/workflows/handle-enhancement.lock.yml | 429 +++++++------ .github/workflows/handle-question.lock.yml | 429 +++++++------ .../workflows/issue-classification.lock.yml | 548 ++++++++++------- .github/workflows/issue-triage.lock.yml | 566 ++++++++++-------- .github/workflows/release-changelog.lock.yml | 484 ++++++++------- .github/workflows/release-changelog.md | 14 +- .../workflows/sdk-consistency-review.lock.yml | 456 ++++++++------ 10 files changed, 2227 insertions(+), 1586 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 02df5e813..9f6f22f95 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -1,19 +1,34 @@ { "entries": { + "actions/checkout@v6.0.2": { + "repo": "actions/checkout", + "version": "v6.0.2", + "sha": "de0fac2e4500dabe0009e67214ff5f5447ce83dd" + }, + "actions/download-artifact@v8.0.0": { + "repo": "actions/download-artifact", + "version": "v8.0.0", + "sha": "70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3" + }, "actions/github-script@v8": { "repo": "actions/github-script", "version": "v8", "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" }, - "github/gh-aw-actions/setup@v0.64.2": { - "repo": "github/gh-aw-actions/setup", - "version": "v0.64.2", - "sha": "f22886a9607f5c27e79742a8bfc5faa34737138b" + "actions/upload-artifact@v7.0.0": { + "repo": "actions/upload-artifact", + "version": "v7.0.0", + "sha": "bbbca2ddaa5d8feaa63e36b76fdaad77386f024f" }, - "github/gh-aw-actions/setup@v0.65.5": { + "github/gh-aw-actions/setup@v0.67.4": { "repo": "github/gh-aw-actions/setup", - "version": "v0.65.5", - "sha": "15b2fa31e9a1b771c9773c162273924d8f5ea516" + "version": "v0.67.4", + "sha": "9d6ae06250fc0ec536a0e5f35de313b35bad7246" + }, + "github/gh-aw/actions/setup@v0.52.1": { + "repo": "github/gh-aw/actions/setup", + "version": "v0.52.1", + "sha": "a86e657586e4ac5f549a790628971ec02f6a4a8f" } } } diff --git a/.github/workflows/handle-bug.lock.yml b/.github/workflows/handle-bug.lock.yml index 6d2c8f981..30f8bf82b 100644 --- a/.github/workflows/handle-bug.lock.yml +++ b/.github/workflows/handle-bug.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a473a22cd67feb7f8f5225639fd989cf71705f78c9fe11c3fc757168e1672b0e","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Handles issues classified as bugs by the triage classifier # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"a473a22cd67feb7f8f5225639fd989cf71705f78c9fe11c3fc757168e1672b0e","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Bug Handler" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} @@ -61,14 +75,17 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Resolve host repo for activation checkout id: resolve-host-repo uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -82,23 +99,23 @@ jobs: id: artifact-prefix env: INPUTS_JSON: ${{ toJSON(inputs) }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Bug Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -112,7 +129,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Cross-repo setup guidance @@ -132,10 +149,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "handle-bug.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -145,7 +163,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -167,7 +185,7 @@ jobs: GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_3df18ed0421fc8c1_EOF' @@ -265,12 +283,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -279,6 +297,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -300,16 +320,21 @@ jobs: outputs: artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -321,22 +346,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -353,137 +379,159 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Parse integrity filter lists id: parse-guard-vars env: GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_788bfbc2e8cbcb67_EOF' - {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["bug","enhancement","question","documentation"],"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_788bfbc2e8cbcb67_EOF' + {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["bug","enhancement","question","documentation"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_788bfbc2e8cbcb67_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_f54453b1fbf89d29_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", - "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"bug\" \"enhancement\" \"question\" \"documentation\"]. Target: *." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_f54453b1fbf89d29_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_f5427c3c6112c498_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"bug\" \"enhancement\" \"question\" \"documentation\"]. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_f5427c3c6112c498_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -506,6 +554,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -514,13 +563,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -545,10 +595,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_5cf2254bdcfe4a71_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_5cf2254bdcfe4a71_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -600,7 +650,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -609,8 +659,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -619,9 +669,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -637,23 +688,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -662,7 +714,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -680,7 +732,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -716,6 +768,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -741,7 +794,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -759,8 +818,12 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -791,17 +854,21 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-handle-bug" + group: "gh-aw-conclusion-handle-bug-${{ inputs.issue_number }}" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -823,14 +890,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Bug Handler" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -844,7 +914,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Bug Handler" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -869,26 +953,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Bug Handler" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -899,9 +968,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -923,7 +995,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -977,9 +1049,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -989,17 +1063,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1045,6 +1120,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/handle-bug" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "handle-bug" @@ -1060,9 +1136,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1094,7 +1173,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\"],\"max\":1,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1102,11 +1181,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: ${{ needs.activation.outputs.artifact_prefix }}safe-output-items + name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/handle-documentation.lock.yml b/.github/workflows/handle-documentation.lock.yml index 9527b0285..2be530a2a 100644 --- a/.github/workflows/handle-documentation.lock.yml +++ b/.github/workflows/handle-documentation.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258058e9a5e3bb707bbcfc9157b7b69f64c06547642da2526a1ff441e3a358dd","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Handles issues classified as documentation-related by the triage classifier # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"258058e9a5e3bb707bbcfc9157b7b69f64c06547642da2526a1ff441e3a358dd","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Documentation Handler" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} @@ -61,14 +75,17 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Resolve host repo for activation checkout id: resolve-host-repo uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -82,23 +99,23 @@ jobs: id: artifact-prefix env: INPUTS_JSON: ${{ toJSON(inputs) }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Documentation Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -112,7 +129,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Cross-repo setup guidance @@ -132,10 +149,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "handle-documentation.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -145,7 +163,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -167,7 +185,7 @@ jobs: GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_c1995fcb77e4eb7d_EOF' @@ -265,12 +283,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -279,6 +297,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -300,16 +320,21 @@ jobs: outputs: artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -321,22 +346,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -353,137 +379,159 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Parse integrity filter lists id: parse-guard-vars env: GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_f287fa0f078c345e_EOF' - {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["documentation"],"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f287fa0f078c345e_EOF' + {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["documentation"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_f287fa0f078c345e_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_9186567e14d4ccb7_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", - "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"documentation\"]. Target: *." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_9186567e14d4ccb7_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_ac435a81bb29f986_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"documentation\"]. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_ac435a81bb29f986_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -506,6 +554,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -514,13 +563,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -545,10 +595,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_728828b4ea6e4249_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_728828b4ea6e4249_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -600,7 +650,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -609,8 +659,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -619,9 +669,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -637,23 +688,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -662,7 +714,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -680,7 +732,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -716,6 +768,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -741,7 +794,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -759,8 +818,12 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -791,17 +854,21 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-handle-documentation" + group: "gh-aw-conclusion-handle-documentation-${{ inputs.issue_number }}" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -823,14 +890,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Documentation Handler" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -844,7 +914,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Documentation Handler" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -869,26 +953,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Documentation Handler" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -899,9 +968,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -923,7 +995,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -977,9 +1049,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -989,17 +1063,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1045,6 +1120,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/handle-documentation" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "handle-documentation" @@ -1060,9 +1136,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1094,7 +1173,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"documentation\"],\"max\":1,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"documentation\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1102,11 +1181,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: ${{ needs.activation.outputs.artifact_prefix }}safe-output-items + name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/handle-enhancement.lock.yml b/.github/workflows/handle-enhancement.lock.yml index 796a875f4..7d39e9d12 100644 --- a/.github/workflows/handle-enhancement.lock.yml +++ b/.github/workflows/handle-enhancement.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"0a1cd53da97b1be36f489e58d1153583dc96c9b436fab3392437a8d498d4d8fb","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Handles issues classified as enhancements by the triage classifier # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"0a1cd53da97b1be36f489e58d1153583dc96c9b436fab3392437a8d498d4d8fb","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Enhancement Handler" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} @@ -61,14 +75,17 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Resolve host repo for activation checkout id: resolve-host-repo uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -82,23 +99,23 @@ jobs: id: artifact-prefix env: INPUTS_JSON: ${{ toJSON(inputs) }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Enhancement Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -112,7 +129,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Cross-repo setup guidance @@ -132,10 +149,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "handle-enhancement.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -145,7 +163,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -167,7 +185,7 @@ jobs: GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_192f9f111edce454_EOF' @@ -265,12 +283,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -279,6 +297,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -300,16 +320,21 @@ jobs: outputs: artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -321,22 +346,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -353,137 +379,159 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Parse integrity filter lists id: parse-guard-vars env: GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_7a0b9826ce5c2de6_EOF' - {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["enhancement"],"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7a0b9826ce5c2de6_EOF' + {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["enhancement"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_7a0b9826ce5c2de6_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_55cb1dd58b982eb8_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", - "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"enhancement\"]. Target: *." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_55cb1dd58b982eb8_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_867d9d8b6cddeef7_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"enhancement\"]. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_867d9d8b6cddeef7_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -506,6 +554,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -514,13 +563,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -545,10 +595,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_fc710c56a8354bbf_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_fc710c56a8354bbf_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -600,7 +650,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -609,8 +659,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -619,9 +669,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -637,23 +688,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -662,7 +714,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -680,7 +732,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -716,6 +768,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -741,7 +794,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -759,8 +818,12 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -791,17 +854,21 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-handle-enhancement" + group: "gh-aw-conclusion-handle-enhancement-${{ inputs.issue_number }}" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -823,14 +890,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Enhancement Handler" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -844,7 +914,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Enhancement Handler" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -869,26 +953,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Enhancement Handler" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -899,9 +968,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -923,7 +995,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -977,9 +1049,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -989,17 +1063,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1045,6 +1120,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/handle-enhancement" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "handle-enhancement" @@ -1060,9 +1136,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1094,7 +1173,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"enhancement\"],\"max\":1,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"enhancement\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1102,11 +1181,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: ${{ needs.activation.outputs.artifact_prefix }}safe-output-items + name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/handle-question.lock.yml b/.github/workflows/handle-question.lock.yml index 545c90428..71def2f69 100644 --- a/.github/workflows/handle-question.lock.yml +++ b/.github/workflows/handle-question.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fb6cc48845814496ea0da474d3030f9e02e7d38b5bb346b70ca525c06c271cb1","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Handles issues classified as questions by the triage classifier # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"fb6cc48845814496ea0da474d3030f9e02e7d38b5bb346b70ca525c06c271cb1","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Question Handler" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }} @@ -61,14 +75,17 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }} target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }} target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Resolve host repo for activation checkout id: resolve-host-repo uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -82,23 +99,23 @@ jobs: id: artifact-prefix env: INPUTS_JSON: ${{ toJSON(inputs) }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Question Handler" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -112,7 +129,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Cross-repo setup guidance @@ -132,10 +149,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "handle-question.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -145,7 +163,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -167,7 +185,7 @@ jobs: GH_AW_INPUTS_ISSUE_NUMBER: ${{ inputs.issue_number }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_0e4131663d1691aa_EOF' @@ -265,12 +283,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -279,6 +297,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -300,16 +320,21 @@ jobs: outputs: artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -321,22 +346,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -353,137 +379,159 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Parse integrity filter lists id: parse-guard-vars env: GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_f18ff0beb4e2bc07_EOF' - {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["question"],"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f18ff0beb4e2bc07_EOF' + {"add_comment":{"max":1,"target":"*"},"add_labels":{"allowed":["question"],"max":1,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_f18ff0beb4e2bc07_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_dfb368f7c5d55467_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", - "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"question\"]. Target: *." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_dfb368f7c5d55467_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_22ca2e095453dc27_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: *.", + "add_labels": " CONSTRAINTS: Maximum 1 label(s) can be added. Only these labels are allowed: [\"question\"]. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_22ca2e095453dc27_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -506,6 +554,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -514,13 +563,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -545,10 +595,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_878c9f46d6eeb406_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_878c9f46d6eeb406_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -600,7 +650,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -609,8 +659,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -619,9 +669,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -637,23 +688,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -662,7 +714,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -680,7 +732,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -716,6 +768,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -741,7 +794,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -759,8 +818,12 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -791,17 +854,21 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-handle-question" + group: "gh-aw-conclusion-handle-question-${{ inputs.issue_number }}" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -823,14 +890,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Question Handler" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -844,7 +914,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Question Handler" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -869,26 +953,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Question Handler" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -899,9 +968,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -923,7 +995,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -977,9 +1049,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -989,17 +1063,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1045,6 +1120,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/handle-question" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "handle-question" @@ -1060,9 +1136,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1094,7 +1173,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"question\"],\"max\":1,\"target\":\"*\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"*\"},\"add_labels\":{\"allowed\":[\"question\"],\"max\":1,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1102,11 +1181,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: ${{ needs.activation.outputs.artifact_prefix }}safe-output-items + name: ${{ needs.activation.outputs.artifact_prefix }}safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/issue-classification.lock.yml b/.github/workflows/issue-classification.lock.yml index 939382dee..e7d194804 100644 --- a/.github/workflows/issue-classification.lock.yml +++ b/.github/workflows/issue-classification.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1c9f9a62a510a7796b96187fbe0537fd05da1c082d8fab86cd7b99bf001aee01","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Classifies newly opened issues and delegates to type-specific handler workflows # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"1c9f9a62a510a7796b96187fbe0537fd05da1c082d8fab86cd7b99bf001aee01","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Issue Classification Agent" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: body: ${{ steps.sanitized.outputs.body }} @@ -61,29 +75,32 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} text: ${{ steps.sanitized.outputs.text }} title: ${{ steps.sanitized.outputs.title }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Issue Classification Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -96,7 +113,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders @@ -108,10 +125,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "issue-classification.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -121,7 +139,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -153,7 +171,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_0e5e0cb2acba7dc0_EOF' @@ -255,12 +273,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -269,6 +287,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -287,16 +307,21 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: issueclassification outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -308,22 +333,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -340,206 +366,228 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Parse integrity filter lists id: parse-guard-vars env: GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_0e1d49da13fc6a56_EOF' - {"add_comment":{"max":1,"target":"triggering"},"call_workflow":{"max":1,"workflow_files":{"handle-bug":"./.github/workflows/handle-bug.lock.yml","handle-documentation":"./.github/workflows/handle-documentation.lock.yml","handle-enhancement":"./.github/workflows/handle-enhancement.lock.yml","handle-question":"./.github/workflows/handle-question.lock.yml"},"workflows":["handle-bug","handle-enhancement","handle-question","handle-documentation"]},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0e1d49da13fc6a56_EOF' + {"add_comment":{"max":1,"target":"triggering"},"call_workflow":{"max":1,"workflow_files":{"handle-bug":"./.github/workflows/handle-bug.lock.yml","handle-documentation":"./.github/workflows/handle-documentation.lock.yml","handle-enhancement":"./.github/workflows/handle-enhancement.lock.yml","handle-question":"./.github/workflows/handle-question.lock.yml"},"workflows":["handle-bug","handle-enhancement","handle-question","handle-documentation"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_0e1d49da13fc6a56_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_cb7604137f200fa1_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: triggering." - }, - "repo_params": {}, - "dynamic_tools": [ - { - "_call_workflow_name": "handle-bug", - "description": "Call the 'handle-bug' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "issue_number": { - "description": "Input parameter 'issue_number' for workflow handle-bug", - "type": "string" + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added. Target: triggering." + }, + "repo_params": {}, + "dynamic_tools": [ + { + "_call_workflow_name": "handle-bug", + "description": "Call the 'handle-bug' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "issue_number": { + "description": "Input parameter 'issue_number' for workflow handle-bug", + "type": "string" + }, + "payload": { + "description": "Input parameter 'payload' for workflow handle-bug", + "type": "string" + } }, - "payload": { - "description": "Input parameter 'payload' for workflow handle-bug", - "type": "string" - } + "required": [ + "issue_number" + ], + "type": "object" }, - "required": [ - "issue_number" - ], - "type": "object" + "name": "handle_bug" }, - "name": "handle_bug" - }, - { - "_call_workflow_name": "handle-enhancement", - "description": "Call the 'handle-enhancement' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "issue_number": { - "description": "Input parameter 'issue_number' for workflow handle-enhancement", - "type": "string" + { + "_call_workflow_name": "handle-enhancement", + "description": "Call the 'handle-enhancement' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "issue_number": { + "description": "Input parameter 'issue_number' for workflow handle-enhancement", + "type": "string" + }, + "payload": { + "description": "Input parameter 'payload' for workflow handle-enhancement", + "type": "string" + } }, - "payload": { - "description": "Input parameter 'payload' for workflow handle-enhancement", - "type": "string" - } + "required": [ + "issue_number" + ], + "type": "object" }, - "required": [ - "issue_number" - ], - "type": "object" + "name": "handle_enhancement" }, - "name": "handle_enhancement" - }, - { - "_call_workflow_name": "handle-question", - "description": "Call the 'handle-question' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "issue_number": { - "description": "Input parameter 'issue_number' for workflow handle-question", - "type": "string" + { + "_call_workflow_name": "handle-question", + "description": "Call the 'handle-question' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "issue_number": { + "description": "Input parameter 'issue_number' for workflow handle-question", + "type": "string" + }, + "payload": { + "description": "Input parameter 'payload' for workflow handle-question", + "type": "string" + } }, - "payload": { - "description": "Input parameter 'payload' for workflow handle-question", - "type": "string" - } + "required": [ + "issue_number" + ], + "type": "object" }, - "required": [ - "issue_number" - ], - "type": "object" + "name": "handle_question" }, - "name": "handle_question" - }, - { - "_call_workflow_name": "handle-documentation", - "description": "Call the 'handle-documentation' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "issue_number": { - "description": "Input parameter 'issue_number' for workflow handle-documentation", - "type": "string" + { + "_call_workflow_name": "handle-documentation", + "description": "Call the 'handle-documentation' reusable workflow via workflow_call. This workflow must support workflow_call and be in .github/workflows/ directory in the same repository.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "issue_number": { + "description": "Input parameter 'issue_number' for workflow handle-documentation", + "type": "string" + }, + "payload": { + "description": "Input parameter 'payload' for workflow handle-documentation", + "type": "string" + } }, - "payload": { - "description": "Input parameter 'payload' for workflow handle-documentation", - "type": "string" - } + "required": [ + "issue_number" + ], + "type": "object" }, - "required": [ - "issue_number" - ], - "type": "object" - }, - "name": "handle_documentation" - } - ] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_cb7604137f200fa1_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_5ae9c10ad5b5014d_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + "name": "handle_documentation" } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + ] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_5ae9c10ad5b5014d_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -562,6 +610,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -570,13 +619,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -601,10 +651,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_5ad084c2b5bc2d53_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_5ad084c2b5bc2d53_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -656,7 +706,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -665,8 +715,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -675,9 +725,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -693,23 +744,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -718,7 +770,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -736,7 +788,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -772,6 +824,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -797,7 +850,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -815,8 +874,12 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -837,6 +900,7 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'handle-bug' permissions: + actions: read contents: read discussions: write issues: write @@ -851,6 +915,7 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'handle-documentation' permissions: + actions: read contents: read discussions: write issues: write @@ -865,6 +930,7 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'handle-enhancement' permissions: + actions: read contents: read discussions: write issues: write @@ -879,6 +945,7 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'handle-question' permissions: + actions: read contents: read discussions: write issues: write @@ -910,14 +977,18 @@ jobs: group: "gh-aw-conclusion-issue-classification" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -939,14 +1010,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Issue Classification Agent" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -960,7 +1034,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Issue Classification Agent" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -985,26 +1073,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Classification Agent" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -1015,9 +1088,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1039,7 +1115,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -1093,9 +1169,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1105,17 +1183,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1148,6 +1227,7 @@ jobs: safe_outputs: needs: + - activation - agent - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' @@ -1160,6 +1240,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-classification" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "issue-classification" @@ -1177,9 +1258,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1211,7 +1295,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"triggering\"},\"call_workflow\":{\"max\":1,\"workflow_files\":{\"handle-bug\":\"./.github/workflows/handle-bug.lock.yml\",\"handle-documentation\":\"./.github/workflows/handle-documentation.lock.yml\",\"handle-enhancement\":\"./.github/workflows/handle-enhancement.lock.yml\",\"handle-question\":\"./.github/workflows/handle-question.lock.yml\"},\"workflows\":[\"handle-bug\",\"handle-enhancement\",\"handle-question\",\"handle-documentation\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"triggering\"},\"call_workflow\":{\"max\":1,\"workflow_files\":{\"handle-bug\":\"./.github/workflows/handle-bug.lock.yml\",\"handle-documentation\":\"./.github/workflows/handle-documentation.lock.yml\",\"handle-enhancement\":\"./.github/workflows/handle-enhancement.lock.yml\",\"handle-question\":\"./.github/workflows/handle-question.lock.yml\"},\"workflows\":[\"handle-bug\",\"handle-enhancement\",\"handle-question\",\"handle-documentation\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1219,11 +1303,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: safe-output-items + name: safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/issue-triage.lock.yml b/.github/workflows/issue-triage.lock.yml index 72f450614..916737807 100644 --- a/.github/workflows/issue-triage.lock.yml +++ b/.github/workflows/issue-triage.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"22ed351fca21814391eea23a7470028e8321a9e2fe21fb95e31b13d0353aee4b","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Triages newly opened issues by labeling, acknowledging, requesting clarification, and closing duplicates # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"22ed351fca21814391eea23a7470028e8321a9e2fe21fb95e31b13d0353aee4b","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Issue Triage Agent" "on": @@ -53,6 +66,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: body: ${{ steps.sanitized.outputs.body }} @@ -61,29 +75,32 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} text: ${{ steps.sanitized.outputs.text }} title: ${{ steps.sanitized.outputs.title }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Issue Triage Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -96,7 +113,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders @@ -108,10 +125,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "issue-triage.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -121,7 +139,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -153,7 +171,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_e74a3944dc48d8ab_EOF' @@ -255,12 +273,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -269,6 +287,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -287,16 +307,21 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: issuetriage outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -308,22 +333,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -340,9 +366,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -354,200 +382,220 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_6607c9cdef4a0243_EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["bug","enhancement","question","documentation","sdk/dotnet","sdk/go","sdk/nodejs","sdk/python","priority/high","priority/low","testing","security","needs-info","duplicate"],"max":10,"target":"triggering"},"close_issue":{"max":1,"target":"triggering"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"update_issue":{"allow_body":true,"max":1,"target":"triggering"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6607c9cdef4a0243_EOF' + {"add_comment":{"max":2},"add_labels":{"allowed":["bug","enhancement","question","documentation","sdk/dotnet","sdk/go","sdk/nodejs","sdk/python","priority/high","priority/low","testing","security","needs-info","duplicate"],"max":10,"target":"triggering"},"close_issue":{"max":1,"target":"triggering"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_issue":{"allow_body":true,"max":1,"target":"triggering"}} GH_AW_SAFE_OUTPUTS_CONFIG_6607c9cdef4a0243_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_1e926a46832e5e70_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 2 comment(s) can be added.", - "add_labels": " CONSTRAINTS: Maximum 10 label(s) can be added. Only these labels are allowed: [\"bug\" \"enhancement\" \"question\" \"documentation\" \"sdk/dotnet\" \"sdk/go\" \"sdk/nodejs\" \"sdk/python\" \"priority/high\" \"priority/low\" \"testing\" \"security\" \"needs-info\" \"duplicate\"]. Target: triggering.", - "close_issue": " CONSTRAINTS: Maximum 1 issue(s) can be closed. Target: triggering.", - "update_issue": " CONSTRAINTS: Maximum 1 issue(s) can be updated. Target: triggering." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_1e926a46832e5e70_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_5410882353594841_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 2 comment(s) can be added.", + "add_labels": " CONSTRAINTS: Maximum 10 label(s) can be added. Only these labels are allowed: [\"bug\" \"enhancement\" \"question\" \"documentation\" \"sdk/dotnet\" \"sdk/go\" \"sdk/nodejs\" \"sdk/python\" \"priority/high\" \"priority/low\" \"testing\" \"security\" \"needs-info\" \"duplicate\"]. Target: triggering.", + "close_issue": " CONSTRAINTS: Maximum 1 issue(s) can be closed. Target: triggering.", + "update_issue": " CONSTRAINTS: Maximum 1 issue(s) can be updated. Target: triggering." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "add_labels": { - "defaultMax": 5, - "fields": { - "item_number": { - "issueNumberOrTemporaryId": true - }, - "labels": { - "required": true, - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "close_issue": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "issue_number": { - "optionalPositiveInteger": true - }, - "repo": { - "type": "string", - "maxLength": 256 + }, + "close_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } } - } - }, - "update_issue": { - "defaultMax": 1, - "fields": { - "assignees": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 39 - }, - "body": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "issue_number": { - "issueOrPRNumber": true - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "milestone": { - "optionalPositiveInteger": true - }, - "operation": { - "type": "string", - "enum": [ - "replace", - "append", - "prepend", - "replace-island" - ] - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "status": { - "type": "string", - "enum": [ - "open", - "closed" - ] - }, - "title": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } }, - "customValidation": "requiresOneOf:status,title,body" + "update_issue": { + "defaultMax": 1, + "fields": { + "assignees": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 39 + }, + "body": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "issue_number": { + "issueOrPRNumber": true + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "milestone": { + "optionalPositiveInteger": true + }, + "operation": { + "type": "string", + "enum": [ + "replace", + "append", + "prepend", + "replace-island" + ] + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "status": { + "type": "string", + "enum": [ + "open", + "closed" + ] + }, + "title": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + }, + "customValidation": "requiresOneOf:status,title,body" + } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_5410882353594841_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -570,6 +618,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -578,13 +627,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -611,10 +661,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_b6b29985f1ee0a9c_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_b6b29985f1ee0a9c_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -663,7 +713,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -672,8 +722,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -682,9 +732,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -700,23 +751,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -725,7 +777,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -743,7 +795,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -779,6 +831,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -804,7 +857,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -822,8 +881,10 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -857,14 +918,18 @@ jobs: group: "gh-aw-conclusion-issue-triage" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -886,14 +951,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Issue Triage Agent" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -907,7 +975,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Issue Triage Agent" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -932,26 +1014,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Issue Triage Agent" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -962,9 +1029,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -986,7 +1056,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -1040,9 +1110,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1052,17 +1124,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1095,6 +1168,7 @@ jobs: safe_outputs: needs: + - activation - agent - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' @@ -1107,6 +1181,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-triage" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "issue-triage" @@ -1122,9 +1197,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1156,7 +1234,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"max\":1,\"target\":\"triggering\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2},\"add_labels\":{\"allowed\":[\"bug\",\"enhancement\",\"question\",\"documentation\",\"sdk/dotnet\",\"sdk/go\",\"sdk/nodejs\",\"sdk/python\",\"priority/high\",\"priority/low\",\"testing\",\"security\",\"needs-info\",\"duplicate\"],\"max\":10,\"target\":\"triggering\"},\"close_issue\":{\"max\":1,\"target\":\"triggering\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_issue\":{\"allow_body\":true,\"max\":1,\"target\":\"triggering\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1164,11 +1242,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: safe-output-items + name: safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/release-changelog.lock.yml b/.github/workflows/release-changelog.lock.yml index 52469db8c..ea2359408 100644 --- a/.github/workflows/release-changelog.lock.yml +++ b/.github/workflows/release-changelog.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c06cce5802b74e1280963eef2e92515d84870d76d9cfdefa84b56c038e2b8da1","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,19 @@ # # Generates release notes from merged PRs/commits. Triggered by the publish workflow or manually via workflow_dispatch. # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"c06cce5802b74e1280963eef2e92515d84870d76d9cfdefa84b56c038e2b8da1","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_CI_TRIGGER_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "Release Changelog Generator" "on": @@ -49,6 +63,7 @@ jobs: activation: runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: comment_id: "" @@ -56,27 +71,30 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "Release Changelog Generator" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -89,7 +107,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders @@ -101,10 +119,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "release-changelog.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -114,7 +133,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -136,7 +155,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_41d0179c6df1e6c3_EOF' @@ -238,12 +257,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -252,6 +271,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -271,16 +292,21 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: releasechangelog outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -292,22 +318,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -324,9 +351,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -338,150 +367,170 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_185484bc160cdce2_EOF' - {"create_pull_request":{"draft":false,"labels":["automation","changelog"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[changelog] "},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"update_release":{"max":1}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_185484bc160cdce2_EOF' + {"create_pull_request":{"draft":false,"labels":["automation","changelog"],"max":1,"max_patch_size":1024,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS"],"protected_path_prefixes":[".github/",".agents/"],"title_prefix":"[changelog] "},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"update_release":{"max":1}} GH_AW_SAFE_OUTPUTS_CONFIG_185484bc160cdce2_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_babbee46c40b8cae_EOF' - { - "description_suffixes": { - "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[changelog] \". Labels [\"automation\" \"changelog\"] will be automatically added.", - "update_release": " CONSTRAINTS: Maximum 1 release(s) can be updated." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_babbee46c40b8cae_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_08c08010b2b8ffb8_EOF' - { - "create_pull_request": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "branch": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "draft": { - "type": "boolean" - }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created. Title will be prefixed with \"[changelog] \". Labels [\"automation\" \"changelog\"] will be automatically added.", + "update_release": " CONSTRAINTS: Maximum 1 release(s) can be updated." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_pull_request": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } } - } - }, - "update_release": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "operation": { - "required": true, - "type": "string", - "enum": [ - "replace", - "append", - "prepend" - ] - }, - "tag": { - "type": "string", - "sanitize": true, - "maxLength": 256 + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + }, + "update_release": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "operation": { + "required": true, + "type": "string", + "enum": [ + "replace", + "append", + "prepend" + ] + }, + "tag": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_08c08010b2b8ffb8_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -504,6 +553,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -512,13 +562,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -545,10 +596,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_d0d73da3b3e2991f_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_d0d73da3b3e2991f_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -597,7 +648,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -606,8 +657,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -616,9 +667,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -634,23 +686,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -659,7 +712,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -677,7 +730,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -713,6 +766,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -738,7 +792,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -756,8 +816,10 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -790,14 +852,18 @@ jobs: group: "gh-aw-conclusion-release-changelog" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -819,14 +885,17 @@ jobs: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Release Changelog Generator" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -840,7 +909,21 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Release Changelog Generator" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -867,40 +950,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Release Changelog Generator" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Handle Create Pull Request Error - id: handle_create_pr_error - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Release Changelog Generator" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_create_pr_error.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -911,9 +965,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -935,7 +992,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -989,9 +1046,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1001,17 +1060,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1056,6 +1116,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/release-changelog" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_WORKFLOW_ID: "release-changelog" @@ -1071,9 +1132,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1133,7 +1197,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"automation\",\"changelog\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[changelog] \"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"update_release\":{\"max\":1}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"draft\":false,\"labels\":[\"automation\",\"changelog\"],\"max\":1,\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"CODEOWNERS\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"title_prefix\":\"[changelog] \"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"update_release\":{\"max\":1}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1142,11 +1206,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: safe-output-items + name: safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore diff --git a/.github/workflows/release-changelog.md b/.github/workflows/release-changelog.md index 30e700dec..aba79d6f5 100644 --- a/.github/workflows/release-changelog.md +++ b/.github/workflows/release-changelog.md @@ -46,12 +46,18 @@ Use the GitHub API to fetch the release corresponding to `${{ github.event.input ### Step 1: Identify the version range -1. The **new version** is the release tag: `${{ github.event.inputs.tag }}` -2. Fetch the release metadata to determine if this is a **stable** or **prerelease** release. -3. Determine the **previous version** to diff against: +1. **Before any `git log`, `git show`, tag lookup, or commit-range query, first convert the workflow checkout into a full clone by running:** + ```bash + git fetch --prune --tags --unshallow origin || git fetch --prune --tags origin + ``` + This is **mandatory**. The workflow checkout may be shallow, which can make tag ranges and commit counts incomplete or outright wrong. Do not trust local git history until this command succeeds. +2. The **new version** is the release tag: `${{ github.event.inputs.tag }}` +3. Fetch the release metadata to determine if this is a **stable** or **prerelease** release. +4. Determine the **previous version** to diff against: - **For stable releases**: find the previous **stable** release (skip prereleases). Check `CHANGELOG.md` for the most recent version heading (`## [vX.Y.Z](...)`), or fall back to listing releases via the API. This means stable changelogs include ALL changes since the last stable release, even if some were already mentioned in prerelease notes. - **For prerelease releases**: find the most recent release of **any kind** (stable or prerelease) that precedes this one. This way prerelease notes only cover what's new since the last release. -4. If no previous release exists at all, use the first commit in the repo as the starting point. +5. If no previous release exists at all, use the first commit in the repo as the starting point. +6. After identifying the range, verify it by listing the commits in `PREVIOUS_TAG..NEW_TAG`. If the local result still looks suspiciously small or inconsistent, do **not** proceed based on local git alone — use the GitHub tools as the source of truth for the commits and PRs in the release. ### Step 2: Gather changes diff --git a/.github/workflows/sdk-consistency-review.lock.yml b/.github/workflows/sdk-consistency-review.lock.yml index 2d71e1a53..06abc2399 100644 --- a/.github/workflows/sdk-consistency-review.lock.yml +++ b/.github/workflows/sdk-consistency-review.lock.yml @@ -1,3 +1,5 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b1f707a5df4bab2e9be118c097a5767ac0b909cf3ee1547f71895c5b33ca342d","compiler_version":"v0.67.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"ed597411d8f924073f98dfc5c65a23a2325f34cd","version":"v8"},{"repo":"actions/upload-artifact","sha":"bbbca2ddaa5d8feaa63e36b76fdaad77386f024f","version":"v7"},{"repo":"github/gh-aw-actions/setup","sha":"9d6ae06250fc0ec536a0e5f35de313b35bad7246","version":"v0.67.4"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.65.5). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.67.4). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -22,7 +24,18 @@ # # Reviews PRs to ensure features are implemented consistently across all SDK language implementations # -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"b1f707a5df4bab2e9be118c097a5767ac0b909cf3ee1547f71895c5b33ca342d","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"} +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 +# - actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 +# - github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 name: "SDK Consistency Review Agent" "on": @@ -62,6 +75,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.id == github.repository_id runs-on: ubuntu-slim permissions: + actions: read contents: read outputs: body: ${{ steps.sanitized.outputs.body }} @@ -70,29 +84,32 @@ jobs: lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} text: ${{ steps.sanitized.outputs.text }} title: ${{ steps.sanitized.outputs.title }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'auto' }} - GH_AW_INFO_VERSION: "latest" - GH_AW_INFO_AGENT_VERSION: "latest" - GH_AW_INFO_CLI_VERSION: "v0.65.5" + GH_AW_INFO_VERSION: "1.0.20" + GH_AW_INFO_AGENT_VERSION: "1.0.20" + GH_AW_INFO_CLI_VERSION: "v0.67.4" GH_AW_INFO_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.10" + GH_AW_INFO_AWF_VERSION: "v0.25.18" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -105,7 +122,7 @@ jobs: await main(core, context); - name: Validate COPILOT_GITHUB_TOKEN secret id: validate-secret - run: ${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders @@ -117,10 +134,11 @@ jobs: .agents sparse-checkout-cone-mode: true fetch-depth: 1 - - name: Check workflow file timestamps + - name: Check workflow lock file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_WORKFLOW_FILE: "sdk-consistency-review.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -130,7 +148,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_COMPILED_VERSION: "v0.65.5" + GH_AW_COMPILED_VERSION: "v0.67.4" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -161,7 +179,7 @@ jobs: GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} # poutine:ignore untrusted_checkout_exec run: | - bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { cat << 'GH_AW_PROMPT_ba8cce6b4497d40e_EOF' @@ -260,12 +278,12 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - name: Print prompt env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt # poutine:ignore untrusted_checkout_exec - run: bash ${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - name: Upload activation artifact if: success() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 @@ -274,6 +292,8 @@ jobs: path: | /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore retention-days: 1 agent: @@ -292,16 +312,21 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: sdkconsistencyreview outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }} model: ${{ needs.activation.outputs.model }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Set runtime paths id: set-runtime-paths run: | @@ -313,22 +338,23 @@ jobs: with: persist-credentials: false - name: Create gh-aw temp directory - run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise - run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" env: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Checkout PR branch id: checkout-pr @@ -345,9 +371,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -359,144 +387,164 @@ jobs: const determineAutomaticLockdown = require('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 ghcr.io/github/gh-aw-mcpg:v0.2.17 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - name: Write Safe Outputs Config run: | - mkdir -p ${RUNNER_TEMP}/gh-aw/safeoutputs + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_8507857a3b512809_EOF' - {"add_comment":{"hide_older_comments":true,"max":1},"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}} + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_8507857a3b512809_EOF' + {"add_comment":{"hide_older_comments":true,"max":1},"create_pull_request_review_comment":{"max":10,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} GH_AW_SAFE_OUTPUTS_CONFIG_8507857a3b512809_EOF - name: Write Safe Outputs Tools - run: | - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_8ec735aad8c63cb6_EOF' - { - "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.", - "create_pull_request_review_comment": " CONSTRAINTS: Maximum 10 review comment(s) can be created. Comments will be on the RIGHT side of the diff." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_SAFE_OUTPUTS_TOOLS_META_8ec735aad8c63cb6_EOF - cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_2e992de302865324_EOF' - { - "add_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "item_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 1 comment(s) can be added.", + "create_pull_request_review_comment": " CONSTRAINTS: Maximum 10 review comment(s) can be created. Comments will be on the RIGHT side of the diff." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + } } - } - }, - "create_pull_request_review_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "line": { - "required": true, - "positiveInteger": true - }, - "path": { - "required": true, - "type": "string" - }, - "pull_request_number": { - "optionalPositiveInteger": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "side": { - "type": "string", - "enum": [ - "LEFT", - "RIGHT" - ] + }, + "create_pull_request_review_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "line": { + "required": true, + "positiveInteger": true + }, + "path": { + "required": true, + "type": "string" + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "side": { + "type": "string", + "enum": [ + "LEFT", + "RIGHT" + ] + }, + "start_line": { + "optionalPositiveInteger": true + } }, - "start_line": { - "optionalPositiveInteger": true + "customValidation": "startLineLessOrEqualLine" + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } } }, - "customValidation": "startLineLessOrEqualLine" - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } } } } - } - GH_AW_SAFE_OUTPUTS_VALIDATION_2e992de302865324_EOF - node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); - name: Generate Safe Outputs MCP Server Config id: safe-outputs-config run: | @@ -519,6 +567,7 @@ jobs: id: safe-outputs-start env: DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json @@ -527,13 +576,14 @@ jobs: run: | # Environment variables are set above to prevent template injection export DEBUG + export GH_AW_SAFE_OUTPUTS export GH_AW_SAFE_OUTPUTS_PORT export GH_AW_SAFE_OUTPUTS_API_KEY export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - bash ${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - name: Start MCP Gateway id: start-mcp-gateway @@ -560,10 +610,10 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.11' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.17' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_73099b6c804f5a74_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh + cat << GH_AW_MCP_CONFIG_73099b6c804f5a74_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" { "mcpServers": { "github": { @@ -612,7 +662,7 @@ jobs: path: /tmp/gh-aw - name: Clean git credentials continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): @@ -621,8 +671,8 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} @@ -631,9 +681,10 @@ jobs: GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -649,23 +700,24 @@ jobs: id: detect-inference-error if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} run: | git config --global user.email "github-actions[bot]@users.noreply.github.com" git config --global user.name "github-actions[bot]" git config --global am.keepcr true # Re-authenticate git with GitHub token SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" echo "Git configured with standard GitHub Actions identity" - name: Copy Copilot session state files to logs if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - name: Stop MCP Gateway if: always() continue-on-error: true @@ -674,7 +726,7 @@ jobs: MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} run: | - bash ${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -692,7 +744,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() - run: bash ${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - name: Copy Safe Outputs if: always() env: @@ -728,6 +780,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() + id: parse-mcp-gateway uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | @@ -753,7 +806,13 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -771,8 +830,10 @@ jobs: /tmp/gh-aw/sandbox/agent/logs/ /tmp/gh-aw/redacted-urls.log /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch @@ -806,14 +867,18 @@ jobs: group: "gh-aw-conclusion-sdk-consistency-review" cancel-in-progress: false outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -836,14 +901,17 @@ jobs: GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "SDK Consistency Review Agent" GH_AW_TRACKER_ID: "sdk-consistency-review" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/noop.cjs'); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); await main(); - - name: Record Missing Tool + - name: Record missing tool id: missing_tool uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: @@ -858,7 +926,22 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); await main(); - - name: Handle Agent Failure + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "SDK Consistency Review Agent" + GH_AW_TRACKER_ID: "sdk-consistency-review" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure id: handle_agent_failure if: always() uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 @@ -884,27 +967,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); await main(); - - name: Handle No-Op Message - id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "SDK Consistency Review Agent" - GH_AW_TRACKER_ID: "sdk-consistency-review" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); detection: - needs: agent + needs: + - activation + - agent if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest @@ -915,9 +982,12 @@ jobs: detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -939,7 +1009,7 @@ jobs: persist-credentials: false # --- Threat Detection --- - name: Download container images - run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.18 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.18 ghcr.io/github/gh-aw-firewall/squid:0.25.18 - name: Check if detection needed id: detection_guard if: always() @@ -993,9 +1063,11 @@ jobs: mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log - name: Install GitHub Copilot CLI - run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.20 + env: + GH_HOST: github.com - name: Install AWF binary - run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.18 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' id: detection_agentic_execution @@ -1005,17 +1077,18 @@ jobs: set -o pipefail touch /tmp/gh-aw/agent-step-summary.md # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --skip-pull --enable-api-proxy \ - -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.18 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.65.5 + GH_AW_VERSION: v0.67.4 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1048,6 +1121,7 @@ jobs: safe_outputs: needs: + - activation - agent - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' @@ -1060,6 +1134,7 @@ jobs: timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/sdk-consistency-review" + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} GH_AW_TRACKER_ID: "sdk-consistency-review" @@ -1076,9 +1151,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5 + id: setup + uses: github/gh-aw-actions/setup@9d6ae06250fc0ec536a0e5f35de313b35bad7246 # v0.67.4 with: destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1110,7 +1188,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_pull_request_review_comment\":{\"max\":10,\"side\":\"RIGHT\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"create_pull_request_review_comment\":{\"max\":10,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1118,11 +1196,11 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); await main(); - - name: Upload Safe Output Items + - name: Upload Safe Outputs Items if: always() uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 with: - name: safe-output-items + name: safe-outputs-items path: /tmp/gh-aw/safe-output-items.jsonl if-no-files-found: ignore From 34b9cb49654622e83d167446bb6d91d0d285c05b Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Fri, 10 Apr 2026 09:12:33 -0700 Subject: [PATCH 07/34] Change SDK status to public preview (#1054) Updated SDK status from technical preview to public preview. --- java/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/README.md b/java/README.md index c355c7297..f197cb549 100644 --- a/java/README.md +++ b/java/README.md @@ -12,7 +12,7 @@ Java SDK for programmatic control of GitHub Copilot CLI via JSON-RPC. **📦 The Java SDK is maintained in a separate repository: [`github/copilot-sdk-java`](https://github.com/github/copilot-sdk-java)** -> **Note:** This SDK is in technical preview and may change in breaking ways. +> **Note:** This SDK is in public preview and may change in breaking ways. ```java import com.github.copilot.sdk.CopilotClient; From 11fc542eadc0463bd670c4c04f4aad06d5de6c27 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Fri, 10 Apr 2026 09:13:28 -0700 Subject: [PATCH 08/34] Update Java section with Cookbook link (#1023) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4045c65f0..644152040 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ The GitHub Copilot SDK exposes the same engine behind Copilot CLI: a production- | **Python** | [`python/`](./python/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/python/README.md) | `pip install github-copilot-sdk` | | **Go** | [`go/`](./go/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/go/README.md) | `go get github.com/github/copilot-sdk/go` | | **.NET** | [`dotnet/`](./dotnet/) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/dotnet/README.md) | `dotnet add package GitHub.Copilot.SDK` | -| **Java** | [`github/copilot-sdk-java`](https://github.com/github/copilot-sdk-java) | WIP | Maven coordinates
`com.github:copilot-sdk-java`
See instructions for [Maven](https://github.com/github/copilot-sdk-java?tab=readme-ov-file#maven) and [Gradle](https://github.com/github/copilot-sdk-java?tab=readme-ov-file#gradle) | +| **Java** | [`github/copilot-sdk-java`](https://github.com/github/copilot-sdk-java) | [Cookbook](https://github.com/github/awesome-copilot/blob/main/cookbook/copilot-sdk/java/README.md) | Maven coordinates
`com.github:copilot-sdk-java`
See instructions for [Maven](https://github.com/github/copilot-sdk-java?tab=readme-ov-file#maven) and [Gradle](https://github.com/github/copilot-sdk-java?tab=readme-ov-file#gradle) | See the individual SDK READMEs for installation, usage examples, and API reference. From a76424073a62e7f285d000560671bd9ee353cb68 Mon Sep 17 00:00:00 2001 From: Sanzo <164551283+sanzofr@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:13:04 +0530 Subject: [PATCH 09/34] =?UTF-8?q?Docs:=20clarify=20Copilot=20CLI=20is=20bu?= =?UTF-8?q?ndled=20with=20SDKs=20and=20update=20installatio=E2=80=A6=20(#9?= =?UTF-8?q?88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add installation lnk * Move more heavily to bundled-first recommendation --------- Co-authored-by: Steve Sanderson --- README.md | 2 +- docs/index.md | 4 +- docs/setup/bundled-cli.md | 181 +++++--------------------------------- docs/setup/index.md | 13 ++- docs/setup/local-cli.md | 132 ++++++++------------------- 5 files changed, 70 insertions(+), 262 deletions(-) diff --git a/README.md b/README.md index 644152040..838847820 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Quick steps: 1. **(Optional) Install the Copilot CLI** For Node.js, Python, and .NET SDKs, the Copilot CLI is bundled automatically and no separate installation is required. -For the Go SDK, install the CLI manually or ensure `copilot` is available in your PATH. +For the Go SDK, [install the CLI manually](https://github.com/features/copilot/cli) or ensure `copilot` is available in your PATH. 2. **Install your preferred SDK** using the commands above. diff --git a/docs/index.md b/docs/index.md index 04ef99bd8..1b89439ae 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,8 +22,8 @@ Step-by-step tutorial that takes you from zero to a working Copilot app with str How to configure and deploy the SDK for your use case. -- [Local CLI](./setup/local-cli.md) — simplest path, uses your signed-in CLI -- [Bundled CLI](./setup/bundled-cli.md) — ship the CLI with your app +- [Default Setup (Bundled CLI)](./setup/bundled-cli.md) — the SDK includes the CLI automatically +- [Local CLI](./setup/local-cli.md) — use your own CLI binary or running instance - [Backend Services](./setup/backend-services.md) — server-side with headless CLI over TCP - [GitHub OAuth](./setup/github-oauth.md) — implement the OAuth flow - [Azure Managed Identity](./setup/azure-managed-identity.md) — BYOK with Azure AI Foundry diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md index 516b1fe21..7419d4c18 100644 --- a/docs/setup/bundled-cli.md +++ b/docs/setup/bundled-cli.md @@ -1,76 +1,43 @@ -# Bundled CLI Setup +# Default Setup (Bundled CLI) -Package the Copilot CLI alongside your application so users don't need to install or configure anything separately. Your app ships with everything it needs. +The Node.js, Python, and .NET SDKs include the Copilot CLI as a dependency — your app ships with everything it needs, with no extra installation or configuration required. -**Best for:** Desktop apps, standalone tools, Electron apps, distributable CLI utilities. +**Best for:** Most applications — desktop apps, standalone tools, CLI utilities, prototypes, and more. ## How It Works -Instead of relying on a globally installed CLI, you include the CLI binary in your application bundle. The SDK points to your bundled copy via the `cliPath` option. +When you install the SDK, the Copilot CLI binary is included automatically. The SDK starts it as a child process and communicates over stdio. There's nothing extra to configure. ```mermaid flowchart TB - subgraph Bundle["Your Distributed App"] + subgraph Bundle["Your Application"] App["Application Code"] SDK["SDK Client"] - CLIBin["Copilot CLI Binary
(bundled)"] + CLIBin["Copilot CLI Binary
(included with SDK)"] end App --> SDK - SDK -- "cliPath" --> CLIBin + SDK --> CLIBin CLIBin -- "API calls" --> Copilot["☁️ GitHub Copilot"] style Bundle fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 ``` **Key characteristics:** -- CLI binary ships with your app — no separate install needed -- You control the exact CLI version your app uses +- CLI binary is included with the SDK — no separate install needed +- The SDK manages the CLI version to ensure compatibility - Users authenticate through your app (or use env vars / BYOK) - Sessions are managed per-user on their machine -## Architecture: Bundled vs. Installed - -```mermaid -flowchart LR - subgraph Installed["Standard Setup"] - A1["Your App"] --> SDK1["SDK"] - SDK1 --> CLI1["Global CLI
(/usr/local/bin/copilot)"] - end - - subgraph Bundled["Bundled Setup"] - A2["Your App"] --> SDK2["SDK"] - SDK2 --> CLI2["Bundled CLI
(./vendor/copilot)"] - end - - style Installed fill:#161b22,stroke:#8b949e,color:#c9d1d9 - style Bundled fill:#0d1117,stroke:#3fb950,color:#c9d1d9 -``` - -## Setup - -### 1. Include the CLI in Your Project - -The CLI is distributed as part of the `@github/copilot` npm package. You can also obtain platform-specific binaries for your distribution pipeline. - -```bash -# The CLI is available from the @github/copilot package -npm install @github/copilot -``` - -### 2. Point the SDK to Your Bundled CLI +## Quick Start
Node.js / TypeScript ```typescript import { CopilotClient } from "@github/copilot-sdk"; -import path from "path"; -const client = new CopilotClient({ - // Point to the CLI binary in your app bundle - cliPath: path.join(__dirname, "vendor", "copilot"), -}); +const client = new CopilotClient(); const session = await client.createSession({ model: "gpt-4.1" }); const response = await session.sendAndWait({ prompt: "Hello!" }); @@ -87,11 +54,8 @@ await client.stop(); ```python from copilot import CopilotClient from copilot.session import PermissionHandler -from pathlib import Path -client = CopilotClient({ - "cli_path": str(Path(__file__).parent / "vendor" / "copilot"), -}) +client = CopilotClient() await client.start() session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-4.1") @@ -106,6 +70,8 @@ await client.stop()
Go +> **Note:** The Go SDK does not bundle the CLI. You must install the CLI separately or set `CLIPath` to point to an existing binary. See [Local CLI Setup](./local-cli.md) for details. + ```go package main @@ -120,9 +86,7 @@ import ( func main() { ctx := context.Background() - client := copilot.NewClient(&copilot.ClientOptions{ - CLIPath: "./vendor/copilot", - }) + client := copilot.NewClient(nil) if err := client.Start(ctx); err != nil { log.Fatal(err) } @@ -138,9 +102,7 @@ func main() { ```go -client := copilot.NewClient(&copilot.ClientOptions{ - CLIPath:"./vendor/copilot", -}) +client := copilot.NewClient(nil) if err := client.Start(ctx); err != nil { log.Fatal(err) } @@ -159,11 +121,7 @@ if d, ok := response.Data.(*copilot.AssistantMessageData); ok { .NET ```csharp -var client = new CopilotClient(new CopilotClientOptions -{ - CliPath = Path.Combine(AppContext.BaseDirectory, "vendor", "copilot"), -}); - +await using var client = new CopilotClient(); await using var session = await client.CreateSessionAsync( new SessionConfig { Model = "gpt-4.1" }); @@ -206,7 +164,7 @@ client.stop().get(); ## Authentication Strategies -When bundling, you need to decide how your users will authenticate. Here are the common patterns: +You need to decide how your users will authenticate. Here are the common patterns: ```mermaid flowchart TB @@ -225,13 +183,11 @@ flowchart TB ### Option A: User's Signed-In Credentials (Simplest) -The user signs in to the CLI once, and your bundled app uses those credentials. No extra code needed — this is the default behavior. +The user signs in to the CLI once, and your app uses those credentials. No extra code needed — this is the default behavior. ```typescript -const client = new CopilotClient({ - cliPath: path.join(__dirname, "vendor", "copilot"), - // Default: uses signed-in user credentials -}); +const client = new CopilotClient(); +// Default: uses signed-in user credentials ``` ### Option B: Token via Environment Variable @@ -240,7 +196,6 @@ Ship your app with instructions to set a token, or set it programmatically: ```typescript const client = new CopilotClient({ - cliPath: path.join(__dirname, "vendor", "copilot"), env: { COPILOT_GITHUB_TOKEN: getUserToken(), // Your app provides the token }, @@ -252,9 +207,7 @@ const client = new CopilotClient({ If you manage your own model provider keys, users don't need GitHub accounts at all: ```typescript -const client = new CopilotClient({ - cliPath: path.join(__dirname, "vendor", "copilot"), -}); +const client = new CopilotClient(); const session = await client.createSession({ model: "gpt-4.1", @@ -270,12 +223,10 @@ See the **[BYOK guide](../auth/byok.md)** for full details. ## Session Management -Bundled apps typically want named sessions so users can resume conversations: +Apps typically want named sessions so users can resume conversations: ```typescript -const client = new CopilotClient({ - cliPath: path.join(__dirname, "vendor", "copilot"), -}); +const client = new CopilotClient(); // Create a session tied to the user's project const sessionId = `project-${projectName}`; @@ -291,90 +242,6 @@ const resumed = await client.resumeSession(sessionId); Session state persists at `~/.copilot/session-state/{sessionId}/`. -## Distribution Patterns - -### Desktop App (Electron, Tauri) - -```mermaid -flowchart TB - subgraph Electron["Desktop App Package"] - UI["App UI"] --> Main["Main Process"] - Main --> SDK["SDK Client"] - SDK --> CLI["Copilot CLI
(in app resources)"] - end - CLI --> Cloud["☁️ GitHub Copilot"] - - style Electron fill:#0d1117,stroke:#58a6ff,color:#c9d1d9 -``` - -Include the CLI binary in your app's resources directory: - -```typescript -import { app } from "electron"; -import path from "path"; - -const cliPath = path.join( - app.isPackaged ? process.resourcesPath : __dirname, - "copilot" -); - -const client = new CopilotClient({ cliPath }); -``` - -### CLI Tool - -For distributable CLI tools, resolve the path relative to your binary: - -```typescript -import { fileURLToPath } from "url"; -import path from "path"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const cliPath = path.join(__dirname, "..", "vendor", "copilot"); - -const client = new CopilotClient({ cliPath }); -``` - -## Platform-Specific Binaries - -When distributing for multiple platforms, include the correct binary for each: - -``` -my-app/ -├── vendor/ -│ ├── copilot-darwin-arm64 # macOS Apple Silicon -│ ├── copilot-darwin-x64 # macOS Intel -│ ├── copilot-linux-x64 # Linux x64 -│ └── copilot-win-x64.exe # Windows x64 -└── src/ - └── index.ts -``` - -```typescript -import os from "os"; - -function getCLIPath(): string { - const platform = process.platform; // "darwin", "linux", "win32" - const arch = os.arch(); // "arm64", "x64" - const ext = platform === "win32" ? ".exe" : ""; - const name = `copilot-${platform}-${arch}${ext}`; - return path.join(__dirname, "vendor", name); -} - -const client = new CopilotClient({ - cliPath: getCLIPath(), -}); -``` - -## Limitations - -| Limitation | Details | -|------------|---------| -| **Bundle size** | CLI binary adds to your app's distribution size | -| **Updates** | You manage CLI version updates in your release cycle | -| **Platform builds** | Need separate binaries for each OS/architecture | -| **Single user** | Each bundled CLI instance serves one user | - ## When to Move On | Need | Next Guide | diff --git a/docs/setup/index.md b/docs/setup/index.md index 268e26688..68daaa008 100644 --- a/docs/setup/index.md +++ b/docs/setup/index.md @@ -38,8 +38,8 @@ The setup guides below help you configure each layer for your scenario. You're building a personal assistant, side project, or experimental app. You want the simplest path to getting Copilot in your code. **Start with:** -1. **[Local CLI](./local-cli.md)** — Use the CLI already signed in on your machine -2. **[Bundled CLI](./bundled-cli.md)** — Package everything into a standalone app +1. **[Default Setup](./bundled-cli.md)** — The SDK includes the CLI automatically — just install and go +2. **[Local CLI](./local-cli.md)** — Use your own CLI binary or running instance (advanced) ### 🏢 Internal App Developer @@ -82,8 +82,8 @@ Use this table to find the right guides based on what you need to do: | What you need | Guide | |---------------|-------| -| Simplest possible setup | [Local CLI](./local-cli.md) | -| Ship a standalone app with Copilot | [Bundled CLI](./bundled-cli.md) | +| Getting started quickly | [Default Setup (Bundled CLI)](./bundled-cli.md) | +| Use your own CLI binary or server | [Local CLI](./local-cli.md) | | Users sign in with GitHub | [GitHub OAuth](./github-oauth.md) | | Use your own model keys (OpenAI, Azure, etc.) | [BYOK](../auth/byok.md) | | Azure BYOK with Managed Identity (no API keys) | [Azure Managed Identity](./azure-managed-identity.md) | @@ -129,11 +129,10 @@ flowchart LR All guides assume you have: -- **Copilot CLI** installed ([Installation guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)) -- **One of the SDKs** installed: +- **One of the SDKs** installed (Node.js, Python, and .NET SDKs include the CLI automatically): - Node.js: `npm install @github/copilot-sdk` - Python: `pip install github-copilot-sdk` - - Go: `go get github.com/github/copilot-sdk/go` + - Go: `go get github.com/github/copilot-sdk/go` (requires separate CLI installation) - .NET: `dotnet add package GitHub.Copilot.SDK` If you're brand new, start with the **[Getting Started tutorial](../getting-started.md)** first, then come back here for production configuration. diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 77d7a5e66..48092b735 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -1,18 +1,18 @@ # Local CLI Setup -Use the Copilot SDK with a Copilot CLI instance signed in on your machine. Depending on the SDK, this may be a bundled CLI (included automatically) or a system-installed CLI available in your PATH. This is the simplest configuration — zero auth code, zero infrastructure. +Use a specific CLI binary instead of the SDK's bundled CLI. This is an advanced option — you supply the CLI path explicitly, and you are responsible for ensuring version compatibility with the SDK. -**Best for:** Personal projects, prototyping, local development, learning the SDK. +**Use when:** You need to pin a specific CLI version, or work with the Go SDK (which does not bundle a CLI). ## How It Works -When a Copilot CLI instance is available (either bundled with the SDK or installed on your system) and signed in, credentials are stored in the system keychain. The SDK automatically starts the CLI as a child process and uses those stored credentials. +By default, the Node.js, Python, and .NET SDKs include their own CLI dependency (see [Default Setup](./bundled-cli.md)). If you need to override this — for example, to use a system-installed CLI — you can use the `cliPath` option. ```mermaid flowchart LR subgraph YourMachine["Your Machine"] App["Your App"] --> SDK["SDK Client"] - SDK -- "stdio" --> CLI["Copilot CLI
(auto-started)"] + SDK -- "cliPath" --> CLI["Copilot CLI
(your own binary)"] CLI --> Keychain["🔐 System Keychain
(stored credentials)"] end CLI -- "API calls" --> Copilot["☁️ GitHub Copilot"] @@ -21,14 +21,14 @@ flowchart LR ``` **Key characteristics:** -- CLI is spawned automatically by the SDK (using a bundled CLI or a system-installed CLI if available) -- Authentication uses the signed-in user's credentials from the system keychain -- Communication happens over stdio (stdin/stdout) — no network ports -- Sessions are local to your machine +- You explicitly provide the CLI binary path +- You are responsible for CLI version compatibility with the SDK +- Authentication uses the signed-in user's credentials from the system keychain (or env vars) +- Communication happens over stdio -## Quick Start +## Configuration -The default configuration requires no options at all: +### Using a local CLI binary
Node.js / TypeScript @@ -36,9 +36,11 @@ The default configuration requires no options at all: ```typescript import { CopilotClient } from "@github/copilot-sdk"; -const client = new CopilotClient(); -const session = await client.createSession({ model: "gpt-4.1" }); +const client = new CopilotClient({ + cliPath: "/usr/local/bin/copilot", +}); +const session = await client.createSession({ model: "gpt-4.1" }); const response = await session.sendAndWait({ prompt: "Hello!" }); console.log(response?.data.content); @@ -54,7 +56,9 @@ await client.stop(); from copilot import CopilotClient from copilot.session import PermissionHandler -client = CopilotClient() +client = CopilotClient({ + "cli_path": "/usr/local/bin/copilot", +}) await client.start() session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-4.1") @@ -69,6 +73,8 @@ await client.stop()
Go +> **Note:** The Go SDK does not bundle a CLI, so you must always provide `CLIPath`. + ```go package main @@ -83,7 +89,9 @@ import ( func main() { ctx := context.Background() - client := copilot.NewClient(nil) + client := copilot.NewClient(&copilot.ClientOptions{ + CLIPath: "/usr/local/bin/copilot", + }) if err := client.Start(ctx); err != nil { log.Fatal(err) } @@ -91,15 +99,15 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) - } + fmt.Println(*response.Data.Content) } ``` ```go -client := copilot.NewClient(nil) +client := copilot.NewClient(&copilot.ClientOptions{ + CLIPath: "/usr/local/bin/copilot", +}) if err := client.Start(ctx); err != nil { log.Fatal(err) } @@ -107,9 +115,7 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) -} +fmt.Println(*response.Data.Content) ```
@@ -118,7 +124,11 @@ if d, ok := response.Data.(*copilot.AssistantMessageData); ok { .NET ```csharp -await using var client = new CopilotClient(); +var client = new CopilotClient(new CopilotClientOptions +{ + CliPath = "/usr/local/bin/copilot", +}); + await using var session = await client.CreateSessionAsync( new SessionConfig { Model = "gpt-4.1" }); @@ -129,73 +139,16 @@ Console.WriteLine(response?.Data.Content);
-
-Java - -```java -import com.github.copilot.sdk.CopilotClient; -import com.github.copilot.sdk.events.*; -import com.github.copilot.sdk.json.*; - -var client = new CopilotClient(); -client.start().get(); - -var session = client.createSession(new SessionConfig() - .setModel("gpt-4.1") - .setOnPermissionRequest(request -> PermissionDecision.allow())).get(); - -var response = session.sendAndWait(new MessageOptions() - .setPrompt("Hello!")).get(); -System.out.println(response.getData().content()); - -client.stop().get(); -``` - -
- -That's it. The SDK handles everything: starting the CLI, authenticating, and managing the session. - -## What's Happening Under the Hood - -```mermaid -sequenceDiagram - participant App as Your App - participant SDK as SDK Client - participant CLI as Copilot CLI - participant GH as GitHub API - - App->>SDK: new CopilotClient() - Note over SDK: Locates CLI binary - - App->>SDK: createSession() - SDK->>CLI: Spawn process (stdio) - CLI->>CLI: Load credentials from keychain - CLI->>GH: Authenticate - GH-->>CLI: ✅ Valid session - CLI-->>SDK: Session created - SDK-->>App: Session ready - - App->>SDK: sendAndWait("Hello!") - SDK->>CLI: JSON-RPC request - CLI->>GH: Model API call - GH-->>CLI: Response - CLI-->>SDK: JSON-RPC response - SDK-->>App: Response data -``` - -## Configuration Options - -While defaults work great, you can customize the local setup: +## Additional Options ```typescript const client = new CopilotClient({ - // Override CLI location (by default, the SDK uses a bundled CLI or resolves one from your system) cliPath: "/usr/local/bin/copilot", // Set log level for debugging logLevel: "debug", - // Pass extra CLI arguments (example: set a custom log directory) + // Pass extra CLI arguments cliArgs: ["--log-dir=/tmp/copilot-logs"], // Set working directory @@ -218,7 +171,7 @@ The SDK picks these up automatically — no code changes needed. ## Managing Sessions -With the local CLI, sessions default to ephemeral. To create resumable sessions, provide your own session ID: +Sessions default to ephemeral. To create resumable sessions, provide your own session ID: ```typescript // Create a named session @@ -237,24 +190,13 @@ Session state is stored locally at `~/.copilot/session-state/{sessionId}/`. | Limitation | Details | |------------|---------| +| **Version compatibility** | You must ensure your CLI version is compatible with the SDK | | **Single user** | Credentials are tied to whoever signed in to the CLI | | **Local only** | The CLI runs on the same machine as your app | | **No multi-tenant** | Can't serve multiple users from one CLI instance | -| **Requires CLI login** | User must run `copilot` and authenticate first | - -## When to Move On - -If you need any of these, it's time to pick a more advanced setup: - -| Need | Next Guide | -|------|-----------| -| Ship your app to others | [Bundled CLI](./bundled-cli.md) | -| Multiple users signing in | [GitHub OAuth](./github-oauth.md) | -| Run on a server | [Backend Services](./backend-services.md) | -| Use your own model keys | [BYOK](../auth/byok.md) | ## Next Steps +- **[Default Setup](./bundled-cli.md)** — Use the SDK's built-in CLI (recommended for most use cases) - **[Getting Started tutorial](../getting-started.md)** — Build a complete interactive app - **[Authentication docs](../auth/index.md)** — All auth methods in detail -- **[Session Persistence](../features/session-persistence.md)** — Advanced session management From 70b77210e6554877777b4206249183d79551901d Mon Sep 17 00:00:00 2001 From: Ian Lynagh Date: Fri, 10 Apr 2026 18:00:34 +0100 Subject: [PATCH 10/34] Clear the CLI startup timeout when we are told to stop (#1046) Otherwise we won't terminate until the 10 seconds are up. --- nodejs/src/client.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 0780ba6ea..c5b84a6d4 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -200,6 +200,7 @@ function getBundledCliPath(): string { * ``` */ export class CopilotClient { + private cliStartTimeout: ReturnType | null = null; private cliProcess: ChildProcess | null = null; private connection: MessageConnection | null = null; private socket: Socket | null = null; @@ -541,6 +542,10 @@ export class CopilotClient { } this.cliProcess = null; } + if (this.cliStartTimeout) { + clearTimeout(this.cliStartTimeout); + this.cliStartTimeout = null; + } this.state = "disconnected"; this.actualPort = null; @@ -614,6 +619,11 @@ export class CopilotClient { this.cliProcess = null; } + if (this.cliStartTimeout) { + clearTimeout(this.cliStartTimeout); + this.cliStartTimeout = null; + } + this.state = "disconnected"; this.actualPort = null; this.stderrBuffer = ""; @@ -1526,7 +1536,7 @@ export class CopilotClient { }); // Timeout after 10 seconds - setTimeout(() => { + this.cliStartTimeout = setTimeout(() => { if (!resolved) { resolved = true; reject(new Error("Timeout waiting for CLI server to start")); From ca936135ce465b45cdcab9ea8bb12fc35cee316f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 12 Apr 2026 20:42:33 -0400 Subject: [PATCH 11/34] Enhance C# generator with richer typing and data annotations (#1067) * Enhance C# generator with richer typing and data annotations - integer -> long, number -> double (was double for both) - format: date-time -> DateTimeOffset, uuid -> Guid, duration -> TimeSpan - Add MillisecondsTimeSpanConverter for TimeSpan JSON serialization - Emit [Range], [RegularExpression], [Url], [MinLength], [MaxLength] - Emit [StringSyntax(Uri)], [StringSyntax(Regex)], [Base64String] - Change all public collections from concrete to interface types (List -> IList, Dictionary -> IDictionary) - Lazy-initialize collection properties via field ??= pattern Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update ListSessionsAsync return type in docs to IList Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix clone comparer preservation and byok doc example Preserve dictionary comparer in SessionConfig/ResumeSessionConfig Clone() by checking for Dictionary<> and passing its Comparer. Fix byok.md to use Task.FromResult>() for the updated delegate signature. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: Range(Type,string,string) for long, add SDK using to Rpc Use Range(typeof(long), ...) overload since RangeAttribute has no long constructor. Add 'using GitHub.Copilot.SDK' to Rpc.cs header so MillisecondsTimeSpanConverter resolves when duration fields exist. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Go doc example: type-assert SessionEventData to AssistantMessageData The Go SDK uses per-event-type data structs, so response.Data is a SessionEventData interface. Access Content by type-asserting to *copilot.AssistantMessageData. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove unnecessary using GitHub.Copilot.SDK from Rpc.cs generator Rpc.cs is in namespace GitHub.Copilot.SDK.Rpc, a child of GitHub.Copilot.SDK, so types from the parent namespace resolve automatically without an explicit using directive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/auth/byok.md | 2 +- docs/setup/local-cli.md | 8 +- dotnet/README.md | 2 +- dotnet/src/Client.cs | 42 +++---- dotnet/src/Generated/Rpc.cs | 45 +++---- dotnet/src/Generated/SessionEvents.cs | 22 ++-- dotnet/src/MillisecondsTimeSpanConverter.cs | 22 ++++ dotnet/src/Session.cs | 4 +- dotnet/src/Types.cs | 80 +++++++------ dotnet/test/ClientTests.cs | 6 +- dotnet/test/ElicitationTests.cs | 2 +- dotnet/test/SessionTests.cs | 2 +- scripts/codegen/csharp.ts | 126 ++++++++++++++++++-- 13 files changed, 258 insertions(+), 105 deletions(-) create mode 100644 dotnet/src/MillisecondsTimeSpanConverter.cs diff --git a/docs/auth/byok.md b/docs/auth/byok.md index d3d4e4106..4bb88f5aa 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -426,7 +426,7 @@ using GitHub.Copilot.SDK; var client = new CopilotClient(new CopilotClientOptions { - OnListModels = (ct) => Task.FromResult(new List + OnListModels = (ct) => Task.FromResult>(new List { new() { diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 48092b735..845a20af5 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -99,7 +99,9 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` @@ -115,7 +117,9 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -fmt.Println(*response.Data.Content) +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) +} ```
diff --git a/dotnet/README.md b/dotnet/README.md index 4e6cd7c4e..3e6def504 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -131,7 +131,7 @@ Ping the server to check connectivity. Get current connection state. -##### `ListSessionsAsync(): Task>` +##### `ListSessionsAsync(): Task>` List all available sessions. diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 732c15447..29b49c294 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -75,7 +75,7 @@ public sealed partial class CopilotClient : IDisposable, IAsyncDisposable private int? _negotiatedProtocolVersion; private List? _modelsCache; private readonly SemaphoreSlim _modelsCacheLock = new(1, 1); - private readonly Func>>? _onListModels; + private readonly Func>>? _onListModels; private readonly List> _lifecycleHandlers = []; private readonly Dictionary>> _typedLifecycleHandlers = []; private readonly object _lifecycleHandlersLock = new(); @@ -735,7 +735,7 @@ public async Task GetAuthStatusAsync(CancellationToken ca /// The cache is cleared when the client disconnects. /// /// Thrown when the client is not connected or not authenticated. - public async Task> ListModelsAsync(CancellationToken cancellationToken = default) + public async Task> ListModelsAsync(CancellationToken cancellationToken = default) { await _modelsCacheLock.WaitAsync(cancellationToken); try @@ -746,7 +746,7 @@ public async Task> ListModelsAsync(CancellationToken cancellatio return [.. _modelsCache]; // Return a copy to prevent cache mutation } - List models; + IList models; if (_onListModels is not null) { // Use custom handler instead of CLI RPC @@ -847,7 +847,7 @@ public async Task DeleteSessionAsync(string sessionId, CancellationToken cancell /// } /// /// - public async Task> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default) + public async Task> ListSessionsAsync(SessionListFilter? filter = null, CancellationToken cancellationToken = default) { var connection = await EnsureConnectedAsync(cancellationToken); @@ -1467,7 +1467,7 @@ public void OnSessionLifecycle(string type, string sessionId, JsonElement? metad client.DispatchLifecycleEvent(evt); } - public async Task OnUserInputRequest(string sessionId, string question, List? choices = null, bool? allowFreeform = null) + public async Task OnUserInputRequest(string sessionId, string question, IList? choices = null, bool? allowFreeform = null) { var session = client.GetSession(sessionId) ?? throw new ArgumentException($"Unknown session {sessionId}"); var request = new UserInputRequest @@ -1621,26 +1621,26 @@ internal record CreateSessionRequest( string? SessionId, string? ClientName, string? ReasoningEffort, - List? Tools, + IList? Tools, SystemMessageConfig? SystemMessage, - List? AvailableTools, - List? ExcludedTools, + IList? AvailableTools, + IList? ExcludedTools, ProviderConfig? Provider, bool? RequestPermission, bool? RequestUserInput, bool? Hooks, string? WorkingDirectory, bool? Streaming, - Dictionary? McpServers, + IDictionary? McpServers, string? EnvValueMode, - List? CustomAgents, + IList? CustomAgents, string? Agent, string? ConfigDir, bool? EnableConfigDiscovery, - List? SkillDirectories, - List? DisabledSkills, + IList? SkillDirectories, + IList? DisabledSkills, InfiniteSessionConfig? InfiniteSessions, - List? Commands = null, + IList? Commands = null, bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, @@ -1673,10 +1673,10 @@ internal record ResumeSessionRequest( string? ClientName, string? Model, string? ReasoningEffort, - List? Tools, + IList? Tools, SystemMessageConfig? SystemMessage, - List? AvailableTools, - List? ExcludedTools, + IList? AvailableTools, + IList? ExcludedTools, ProviderConfig? Provider, bool? RequestPermission, bool? RequestUserInput, @@ -1686,14 +1686,14 @@ internal record ResumeSessionRequest( bool? EnableConfigDiscovery, bool? DisableResume, bool? Streaming, - Dictionary? McpServers, + IDictionary? McpServers, string? EnvValueMode, - List? CustomAgents, + IList? CustomAgents, string? Agent, - List? SkillDirectories, - List? DisabledSkills, + IList? SkillDirectories, + IList? DisabledSkills, InfiniteSessionConfig? InfiniteSessions, - List? Commands = null, + IList? Commands = null, bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index b06b68676..387702685 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -5,6 +5,7 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -60,7 +61,7 @@ public class ModelCapabilitiesLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] - public List SupportedMediaTypes { get => field ??= []; set; } + public IList SupportedMediaTypes { get => field ??= []; set; } /// Maximum number of images per prompt. [JsonPropertyName("max_prompt_images")] @@ -148,7 +149,7 @@ public class Model /// Supported reasoning effort levels (only present if model supports reasoning effort). [JsonPropertyName("supportedReasoningEfforts")] - public List? SupportedReasoningEfforts { get; set; } + public IList? SupportedReasoningEfforts { get; set; } /// Default reasoning effort level (only present if model supports reasoning effort). [JsonPropertyName("defaultReasoningEffort")] @@ -160,7 +161,7 @@ public class ModelsListResult { /// List of available models with full metadata. [JsonPropertyName("models")] - public List Models { get => field ??= []; set; } + public IList Models { get => field ??= []; set; } } /// RPC data type for Tool operations. @@ -180,7 +181,7 @@ public class Tool /// JSON Schema for the tool's input parameters. [JsonPropertyName("parameters")] - public Dictionary? Parameters { get; set; } + public IDictionary? Parameters { get; set; } /// Optional instructions for how to use this tool effectively. [JsonPropertyName("instructions")] @@ -192,7 +193,7 @@ public class ToolsListResult { /// List of available built-in tools with metadata. [JsonPropertyName("tools")] - public List Tools { get => field ??= []; set; } + public IList Tools { get => field ??= []; set; } } /// RPC data type for ToolsList operations. @@ -236,7 +237,7 @@ public class AccountGetQuotaResult { /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions). [JsonPropertyName("quotaSnapshots")] - public Dictionary QuotaSnapshots { get => field ??= []; set; } + public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } } /// RPC data type for SessionFsSetProvider operations. @@ -313,6 +314,8 @@ internal class SessionLogRequest public bool? Ephemeral { get; set; } /// Optional URL the user can open in their browser for more details. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public string? Url { get; set; } } @@ -358,7 +361,7 @@ public class ModelCapabilitiesOverrideLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] - public List? SupportedMediaTypes { get; set; } + public IList? SupportedMediaTypes { get; set; } /// Maximum number of images per prompt. [JsonPropertyName("max_prompt_images")] @@ -516,7 +519,7 @@ public class SessionWorkspaceListFilesResult { /// Relative file paths in the workspace files directory. [JsonPropertyName("files")] - public List Files { get => field ??= []; set; } + public IList Files { get => field ??= []; set; } } /// RPC data type for SessionWorkspaceListFiles operations. @@ -612,7 +615,7 @@ public class SessionAgentListResult { /// Available custom agents. [JsonPropertyName("agents")] - public List Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentList operations. @@ -717,7 +720,7 @@ public class SessionAgentReloadResult { /// Reloaded custom agents. [JsonPropertyName("agents")] - public List Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentReload operations. @@ -763,7 +766,7 @@ public class SessionSkillsListResult { /// Available skills. [JsonPropertyName("skills")] - public List Skills { get => field ??= []; set; } + public IList Skills { get => field ??= []; set; } } /// RPC data type for SessionSkillsList operations. @@ -854,7 +857,7 @@ public class SessionMcpListResult { /// Configured MCP servers. [JsonPropertyName("servers")] - public List Servers { get => field ??= []; set; } + public IList Servers { get => field ??= []; set; } } /// RPC data type for SessionMcpList operations. @@ -945,7 +948,7 @@ public class SessionPluginsListResult { /// Installed plugins. [JsonPropertyName("plugins")] - public List Plugins { get => field ??= []; set; } + public IList Plugins { get => field ??= []; set; } } /// RPC data type for SessionPluginsList operations. @@ -978,7 +981,7 @@ public class Extension /// Process ID if the extension is running. [JsonPropertyName("pid")] - public double? Pid { get; set; } + public long? Pid { get; set; } } /// RPC data type for SessionExtensionsList operations. @@ -987,7 +990,7 @@ public class SessionExtensionsListResult { /// Discovered extensions and their current status. [JsonPropertyName("extensions")] - public List Extensions { get => field ??= []; set; } + public IList Extensions { get => field ??= []; set; } } /// RPC data type for SessionExtensionsList operations. @@ -1113,7 +1116,7 @@ public class SessionUiElicitationResult /// The form values submitted by the user (present when action is 'accept'). [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// JSON Schema describing the form fields to present to the user. @@ -1125,11 +1128,11 @@ public class SessionUiElicitationRequestRequestedSchema /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] - public Dictionary Properties { get => field ??= []; set; } + public IDictionary Properties { get => field ??= new Dictionary(); set; } /// List of required field names. [JsonPropertyName("required")] - public List? Required { get; set; } + public IList? Required { get; set; } } /// RPC data type for SessionUiElicitation operations. @@ -1165,7 +1168,7 @@ public class SessionUiHandlePendingElicitationRequestResult /// The form values submitted by the user (present when action is 'accept'). [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// RPC data type for SessionUiHandlePendingElicitation operations. @@ -1449,7 +1452,7 @@ public class SessionFsReaddirResult { /// Entry names in the directory. [JsonPropertyName("entries")] - public List Entries { get => field ??= []; set; } + public IList Entries { get => field ??= []; set; } } /// RPC data type for SessionFsReaddir operations. @@ -1481,7 +1484,7 @@ public class SessionFsReaddirWithTypesResult { /// Directory entries with type information. [JsonPropertyName("entries")] - public List Entries { get => field ??= []; set; } + public IList Entries { get => field ??= []; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index dfd3b761f..c627aca4f 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -5,7 +5,9 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: session-events.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; @@ -1196,7 +1198,7 @@ public partial class SessionErrorData /// HTTP status code from the upstream request, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("statusCode")] - public double? StatusCode { get; set; } + public long? StatusCode { get; set; } /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1204,6 +1206,8 @@ public partial class SessionErrorData public string? ProviderCallId { get; set; } /// Optional URL associated with this error that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1238,6 +1242,8 @@ public partial class SessionInfoData public required string Message { get; set; } /// Optional URL associated with this message that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1255,6 +1261,8 @@ public partial class SessionWarningData public required string Message { get; set; } /// Optional URL associated with this warning that the user can open in a browser. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } @@ -1430,7 +1438,7 @@ public partial class SessionShutdownData /// Per-model usage breakdown, keyed by model identifier. [JsonPropertyName("modelMetrics")] - public required Dictionary ModelMetrics { get; set; } + public required IDictionary ModelMetrics { get; set; } /// Model that was selected at the time of shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1886,7 +1894,7 @@ public partial class AssistantUsageData /// Per-quota resource usage snapshots, keyed by quota identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("quotaSnapshots")] - public Dictionary? QuotaSnapshots { get; set; } + public IDictionary? QuotaSnapshots { get; set; } /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2019,7 +2027,7 @@ public partial class ToolExecutionCompleteData /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolTelemetry")] - public Dictionary? ToolTelemetry { get; set; } + public IDictionary? ToolTelemetry { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2383,7 +2391,7 @@ public partial class ElicitationCompletedData /// The submitted form data when action is 'accept'; keys match the requested schema fields. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("content")] - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// Sampling request from an MCP server; contains the server name and a requestId for correlation. @@ -3270,7 +3278,7 @@ public partial class SystemMessageDataMetadata /// Template variables used when constructing the prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("variables")] - public Dictionary? Variables { get; set; } + public IDictionary? Variables { get; set; } } /// The agent_completed variant of . @@ -3678,7 +3686,7 @@ public partial class ElicitationRequestedDataRequestedSchema /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] - public required Dictionary Properties { get; set; } + public required IDictionary Properties { get; set; } /// List of required field names. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] diff --git a/dotnet/src/MillisecondsTimeSpanConverter.cs b/dotnet/src/MillisecondsTimeSpanConverter.cs new file mode 100644 index 000000000..696d053dd --- /dev/null +++ b/dotnet/src/MillisecondsTimeSpanConverter.cs @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using System.ComponentModel; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace GitHub.Copilot.SDK; + +/// Converts between JSON numeric milliseconds and . +[EditorBrowsable(EditorBrowsableState.Never)] +public sealed class MillisecondsTimeSpanConverter : JsonConverter +{ + /// + public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => + TimeSpan.FromMilliseconds(reader.GetDouble()); + + /// + public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) => + writer.WriteNumberValue(value.TotalMilliseconds); +} diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 189cdfaff..2a2778b3c 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -1219,7 +1219,7 @@ internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty; public string Prompt { get; init; } = string.Empty; - public List? Attachments { get; init; } + public IList? Attachments { get; init; } public string? Mode { get; init; } public string? Traceparent { get; init; } public string? Tracestate { get; init; } @@ -1237,7 +1237,7 @@ internal record GetMessagesRequest internal record GetMessagesResponse { - public List Events { get; init; } = []; + public IList Events { get => field ??= []; init; } } internal record SessionAbortRequest diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 8ee146dee..970d44f76 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -149,7 +149,7 @@ public string? GithubToken /// querying the CLI server. Useful in BYOK mode to return models /// available from your custom provider. ///
- public Func>>? OnListModels { get; set; } + public Func>>? OnListModels { get; set; } /// /// Custom session filesystem provider configuration. @@ -293,7 +293,7 @@ public class ToolResultObject /// Binary results (e.g., images) to be consumed by the language model. /// [JsonPropertyName("binaryResultsForLlm")] - public List? BinaryResultsForLlm { get; set; } + public IList? BinaryResultsForLlm { get; set; } /// /// Result type indicator. @@ -323,7 +323,7 @@ public class ToolResultObject /// Custom telemetry data associated with the tool execution. /// [JsonPropertyName("toolTelemetry")] - public Dictionary? ToolTelemetry { get; set; } + public IDictionary? ToolTelemetry { get; set; } /// /// Converts the result of an invocation into a @@ -540,7 +540,7 @@ public class PermissionRequestResult /// Permission rules to apply for the decision. /// [JsonPropertyName("rules")] - public List? Rules { get; set; } + public IList? Rules { get; set; } } /// @@ -578,7 +578,7 @@ public class UserInputRequest /// Optional choices for multiple choice questions. /// [JsonPropertyName("choices")] - public List? Choices { get; set; } + public IList? Choices { get; set; } /// /// Whether freeform text input is allowed. @@ -696,13 +696,13 @@ public class ElicitationSchema /// Form field definitions, keyed by field name. /// [JsonPropertyName("properties")] - public Dictionary Properties { get; set; } = []; + public IDictionary Properties { get => field ??= new Dictionary(); set; } /// /// List of required field names. /// [JsonPropertyName("required")] - public List? Required { get; set; } + public IList? Required { get; set; } } /// @@ -734,7 +734,7 @@ public class ElicitationResult /// /// Form values submitted by the user (present when is Accept). /// - public Dictionary? Content { get; set; } + public IDictionary? Content { get; set; } } /// @@ -1127,7 +1127,7 @@ public class SessionStartHookOutput /// Modified session configuration to apply at startup. /// [JsonPropertyName("modifiedConfig")] - public Dictionary? ModifiedConfig { get; set; } + public IDictionary? ModifiedConfig { get; set; } } /// @@ -1193,7 +1193,7 @@ public class SessionEndHookOutput /// List of cleanup action identifiers to execute after the session ends. /// [JsonPropertyName("cleanupActions")] - public List? CleanupActions { get; set; } + public IList? CleanupActions { get; set; } /// /// Summary of the session to persist for future reference. @@ -1438,7 +1438,7 @@ public class SystemMessageConfig /// Section-level overrides for customize mode. /// Keys are section identifiers (see ). /// - public Dictionary? Sections { get; set; } + public IDictionary? Sections { get; set; } } /// @@ -1517,7 +1517,7 @@ private protected McpServerConfig() { } /// List of tools to include from this server. Empty list means none. Use "*" for all. /// [JsonPropertyName("tools")] - public List Tools { get; set; } = []; + public IList Tools { get => field ??= []; set; } /// /// The server type discriminator. @@ -1551,13 +1551,13 @@ public sealed class McpStdioServerConfig : McpServerConfig /// Arguments to pass to the command. /// [JsonPropertyName("args")] - public List Args { get; set; } = []; + public IList Args { get => field ??= []; set; } /// /// Environment variables to pass to the server. /// [JsonPropertyName("env")] - public Dictionary? Env { get; set; } + public IDictionary? Env { get; set; } /// /// Working directory for the server process. @@ -1585,7 +1585,7 @@ public sealed class McpHttpServerConfig : McpServerConfig /// Optional HTTP headers to include in requests. /// [JsonPropertyName("headers")] - public Dictionary? Headers { get; set; } + public IDictionary? Headers { get; set; } } // ============================================================================ @@ -1619,7 +1619,7 @@ public class CustomAgentConfig /// List of tool names the agent can use. Null for all tools. /// [JsonPropertyName("tools")] - public List? Tools { get; set; } + public IList? Tools { get; set; } /// /// The prompt content for the agent. @@ -1631,7 +1631,7 @@ public class CustomAgentConfig /// MCP servers specific to this agent. /// [JsonPropertyName("mcpServers")] - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Whether the agent should be available for model inference. @@ -1700,7 +1700,9 @@ protected SessionConfig(SessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? (other.McpServers is Dictionary dict + ? new Dictionary(dict, dict.Comparer) + : new Dictionary(other.McpServers)) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1777,11 +1779,11 @@ protected SessionConfig(SessionConfig? other) /// /// List of tool names to allow; only these tools will be available when specified. /// - public List? AvailableTools { get; set; } + public IList? AvailableTools { get; set; } /// /// List of tool names to exclude from the session. /// - public List? ExcludedTools { get; set; } + public IList? ExcludedTools { get; set; } /// /// Custom model provider configuration for the session. /// @@ -1804,7 +1806,7 @@ protected SessionConfig(SessionConfig? other) /// When the CLI has a TUI, each command appears as /name for the user to invoke. /// The handler is called when the user executes the command. /// - public List? Commands { get; set; } + public IList? Commands { get; set; } /// /// Handler for elicitation requests from the server or MCP tools. @@ -1834,12 +1836,12 @@ protected SessionConfig(SessionConfig? other) /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. /// - public List? CustomAgents { get; set; } + public IList? CustomAgents { get; set; } /// /// Name of the custom agent to activate when the session starts. @@ -1850,12 +1852,12 @@ protected SessionConfig(SessionConfig? other) /// /// Directories to load skills from. /// - public List? SkillDirectories { get; set; } + public IList? SkillDirectories { get; set; } /// /// List of skill names to disable. /// - public List? DisabledSkills { get; set; } + public IList? DisabledSkills { get; set; } /// /// Infinite session configuration for persistent workspaces and automatic compaction. @@ -1928,7 +1930,9 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; McpServers = other.McpServers is not null - ? new Dictionary(other.McpServers, other.McpServers.Comparer) + ? (other.McpServers is Dictionary dict + ? new Dictionary(dict, dict.Comparer) + : new Dictionary(other.McpServers)) : null; Model = other.Model; ModelCapabilities = other.ModelCapabilities; @@ -1971,13 +1975,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// List of tool names to allow. When specified, only these tools will be available. /// Takes precedence over ExcludedTools. /// - public List? AvailableTools { get; set; } + public IList? AvailableTools { get; set; } /// /// List of tool names to disable. All other tools remain available. /// Ignored if AvailableTools is specified. /// - public List? ExcludedTools { get; set; } + public IList? ExcludedTools { get; set; } /// /// Custom model provider configuration for the resumed session. @@ -2012,7 +2016,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// When the CLI has a TUI, each command appears as /name for the user to invoke. /// The handler is called when the user executes the command. /// - public List? Commands { get; set; } + public IList? Commands { get; set; } /// /// Handler for elicitation requests from the server or MCP tools. @@ -2066,12 +2070,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). /// - public Dictionary? McpServers { get; set; } + public IDictionary? McpServers { get; set; } /// /// Custom agent configurations for the session. /// - public List? CustomAgents { get; set; } + public IList? CustomAgents { get; set; } /// /// Name of the custom agent to activate when the session starts. @@ -2082,12 +2086,12 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// /// Directories to load skills from. /// - public List? SkillDirectories { get; set; } + public IList? SkillDirectories { get; set; } /// /// List of skill names to disable. /// - public List? DisabledSkills { get; set; } + public IList? DisabledSkills { get; set; } /// /// Infinite session configuration for persistent workspaces and automatic compaction. @@ -2152,7 +2156,7 @@ protected MessageOptions(MessageOptions? other) /// /// File or data attachments to include with the message. /// - public List? Attachments { get; set; } + public IList? Attachments { get; set; } /// /// Interaction mode for the message (e.g., "plan", "edit"). /// @@ -2320,7 +2324,7 @@ public class ModelVisionLimits /// List of supported image MIME types (e.g., "image/png", "image/jpeg"). /// [JsonPropertyName("supported_media_types")] - public List SupportedMediaTypes { get; set; } = []; + public IList SupportedMediaTypes { get => field ??= []; set; } /// /// Maximum number of images allowed in a single prompt. @@ -2452,7 +2456,7 @@ public class ModelInfo /// Supported reasoning effort levels (only present if model supports reasoning effort) [JsonPropertyName("supportedReasoningEfforts")] - public List? SupportedReasoningEfforts { get; set; } + public IList? SupportedReasoningEfforts { get; set; } /// Default reasoning effort level (only present if model supports reasoning effort) [JsonPropertyName("defaultReasoningEffort")] @@ -2468,7 +2472,7 @@ public class GetModelsResponse /// List of available models. /// [JsonPropertyName("models")] - public List Models { get; set; } = []; + public IList Models { get => field ??= []; set; } } // ============================================================================ @@ -2597,7 +2601,7 @@ public class SystemMessageTransformRpcResponse /// The transformed sections keyed by section identifier. /// [JsonPropertyName("sections")] - public Dictionary? Sections { get; set; } + public IDictionary? Sections { get; set; } } [JsonSourceGenerationOptions( diff --git a/dotnet/test/ClientTests.cs b/dotnet/test/ClientTests.cs index 6c70ffaa3..c62c5bc3f 100644 --- a/dotnet/test/ClientTests.cs +++ b/dotnet/test/ClientTests.cs @@ -278,7 +278,7 @@ public async Task Should_Throw_When_ResumeSession_Called_Without_PermissionHandl [Fact] public async Task ListModels_WithCustomHandler_CallsHandler() { - var customModels = new List + IList customModels = new List { new() { @@ -312,7 +312,7 @@ public async Task ListModels_WithCustomHandler_CallsHandler() [Fact] public async Task ListModels_WithCustomHandler_CachesResults() { - var customModels = new List + IList customModels = new List { new() { @@ -345,7 +345,7 @@ public async Task ListModels_WithCustomHandler_CachesResults() [Fact] public async Task ListModels_WithCustomHandler_WorksWithoutStart() { - var customModels = new List + IList customModels = new List { new() { diff --git a/dotnet/test/ElicitationTests.cs b/dotnet/test/ElicitationTests.cs index e3048e4c9..f91fe2d19 100644 --- a/dotnet/test/ElicitationTests.cs +++ b/dotnet/test/ElicitationTests.cs @@ -62,7 +62,7 @@ await session.Ui.ElicitationAsync(new ElicitationParams Message = "Enter name", RequestedSchema = new ElicitationSchema { - Properties = new() { ["name"] = new Dictionary { ["type"] = "string" } }, + Properties = new Dictionary() { ["name"] = new Dictionary { ["type"] = "string" } }, Required = ["name"], }, }); diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 9bd03f186..5200d6de5 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -397,7 +397,7 @@ public async Task Should_List_Sessions_With_Context() var sessions = await Client.ListSessionsAsync(); Assert.NotEmpty(sessions); - var ourSession = sessions.Find(s => s.SessionId == session.SessionId); + var ourSession = sessions.FirstOrDefault(s => s.SessionId == session.SessionId); Assert.NotNull(ourSession); // Context may be present on sessions that have been persisted with workspace.yaml diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 9049cb38c..63968077e 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -158,13 +158,25 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: if (format === "date-time") return "DateTimeOffset?"; return "string?"; } + if (nonNullTypes.length === 1 && (nonNullTypes[0] === "number" || nonNullTypes[0] === "integer")) { + if (format === "duration") { + return "TimeSpan?"; + } + return nonNullTypes[0] === "integer" ? "long?" : "double?"; + } } if (type === "string") { if (format === "uuid") return required ? "Guid" : "Guid?"; if (format === "date-time") return required ? "DateTimeOffset" : "DateTimeOffset?"; return required ? "string" : "string?"; } - if (type === "number" || type === "integer") return required ? "double" : "double?"; + if (type === "number" || type === "integer") { + if (format === "duration") { + return required ? "TimeSpan" : "TimeSpan?"; + } + if (type === "integer") return required ? "long" : "long?"; + return required ? "double" : "double?"; + } if (type === "boolean") return required ? "bool" : "bool?"; if (type === "array") { const items = schema.items as JSONSchema7 | undefined; @@ -174,13 +186,99 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: if (type === "object") { if (schema.additionalProperties && typeof schema.additionalProperties === "object") { const valueType = schemaTypeToCSharp(schema.additionalProperties as JSONSchema7, true, knownTypes); - return required ? `Dictionary` : `Dictionary?`; + return required ? `IDictionary` : `IDictionary?`; } return required ? "object" : "object?"; } return required ? "object" : "object?"; } +/** Tracks whether any TimeSpan property was emitted so the converter can be generated. */ + + +/** + * Emit C# data-annotation attributes for a JSON Schema property. + * Returns an array of attribute lines (without trailing newlines). + */ +function emitDataAnnotations(schema: JSONSchema7, indent: string): string[] { + const attrs: string[] = []; + const format = schema.format; + + // [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] for format: "uri" + if (format === "uri") { + attrs.push(`${indent}[Url]`); + attrs.push(`${indent}[StringSyntax(StringSyntaxAttribute.Uri)]`); + } + + // [StringSyntax(StringSyntaxAttribute.Regex)] for format: "regex" + if (format === "regex") { + attrs.push(`${indent}[StringSyntax(StringSyntaxAttribute.Regex)]`); + } + + // [Base64String] for base64-encoded string properties + if (format === "byte" || (schema as Record).contentEncoding === "base64") { + attrs.push(`${indent}[Base64String]`); + } + + // [Range] for minimum/maximum + const hasMin = typeof schema.minimum === "number"; + const hasMax = typeof schema.maximum === "number"; + if (hasMin || hasMax) { + const namedArgs: string[] = []; + if (schema.exclusiveMinimum === true) namedArgs.push("MinimumIsExclusive = true"); + if (schema.exclusiveMaximum === true) namedArgs.push("MaximumIsExclusive = true"); + const namedSuffix = namedArgs.length > 0 ? `, ${namedArgs.join(", ")}` : ""; + if (schema.type === "integer") { + // Use Range(Type, string, string) overload since RangeAttribute has no long constructor + const min = hasMin ? String(schema.minimum) : "long.MinValue"; + const max = hasMax ? String(schema.maximum) : "long.MaxValue"; + attrs.push(`${indent}[Range(typeof(long), "${min}", "${max}"${namedSuffix})]`); + } else { + const min = hasMin ? String(schema.minimum) : "double.MinValue"; + const max = hasMax ? String(schema.maximum) : "double.MaxValue"; + attrs.push(`${indent}[Range(${min}, ${max}${namedSuffix})]`); + } + } + + // [RegularExpression] for pattern + if (typeof schema.pattern === "string") { + const escaped = schema.pattern.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); + attrs.push(`${indent}[RegularExpression("${escaped}")]`); + } + + // [MinLength] / [MaxLength] for string constraints + if (typeof schema.minLength === "number") { + attrs.push(`${indent}[MinLength(${schema.minLength})]`); + } + if (typeof schema.maxLength === "number") { + attrs.push(`${indent}[MaxLength(${schema.maxLength})]`); + } + + return attrs; +} + +/** + * Returns true when a TimeSpan-typed property needs a [JsonConverter] attribute. + * + * NOTE: The runtime schema uses `format: "duration"` on numeric (integer/number) fields + * to mean "a duration value expressed in milliseconds". This differs from the JSON Schema + * spec, where `format: "duration"` denotes an ISO 8601 duration string (e.g. "PT1H30M"). + * The generator and runtime agree on this convention, so we map these to TimeSpan with a + * milliseconds-based JSON converter rather than expecting ISO 8601 strings. + */ +function isDurationProperty(schema: JSONSchema7): boolean { + if (schema.format === "duration") { + const t = schema.type; + if (t === "number" || t === "integer") return true; + if (Array.isArray(t)) { + const nonNull = (t as string[]).filter((x) => x !== "null"); + if (nonNull.length === 1 && (nonNull[0] === "number" || nonNull[0] === "integer")) return true; + } + } + return false; +} + + const COPYRIGHT = `/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. *--------------------------------------------------------------------------------------------*/`; @@ -351,6 +449,8 @@ function generateDerivedClass( const csharpType = resolveSessionPropertyType(propSchema as JSONSchema7, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " ")); + lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " ")); + if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); const reqMod = isReq && !csharpType.endsWith("?") ? "required " : ""; @@ -383,6 +483,8 @@ function generateNestedClass( const csharpType = resolveSessionPropertyType(prop, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); + lines.push(...emitDataAnnotations(prop, " ")); + if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); const reqMod = isReq && !csharpType.endsWith("?") ? "required " : ""; @@ -479,6 +581,8 @@ function generateDataClass(variant: EventVariant, knownTypes: Map` : `List<${itemClass}>?`; + return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; } const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes); - return isRequired ? `List<${itemType}>` : `List<${itemType}>?`; + return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`; } if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") { const vs = schema.additionalProperties as JSONSchema7; if (vs.type === "object" && vs.properties) { const valClass = `${parentClassName}${propName}Value`; classes.push(emitRpcClass(valClass, vs, "public", classes)); - return isRequired ? `Dictionary` : `Dictionary?`; + return isRequired ? `IDictionary` : `IDictionary?`; } const valueType = schemaTypeToCSharp(vs, true, rpcKnownTypes); - return isRequired ? `Dictionary` : `Dictionary?`; + return isRequired ? `IDictionary` : `IDictionary?`; } return schemaTypeToCSharp(schema, isRequired, rpcKnownTypes); } @@ -680,6 +786,8 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi const csharpType = resolveRpcType(prop, isReq, className, csharpName, extraClasses); lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); + lines.push(...emitDataAnnotations(prop, " ")); + if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); lines.push(` [JsonPropertyName("${propName}")]`); let defaultVal = ""; @@ -687,8 +795,11 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi if (isReq && !csharpType.endsWith("?")) { if (csharpType === "string") defaultVal = " = string.Empty;"; else if (csharpType === "object") defaultVal = " = null!;"; - else if (csharpType.startsWith("List<") || csharpType.startsWith("Dictionary<")) { + else if (csharpType.startsWith("IList<")) { propAccessors = "{ get => field ??= []; set; }"; + } else if (csharpType.startsWith("IDictionary<")) { + const concreteType = csharpType.replace("IDictionary<", "Dictionary<"); + propAccessors = `{ get => field ??= new ${concreteType}(); set; }`; } else if (emittedRpcClasses.has(csharpType)) { propAccessors = "{ get => field ??= new(); set; }"; } @@ -1082,6 +1193,7 @@ function generateRpcCode(schema: ApiSchema): string { // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; From 16f0ba278ebb25e2cd6326f932d60517ea926431 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 22:10:17 -0400 Subject: [PATCH 12/34] Update @github/copilot to 1.0.22 (#1055) * Update @github/copilot to 1.0.22 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * fix: avoid .NET RPC schema collisions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: simplify C# RPC class tracking Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore .NET collection interfaces Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Mackinnon Buck Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Stephen Toub --- dotnet/src/Generated/Rpc.cs | 249 ++++++++++++- dotnet/src/Generated/SessionEvents.cs | 53 ++- go/generated_session_events.go | 28 +- go/rpc/generated_rpc.go | 205 ++++++++++- nodejs/package-lock.json | 56 +-- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 229 ++++++++++++ nodejs/src/generated/session-events.ts | 22 +- python/copilot/generated/rpc.py | 386 ++++++++++++++++++++- python/copilot/generated/session_events.py | 50 ++- scripts/codegen/csharp.ts | 37 +- test/harness/package-lock.json | 56 +-- test/harness/package.json | 2 +- 14 files changed, 1258 insertions(+), 119 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 387702685..0caa4bbd2 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -240,6 +240,42 @@ public class AccountGetQuotaResult public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } } +/// RPC data type for DiscoveredMcpServer operations. +public class DiscoveredMcpServer +{ + /// Server name (config key). + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Server type: local, stdio, http, or sse. + [JsonPropertyName("type")] + public string? Type { get; set; } + + /// Configuration source. + [JsonPropertyName("source")] + public DiscoveredMcpServerSource Source { get; set; } + + /// Whether the server is enabled (not in the disabled list). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } +} + +/// RPC data type for McpDiscover operations. +public class McpDiscoverResult +{ + /// MCP servers discovered from all sources. + [JsonPropertyName("servers")] + public IList Servers { get => field ??= []; set; } +} + +/// RPC data type for McpDiscover operations. +internal class McpDiscoverRequest +{ + /// Working directory used as context for discovery (e.g., plugin resolution). + [JsonPropertyName("workingDirectory")] + public string? WorkingDirectory { get; set; } +} + /// RPC data type for SessionFsSetProvider operations. public class SessionFsSetProviderResult { @@ -1070,15 +1106,15 @@ internal class SessionToolsHandlePendingToolCallRequest [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Gets or sets the requestId value. + /// Request ID of the pending tool call. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; - /// Gets or sets the result value. + /// Tool call result (string or expanded result object). [JsonPropertyName("result")] public object? Result { get; set; } - /// Gets or sets the error value. + /// Error message if the tool call failed. [JsonPropertyName("error")] public string? Error { get; set; } } @@ -1086,7 +1122,7 @@ internal class SessionToolsHandlePendingToolCallRequest /// RPC data type for SessionCommandsHandlePendingCommand operations. public class SessionCommandsHandlePendingCommandResult { - /// Gets or sets the success value. + /// Whether the command was handled successfully. [JsonPropertyName("success")] public bool Success { get; set; } } @@ -1202,7 +1238,7 @@ internal class SessionPermissionsHandlePendingPermissionRequestRequest [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// Gets or sets the requestId value. + /// Request ID of the pending permission request. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; @@ -1263,6 +1299,34 @@ internal class SessionShellKillRequest public SessionShellKillRequestSignal? Signal { get; set; } } +/// Post-compaction context window usage breakdown. +public class SessionHistoryCompactResultContextWindow +{ + /// Maximum token count for the model's context window. + [JsonPropertyName("tokenLimit")] + public double TokenLimit { get; set; } + + /// Current total tokens in the context window (system + conversation + tool definitions). + [JsonPropertyName("currentTokens")] + public double CurrentTokens { get; set; } + + /// Current number of messages in the conversation. + [JsonPropertyName("messagesLength")] + public double MessagesLength { get; set; } + + /// Token count from system message(s). + [JsonPropertyName("systemTokens")] + public double? SystemTokens { get; set; } + + /// Token count from non-system messages (user, assistant, tool). + [JsonPropertyName("conversationTokens")] + public double? ConversationTokens { get; set; } + + /// Token count from tool definitions. + [JsonPropertyName("toolDefinitionsTokens")] + public double? ToolDefinitionsTokens { get; set; } +} + /// RPC data type for SessionHistoryCompact operations. [Experimental(Diagnostics.Experimental)] public class SessionHistoryCompactResult @@ -1278,6 +1342,10 @@ public class SessionHistoryCompactResult /// Number of messages removed during compaction. [JsonPropertyName("messagesRemoved")] public double MessagesRemoved { get; set; } + + /// Post-compaction context window usage breakdown. + [JsonPropertyName("contextWindow")] + public SessionHistoryCompactResultContextWindow? ContextWindow { get; set; } } /// RPC data type for SessionHistoryCompact operations. @@ -1311,6 +1379,116 @@ internal class SessionHistoryTruncateRequest public string EventId { get; set; } = string.Empty; } +/// Aggregated code change metrics. +public class SessionUsageGetMetricsResultCodeChanges +{ + /// Total lines of code added. + [JsonPropertyName("linesAdded")] + public long LinesAdded { get; set; } + + /// Total lines of code removed. + [JsonPropertyName("linesRemoved")] + public long LinesRemoved { get; set; } + + /// Number of distinct files modified. + [JsonPropertyName("filesModifiedCount")] + public long FilesModifiedCount { get; set; } +} + +/// Request count and cost metrics for this model. +public class SessionUsageGetMetricsResultModelMetricsValueRequests +{ + /// Number of API requests made with this model. + [JsonPropertyName("count")] + public long Count { get; set; } + + /// User-initiated premium request cost (with multiplier applied). + [JsonPropertyName("cost")] + public double Cost { get; set; } +} + +/// Token usage metrics for this model. +public class SessionUsageGetMetricsResultModelMetricsValueUsage +{ + /// Total input tokens consumed. + [JsonPropertyName("inputTokens")] + public long InputTokens { get; set; } + + /// Total output tokens produced. + [JsonPropertyName("outputTokens")] + public long OutputTokens { get; set; } + + /// Total tokens read from prompt cache. + [JsonPropertyName("cacheReadTokens")] + public long CacheReadTokens { get; set; } + + /// Total tokens written to prompt cache. + [JsonPropertyName("cacheWriteTokens")] + public long CacheWriteTokens { get; set; } +} + +/// RPC data type for SessionUsageGetMetricsResultModelMetricsValue operations. +public class SessionUsageGetMetricsResultModelMetricsValue +{ + /// Request count and cost metrics for this model. + [JsonPropertyName("requests")] + public SessionUsageGetMetricsResultModelMetricsValueRequests Requests { get => field ??= new(); set; } + + /// Token usage metrics for this model. + [JsonPropertyName("usage")] + public SessionUsageGetMetricsResultModelMetricsValueUsage Usage { get => field ??= new(); set; } +} + +/// RPC data type for SessionUsageGetMetrics operations. +[Experimental(Diagnostics.Experimental)] +public class SessionUsageGetMetricsResult +{ + /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). + [JsonPropertyName("totalPremiumRequestCost")] + public double TotalPremiumRequestCost { get; set; } + + /// Raw count of user-initiated API requests. + [JsonPropertyName("totalUserRequests")] + public long TotalUserRequests { get; set; } + + /// Total time spent in model API calls (milliseconds). + [JsonPropertyName("totalApiDurationMs")] + public double TotalApiDurationMs { get; set; } + + /// Session start timestamp (epoch milliseconds). + [JsonPropertyName("sessionStartTime")] + public long SessionStartTime { get; set; } + + /// Aggregated code change metrics. + [JsonPropertyName("codeChanges")] + public SessionUsageGetMetricsResultCodeChanges CodeChanges { get => field ??= new(); set; } + + /// Per-model token and request metrics, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } + + /// Currently active model identifier. + [JsonPropertyName("currentModel")] + public string? CurrentModel { get; set; } + + /// Input tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallInputTokens")] + public long LastCallInputTokens { get; set; } + + /// Output tokens from the most recent main-agent API call. + [JsonPropertyName("lastCallOutputTokens")] + public long LastCallOutputTokens { get; set; } +} + +/// RPC data type for SessionUsageGetMetrics operations. +[Experimental(Diagnostics.Experimental)] +internal class SessionUsageGetMetricsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// RPC data type for SessionFsReadFile operations. public class SessionFsReadFileResult { @@ -1535,6 +1713,25 @@ public class SessionFsRenameParams public string Dest { get; set; } = string.Empty; } +/// Configuration source. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum DiscoveredMcpServerSource +{ + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The workspace variant. + [JsonStringEnumMemberName("workspace")] + Workspace, + /// The plugin variant. + [JsonStringEnumMemberName("plugin")] + Plugin, + /// The builtin variant. + [JsonStringEnumMemberName("builtin")] + Builtin, +} + + /// Path conventions used by this filesystem. [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionFsSetProviderRequestConventions @@ -1785,6 +1982,13 @@ internal ServerMcpApi(JsonRpc rpc) { _rpc = rpc; } + + /// Calls "mcp.discover". + public async Task DiscoverAsync(string? workingDirectory = null, CancellationToken cancellationToken = default) + { + var request = new McpDiscoverRequest { WorkingDirectory = workingDirectory }; + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.discover", [request], cancellationToken); + } } /// Provides server-scoped SessionFs APIs. @@ -1850,6 +2054,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId) Permissions = new PermissionsApi(rpc, sessionId); Shell = new ShellApi(rpc, sessionId); History = new HistoryApi(rpc, sessionId); + Usage = new UsageApi(rpc, sessionId); } /// Model APIs. @@ -1900,6 +2105,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId) /// History APIs. public HistoryApi History { get; } + /// Usage APIs. + public UsageApi Usage { get; } + /// Calls "session.log". public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) { @@ -2389,6 +2597,27 @@ public async Task TruncateAsync(string eventId, Ca } } +/// Provides session-scoped Usage APIs. +[Experimental(Diagnostics.Experimental)] +public class UsageApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal UsageApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.usage.getMetrics". + public async Task GetMetricsAsync(CancellationToken cancellationToken = default) + { + var request = new SessionUsageGetMetricsRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.usage.getMetrics", [request], cancellationToken); + } +} + /// Handles `sessionFs` client session API methods. public interface ISessionFsHandler { @@ -2541,8 +2770,11 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncMemory storage permission request. +/// Memory operation permission request. /// The memory variant of . public partial class PermissionRequestMemory : PermissionRequest { @@ -3578,17 +3578,34 @@ public partial class PermissionRequestMemory : PermissionRequest [JsonPropertyName("toolCallId")] public string? ToolCallId { get; set; } - /// Topic or subject of the memory being stored. + /// Whether this is a store or vote memory operation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("action")] + public PermissionRequestMemoryAction? Action { get; set; } + + /// Topic or subject of the memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("subject")] - public required string Subject { get; set; } + public string? Subject { get; set; } - /// The fact or convention being stored. + /// The fact being stored or voted on. [JsonPropertyName("fact")] public required string Fact { get; set; } - /// Source references for the stored fact. + /// Source references for the stored fact (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("citations")] - public required string Citations { get; set; } + public string? Citations { get; set; } + + /// Vote direction (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("direction")] + public PermissionRequestMemoryDirection? Direction { get; set; } + + /// Reason for the vote (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } } /// Custom tool invocation permission request. @@ -3983,6 +4000,30 @@ public enum SystemNotificationDataKindAgentCompletedStatus Failed, } +/// Whether this is a store or vote memory operation. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionRequestMemoryAction +{ + /// The store variant. + [JsonStringEnumMemberName("store")] + Store, + /// The vote variant. + [JsonStringEnumMemberName("vote")] + Vote, +} + +/// Vote direction (vote only). +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionRequestMemoryDirection +{ + /// The upvote variant. + [JsonStringEnumMemberName("upvote")] + Upvote, + /// The downvote variant. + [JsonStringEnumMemberName("downvote")] + Downvote, +} + /// The outcome of the permission request. [JsonConverter(typeof(JsonStringEnumConverter))] public enum PermissionCompletedDataResultKind diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 0599e7fcc..1bd2e8959 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1993,12 +1993,18 @@ type PermissionRequestedDataPermissionRequest struct { ReadOnly *bool `json:"readOnly,omitempty"` // URL to be fetched URL *string `json:"url,omitempty"` - // Topic or subject of the memory being stored + // Whether this is a store or vote memory operation + Action *PermissionRequestedDataPermissionRequestAction `json:"action,omitempty"` + // Topic or subject of the memory (store only) Subject *string `json:"subject,omitempty"` - // The fact or convention being stored + // The fact being stored or voted on Fact *string `json:"fact,omitempty"` - // Source references for the stored fact + // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` + // Vote direction (vote only) + Direction *PermissionRequestedDataPermissionRequestDirection `json:"direction,omitempty"` + // Reason for the vote (vote only) + Reason *string `json:"reason,omitempty"` // Description of what the custom tool does ToolDescription *string `json:"toolDescription,omitempty"` // Arguments of the tool call being gated @@ -2237,6 +2243,22 @@ const ( PermissionRequestedDataPermissionRequestKindHook PermissionRequestedDataPermissionRequestKind = "hook" ) +// Whether this is a store or vote memory operation +type PermissionRequestedDataPermissionRequestAction string + +const ( + PermissionRequestedDataPermissionRequestActionStore PermissionRequestedDataPermissionRequestAction = "store" + PermissionRequestedDataPermissionRequestActionVote PermissionRequestedDataPermissionRequestAction = "vote" +) + +// Vote direction (vote only) +type PermissionRequestedDataPermissionRequestDirection string + +const ( + PermissionRequestedDataPermissionRequestDirectionUpvote PermissionRequestedDataPermissionRequestDirection = "upvote" + PermissionRequestedDataPermissionRequestDirectionDownvote PermissionRequestedDataPermissionRequestDirection = "downvote" +) + // The outcome of the permission request type PermissionCompletedDataResultKind string diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 6782f499d..698b3e95e 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -222,6 +222,27 @@ type MCPConfigRemoveParams struct { Name string `json:"name"` } +type MCPDiscoverResult struct { + // MCP servers discovered from all sources + Servers []DiscoveredMCPServer `json:"servers"` +} + +type DiscoveredMCPServer struct { + // Whether the server is enabled (not in the disabled list) + Enabled bool `json:"enabled"` + // Server name (config key) + Name string `json:"name"` + // Configuration source + Source ServerSource `json:"source"` + // Server type: local, stdio, http, or sse + Type *string `json:"type,omitempty"` +} + +type MCPDiscoverParams struct { + // Working directory used as context for discovery (e.g., plugin resolution) + WorkingDirectory *string `json:"workingDirectory,omitempty"` +} + type SessionFSSetProviderResult struct { // Whether the provider was set successfully Success bool `json:"success"` @@ -528,10 +549,10 @@ type SessionMCPReloadResult struct { // Experimental: SessionPluginsListResult is part of an experimental API and may change or be removed. type SessionPluginsListResult struct { // Installed plugins - Plugins []Plugin `json:"plugins"` + Plugins []PluginElement `json:"plugins"` } -type Plugin struct { +type PluginElement struct { // Whether the plugin is currently enabled Enabled bool `json:"enabled"` // Marketplace the plugin came from @@ -556,7 +577,7 @@ type Extension struct { // Process ID if the extension is running PID *int64 `json:"pid,omitempty"` // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) - Source Source `json:"source"` + Source ExtensionSource `json:"source"` // Current status: running, disabled, failed, or starting Status ExtensionStatus `json:"status"` } @@ -591,19 +612,27 @@ type SessionToolsHandlePendingToolCallResult struct { } type SessionToolsHandlePendingToolCallParams struct { - Error *string `json:"error,omitempty"` - RequestID string `json:"requestId"` - Result *ResultUnion `json:"result"` + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Request ID of the pending tool call + RequestID string `json:"requestId"` + // Tool call result (string or expanded result object) + Result *ResultUnion `json:"result"` } type ResultResult struct { - Error *string `json:"error,omitempty"` - ResultType *string `json:"resultType,omitempty"` - TextResultForLlm string `json:"textResultForLlm"` - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Type of the tool result + ResultType *string `json:"resultType,omitempty"` + // Text result to send back to the LLM + TextResultForLlm string `json:"textResultForLlm"` + // Telemetry data from tool execution + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } type SessionCommandsHandlePendingCommandResult struct { + // Whether the command was handled successfully Success bool `json:"success"` } @@ -699,17 +728,36 @@ type SessionPermissionsHandlePendingPermissionRequestResult struct { } type SessionPermissionsHandlePendingPermissionRequestParams struct { + // Request ID of the pending permission request RequestID string `json:"requestId"` Result SessionPermissionsHandlePendingPermissionRequestParamsResult `json:"result"` } type SessionPermissionsHandlePendingPermissionRequestParamsResult struct { - Kind Kind `json:"kind"` - Rules []any `json:"rules,omitempty"` - Feedback *string `json:"feedback,omitempty"` - Message *string `json:"message,omitempty"` - Path *string `json:"path,omitempty"` - Interrupt *bool `json:"interrupt,omitempty"` + // The permission request was approved + // + // Denied because approval rules explicitly blocked it + // + // Denied because no approval rule matched and user confirmation was unavailable + // + // Denied by the user during an interactive prompt + // + // Denied by the organization's content exclusion policy + // + // Denied by a permission request hook registered by an extension or plugin + Kind Kind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules,omitempty"` + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Human-readable explanation of why the path was excluded + // + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` + // File path that triggered the exclusion + Path *string `json:"path,omitempty"` + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` } type SessionLogResult struct { @@ -757,6 +805,8 @@ type SessionShellKillParams struct { // Experimental: SessionHistoryCompactResult is part of an experimental API and may change or be removed. type SessionHistoryCompactResult struct { + // Post-compaction context window usage breakdown + ContextWindow *ContextWindow `json:"contextWindow,omitempty"` // Number of messages removed during compaction MessagesRemoved float64 `json:"messagesRemoved"` // Whether compaction completed successfully @@ -765,6 +815,22 @@ type SessionHistoryCompactResult struct { TokensRemoved float64 `json:"tokensRemoved"` } +// Post-compaction context window usage breakdown +type ContextWindow struct { + // Token count from non-system messages (user, assistant, tool) + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Current total tokens in the context window (system + conversation + tool definitions) + CurrentTokens float64 `json:"currentTokens"` + // Current number of messages in the conversation + MessagesLength float64 `json:"messagesLength"` + // Token count from system message(s) + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Maximum token count for the model's context window + TokenLimit float64 `json:"tokenLimit"` + // Token count from tool definitions + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +} + // Experimental: SessionHistoryTruncateResult is part of an experimental API and may change or be removed. type SessionHistoryTruncateResult struct { // Number of events that were removed @@ -777,6 +843,66 @@ type SessionHistoryTruncateParams struct { EventID string `json:"eventId"` } +// Experimental: SessionUsageGetMetricsResult is part of an experimental API and may change or be removed. +type SessionUsageGetMetricsResult struct { + // Aggregated code change metrics + CodeChanges CodeChanges `json:"codeChanges"` + // Currently active model identifier + CurrentModel *string `json:"currentModel,omitempty"` + // Input tokens from the most recent main-agent API call + LastCallInputTokens int64 `json:"lastCallInputTokens"` + // Output tokens from the most recent main-agent API call + LastCallOutputTokens int64 `json:"lastCallOutputTokens"` + // Per-model token and request metrics, keyed by model identifier + ModelMetrics map[string]ModelMetric `json:"modelMetrics"` + // Session start timestamp (epoch milliseconds) + SessionStartTime int64 `json:"sessionStartTime"` + // Total time spent in model API calls (milliseconds) + TotalAPIDurationMS float64 `json:"totalApiDurationMs"` + // Total user-initiated premium request cost across all models (may be fractional due to + // multipliers) + TotalPremiumRequestCost float64 `json:"totalPremiumRequestCost"` + // Raw count of user-initiated API requests + TotalUserRequests int64 `json:"totalUserRequests"` +} + +// Aggregated code change metrics +type CodeChanges struct { + // Number of distinct files modified + FilesModifiedCount int64 `json:"filesModifiedCount"` + // Total lines of code added + LinesAdded int64 `json:"linesAdded"` + // Total lines of code removed + LinesRemoved int64 `json:"linesRemoved"` +} + +type ModelMetric struct { + // Request count and cost metrics for this model + Requests Requests `json:"requests"` + // Token usage metrics for this model + Usage Usage `json:"usage"` +} + +// Request count and cost metrics for this model +type Requests struct { + // User-initiated premium request cost (with multiplier applied) + Cost float64 `json:"cost"` + // Number of API requests made with this model + Count int64 `json:"count"` +} + +// Token usage metrics for this model +type Usage struct { + // Total tokens read from prompt cache + CacheReadTokens int64 `json:"cacheReadTokens"` + // Total tokens written to prompt cache + CacheWriteTokens int64 `json:"cacheWriteTokens"` + // Total input tokens consumed + InputTokens int64 `json:"inputTokens"` + // Total output tokens produced + OutputTokens int64 `json:"outputTokens"` +} + type SessionFSReadFileResult struct { // File content as UTF-8 string Content string `json:"content"` @@ -922,6 +1048,16 @@ const ( ServerTypeStdio ServerType = "stdio" ) +// Configuration source +type ServerSource string + +const ( + ServerSourceBuiltin ServerSource = "builtin" + ServerSourcePlugin ServerSource = "plugin" + ServerSourceUser ServerSource = "user" + ServerSourceWorkspace ServerSource = "workspace" +) + // Path conventions used by this filesystem type Conventions string @@ -956,11 +1092,11 @@ const ( ) // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) -type Source string +type ExtensionSource string const ( - SourceProject Source = "project" - SourceUser Source = "user" + ExtensionSourceUser ExtensionSource = "user" + ExtensionSourceProject ExtensionSource = "project" ) // Current status: running, disabled, failed, or starting @@ -1056,6 +1192,7 @@ type FilterMappingUnion struct { EnumMap map[string]FilterMappingEnum } +// Tool call result (string or expanded result object) type ResultUnion struct { ResultResult *ResultResult String *string @@ -1116,6 +1253,18 @@ func (a *ServerAccountApi) GetQuota(ctx context.Context) (*AccountGetQuotaResult type ServerMcpApi serverApi +func (a *ServerMcpApi) Discover(ctx context.Context, params *MCPDiscoverParams) (*MCPDiscoverResult, error) { + raw, err := a.client.Request("mcp.discover", params) + if err != nil { + return nil, err + } + var result MCPDiscoverResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + type ServerSessionFsApi serverApi func (a *ServerSessionFsApi) SetProvider(ctx context.Context, params *SessionFSSetProviderParams) (*SessionFSSetProviderResult, error) { @@ -1812,6 +1961,22 @@ func (a *HistoryApi) Truncate(ctx context.Context, params *SessionHistoryTruncat return &result, nil } +// Experimental: UsageApi contains experimental APIs that may change or be removed. +type UsageApi sessionApi + +func (a *UsageApi) GetMetrics(ctx context.Context) (*SessionUsageGetMetricsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.usage.getMetrics", req) + if err != nil { + return nil, err + } + var result SessionUsageGetMetricsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // SessionRpc provides typed session-scoped RPC methods. type SessionRpc struct { common sessionApi // Reuse a single struct instead of allocating one for each service on the heap. @@ -1832,6 +1997,7 @@ type SessionRpc struct { Permissions *PermissionsApi Shell *ShellApi History *HistoryApi + Usage *UsageApi } func (a *SessionRpc) Log(ctx context.Context, params *SessionLogParams) (*SessionLogResult, error) { @@ -1878,6 +2044,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { r.Permissions = (*PermissionsApi)(&r.common) r.Shell = (*ShellApi)(&r.common) r.History = (*HistoryApi)(&r.common) + r.Usage = (*UsageApi)(&r.common) return r } diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 84754e70f..55c3a4f24 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.21", + "@github/copilot": "^1.0.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.21.tgz", - "integrity": "sha512-P+nORjNKAtl92jYCG6Qr1Rsw2JoyScgeQSkIR6O2WB37WS5JVdA4ax1WVualMbfuc9V58CPHX6fwyNpkI89FkQ==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.22.tgz", + "integrity": "sha512-BR9oTJ1tQ51RV81xcxmlZe0zB3Tf8i/vFsKSTm2f5wRLJgtuVl2LgaFStoI/peTFcmgtZbhrqsnWTu5GkEPK5Q==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.21", - "@github/copilot-darwin-x64": "1.0.21", - "@github/copilot-linux-arm64": "1.0.21", - "@github/copilot-linux-x64": "1.0.21", - "@github/copilot-win32-arm64": "1.0.21", - "@github/copilot-win32-x64": "1.0.21" + "@github/copilot-darwin-arm64": "1.0.22", + "@github/copilot-darwin-x64": "1.0.22", + "@github/copilot-linux-arm64": "1.0.22", + "@github/copilot-linux-x64": "1.0.22", + "@github/copilot-win32-arm64": "1.0.22", + "@github/copilot-win32-x64": "1.0.22" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.21.tgz", - "integrity": "sha512-aB+s9ldTwcyCOYmzjcQ4SknV6g81z92T8aUJEJZBwOXOTBeWKAJtk16ooAKangZgdwuLgO3or1JUjx1FJAm5nQ==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.22.tgz", + "integrity": "sha512-cK42uX+oz46Cjsb7z+rdPw+DIGczfVSFWlc1WDcdVlwBW4cEfV0pzFXExpN1r1z179TFgAaVMbhkgLqhOZ/PeQ==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.21.tgz", - "integrity": "sha512-aNad81DOGuGShmaiFNIxBUSZLwte0dXmDYkGfAF9WJIgY4qP4A8CPWFoNr8//gY+4CwaIf9V+f/OC6k2BdECbw==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.22.tgz", + "integrity": "sha512-Pmw0ipF+yeLbP6JctsEoMS2LUCpVdC2r557BnCoe48BN8lO8i9JLnkpuDDrJ1AZuCk1VjnujFKEQywOOdfVlpA==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.21.tgz", - "integrity": "sha512-FL0NsCnHax4czHVv1S8iBqPLGZDhZ28N3+6nT29xWGhmjBWTkIofxLThKUPcyyMsfPTTxIlrdwWa8qQc5z2Q+g==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.22.tgz", + "integrity": "sha512-WVgG67VmZgHoD7GMlkTxEVe1qK8k9Ek9A02/Da7obpsDdtBInt3nJTwBEgm4cNDM4XaenQH17/jmwVtTwXB6lw==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.21.tgz", - "integrity": "sha512-S7pWVI16hesZtxYbIyfw+MHZpc5ESoGKUVr5Y+lZJNaM2340gJGPQzQwSpvKIRMLHRKI2hXLwciAnYeMFxE/Tg==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.22.tgz", + "integrity": "sha512-XRkHVFmdC7FMrczXOdPjbNKiknMr13asKtwJoErJO/Xdy4cmzKQHSvNsBk8VNrr7oyWrUcB1F6mbIxb2LFxPOw==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.21.tgz", - "integrity": "sha512-a9qc2Ku+XbyBkXCclbIvBbIVnECACTIWnPctmXWsQeSdeapGxgfHGux7y8hAFV5j6+nhCm6cnyEMS3rkZjAhdA==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.22.tgz", + "integrity": "sha512-Ao6gv1f2ZV+HVlkB1MV7YFdCuaB3NcFCnNu0a6/WLl2ypsfP1vWosPPkIB32jQJeBkT9ku3exOZLRj+XC0P3Mg==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.21.tgz", - "integrity": "sha512-9klu+7NQ6tEyb8sibb0rsbimBivDrnNltZho10Bgbf1wh3o+erTjffXDjW9Zkyaw8lZA9Fz8bqhVkKntZq58Lg==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.22.tgz", + "integrity": "sha512-EppcL+3TpxC+X/eQEIYtkN0PaA3/cvtI9UJqldLIkKDPXNYk/0mw877Ru9ypRcBWBWokDN6iKIWk5IxYH+JIvg==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index e79814992..6a0ef9567 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.21", + "@github/copilot": "^1.0.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index d95f5582a..3c5ebfd97 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.21", + "@github/copilot": "^1.0.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 753a6a65f..1733e5cd9 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -361,6 +361,38 @@ export interface McpConfigRemoveParams { name: string; } +export interface McpDiscoverResult { + /** + * MCP servers discovered from all sources + */ + servers: DiscoveredMcpServer[]; +} +export interface DiscoveredMcpServer { + /** + * Server name (config key) + */ + name: string; + /** + * Server type: local, stdio, http, or sse + */ + type?: string; + /** + * Configuration source + */ + source: "user" | "workspace" | "plugin" | "builtin"; + /** + * Whether the server is enabled (not in the disabled list) + */ + enabled: boolean; +} + +export interface McpDiscoverParams { + /** + * Working directory used as context for discovery (e.g., plugin resolution) + */ + workingDirectory?: string; +} + export interface SessionFsSetProviderResult { /** * Whether the provider was set successfully @@ -1035,21 +1067,45 @@ export interface SessionToolsHandlePendingToolCallParams { * Target session identifier */ sessionId: string; + /** + * Request ID of the pending tool call + */ requestId: string; + /** + * Tool call result (string or expanded result object) + */ result?: | string | { + /** + * Text result to send back to the LLM + */ textResultForLlm: string; + /** + * Type of the tool result + */ resultType?: string; + /** + * Error message if the tool call failed + */ error?: string; + /** + * Telemetry data from tool execution + */ toolTelemetry?: { [k: string]: unknown; }; }; + /** + * Error message if the tool call failed + */ error?: string; } export interface SessionCommandsHandlePendingCommandResult { + /** + * Whether the command was handled successfully + */ success: boolean; } @@ -1223,30 +1279,69 @@ export interface SessionPermissionsHandlePendingPermissionRequestParams { * Target session identifier */ sessionId: string; + /** + * Request ID of the pending permission request + */ requestId: string; result: | { + /** + * The permission request was approved + */ kind: "approved"; } | { + /** + * Denied because approval rules explicitly blocked it + */ kind: "denied-by-rules"; + /** + * Rules that denied the request + */ rules: unknown[]; } | { + /** + * Denied because no approval rule matched and user confirmation was unavailable + */ kind: "denied-no-approval-rule-and-could-not-request-from-user"; } | { + /** + * Denied by the user during an interactive prompt + */ kind: "denied-interactively-by-user"; + /** + * Optional feedback from the user explaining the denial + */ feedback?: string; } | { + /** + * Denied by the organization's content exclusion policy + */ kind: "denied-by-content-exclusion-policy"; + /** + * File path that triggered the exclusion + */ path: string; + /** + * Human-readable explanation of why the path was excluded + */ message: string; } | { + /** + * Denied by a permission request hook registered by an extension or plugin + */ kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ message?: string; + /** + * Whether to interrupt the current agent turn + */ interrupt?: boolean; }; } @@ -1343,6 +1438,35 @@ export interface SessionHistoryCompactResult { * Number of messages removed during compaction */ messagesRemoved: number; + /** + * Post-compaction context window usage breakdown + */ + contextWindow?: { + /** + * Maximum token count for the model's context window + */ + tokenLimit: number; + /** + * Current total tokens in the context window (system + conversation + tool definitions) + */ + currentTokens: number; + /** + * Current number of messages in the conversation + */ + messagesLength: number; + /** + * Token count from system message(s) + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) + */ + conversationTokens?: number; + /** + * Token count from tool definitions + */ + toolDefinitionsTokens?: number; + }; } /** @experimental */ @@ -1373,6 +1497,104 @@ export interface SessionHistoryTruncateParams { eventId: string; } +/** @experimental */ +export interface SessionUsageGetMetricsResult { + /** + * Total user-initiated premium request cost across all models (may be fractional due to multipliers) + */ + totalPremiumRequestCost: number; + /** + * Raw count of user-initiated API requests + */ + totalUserRequests: number; + /** + * Total time spent in model API calls (milliseconds) + */ + totalApiDurationMs: number; + /** + * Session start timestamp (epoch milliseconds) + */ + sessionStartTime: number; + /** + * Aggregated code change metrics + */ + codeChanges: { + /** + * Total lines of code added + */ + linesAdded: number; + /** + * Total lines of code removed + */ + linesRemoved: number; + /** + * Number of distinct files modified + */ + filesModifiedCount: number; + }; + /** + * Per-model token and request metrics, keyed by model identifier + */ + modelMetrics: { + [k: string]: { + /** + * Request count and cost metrics for this model + */ + requests: { + /** + * Number of API requests made with this model + */ + count: number; + /** + * User-initiated premium request cost (with multiplier applied) + */ + cost: number; + }; + /** + * Token usage metrics for this model + */ + usage: { + /** + * Total input tokens consumed + */ + inputTokens: number; + /** + * Total output tokens produced + */ + outputTokens: number; + /** + * Total tokens read from prompt cache + */ + cacheReadTokens: number; + /** + * Total tokens written to prompt cache + */ + cacheWriteTokens: number; + }; + }; + }; + /** + * Currently active model identifier + */ + currentModel?: string; + /** + * Input tokens from the most recent main-agent API call + */ + lastCallInputTokens: number; + /** + * Output tokens from the most recent main-agent API call + */ + lastCallOutputTokens: number; +} + +/** @experimental */ +export interface SessionUsageGetMetricsParams { + /** + * Target session identifier + */ + sessionId: string; +} + export interface SessionFsReadFileResult { /** * File content as UTF-8 string @@ -1607,6 +1829,8 @@ export function createServerRpc(connection: MessageConnection) { remove: async (params: McpConfigRemoveParams): Promise => connection.sendRequest("mcp.config.remove", params), }, + discover: async (params: McpDiscoverParams): Promise => + connection.sendRequest("mcp.discover", params), }, sessionFs: { setProvider: async (params: SessionFsSetProviderParams): Promise => @@ -1740,6 +1964,11 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin truncate: async (params: Omit): Promise => connection.sendRequest("session.history.truncate", { sessionId, ...params }), }, + /** @experimental */ + usage: { + getMetrics: async (): Promise => + connection.sendRequest("session.usage.getMetrics", { sessionId }), + }, }; } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index e9bc2a550..7cfc60522 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -2785,17 +2785,29 @@ export type SessionEvent = */ toolCallId?: string; /** - * Topic or subject of the memory being stored + * Whether this is a store or vote memory operation */ - subject: string; + action?: "store" | "vote"; /** - * The fact or convention being stored + * Topic or subject of the memory (store only) + */ + subject?: string; + /** + * The fact being stored or voted on */ fact: string; /** - * Source references for the stored fact + * Source references for the stored fact (store only) + */ + citations?: string; + /** + * Vote direction (vote only) + */ + direction?: "upvote" | "downvote"; + /** + * Reason for the vote (vote only) */ - citations: string; + reason?: string; } | { /** diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 43bb879be..19265c557 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -790,6 +790,83 @@ def to_dict(self) -> dict: return result +class ServerSource(Enum): + """Configuration source""" + + BUILTIN = "builtin" + PLUGIN = "plugin" + USER = "user" + WORKSPACE = "workspace" + + +@dataclass +class DiscoveredMCPServer: + enabled: bool + """Whether the server is enabled (not in the disabled list)""" + + name: str + """Server name (config key)""" + + source: ServerSource + """Configuration source""" + + type: str | None = None + """Server type: local, stdio, http, or sse""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredMCPServer': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = ServerSource(obj.get("source")) + type = from_union([from_str, from_none], obj.get("type")) + return DiscoveredMCPServer(enabled, name, source, type) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = to_enum(ServerSource, self.source) + if self.type is not None: + result["type"] = from_union([from_str, from_none], self.type) + return result + + +@dataclass +class MCPDiscoverResult: + servers: list[DiscoveredMCPServer] + """MCP servers discovered from all sources""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPDiscoverResult': + assert isinstance(obj, dict) + servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) + return MCPDiscoverResult(servers) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) + return result + + +@dataclass +class MCPDiscoverParams: + working_directory: str | None = None + """Working directory used as context for discovery (e.g., plugin resolution)""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPDiscoverParams': + assert isinstance(obj, dict) + working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) + return MCPDiscoverParams(working_directory) + + def to_dict(self) -> dict: + result: dict = {} + if self.working_directory is not None: + result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) + return result + + @dataclass class SessionFSSetProviderResult: success: bool @@ -1847,7 +1924,7 @@ def to_dict(self) -> dict: return result -class Source(Enum): +class ExtensionSource(Enum): """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" PROJECT = "project" @@ -1871,7 +1948,7 @@ class Extension: name: str """Extension name (directory name)""" - source: Source + source: ExtensionSource """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" status: ExtensionStatus @@ -1885,7 +1962,7 @@ def from_dict(obj: Any) -> 'Extension': assert isinstance(obj, dict) id = from_str(obj.get("id")) name = from_str(obj.get("name")) - source = Source(obj.get("source")) + source = ExtensionSource(obj.get("source")) status = ExtensionStatus(obj.get("status")) pid = from_union([from_int, from_none], obj.get("pid")) return Extension(id, name, source, status, pid) @@ -1894,7 +1971,7 @@ def to_dict(self) -> dict: result: dict = {} result["id"] = from_str(self.id) result["name"] = from_str(self.name) - result["source"] = to_enum(Source, self.source) + result["source"] = to_enum(ExtensionSource, self.source) result["status"] = to_enum(ExtensionStatus, self.status) if self.pid is not None: result["pid"] = from_union([from_int, from_none], self.pid) @@ -2014,9 +2091,16 @@ def to_dict(self) -> dict: @dataclass class ResultResult: text_result_for_llm: str + """Text result to send back to the LLM""" + error: str | None = None + """Error message if the tool call failed""" + result_type: str | None = None + """Type of the tool result""" + tool_telemetry: dict[str, Any] | None = None + """Telemetry data from tool execution""" @staticmethod def from_dict(obj: Any) -> 'ResultResult': @@ -2042,8 +2126,13 @@ def to_dict(self) -> dict: @dataclass class SessionToolsHandlePendingToolCallParams: request_id: str + """Request ID of the pending tool call""" + error: str | None = None + """Error message if the tool call failed""" + result: ResultResult | str | None = None + """Tool call result (string or expanded result object)""" @staticmethod def from_dict(obj: Any) -> 'SessionToolsHandlePendingToolCallParams': @@ -2066,6 +2155,7 @@ def to_dict(self) -> dict: @dataclass class SessionCommandsHandlePendingCommandResult: success: bool + """Whether the command was handled successfully""" @staticmethod def from_dict(obj: Any) -> 'SessionCommandsHandlePendingCommandResult': @@ -2438,11 +2528,34 @@ class Kind(Enum): @dataclass class SessionPermissionsHandlePendingPermissionRequestParamsResult: kind: Kind + """The permission request was approved + + Denied because approval rules explicitly blocked it + + Denied because no approval rule matched and user confirmation was unavailable + + Denied by the user during an interactive prompt + + Denied by the organization's content exclusion policy + + Denied by a permission request hook registered by an extension or plugin + """ rules: list[Any] | None = None + """Rules that denied the request""" + feedback: str | None = None + """Optional feedback from the user explaining the denial""" + message: str | None = None + """Human-readable explanation of why the path was excluded + + Optional message from the hook explaining the denial + """ path: str | None = None + """File path that triggered the exclusion""" + interrupt: bool | None = None + """Whether to interrupt the current agent turn""" @staticmethod def from_dict(obj: Any) -> 'SessionPermissionsHandlePendingPermissionRequestParamsResult': @@ -2474,6 +2587,8 @@ def to_dict(self) -> dict: @dataclass class SessionPermissionsHandlePendingPermissionRequestParams: request_id: str + """Request ID of the pending permission request""" + result: SessionPermissionsHandlePendingPermissionRequestParamsResult @staticmethod @@ -2646,6 +2761,53 @@ def to_dict(self) -> dict: return result +@dataclass +class ContextWindow: + """Post-compaction context window usage breakdown""" + + current_tokens: float + """Current total tokens in the context window (system + conversation + tool definitions)""" + + messages_length: float + """Current number of messages in the conversation""" + + token_limit: float + """Maximum token count for the model's context window""" + + conversation_tokens: float | None = None + """Token count from non-system messages (user, assistant, tool)""" + + system_tokens: float | None = None + """Token count from system message(s)""" + + tool_definitions_tokens: float | None = None + """Token count from tool definitions""" + + @staticmethod + def from_dict(obj: Any) -> 'ContextWindow': + assert isinstance(obj, dict) + current_tokens = from_float(obj.get("currentTokens")) + messages_length = from_float(obj.get("messagesLength")) + token_limit = from_float(obj.get("tokenLimit")) + conversation_tokens = from_union([from_float, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_float, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_float, from_none], obj.get("toolDefinitionsTokens")) + return ContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["currentTokens"] = to_float(self.current_tokens) + result["messagesLength"] = to_float(self.messages_length) + result["tokenLimit"] = to_float(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([to_float, from_none], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([to_float, from_none], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([to_float, from_none], self.tool_definitions_tokens) + return result + + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionHistoryCompactResult: @@ -2658,19 +2820,25 @@ class SessionHistoryCompactResult: tokens_removed: float """Number of tokens freed by compaction""" + context_window: ContextWindow | None = None + """Post-compaction context window usage breakdown""" + @staticmethod def from_dict(obj: Any) -> 'SessionHistoryCompactResult': assert isinstance(obj, dict) messages_removed = from_float(obj.get("messagesRemoved")) success = from_bool(obj.get("success")) tokens_removed = from_float(obj.get("tokensRemoved")) - return SessionHistoryCompactResult(messages_removed, success, tokens_removed) + context_window = from_union([ContextWindow.from_dict, from_none], obj.get("contextWindow")) + return SessionHistoryCompactResult(messages_removed, success, tokens_removed, context_window) def to_dict(self) -> dict: result: dict = {} result["messagesRemoved"] = to_float(self.messages_removed) result["success"] = from_bool(self.success) result["tokensRemoved"] = to_float(self.tokens_removed) + if self.context_window is not None: + result["contextWindow"] = from_union([lambda x: to_class(ContextWindow, x), from_none], self.context_window) return result @@ -2710,6 +2878,175 @@ def to_dict(self) -> dict: return result +@dataclass +class CodeChanges: + """Aggregated code change metrics""" + + files_modified_count: int + """Number of distinct files modified""" + + lines_added: int + """Total lines of code added""" + + lines_removed: int + """Total lines of code removed""" + + @staticmethod + def from_dict(obj: Any) -> 'CodeChanges': + assert isinstance(obj, dict) + files_modified_count = from_int(obj.get("filesModifiedCount")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + return CodeChanges(files_modified_count, lines_added, lines_removed) + + def to_dict(self) -> dict: + result: dict = {} + result["filesModifiedCount"] = from_int(self.files_modified_count) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) + return result + + +@dataclass +class Requests: + """Request count and cost metrics for this model""" + + cost: float + """User-initiated premium request cost (with multiplier applied)""" + + count: int + """Number of API requests made with this model""" + + @staticmethod + def from_dict(obj: Any) -> 'Requests': + assert isinstance(obj, dict) + cost = from_float(obj.get("cost")) + count = from_int(obj.get("count")) + return Requests(cost, count) + + def to_dict(self) -> dict: + result: dict = {} + result["cost"] = to_float(self.cost) + result["count"] = from_int(self.count) + return result + + +@dataclass +class Usage: + """Token usage metrics for this model""" + + cache_read_tokens: int + """Total tokens read from prompt cache""" + + cache_write_tokens: int + """Total tokens written to prompt cache""" + + input_tokens: int + """Total input tokens consumed""" + + output_tokens: int + """Total output tokens produced""" + + @staticmethod + def from_dict(obj: Any) -> 'Usage': + assert isinstance(obj, dict) + cache_read_tokens = from_int(obj.get("cacheReadTokens")) + cache_write_tokens = from_int(obj.get("cacheWriteTokens")) + input_tokens = from_int(obj.get("inputTokens")) + output_tokens = from_int(obj.get("outputTokens")) + return Usage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["cacheReadTokens"] = from_int(self.cache_read_tokens) + result["cacheWriteTokens"] = from_int(self.cache_write_tokens) + result["inputTokens"] = from_int(self.input_tokens) + result["outputTokens"] = from_int(self.output_tokens) + return result + + +@dataclass +class ModelMetric: + requests: Requests + """Request count and cost metrics for this model""" + + usage: Usage + """Token usage metrics for this model""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelMetric': + assert isinstance(obj, dict) + requests = Requests.from_dict(obj.get("requests")) + usage = Usage.from_dict(obj.get("usage")) + return ModelMetric(requests, usage) + + def to_dict(self) -> dict: + result: dict = {} + result["requests"] = to_class(Requests, self.requests) + result["usage"] = to_class(Usage, self.usage) + return result + + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class SessionUsageGetMetricsResult: + code_changes: CodeChanges + """Aggregated code change metrics""" + + last_call_input_tokens: int + """Input tokens from the most recent main-agent API call""" + + last_call_output_tokens: int + """Output tokens from the most recent main-agent API call""" + + model_metrics: dict[str, ModelMetric] + """Per-model token and request metrics, keyed by model identifier""" + + session_start_time: int + """Session start timestamp (epoch milliseconds)""" + + total_api_duration_ms: float + """Total time spent in model API calls (milliseconds)""" + + total_premium_request_cost: float + """Total user-initiated premium request cost across all models (may be fractional due to + multipliers) + """ + total_user_requests: int + """Raw count of user-initiated API requests""" + + current_model: str | None = None + """Currently active model identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionUsageGetMetricsResult': + assert isinstance(obj, dict) + code_changes = CodeChanges.from_dict(obj.get("codeChanges")) + last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) + last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) + model_metrics = from_dict(ModelMetric.from_dict, obj.get("modelMetrics")) + session_start_time = from_int(obj.get("sessionStartTime")) + total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) + total_user_requests = from_int(obj.get("totalUserRequests")) + current_model = from_union([from_str, from_none], obj.get("currentModel")) + return SessionUsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) + + def to_dict(self) -> dict: + result: dict = {} + result["codeChanges"] = to_class(CodeChanges, self.code_changes) + result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) + result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) + result["modelMetrics"] = from_dict(lambda x: to_class(ModelMetric, x), self.model_metrics) + result["sessionStartTime"] = from_int(self.session_start_time) + result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) + result["totalUserRequests"] = from_int(self.total_user_requests) + if self.current_model is not None: + result["currentModel"] = from_union([from_str, from_none], self.current_model) + return result + + @dataclass class SessionFSReadFileResult: content: str @@ -3195,6 +3532,22 @@ def mcp_config_remove_params_to_dict(x: MCPConfigRemoveParams) -> Any: return to_class(MCPConfigRemoveParams, x) +def mcp_discover_result_from_dict(s: Any) -> MCPDiscoverResult: + return MCPDiscoverResult.from_dict(s) + + +def mcp_discover_result_to_dict(x: MCPDiscoverResult) -> Any: + return to_class(MCPDiscoverResult, x) + + +def mcp_discover_params_from_dict(s: Any) -> MCPDiscoverParams: + return MCPDiscoverParams.from_dict(s) + + +def mcp_discover_params_to_dict(x: MCPDiscoverParams) -> Any: + return to_class(MCPDiscoverParams, x) + + def session_fs_set_provider_result_from_dict(s: Any) -> SessionFSSetProviderResult: return SessionFSSetProviderResult.from_dict(s) @@ -3715,6 +4068,14 @@ def session_history_truncate_params_to_dict(x: SessionHistoryTruncateParams) -> return to_class(SessionHistoryTruncateParams, x) +def session_usage_get_metrics_result_from_dict(s: Any) -> SessionUsageGetMetricsResult: + return SessionUsageGetMetricsResult.from_dict(s) + + +def session_usage_get_metrics_result_to_dict(x: SessionUsageGetMetricsResult) -> Any: + return to_class(SessionUsageGetMetricsResult, x) + + def session_fs_read_file_result_from_dict(s: Any) -> SessionFSReadFileResult: return SessionFSReadFileResult.from_dict(s) @@ -3871,6 +4232,10 @@ class ServerMcpApi: def __init__(self, client: "JsonRpcClient"): self._client = client + async def discover(self, params: MCPDiscoverParams, *, timeout: float | None = None) -> MCPDiscoverResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return MCPDiscoverResult.from_dict(await self._client.request("mcp.discover", params_dict, **_timeout_kwargs(timeout))) + class ServerSessionFsApi: def __init__(self, client: "JsonRpcClient"): @@ -4166,6 +4531,16 @@ async def truncate(self, params: SessionHistoryTruncateParams, *, timeout: float return SessionHistoryTruncateResult.from_dict(await self._client.request("session.history.truncate", params_dict, **_timeout_kwargs(timeout))) +# Experimental: this API group is experimental and may change or be removed. +class UsageApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get_metrics(self, *, timeout: float | None = None) -> SessionUsageGetMetricsResult: + return SessionUsageGetMetricsResult.from_dict(await self._client.request("session.usage.getMetrics", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + class SessionRpc: """Typed session-scoped RPC methods.""" def __init__(self, client: "JsonRpcClient", session_id: str): @@ -4187,6 +4562,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.permissions = PermissionsApi(client, session_id) self.shell = ShellApi(client, session_id) self.history = HistoryApi(client, session_id) + self.usage = UsageApi(client, session_id) async def log(self, params: SessionLogParams, *, timeout: float | None = None) -> SessionLogResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index dea0e79fd..2c29c791a 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -78,7 +78,7 @@ def from_int(x: Any) -> int: return x -class Action(Enum): +class DataAction(Enum): """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) """ @@ -857,6 +857,13 @@ class Operation(Enum): UPDATE = "update" +class PermissionRequestAction(Enum): + """Whether this is a store or vote memory operation""" + + STORE = "store" + VOTE = "vote" + + @dataclass class PermissionRequestCommand: identifier: str @@ -879,6 +886,13 @@ def to_dict(self) -> dict: return result +class Direction(Enum): + """Vote direction (vote only)""" + + DOWNVOTE = "downvote" + UPVOTE = "upvote" + + class PermissionRequestKind(Enum): CUSTOM_TOOL = "custom-tool" HOOK = "hook" @@ -921,7 +935,7 @@ class PermissionRequest: URL access permission request - Memory storage permission request + Memory operation permission request Custom tool invocation permission request @@ -999,14 +1013,23 @@ class PermissionRequest: url: str | None = None """URL to be fetched""" + action: PermissionRequestAction | None = None + """Whether this is a store or vote memory operation""" + citations: str | None = None - """Source references for the stored fact""" + """Source references for the stored fact (store only)""" + + direction: Direction | None = None + """Vote direction (vote only)""" fact: str | None = None - """The fact or convention being stored""" + """The fact being stored or voted on""" + + reason: str | None = None + """Reason for the vote (vote only)""" subject: str | None = None - """Topic or subject of the memory being stored""" + """Topic or subject of the memory (store only)""" tool_description: str | None = None """Description of what the custom tool does""" @@ -1040,13 +1063,16 @@ def from_dict(obj: Any) -> 'PermissionRequest': tool_name = from_union([from_str, from_none], obj.get("toolName")) tool_title = from_union([from_str, from_none], obj.get("toolTitle")) url = from_union([from_str, from_none], obj.get("url")) + action = from_union([PermissionRequestAction, from_none], obj.get("action")) citations = from_union([from_str, from_none], obj.get("citations")) + direction = from_union([Direction, from_none], obj.get("direction")) fact = from_union([from_str, from_none], obj.get("fact")) + reason = from_union([from_str, from_none], obj.get("reason")) subject = from_union([from_str, from_none], obj.get("subject")) tool_description = from_union([from_str, from_none], obj.get("toolDescription")) hook_message = from_union([from_str, from_none], obj.get("hookMessage")) tool_args = obj.get("toolArgs") - return PermissionRequest(kind, can_offer_session_approval, commands, full_command_text, has_write_file_redirection, intention, possible_paths, possible_urls, tool_call_id, warning, diff, file_name, new_file_contents, path, args, read_only, server_name, tool_name, tool_title, url, citations, fact, subject, tool_description, hook_message, tool_args) + return PermissionRequest(kind, can_offer_session_approval, commands, full_command_text, has_write_file_redirection, intention, possible_paths, possible_urls, tool_call_id, warning, diff, file_name, new_file_contents, path, args, read_only, server_name, tool_name, tool_title, url, action, citations, direction, fact, reason, subject, tool_description, hook_message, tool_args) def to_dict(self) -> dict: result: dict = {} @@ -1089,10 +1115,16 @@ def to_dict(self) -> dict: result["toolTitle"] = from_union([from_str, from_none], self.tool_title) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) + if self.action is not None: + result["action"] = from_union([lambda x: to_enum(PermissionRequestAction, x), from_none], self.action) if self.citations is not None: result["citations"] = from_union([from_str, from_none], self.citations) + if self.direction is not None: + result["direction"] = from_union([lambda x: to_enum(Direction, x), from_none], self.direction) if self.fact is not None: result["fact"] = from_union([from_str, from_none], self.fact) + if self.reason is not None: + result["reason"] = from_union([from_str, from_none], self.reason) if self.subject is not None: result["subject"] = from_union([from_str, from_none], self.subject) if self.tool_description is not None: @@ -2495,7 +2527,7 @@ class Data: requested_schema: RequestedSchema | None = None """JSON Schema describing the form fields to present to the user (form mode only)""" - action: Action | None = None + action: DataAction | None = None """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) """ @@ -2731,7 +2763,7 @@ def from_dict(obj: Any) -> 'Data': elicitation_source = from_union([from_str, from_none], obj.get("elicitationSource")) mode = from_union([Mode, from_none], obj.get("mode")) requested_schema = from_union([RequestedSchema.from_dict, from_none], obj.get("requestedSchema")) - action = from_union([Action, from_none], obj.get("action")) + action = from_union([DataAction, from_none], obj.get("action")) mcp_request_id = from_union([from_float, from_str, from_none], obj.get("mcpRequestId")) server_name = from_union([from_str, from_none], obj.get("serverName")) server_url = from_union([from_str, from_none], obj.get("serverUrl")) @@ -3058,7 +3090,7 @@ def to_dict(self) -> dict: if self.requested_schema is not None: result["requestedSchema"] = from_union([lambda x: to_class(RequestedSchema, x), from_none], self.requested_schema) if self.action is not None: - result["action"] = from_union([lambda x: to_enum(Action, x), from_none], self.action) + result["action"] = from_union([lambda x: to_enum(DataAction, x), from_none], self.action) if self.mcp_request_id is not None: result["mcpRequestId"] = from_union([to_float, from_str, from_none], self.mcp_request_id) if self.server_name is not None: diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 63968077e..e6042eae5 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -701,7 +701,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise // RPC TYPES // ══════════════════════════════════════════════════════════════════════════════ -let emittedRpcClasses = new Set(); +let emittedRpcClassSchemas = new Map(); let experimentalRpcTypes = new Set(); let rpcKnownTypes = new Map(); let rpcEnumOutput: string[] = []; @@ -722,6 +722,17 @@ function paramsTypeName(rpcMethod: string): string { return `${typeToClassName(rpcMethod)}Params`; } +function stableStringify(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map((item) => stableStringify(item)).join(",")}]`; + } + if (value && typeof value === "object") { + const entries = Object.entries(value as Record).sort(([a], [b]) => a.localeCompare(b)); + return `{${entries.map(([key, entryValue]) => `${JSON.stringify(key)}:${stableStringify(entryValue)}`).join(",")}}`; + } + return JSON.stringify(value); +} + function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassName: string, propName: string, classes: string[]): string { // Handle anyOf: [T, null] → T? (nullable typed property) if (schema.anyOf) { @@ -744,8 +755,8 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam if (schema.type === "array" && schema.items) { const items = schema.items as JSONSchema7; if (items.type === "object" && items.properties) { - const itemClass = singularPascal(propName); - if (!emittedRpcClasses.has(itemClass)) classes.push(emitRpcClass(itemClass, items, "public", classes)); + const itemClass = (items.title as string) ?? singularPascal(propName); + classes.push(emitRpcClass(itemClass, items, "public", classes)); return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; } const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes); @@ -765,8 +776,18 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam } function emitRpcClass(className: string, schema: JSONSchema7, visibility: "public" | "internal", extraClasses: string[]): string { - if (emittedRpcClasses.has(className)) return ""; - emittedRpcClasses.add(className); + const schemaKey = stableStringify(schema); + const existingSchema = emittedRpcClassSchemas.get(className); + if (existingSchema) { + if (existingSchema !== schemaKey) { + throw new Error( + `Conflicting RPC class name "${className}" for different schemas. Add a schema title/withTypeName to disambiguate.` + ); + } + return ""; + } + + emittedRpcClassSchemas.set(className, schemaKey); const requiredSet = new Set(schema.required || []); const lines: string[] = []; @@ -800,7 +821,7 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi } else if (csharpType.startsWith("IDictionary<")) { const concreteType = csharpType.replace("IDictionary<", "Dictionary<"); propAccessors = `{ get => field ??= new ${concreteType}(); set; }`; - } else if (emittedRpcClasses.has(csharpType)) { + } else if (emittedRpcClassSchemas.has(csharpType)) { propAccessors = "{ get => field ??= new(); set; }"; } } @@ -1171,7 +1192,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, } function generateRpcCode(schema: ApiSchema): string { - emittedRpcClasses.clear(); + emittedRpcClassSchemas.clear(); experimentalRpcTypes.clear(); rpcKnownTypes.clear(); rpcEnumOutput = []; @@ -1216,7 +1237,7 @@ internal static class Diagnostics if (clientSessionParts.length > 0) lines.push(...clientSessionParts, ""); // Add JsonSerializerContext for AOT/trimming support - const typeNames = [...emittedRpcClasses].sort(); + const typeNames = [...emittedRpcClassSchemas.keys()].sort(); if (typeNames.length > 0) { lines.push(`[JsonSourceGenerationOptions(`); lines.push(` JsonSerializerDefaults.Web,`); diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 7b3277eba..691d66bf9 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.21", + "@github/copilot": "^1.0.22", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.21.tgz", - "integrity": "sha512-P+nORjNKAtl92jYCG6Qr1Rsw2JoyScgeQSkIR6O2WB37WS5JVdA4ax1WVualMbfuc9V58CPHX6fwyNpkI89FkQ==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.22.tgz", + "integrity": "sha512-BR9oTJ1tQ51RV81xcxmlZe0zB3Tf8i/vFsKSTm2f5wRLJgtuVl2LgaFStoI/peTFcmgtZbhrqsnWTu5GkEPK5Q==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.21", - "@github/copilot-darwin-x64": "1.0.21", - "@github/copilot-linux-arm64": "1.0.21", - "@github/copilot-linux-x64": "1.0.21", - "@github/copilot-win32-arm64": "1.0.21", - "@github/copilot-win32-x64": "1.0.21" + "@github/copilot-darwin-arm64": "1.0.22", + "@github/copilot-darwin-x64": "1.0.22", + "@github/copilot-linux-arm64": "1.0.22", + "@github/copilot-linux-x64": "1.0.22", + "@github/copilot-win32-arm64": "1.0.22", + "@github/copilot-win32-x64": "1.0.22" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.21.tgz", - "integrity": "sha512-aB+s9ldTwcyCOYmzjcQ4SknV6g81z92T8aUJEJZBwOXOTBeWKAJtk16ooAKangZgdwuLgO3or1JUjx1FJAm5nQ==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.22.tgz", + "integrity": "sha512-cK42uX+oz46Cjsb7z+rdPw+DIGczfVSFWlc1WDcdVlwBW4cEfV0pzFXExpN1r1z179TFgAaVMbhkgLqhOZ/PeQ==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.21.tgz", - "integrity": "sha512-aNad81DOGuGShmaiFNIxBUSZLwte0dXmDYkGfAF9WJIgY4qP4A8CPWFoNr8//gY+4CwaIf9V+f/OC6k2BdECbw==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.22.tgz", + "integrity": "sha512-Pmw0ipF+yeLbP6JctsEoMS2LUCpVdC2r557BnCoe48BN8lO8i9JLnkpuDDrJ1AZuCk1VjnujFKEQywOOdfVlpA==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.21.tgz", - "integrity": "sha512-FL0NsCnHax4czHVv1S8iBqPLGZDhZ28N3+6nT29xWGhmjBWTkIofxLThKUPcyyMsfPTTxIlrdwWa8qQc5z2Q+g==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.22.tgz", + "integrity": "sha512-WVgG67VmZgHoD7GMlkTxEVe1qK8k9Ek9A02/Da7obpsDdtBInt3nJTwBEgm4cNDM4XaenQH17/jmwVtTwXB6lw==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.21.tgz", - "integrity": "sha512-S7pWVI16hesZtxYbIyfw+MHZpc5ESoGKUVr5Y+lZJNaM2340gJGPQzQwSpvKIRMLHRKI2hXLwciAnYeMFxE/Tg==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.22.tgz", + "integrity": "sha512-XRkHVFmdC7FMrczXOdPjbNKiknMr13asKtwJoErJO/Xdy4cmzKQHSvNsBk8VNrr7oyWrUcB1F6mbIxb2LFxPOw==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.21.tgz", - "integrity": "sha512-a9qc2Ku+XbyBkXCclbIvBbIVnECACTIWnPctmXWsQeSdeapGxgfHGux7y8hAFV5j6+nhCm6cnyEMS3rkZjAhdA==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.22.tgz", + "integrity": "sha512-Ao6gv1f2ZV+HVlkB1MV7YFdCuaB3NcFCnNu0a6/WLl2ypsfP1vWosPPkIB32jQJeBkT9ku3exOZLRj+XC0P3Mg==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.21.tgz", - "integrity": "sha512-9klu+7NQ6tEyb8sibb0rsbimBivDrnNltZho10Bgbf1wh3o+erTjffXDjW9Zkyaw8lZA9Fz8bqhVkKntZq58Lg==", + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.22.tgz", + "integrity": "sha512-EppcL+3TpxC+X/eQEIYtkN0PaA3/cvtI9UJqldLIkKDPXNYk/0mw877Ru9ypRcBWBWokDN6iKIWk5IxYH+JIvg==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index d9b9ea64b..def9f09cf 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.21", + "@github/copilot": "^1.0.22", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From 68f46b34eee63bfc5f31ca585c85ebadf7be31e3 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 14 Apr 2026 13:02:19 +0100 Subject: [PATCH 13/34] Naming improvements via codegen changes (#1043) --- docs/features/image-input.md | 16 +- dotnet/src/Client.cs | 5 + dotnet/src/Generated/Rpc.cs | 1158 ++++---- dotnet/src/Generated/SessionEvents.cs | 526 ++-- dotnet/src/Session.cs | 30 +- dotnet/src/Types.cs | 8 +- dotnet/test/CloneTests.cs | 6 +- dotnet/test/ElicitationTests.cs | 18 +- .../MultiClientCommandsElicitationTests.cs | 4 +- dotnet/test/MultiClientTests.cs | 4 +- dotnet/test/RpcTests.cs | 10 +- dotnet/test/SessionEventSerializationTests.cs | 10 +- dotnet/test/SessionFsTests.cs | 34 +- dotnet/test/SessionTests.cs | 6 +- go/client.go | 4 +- go/client_test.go | 4 +- go/generated_session_events.go | 462 +-- go/internal/e2e/agent_and_compact_rpc_test.go | 4 +- go/internal/e2e/rpc_test.go | 32 +- go/internal/e2e/session_fs_test.go | 63 +- go/internal/e2e/session_test.go | 4 +- go/rpc/generated_rpc.go | 931 +++--- go/rpc/result_union.go | 22 +- go/session.go | 104 +- go/session_test.go | 4 +- go/types.go | 2 +- nodejs/package-lock.json | 56 +- nodejs/package.json | 2 +- nodejs/src/generated/rpc.ts | 853 +++--- nodejs/src/generated/session-events.ts | 82 +- nodejs/src/types.ts | 6 +- nodejs/test/e2e/rpc.test.ts | 14 +- python/copilot/client.py | 40 +- python/copilot/generated/rpc.py | 2557 +++++++---------- python/copilot/generated/session_events.py | 578 ++-- python/copilot/session.py | 110 +- python/e2e/test_agent_and_compact_rpc.py | 8 +- python/e2e/test_rpc.py | 36 +- python/test_commands_and_elicitation.py | 8 +- python/test_rpc_timeout.py | 18 +- scripts/codegen/csharp.ts | 186 +- scripts/codegen/go.ts | 227 +- scripts/codegen/python.ts | 193 +- scripts/codegen/typescript.ts | 128 +- scripts/codegen/utils.ts | 184 +- .../prompts/attachments/csharp/Program.cs | 2 +- ...ompact_session_history_after_messages.yaml | 37 +- ...with_compaction_while_using_sessionfs.yaml | 38 +- 48 files changed, 4401 insertions(+), 4433 deletions(-) diff --git a/docs/features/image-input.md b/docs/features/image-input.md index 91d3cc75a..409130bbd 100644 --- a/docs/features/image-input.md +++ b/docs/features/image-input.md @@ -178,9 +178,9 @@ public static class ImageInputExample await session.SendAsync(new MessageOptions { Prompt = "Describe what you see in this image", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemFile + new UserMessageAttachmentFile { Path = "/absolute/path/to/screenshot.png", DisplayName = "screenshot.png", @@ -206,9 +206,9 @@ await using var session = await client.CreateSessionAsync(new SessionConfig await session.SendAsync(new MessageOptions { Prompt = "Describe what you see in this image", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemFile + new UserMessageAttachmentFile { Path = "/absolute/path/to/screenshot.png", DisplayName = "screenshot.png", @@ -396,9 +396,9 @@ public static class BlobAttachmentExample await session.SendAsync(new MessageOptions { Prompt = "Describe what you see in this image", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemBlob + new UserMessageAttachmentBlob { Data = base64ImageData, MimeType = "image/png", @@ -415,9 +415,9 @@ public static class BlobAttachmentExample await session.SendAsync(new MessageOptions { Prompt = "Describe what you see in this image", - Attachments = new List + Attachments = new List { - new UserMessageDataAttachmentsItemBlob + new UserMessageAttachmentBlob { Data = base64ImageData, MimeType = "image/png", diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 29b49c294..0124008f4 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1043,6 +1043,11 @@ internal static async Task InvokeRpcAsync(JsonRpc rpc, string method, obje return await InvokeRpcAsync(rpc, method, args, null, cancellationToken); } + internal static async Task InvokeRpcAsync(JsonRpc rpc, string method, object?[]? args, CancellationToken cancellationToken) + { + await InvokeRpcAsync(rpc, method, args, null, cancellationToken); + } + internal static async Task InvokeRpcAsync(JsonRpc rpc, string method, object?[]? args, StringBuilder? stderrBuffer, CancellationToken cancellationToken) { try diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 0caa4bbd2..0bea6e8db 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -21,7 +21,7 @@ internal static class Diagnostics } /// RPC data type for Ping operations. -public class PingResult +public sealed class PingResult { /// Echoed message (or default greeting). [JsonPropertyName("message")] @@ -29,15 +29,15 @@ public class PingResult /// Server timestamp in milliseconds. [JsonPropertyName("timestamp")] - public double Timestamp { get; set; } + public long Timestamp { get; set; } /// Server protocol version number. [JsonPropertyName("protocolVersion")] - public double ProtocolVersion { get; set; } + public long ProtocolVersion { get; set; } } /// RPC data type for Ping operations. -internal class PingRequest +internal sealed class PingRequest { /// Optional message to echo back. [JsonPropertyName("message")] @@ -45,7 +45,7 @@ internal class PingRequest } /// Feature flags indicating what the model supports. -public class ModelCapabilitiesSupports +public sealed class ModelCapabilitiesSupports { /// Whether this model supports vision/image input. [JsonPropertyName("vision")] @@ -57,35 +57,40 @@ public class ModelCapabilitiesSupports } /// Vision-specific limits. -public class ModelCapabilitiesLimitsVision +public sealed class ModelCapabilitiesLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] public IList SupportedMediaTypes { get => field ??= []; set; } /// Maximum number of images per prompt. + [Range((double)1, (double)long.MaxValue)] [JsonPropertyName("max_prompt_images")] - public double MaxPromptImages { get; set; } + public long MaxPromptImages { get; set; } /// Maximum image size in bytes. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_prompt_image_size")] - public double MaxPromptImageSize { get; set; } + public long MaxPromptImageSize { get; set; } } /// Token limits for prompts, outputs, and context window. -public class ModelCapabilitiesLimits +public sealed class ModelCapabilitiesLimits { /// Maximum number of prompt/input tokens. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_prompt_tokens")] - public double? MaxPromptTokens { get; set; } + public long? MaxPromptTokens { get; set; } /// Maximum number of output/completion tokens. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_output_tokens")] - public double? MaxOutputTokens { get; set; } + public long? MaxOutputTokens { get; set; } /// Maximum total context window size in tokens. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_context_window_tokens")] - public double MaxContextWindowTokens { get; set; } + public long MaxContextWindowTokens { get; set; } /// Vision-specific limits. [JsonPropertyName("vision")] @@ -93,7 +98,7 @@ public class ModelCapabilitiesLimits } /// Model capabilities and limits. -public class ModelCapabilities +public sealed class ModelCapabilities { /// Feature flags indicating what the model supports. [JsonPropertyName("supports")] @@ -105,7 +110,7 @@ public class ModelCapabilities } /// Policy state (if applicable). -public class ModelPolicy +public sealed class ModelPolicy { /// Current policy state for this model. [JsonPropertyName("state")] @@ -117,7 +122,7 @@ public class ModelPolicy } /// Billing information. -public class ModelBilling +public sealed class ModelBilling { /// Billing cost multiplier relative to the base rate. [JsonPropertyName("multiplier")] @@ -125,7 +130,7 @@ public class ModelBilling } /// RPC data type for Model operations. -public class Model +public sealed class Model { /// Model identifier (e.g., "claude-sonnet-4.5"). [JsonPropertyName("id")] @@ -156,8 +161,8 @@ public class Model public string? DefaultReasoningEffort { get; set; } } -/// RPC data type for ModelsList operations. -public class ModelsListResult +/// RPC data type for ModelList operations. +public sealed class ModelList { /// List of available models with full metadata. [JsonPropertyName("models")] @@ -165,7 +170,7 @@ public class ModelsListResult } /// RPC data type for Tool operations. -public class Tool +public sealed class Tool { /// Tool identifier (e.g., "bash", "grep", "str_replace_editor"). [JsonPropertyName("name")] @@ -188,8 +193,8 @@ public class Tool public string? Instructions { get; set; } } -/// RPC data type for ToolsList operations. -public class ToolsListResult +/// RPC data type for ToolList operations. +public sealed class ToolList { /// List of available built-in tools with metadata. [JsonPropertyName("tools")] @@ -197,31 +202,33 @@ public class ToolsListResult } /// RPC data type for ToolsList operations. -internal class ToolsListRequest +internal sealed class ToolsListRequest { /// Optional model ID — when provided, the returned tool list reflects model-specific overrides. [JsonPropertyName("model")] public string? Model { get; set; } } -/// RPC data type for AccountGetQuotaResultQuotaSnapshotsValue operations. -public class AccountGetQuotaResultQuotaSnapshotsValue +/// RPC data type for AccountQuotaSnapshot operations. +public sealed class AccountQuotaSnapshot { /// Number of requests included in the entitlement. [JsonPropertyName("entitlementRequests")] - public double EntitlementRequests { get; set; } + public long EntitlementRequests { get; set; } /// Number of requests used so far this period. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("usedRequests")] - public double UsedRequests { get; set; } + public long UsedRequests { get; set; } /// Percentage of entitlement remaining. [JsonPropertyName("remainingPercentage")] public double RemainingPercentage { get; set; } /// Number of overage requests made this period. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("overage")] - public double Overage { get; set; } + public long Overage { get; set; } /// Whether pay-per-request usage is allowed when quota is exhausted. [JsonPropertyName("overageAllowedWithExhaustedQuota")] @@ -229,27 +236,28 @@ public class AccountGetQuotaResultQuotaSnapshotsValue /// Date when the quota resets (ISO 8601). [JsonPropertyName("resetDate")] - public string? ResetDate { get; set; } + public DateTimeOffset? ResetDate { get; set; } } /// RPC data type for AccountGetQuota operations. -public class AccountGetQuotaResult +public sealed class AccountGetQuotaResult { /// Quota snapshots keyed by type (e.g., chat, completions, premium_interactions). [JsonPropertyName("quotaSnapshots")] - public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } + public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } } /// RPC data type for DiscoveredMcpServer operations. -public class DiscoveredMcpServer +public sealed class DiscoveredMcpServer { /// Server name (config key). + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Server type: local, stdio, http, or sse. + /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). [JsonPropertyName("type")] - public string? Type { get; set; } + public DiscoveredMcpServerType? Type { get; set; } /// Configuration source. [JsonPropertyName("source")] @@ -261,7 +269,7 @@ public class DiscoveredMcpServer } /// RPC data type for McpDiscover operations. -public class McpDiscoverResult +public sealed class McpDiscoverResult { /// MCP servers discovered from all sources. [JsonPropertyName("servers")] @@ -269,7 +277,7 @@ public class McpDiscoverResult } /// RPC data type for McpDiscover operations. -internal class McpDiscoverRequest +internal sealed class McpDiscoverRequest { /// Working directory used as context for discovery (e.g., plugin resolution). [JsonPropertyName("workingDirectory")] @@ -277,7 +285,7 @@ internal class McpDiscoverRequest } /// RPC data type for SessionFsSetProvider operations. -public class SessionFsSetProviderResult +public sealed class SessionFsSetProviderResult { /// Whether the provider was set successfully. [JsonPropertyName("success")] @@ -285,7 +293,7 @@ public class SessionFsSetProviderResult } /// RPC data type for SessionFsSetProvider operations. -internal class SessionFsSetProviderRequest +internal sealed class SessionFsSetProviderRequest { /// Initial working directory for sessions. [JsonPropertyName("initialCwd")] @@ -297,12 +305,12 @@ internal class SessionFsSetProviderRequest /// Path conventions used by this filesystem. [JsonPropertyName("conventions")] - public SessionFsSetProviderRequestConventions Conventions { get; set; } + public SessionFsSetProviderConventions Conventions { get; set; } } /// RPC data type for SessionsFork operations. [Experimental(Diagnostics.Experimental)] -public class SessionsForkResult +public sealed class SessionsForkResult { /// The new forked session's ID. [JsonPropertyName("sessionId")] @@ -311,7 +319,7 @@ public class SessionsForkResult /// RPC data type for SessionsFork operations. [Experimental(Diagnostics.Experimental)] -internal class SessionsForkRequest +internal sealed class SessionsForkRequest { /// Source session ID to fork from. [JsonPropertyName("sessionId")] @@ -322,16 +330,16 @@ internal class SessionsForkRequest public string? ToEventId { get; set; } } -/// RPC data type for SessionLog operations. -public class SessionLogResult +/// RPC data type for Log operations. +public sealed class LogResult { /// The unique identifier of the emitted session event. [JsonPropertyName("eventId")] public Guid EventId { get; set; } } -/// RPC data type for SessionLog operations. -internal class SessionLogRequest +/// RPC data type for Log operations. +internal sealed class LogRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -343,7 +351,7 @@ internal class SessionLogRequest /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". [JsonPropertyName("level")] - public SessionLogRequestLevel? Level { get; set; } + public SessionLogLevel? Level { get; set; } /// When true, the message is transient and not persisted to the session event log on disk. [JsonPropertyName("ephemeral")] @@ -356,8 +364,8 @@ internal class SessionLogRequest public string? Url { get; set; } } -/// RPC data type for SessionModelGetCurrent operations. -public class SessionModelGetCurrentResult +/// RPC data type for CurrentModel operations. +public sealed class CurrentModel { /// Currently active model identifier. [JsonPropertyName("modelId")] @@ -365,15 +373,15 @@ public class SessionModelGetCurrentResult } /// RPC data type for SessionModelGetCurrent operations. -internal class SessionModelGetCurrentRequest +internal sealed class SessionModelGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionModelSwitchTo operations. -public class SessionModelSwitchToResult +/// RPC data type for ModelSwitchTo operations. +public sealed class ModelSwitchToResult { /// Currently active model identifier after the switch. [JsonPropertyName("modelId")] @@ -381,7 +389,7 @@ public class SessionModelSwitchToResult } /// Feature flags indicating what the model supports. -public class ModelCapabilitiesOverrideSupports +public sealed class ModelCapabilitiesOverrideSupports { /// Gets or sets the vision value. [JsonPropertyName("vision")] @@ -393,35 +401,40 @@ public class ModelCapabilitiesOverrideSupports } /// RPC data type for ModelCapabilitiesOverrideLimitsVision operations. -public class ModelCapabilitiesOverrideLimitsVision +public sealed class ModelCapabilitiesOverrideLimitsVision { /// MIME types the model accepts. [JsonPropertyName("supported_media_types")] public IList? SupportedMediaTypes { get; set; } /// Maximum number of images per prompt. + [Range((double)1, (double)long.MaxValue)] [JsonPropertyName("max_prompt_images")] - public double? MaxPromptImages { get; set; } + public long? MaxPromptImages { get; set; } /// Maximum image size in bytes. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_prompt_image_size")] - public double? MaxPromptImageSize { get; set; } + public long? MaxPromptImageSize { get; set; } } /// Token limits for prompts, outputs, and context window. -public class ModelCapabilitiesOverrideLimits +public sealed class ModelCapabilitiesOverrideLimits { /// Gets or sets the max_prompt_tokens value. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_prompt_tokens")] - public double? MaxPromptTokens { get; set; } + public long? MaxPromptTokens { get; set; } /// Gets or sets the max_output_tokens value. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_output_tokens")] - public double? MaxOutputTokens { get; set; } + public long? MaxOutputTokens { get; set; } /// Maximum total context window size in tokens. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_context_window_tokens")] - public double? MaxContextWindowTokens { get; set; } + public long? MaxContextWindowTokens { get; set; } /// Gets or sets the vision value. [JsonPropertyName("vision")] @@ -429,7 +442,7 @@ public class ModelCapabilitiesOverrideLimits } /// Override individual model capabilities resolved by the runtime. -public class ModelCapabilitiesOverride +public sealed class ModelCapabilitiesOverride { /// Feature flags indicating what the model supports. [JsonPropertyName("supports")] @@ -440,8 +453,8 @@ public class ModelCapabilitiesOverride public ModelCapabilitiesOverrideLimits? Limits { get; set; } } -/// RPC data type for SessionModelSwitchTo operations. -internal class SessionModelSwitchToRequest +/// RPC data type for ModelSwitchTo operations. +internal sealed class ModelSwitchToRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -461,43 +474,27 @@ internal class SessionModelSwitchToRequest } /// RPC data type for SessionModeGet operations. -public class SessionModeGetResult -{ - /// The current agent mode. - [JsonPropertyName("mode")] - public SessionModeGetResultMode Mode { get; set; } -} - -/// RPC data type for SessionModeGet operations. -internal class SessionModeGetRequest +internal sealed class SessionModeGetRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionModeSet operations. -public class SessionModeSetResult -{ - /// The agent mode after switching. - [JsonPropertyName("mode")] - public SessionModeGetResultMode Mode { get; set; } -} - -/// RPC data type for SessionModeSet operations. -internal class SessionModeSetRequest +/// RPC data type for ModeSet operations. +internal sealed class ModeSetRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; - /// The mode to switch to. Valid values: "interactive", "plan", "autopilot". + /// The agent mode. Valid values: "interactive", "plan", "autopilot". [JsonPropertyName("mode")] - public SessionModeGetResultMode Mode { get; set; } + public SessionMode Mode { get; set; } } -/// RPC data type for SessionPlanRead operations. -public class SessionPlanReadResult +/// RPC data type for PlanRead operations. +public sealed class PlanReadResult { /// Whether the plan file exists in the workspace. [JsonPropertyName("exists")] @@ -513,20 +510,15 @@ public class SessionPlanReadResult } /// RPC data type for SessionPlanRead operations. -internal class SessionPlanReadRequest +internal sealed class SessionPlanReadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionPlanUpdate operations. -public class SessionPlanUpdateResult -{ -} - -/// RPC data type for SessionPlanUpdate operations. -internal class SessionPlanUpdateRequest +/// RPC data type for PlanUpdate operations. +internal sealed class PlanUpdateRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -538,20 +530,15 @@ internal class SessionPlanUpdateRequest } /// RPC data type for SessionPlanDelete operations. -public class SessionPlanDeleteResult -{ -} - -/// RPC data type for SessionPlanDelete operations. -internal class SessionPlanDeleteRequest +internal sealed class SessionPlanDeleteRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionWorkspaceListFiles operations. -public class SessionWorkspaceListFilesResult +/// RPC data type for WorkspaceListFiles operations. +public sealed class WorkspaceListFilesResult { /// Relative file paths in the workspace files directory. [JsonPropertyName("files")] @@ -559,23 +546,23 @@ public class SessionWorkspaceListFilesResult } /// RPC data type for SessionWorkspaceListFiles operations. -internal class SessionWorkspaceListFilesRequest +internal sealed class SessionWorkspaceListFilesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionWorkspaceReadFile operations. -public class SessionWorkspaceReadFileResult +/// RPC data type for WorkspaceReadFile operations. +public sealed class WorkspaceReadFileResult { /// File content as a UTF-8 string. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; } -/// RPC data type for SessionWorkspaceReadFile operations. -internal class SessionWorkspaceReadFileRequest +/// RPC data type for WorkspaceReadFile operations. +internal sealed class WorkspaceReadFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -586,13 +573,8 @@ internal class SessionWorkspaceReadFileRequest public string Path { get; set; } = string.Empty; } -/// RPC data type for SessionWorkspaceCreateFile operations. -public class SessionWorkspaceCreateFileResult -{ -} - -/// RPC data type for SessionWorkspaceCreateFile operations. -internal class SessionWorkspaceCreateFileRequest +/// RPC data type for WorkspaceCreateFile operations. +internal sealed class WorkspaceCreateFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -607,18 +589,18 @@ internal class SessionWorkspaceCreateFileRequest public string Content { get; set; } = string.Empty; } -/// RPC data type for SessionFleetStart operations. +/// RPC data type for FleetStart operations. [Experimental(Diagnostics.Experimental)] -public class SessionFleetStartResult +public sealed class FleetStartResult { /// Whether fleet mode was successfully activated. [JsonPropertyName("started")] public bool Started { get; set; } } -/// RPC data type for SessionFleetStart operations. +/// RPC data type for FleetStart operations. [Experimental(Diagnostics.Experimental)] -internal class SessionFleetStartRequest +internal sealed class FleetStartRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -630,7 +612,7 @@ internal class SessionFleetStartRequest } /// RPC data type for Agent operations. -public class Agent +public sealed class Agent { /// Unique identifier of the custom agent. [JsonPropertyName("name")] @@ -645,9 +627,9 @@ public class Agent public string Description { get; set; } = string.Empty; } -/// RPC data type for SessionAgentList operations. +/// RPC data type for AgentList operations. [Experimental(Diagnostics.Experimental)] -public class SessionAgentListResult +public sealed class AgentList { /// Available custom agents. [JsonPropertyName("agents")] @@ -656,15 +638,15 @@ public class SessionAgentListResult /// RPC data type for SessionAgentList operations. [Experimental(Diagnostics.Experimental)] -internal class SessionAgentListRequest +internal sealed class SessionAgentListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionAgentGetCurrentResultAgent operations. -public class SessionAgentGetCurrentResultAgent +/// RPC data type for AgentGetCurrentResultAgent operations. +public sealed class AgentGetCurrentResultAgent { /// Unique identifier of the custom agent. [JsonPropertyName("name")] @@ -679,18 +661,18 @@ public class SessionAgentGetCurrentResultAgent public string Description { get; set; } = string.Empty; } -/// RPC data type for SessionAgentGetCurrent operations. +/// RPC data type for AgentGetCurrent operations. [Experimental(Diagnostics.Experimental)] -public class SessionAgentGetCurrentResult +public sealed class AgentGetCurrentResult { /// Currently selected custom agent, or null if using the default agent. [JsonPropertyName("agent")] - public SessionAgentGetCurrentResultAgent? Agent { get; set; } + public AgentGetCurrentResultAgent? Agent { get; set; } } /// RPC data type for SessionAgentGetCurrent operations. [Experimental(Diagnostics.Experimental)] -internal class SessionAgentGetCurrentRequest +internal sealed class SessionAgentGetCurrentRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -698,7 +680,7 @@ internal class SessionAgentGetCurrentRequest } /// The newly selected custom agent. -public class SessionAgentSelectResultAgent +public sealed class AgentSelectAgent { /// Unique identifier of the custom agent. [JsonPropertyName("name")] @@ -713,18 +695,18 @@ public class SessionAgentSelectResultAgent public string Description { get; set; } = string.Empty; } -/// RPC data type for SessionAgentSelect operations. +/// RPC data type for AgentSelect operations. [Experimental(Diagnostics.Experimental)] -public class SessionAgentSelectResult +public sealed class AgentSelectResult { /// The newly selected custom agent. [JsonPropertyName("agent")] - public SessionAgentSelectResultAgent Agent { get => field ??= new(); set; } + public AgentSelectAgent Agent { get => field ??= new(); set; } } -/// RPC data type for SessionAgentSelect operations. +/// RPC data type for AgentSelect operations. [Experimental(Diagnostics.Experimental)] -internal class SessionAgentSelectRequest +internal sealed class AgentSelectRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -737,31 +719,41 @@ internal class SessionAgentSelectRequest /// RPC data type for SessionAgentDeselect operations. [Experimental(Diagnostics.Experimental)] -public class SessionAgentDeselectResult -{ -} - -/// RPC data type for SessionAgentDeselect operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionAgentDeselectRequest +internal sealed class SessionAgentDeselectRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionAgentReload operations. +/// RPC data type for AgentReloadAgent operations. +public sealed class AgentReloadAgent +{ + /// Unique identifier of the custom agent. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Human-readable display name. + [JsonPropertyName("displayName")] + public string DisplayName { get; set; } = string.Empty; + + /// Description of the agent's purpose. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; +} + +/// RPC data type for AgentReload operations. [Experimental(Diagnostics.Experimental)] -public class SessionAgentReloadResult +public sealed class AgentReloadResult { /// Reloaded custom agents. [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentReload operations. [Experimental(Diagnostics.Experimental)] -internal class SessionAgentReloadRequest +internal sealed class SessionAgentReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -769,7 +761,7 @@ internal class SessionAgentReloadRequest } /// RPC data type for Skill operations. -public class Skill +public sealed class Skill { /// Unique identifier for the skill. [JsonPropertyName("name")] @@ -796,9 +788,9 @@ public class Skill public string? Path { get; set; } } -/// RPC data type for SessionSkillsList operations. +/// RPC data type for SkillList operations. [Experimental(Diagnostics.Experimental)] -public class SessionSkillsListResult +public sealed class SkillList { /// Available skills. [JsonPropertyName("skills")] @@ -807,22 +799,16 @@ public class SessionSkillsListResult /// RPC data type for SessionSkillsList operations. [Experimental(Diagnostics.Experimental)] -internal class SessionSkillsListRequest +internal sealed class SessionSkillsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionSkillsEnable operations. +/// RPC data type for SkillsEnable operations. [Experimental(Diagnostics.Experimental)] -public class SessionSkillsEnableResult -{ -} - -/// RPC data type for SessionSkillsEnable operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionSkillsEnableRequest +internal sealed class SkillsEnableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -833,15 +819,9 @@ internal class SessionSkillsEnableRequest public string Name { get; set; } = string.Empty; } -/// RPC data type for SessionSkillsDisable operations. -[Experimental(Diagnostics.Experimental)] -public class SessionSkillsDisableResult -{ -} - -/// RPC data type for SessionSkillsDisable operations. +/// RPC data type for SkillsDisable operations. [Experimental(Diagnostics.Experimental)] -internal class SessionSkillsDisableRequest +internal sealed class SkillsDisableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -854,104 +834,83 @@ internal class SessionSkillsDisableRequest /// RPC data type for SessionSkillsReload operations. [Experimental(Diagnostics.Experimental)] -public class SessionSkillsReloadResult -{ -} - -/// RPC data type for SessionSkillsReload operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionSkillsReloadRequest +internal sealed class SessionSkillsReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for Server operations. -public class Server +/// RPC data type for McpServer operations. +public sealed class McpServer { /// Server name (config key). + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonPropertyName("status")] - public ServerStatus Status { get; set; } + public McpServerStatus Status { get; set; } /// Configuration source: user, workspace, plugin, or builtin. [JsonPropertyName("source")] - public string? Source { get; set; } + public McpServerSource? Source { get; set; } /// Error message if the server failed to connect. [JsonPropertyName("error")] public string? Error { get; set; } } -/// RPC data type for SessionMcpList operations. +/// RPC data type for McpServerList operations. [Experimental(Diagnostics.Experimental)] -public class SessionMcpListResult +public sealed class McpServerList { /// Configured MCP servers. [JsonPropertyName("servers")] - public IList Servers { get => field ??= []; set; } + public IList Servers { get => field ??= []; set; } } /// RPC data type for SessionMcpList operations. [Experimental(Diagnostics.Experimental)] -internal class SessionMcpListRequest +internal sealed class SessionMcpListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionMcpEnable operations. +/// RPC data type for McpEnable operations. [Experimental(Diagnostics.Experimental)] -public class SessionMcpEnableResult -{ -} - -/// RPC data type for SessionMcpEnable operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionMcpEnableRequest +internal sealed class McpEnableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; /// Name of the MCP server to enable. + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; } -/// RPC data type for SessionMcpDisable operations. -[Experimental(Diagnostics.Experimental)] -public class SessionMcpDisableResult -{ -} - -/// RPC data type for SessionMcpDisable operations. +/// RPC data type for McpDisable operations. [Experimental(Diagnostics.Experimental)] -internal class SessionMcpDisableRequest +internal sealed class McpDisableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; /// Name of the MCP server to disable. + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; } /// RPC data type for SessionMcpReload operations. [Experimental(Diagnostics.Experimental)] -public class SessionMcpReloadResult -{ -} - -/// RPC data type for SessionMcpReload operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionMcpReloadRequest +internal sealed class SessionMcpReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -959,7 +918,7 @@ internal class SessionMcpReloadRequest } /// RPC data type for Plugin operations. -public class Plugin +public sealed class Plugin { /// Plugin name. [JsonPropertyName("name")] @@ -978,9 +937,9 @@ public class Plugin public bool Enabled { get; set; } } -/// RPC data type for SessionPluginsList operations. +/// RPC data type for PluginList operations. [Experimental(Diagnostics.Experimental)] -public class SessionPluginsListResult +public sealed class PluginList { /// Installed plugins. [JsonPropertyName("plugins")] @@ -989,7 +948,7 @@ public class SessionPluginsListResult /// RPC data type for SessionPluginsList operations. [Experimental(Diagnostics.Experimental)] -internal class SessionPluginsListRequest +internal sealed class SessionPluginsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -997,7 +956,7 @@ internal class SessionPluginsListRequest } /// RPC data type for Extension operations. -public class Extension +public sealed class Extension { /// Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper'). [JsonPropertyName("id")] @@ -1020,9 +979,9 @@ public class Extension public long? Pid { get; set; } } -/// RPC data type for SessionExtensionsList operations. +/// RPC data type for ExtensionList operations. [Experimental(Diagnostics.Experimental)] -public class SessionExtensionsListResult +public sealed class ExtensionList { /// Discovered extensions and their current status. [JsonPropertyName("extensions")] @@ -1031,22 +990,16 @@ public class SessionExtensionsListResult /// RPC data type for SessionExtensionsList operations. [Experimental(Diagnostics.Experimental)] -internal class SessionExtensionsListRequest +internal sealed class SessionExtensionsListRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionExtensionsEnable operations. +/// RPC data type for ExtensionsEnable operations. [Experimental(Diagnostics.Experimental)] -public class SessionExtensionsEnableResult -{ -} - -/// RPC data type for SessionExtensionsEnable operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionExtensionsEnableRequest +internal sealed class ExtensionsEnableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1057,15 +1010,9 @@ internal class SessionExtensionsEnableRequest public string Id { get; set; } = string.Empty; } -/// RPC data type for SessionExtensionsDisable operations. -[Experimental(Diagnostics.Experimental)] -public class SessionExtensionsDisableResult -{ -} - -/// RPC data type for SessionExtensionsDisable operations. +/// RPC data type for ExtensionsDisable operations. [Experimental(Diagnostics.Experimental)] -internal class SessionExtensionsDisableRequest +internal sealed class ExtensionsDisableRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1078,29 +1025,23 @@ internal class SessionExtensionsDisableRequest /// RPC data type for SessionExtensionsReload operations. [Experimental(Diagnostics.Experimental)] -public class SessionExtensionsReloadResult -{ -} - -/// RPC data type for SessionExtensionsReload operations. -[Experimental(Diagnostics.Experimental)] -internal class SessionExtensionsReloadRequest +internal sealed class SessionExtensionsReloadRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionToolsHandlePendingToolCall operations. -public class SessionToolsHandlePendingToolCallResult +/// RPC data type for HandleToolCall operations. +public sealed class HandleToolCallResult { /// Whether the tool call result was handled successfully. [JsonPropertyName("success")] public bool Success { get; set; } } -/// RPC data type for SessionToolsHandlePendingToolCall operations. -internal class SessionToolsHandlePendingToolCallRequest +/// RPC data type for ToolsHandlePendingToolCall operations. +internal sealed class ToolsHandlePendingToolCallRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1119,16 +1060,16 @@ internal class SessionToolsHandlePendingToolCallRequest public string? Error { get; set; } } -/// RPC data type for SessionCommandsHandlePendingCommand operations. -public class SessionCommandsHandlePendingCommandResult +/// RPC data type for CommandsHandlePendingCommand operations. +public sealed class CommandsHandlePendingCommandResult { /// Whether the command was handled successfully. [JsonPropertyName("success")] public bool Success { get; set; } } -/// RPC data type for SessionCommandsHandlePendingCommand operations. -internal class SessionCommandsHandlePendingCommandRequest +/// RPC data type for CommandsHandlePendingCommand operations. +internal sealed class CommandsHandlePendingCommandRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1143,12 +1084,12 @@ internal class SessionCommandsHandlePendingCommandRequest public string? Error { get; set; } } -/// RPC data type for SessionUiElicitation operations. -public class SessionUiElicitationResult +/// The elicitation response (accept with form values, decline, or cancel). +public sealed class UIElicitationResponse { /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). [JsonPropertyName("action")] - public SessionUiElicitationResultAction Action { get; set; } + public UIElicitationResponseAction Action { get; set; } /// The form values submitted by the user (present when action is 'accept'). [JsonPropertyName("content")] @@ -1156,7 +1097,7 @@ public class SessionUiElicitationResult } /// JSON Schema describing the form fields to present to the user. -public class SessionUiElicitationRequestRequestedSchema +public sealed class UIElicitationSchema { /// Schema type indicator (always 'object'). [JsonPropertyName("type")] @@ -1171,8 +1112,8 @@ public class SessionUiElicitationRequestRequestedSchema public IList? Required { get; set; } } -/// RPC data type for SessionUiElicitation operations. -internal class SessionUiElicitationRequest +/// RPC data type for UIElicitation operations. +internal sealed class UIElicitationRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1184,31 +1125,19 @@ internal class SessionUiElicitationRequest /// JSON Schema describing the form fields to present to the user. [JsonPropertyName("requestedSchema")] - public SessionUiElicitationRequestRequestedSchema RequestedSchema { get => field ??= new(); set; } + public UIElicitationSchema RequestedSchema { get => field ??= new(); set; } } -/// RPC data type for SessionUiHandlePendingElicitation operations. -public class SessionUiHandlePendingElicitationResult +/// RPC data type for UIElicitation operations. +public sealed class UIElicitationResult { /// Whether the response was accepted. False if the request was already resolved by another client. [JsonPropertyName("success")] public bool Success { get; set; } } -/// The elicitation response (accept with form values, decline, or cancel). -public class SessionUiHandlePendingElicitationRequestResult -{ - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). - [JsonPropertyName("action")] - public SessionUiElicitationResultAction Action { get; set; } - - /// The form values submitted by the user (present when action is 'accept'). - [JsonPropertyName("content")] - public IDictionary? Content { get; set; } -} - -/// RPC data type for SessionUiHandlePendingElicitation operations. -internal class SessionUiHandlePendingElicitationRequest +/// RPC data type for UIHandlePendingElicitation operations. +internal sealed class UIHandlePendingElicitationRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1220,19 +1149,19 @@ internal class SessionUiHandlePendingElicitationRequest /// The elicitation response (accept with form values, decline, or cancel). [JsonPropertyName("result")] - public SessionUiHandlePendingElicitationRequestResult Result { get => field ??= new(); set; } + public UIElicitationResponse Result { get => field ??= new(); set; } } -/// RPC data type for SessionPermissionsHandlePendingPermissionRequest operations. -public class SessionPermissionsHandlePendingPermissionRequestResult +/// RPC data type for PermissionRequest operations. +public sealed class PermissionRequestResult { /// Whether the permission request was handled successfully. [JsonPropertyName("success")] public bool Success { get; set; } } -/// RPC data type for SessionPermissionsHandlePendingPermissionRequest operations. -internal class SessionPermissionsHandlePendingPermissionRequestRequest +/// RPC data type for PermissionDecision operations. +internal sealed class PermissionDecisionRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1247,16 +1176,16 @@ internal class SessionPermissionsHandlePendingPermissionRequestRequest public object Result { get; set; } = null!; } -/// RPC data type for SessionShellExec operations. -public class SessionShellExecResult +/// RPC data type for ShellExec operations. +public sealed class ShellExecResult { /// Unique identifier for tracking streamed output. [JsonPropertyName("processId")] public string ProcessId { get; set; } = string.Empty; } -/// RPC data type for SessionShellExec operations. -internal class SessionShellExecRequest +/// RPC data type for ShellExec operations. +internal sealed class ShellExecRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1271,20 +1200,22 @@ internal class SessionShellExecRequest public string? Cwd { get; set; } /// Timeout in milliseconds (default: 30000). + [Range((double)0, (double)long.MaxValue)] + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonPropertyName("timeout")] - public double? Timeout { get; set; } + public TimeSpan? Timeout { get; set; } } -/// RPC data type for SessionShellKill operations. -public class SessionShellKillResult +/// RPC data type for ShellKill operations. +public sealed class ShellKillResult { /// Whether the signal was sent successfully. [JsonPropertyName("killed")] public bool Killed { get; set; } } -/// RPC data type for SessionShellKill operations. -internal class SessionShellKillRequest +/// RPC data type for ShellKill operations. +internal sealed class ShellKillRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1296,79 +1227,88 @@ internal class SessionShellKillRequest /// Signal to send (default: SIGTERM). [JsonPropertyName("signal")] - public SessionShellKillRequestSignal? Signal { get; set; } + public ShellKillSignal? Signal { get; set; } } /// Post-compaction context window usage breakdown. -public class SessionHistoryCompactResultContextWindow +public sealed class HistoryCompactContextWindow { /// Maximum token count for the model's context window. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("tokenLimit")] - public double TokenLimit { get; set; } + public long TokenLimit { get; set; } /// Current total tokens in the context window (system + conversation + tool definitions). + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("currentTokens")] - public double CurrentTokens { get; set; } + public long CurrentTokens { get; set; } /// Current number of messages in the conversation. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("messagesLength")] - public double MessagesLength { get; set; } + public long MessagesLength { get; set; } /// Token count from system message(s). + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("systemTokens")] - public double? SystemTokens { get; set; } + public long? SystemTokens { get; set; } /// Token count from non-system messages (user, assistant, tool). + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("conversationTokens")] - public double? ConversationTokens { get; set; } + public long? ConversationTokens { get; set; } /// Token count from tool definitions. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("toolDefinitionsTokens")] - public double? ToolDefinitionsTokens { get; set; } + public long? ToolDefinitionsTokens { get; set; } } -/// RPC data type for SessionHistoryCompact operations. +/// RPC data type for HistoryCompact operations. [Experimental(Diagnostics.Experimental)] -public class SessionHistoryCompactResult +public sealed class HistoryCompactResult { /// Whether compaction completed successfully. [JsonPropertyName("success")] public bool Success { get; set; } /// Number of tokens freed by compaction. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("tokensRemoved")] - public double TokensRemoved { get; set; } + public long TokensRemoved { get; set; } /// Number of messages removed during compaction. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("messagesRemoved")] - public double MessagesRemoved { get; set; } + public long MessagesRemoved { get; set; } /// Post-compaction context window usage breakdown. [JsonPropertyName("contextWindow")] - public SessionHistoryCompactResultContextWindow? ContextWindow { get; set; } + public HistoryCompactContextWindow? ContextWindow { get; set; } } /// RPC data type for SessionHistoryCompact operations. [Experimental(Diagnostics.Experimental)] -internal class SessionHistoryCompactRequest +internal sealed class SessionHistoryCompactRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for SessionHistoryTruncate operations. +/// RPC data type for HistoryTruncate operations. [Experimental(Diagnostics.Experimental)] -public class SessionHistoryTruncateResult +public sealed class HistoryTruncateResult { /// Number of events that were removed. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("eventsRemoved")] - public double EventsRemoved { get; set; } + public long EventsRemoved { get; set; } } -/// RPC data type for SessionHistoryTruncate operations. +/// RPC data type for HistoryTruncate operations. [Experimental(Diagnostics.Experimental)] -internal class SessionHistoryTruncateRequest +internal sealed class HistoryTruncateRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1380,7 +1320,7 @@ internal class SessionHistoryTruncateRequest } /// Aggregated code change metrics. -public class SessionUsageGetMetricsResultCodeChanges +public sealed class UsageMetricsCodeChanges { /// Total lines of code added. [JsonPropertyName("linesAdded")] @@ -1396,7 +1336,7 @@ public class SessionUsageGetMetricsResultCodeChanges } /// Request count and cost metrics for this model. -public class SessionUsageGetMetricsResultModelMetricsValueRequests +public sealed class UsageMetricsModelMetricRequests { /// Number of API requests made with this model. [JsonPropertyName("count")] @@ -1408,52 +1348,64 @@ public class SessionUsageGetMetricsResultModelMetricsValueRequests } /// Token usage metrics for this model. -public class SessionUsageGetMetricsResultModelMetricsValueUsage +public sealed class UsageMetricsModelMetricUsage { /// Total input tokens consumed. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("inputTokens")] public long InputTokens { get; set; } /// Total output tokens produced. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("outputTokens")] public long OutputTokens { get; set; } /// Total tokens read from prompt cache. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("cacheReadTokens")] public long CacheReadTokens { get; set; } /// Total tokens written to prompt cache. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("cacheWriteTokens")] public long CacheWriteTokens { get; set; } + + /// Total output tokens used for reasoning. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("reasoningTokens")] + public long? ReasoningTokens { get; set; } } -/// RPC data type for SessionUsageGetMetricsResultModelMetricsValue operations. -public class SessionUsageGetMetricsResultModelMetricsValue +/// RPC data type for UsageMetricsModelMetric operations. +public sealed class UsageMetricsModelMetric { /// Request count and cost metrics for this model. [JsonPropertyName("requests")] - public SessionUsageGetMetricsResultModelMetricsValueRequests Requests { get => field ??= new(); set; } + public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } /// Token usage metrics for this model. [JsonPropertyName("usage")] - public SessionUsageGetMetricsResultModelMetricsValueUsage Usage { get => field ??= new(); set; } + public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } } -/// RPC data type for SessionUsageGetMetrics operations. +/// RPC data type for UsageGetMetrics operations. [Experimental(Diagnostics.Experimental)] -public class SessionUsageGetMetricsResult +public sealed class UsageGetMetricsResult { /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). [JsonPropertyName("totalPremiumRequestCost")] public double TotalPremiumRequestCost { get; set; } /// Raw count of user-initiated API requests. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("totalUserRequests")] public long TotalUserRequests { get; set; } /// Total time spent in model API calls (milliseconds). + [Range(0, double.MaxValue)] + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonPropertyName("totalApiDurationMs")] - public double TotalApiDurationMs { get; set; } + public TimeSpan TotalApiDurationMs { get; set; } /// Session start timestamp (epoch milliseconds). [JsonPropertyName("sessionStartTime")] @@ -1461,28 +1413,30 @@ public class SessionUsageGetMetricsResult /// Aggregated code change metrics. [JsonPropertyName("codeChanges")] - public SessionUsageGetMetricsResultCodeChanges CodeChanges { get => field ??= new(); set; } + public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); set; } /// Per-model token and request metrics, keyed by model identifier. [JsonPropertyName("modelMetrics")] - public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } + public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } /// Currently active model identifier. [JsonPropertyName("currentModel")] public string? CurrentModel { get; set; } /// Input tokens from the most recent main-agent API call. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("lastCallInputTokens")] public long LastCallInputTokens { get; set; } /// Output tokens from the most recent main-agent API call. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("lastCallOutputTokens")] public long LastCallOutputTokens { get; set; } } /// RPC data type for SessionUsageGetMetrics operations. [Experimental(Diagnostics.Experimental)] -internal class SessionUsageGetMetricsRequest +internal sealed class SessionUsageGetMetricsRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1490,7 +1444,7 @@ internal class SessionUsageGetMetricsRequest } /// RPC data type for SessionFsReadFile operations. -public class SessionFsReadFileResult +public sealed class SessionFsReadFileResult { /// File content as UTF-8 string. [JsonPropertyName("content")] @@ -1498,7 +1452,7 @@ public class SessionFsReadFileResult } /// RPC data type for SessionFsReadFile operations. -public class SessionFsReadFileParams +public sealed class SessionFsReadFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1510,7 +1464,7 @@ public class SessionFsReadFileParams } /// RPC data type for SessionFsWriteFile operations. -public class SessionFsWriteFileParams +public sealed class SessionFsWriteFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1525,12 +1479,13 @@ public class SessionFsWriteFileParams public string Content { get; set; } = string.Empty; /// Optional POSIX-style mode for newly created files. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("mode")] - public double? Mode { get; set; } + public long? Mode { get; set; } } /// RPC data type for SessionFsAppendFile operations. -public class SessionFsAppendFileParams +public sealed class SessionFsAppendFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1545,12 +1500,13 @@ public class SessionFsAppendFileParams public string Content { get; set; } = string.Empty; /// Optional POSIX-style mode for newly created files. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("mode")] - public double? Mode { get; set; } + public long? Mode { get; set; } } /// RPC data type for SessionFsExists operations. -public class SessionFsExistsResult +public sealed class SessionFsExistsResult { /// Whether the path exists. [JsonPropertyName("exists")] @@ -1558,7 +1514,7 @@ public class SessionFsExistsResult } /// RPC data type for SessionFsExists operations. -public class SessionFsExistsParams +public sealed class SessionFsExistsRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1570,7 +1526,7 @@ public class SessionFsExistsParams } /// RPC data type for SessionFsStat operations. -public class SessionFsStatResult +public sealed class SessionFsStatResult { /// Whether the path is a file. [JsonPropertyName("isFile")] @@ -1581,20 +1537,21 @@ public class SessionFsStatResult public bool IsDirectory { get; set; } /// File size in bytes. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("size")] - public double Size { get; set; } + public long Size { get; set; } /// ISO 8601 timestamp of last modification. [JsonPropertyName("mtime")] - public string Mtime { get; set; } = string.Empty; + public DateTimeOffset Mtime { get; set; } /// ISO 8601 timestamp of creation. [JsonPropertyName("birthtime")] - public string Birthtime { get; set; } = string.Empty; + public DateTimeOffset Birthtime { get; set; } } /// RPC data type for SessionFsStat operations. -public class SessionFsStatParams +public sealed class SessionFsStatRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1606,7 +1563,7 @@ public class SessionFsStatParams } /// RPC data type for SessionFsMkdir operations. -public class SessionFsMkdirParams +public sealed class SessionFsMkdirRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1621,12 +1578,13 @@ public class SessionFsMkdirParams public bool? Recursive { get; set; } /// Optional POSIX-style mode for newly created directories. + [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("mode")] - public double? Mode { get; set; } + public long? Mode { get; set; } } /// RPC data type for SessionFsReaddir operations. -public class SessionFsReaddirResult +public sealed class SessionFsReaddirResult { /// Entry names in the directory. [JsonPropertyName("entries")] @@ -1634,7 +1592,7 @@ public class SessionFsReaddirResult } /// RPC data type for SessionFsReaddir operations. -public class SessionFsReaddirParams +public sealed class SessionFsReaddirRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1645,8 +1603,8 @@ public class SessionFsReaddirParams public string Path { get; set; } = string.Empty; } -/// RPC data type for Entry operations. -public class Entry +/// RPC data type for SessionFsReaddirWithTypesEntry operations. +public sealed class SessionFsReaddirWithTypesEntry { /// Entry name. [JsonPropertyName("name")] @@ -1654,19 +1612,19 @@ public class Entry /// Entry type. [JsonPropertyName("type")] - public EntryType Type { get; set; } + public SessionFsReaddirWithTypesEntryType Type { get; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. -public class SessionFsReaddirWithTypesResult +public sealed class SessionFsReaddirWithTypesResult { /// Directory entries with type information. [JsonPropertyName("entries")] - public IList Entries { get => field ??= []; set; } + public IList Entries { get => field ??= []; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. -public class SessionFsReaddirWithTypesParams +public sealed class SessionFsReaddirWithTypesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1678,7 +1636,7 @@ public class SessionFsReaddirWithTypesParams } /// RPC data type for SessionFsRm operations. -public class SessionFsRmParams +public sealed class SessionFsRmRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1698,7 +1656,7 @@ public class SessionFsRmParams } /// RPC data type for SessionFsRename operations. -public class SessionFsRenameParams +public sealed class SessionFsRenameRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1713,6 +1671,25 @@ public class SessionFsRenameParams public string Dest { get; set; } = string.Empty; } +/// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum DiscoveredMcpServerType +{ + /// The stdio variant. + [JsonStringEnumMemberName("stdio")] + Stdio, + /// The http variant. + [JsonStringEnumMemberName("http")] + Http, + /// The sse variant. + [JsonStringEnumMemberName("sse")] + Sse, + /// The memory variant. + [JsonStringEnumMemberName("memory")] + Memory, +} + + /// Configuration source. [JsonConverter(typeof(JsonStringEnumConverter))] public enum DiscoveredMcpServerSource @@ -1733,8 +1710,8 @@ public enum DiscoveredMcpServerSource /// Path conventions used by this filesystem. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionFsSetProviderRequestConventions +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionFsSetProviderConventions { /// The windows variant. [JsonStringEnumMemberName("windows")] @@ -1746,8 +1723,8 @@ public enum SessionFsSetProviderRequestConventions /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionLogRequestLevel +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionLogLevel { /// The info variant. [JsonStringEnumMemberName("info")] @@ -1761,9 +1738,9 @@ public enum SessionLogRequestLevel } -/// The current agent mode. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionModeGetResultMode +/// The agent mode. Valid values: "interactive", "plan", "autopilot". +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionMode { /// The interactive variant. [JsonStringEnumMemberName("interactive")] @@ -1778,8 +1755,8 @@ public enum SessionModeGetResultMode /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ServerStatus +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum McpServerStatus { /// The connected variant. [JsonStringEnumMemberName("connected")] @@ -1802,6 +1779,25 @@ public enum ServerStatus } +/// Configuration source: user, workspace, plugin, or builtin. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum McpServerSource +{ + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The workspace variant. + [JsonStringEnumMemberName("workspace")] + Workspace, + /// The plugin variant. + [JsonStringEnumMemberName("plugin")] + Plugin, + /// The builtin variant. + [JsonStringEnumMemberName("builtin")] + Builtin, +} + + /// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/). [JsonConverter(typeof(JsonStringEnumConverter))] public enum ExtensionSource @@ -1835,8 +1831,8 @@ public enum ExtensionStatus /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionUiElicitationResultAction +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UIElicitationResponseAction { /// The accept variant. [JsonStringEnumMemberName("accept")] @@ -1851,8 +1847,8 @@ public enum SessionUiElicitationResultAction /// Signal to send (default: SIGTERM). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionShellKillRequestSignal +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ShellKillSignal { /// The SIGTERM variant. [JsonStringEnumMemberName("SIGTERM")] @@ -1867,8 +1863,8 @@ public enum SessionShellKillRequestSignal /// Entry type. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum EntryType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionFsReaddirWithTypesEntryType { /// The file variant. [JsonStringEnumMemberName("file")] @@ -1880,7 +1876,7 @@ public enum EntryType /// Provides server-scoped RPC methods (no session required). -public class ServerRpc +public sealed class ServerRpc { private readonly JsonRpc _rpc; @@ -1922,7 +1918,7 @@ public async Task PingAsync(string? message = null, CancellationToke } /// Provides server-scoped Models APIs. -public class ServerModelsApi +public sealed class ServerModelsApi { private readonly JsonRpc _rpc; @@ -1932,14 +1928,14 @@ internal ServerModelsApi(JsonRpc rpc) } /// Calls "models.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { - return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [], cancellationToken); } } /// Provides server-scoped Tools APIs. -public class ServerToolsApi +public sealed class ServerToolsApi { private readonly JsonRpc _rpc; @@ -1949,15 +1945,15 @@ internal ServerToolsApi(JsonRpc rpc) } /// Calls "tools.list". - public async Task ListAsync(string? model = null, CancellationToken cancellationToken = default) + public async Task ListAsync(string? model = null, CancellationToken cancellationToken = default) { var request = new ToolsListRequest { Model = model }; - return await CopilotClient.InvokeRpcAsync(_rpc, "tools.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "tools.list", [request], cancellationToken); } } /// Provides server-scoped Account APIs. -public class ServerAccountApi +public sealed class ServerAccountApi { private readonly JsonRpc _rpc; @@ -1974,7 +1970,7 @@ public async Task GetQuotaAsync(CancellationToken cancell } /// Provides server-scoped Mcp APIs. -public class ServerMcpApi +public sealed class ServerMcpApi { private readonly JsonRpc _rpc; @@ -1992,7 +1988,7 @@ public async Task DiscoverAsync(string? workingDirectory = nu } /// Provides server-scoped SessionFs APIs. -public class ServerSessionFsApi +public sealed class ServerSessionFsApi { private readonly JsonRpc _rpc; @@ -2002,7 +1998,7 @@ internal ServerSessionFsApi(JsonRpc rpc) } /// Calls "sessionFs.setProvider". - public async Task SetProviderAsync(string initialCwd, string sessionStatePath, SessionFsSetProviderRequestConventions conventions, CancellationToken cancellationToken = default) + public async Task SetProviderAsync(string initialCwd, string sessionStatePath, SessionFsSetProviderConventions conventions, CancellationToken cancellationToken = default) { var request = new SessionFsSetProviderRequest { InitialCwd = initialCwd, SessionStatePath = sessionStatePath, Conventions = conventions }; return await CopilotClient.InvokeRpcAsync(_rpc, "sessionFs.setProvider", [request], cancellationToken); @@ -2011,7 +2007,7 @@ public async Task SetProviderAsync(string initialCwd /// Provides server-scoped Sessions APIs. [Experimental(Diagnostics.Experimental)] -public class ServerSessionsApi +public sealed class ServerSessionsApi { private readonly JsonRpc _rpc; @@ -2029,7 +2025,7 @@ public async Task ForkAsync(string sessionId, string? toEven } /// Provides typed session-scoped RPC methods. -public class SessionRpc +public sealed class SessionRpc { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2109,15 +2105,15 @@ internal SessionRpc(JsonRpc rpc, string sessionId) public UsageApi Usage { get; } /// Calls "session.log". - public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) + public async Task LogAsync(string message, SessionLogLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) { - var request = new SessionLogRequest { SessionId = _sessionId, Message = message, Level = level, Ephemeral = ephemeral, Url = url }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.log", [request], cancellationToken); + var request = new LogRequest { SessionId = _sessionId, Message = message, Level = level, Ephemeral = ephemeral, Url = url }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.log", [request], cancellationToken); } } /// Provides session-scoped Model APIs. -public class ModelApi +public sealed class ModelApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2129,22 +2125,22 @@ internal ModelApi(JsonRpc rpc, string sessionId) } /// Calls "session.model.getCurrent". - public async Task GetCurrentAsync(CancellationToken cancellationToken = default) + public async Task GetCurrentAsync(CancellationToken cancellationToken = default) { var request = new SessionModelGetCurrentRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.model.getCurrent", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.model.getCurrent", [request], cancellationToken); } /// Calls "session.model.switchTo". - public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ModelCapabilitiesOverride? modelCapabilities = null, CancellationToken cancellationToken = default) + public async Task SwitchToAsync(string modelId, string? reasoningEffort = null, ModelCapabilitiesOverride? modelCapabilities = null, CancellationToken cancellationToken = default) { - var request = new SessionModelSwitchToRequest { SessionId = _sessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ModelCapabilities = modelCapabilities }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.model.switchTo", [request], cancellationToken); + var request = new ModelSwitchToRequest { SessionId = _sessionId, ModelId = modelId, ReasoningEffort = reasoningEffort, ModelCapabilities = modelCapabilities }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.model.switchTo", [request], cancellationToken); } } /// Provides session-scoped Mode APIs. -public class ModeApi +public sealed class ModeApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2156,22 +2152,22 @@ internal ModeApi(JsonRpc rpc, string sessionId) } /// Calls "session.mode.get". - public async Task GetAsync(CancellationToken cancellationToken = default) + public async Task GetAsync(CancellationToken cancellationToken = default) { var request = new SessionModeGetRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.get", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.get", [request], cancellationToken); } /// Calls "session.mode.set". - public async Task SetAsync(SessionModeGetResultMode mode, CancellationToken cancellationToken = default) + public async Task SetAsync(SessionMode mode, CancellationToken cancellationToken = default) { - var request = new SessionModeSetRequest { SessionId = _sessionId, Mode = mode }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.set", [request], cancellationToken); + var request = new ModeSetRequest { SessionId = _sessionId, Mode = mode }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.mode.set", [request], cancellationToken); } } /// Provides session-scoped Plan APIs. -public class PlanApi +public sealed class PlanApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2183,29 +2179,29 @@ internal PlanApi(JsonRpc rpc, string sessionId) } /// Calls "session.plan.read". - public async Task ReadAsync(CancellationToken cancellationToken = default) + public async Task ReadAsync(CancellationToken cancellationToken = default) { var request = new SessionPlanReadRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.read", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.read", [request], cancellationToken); } /// Calls "session.plan.update". - public async Task UpdateAsync(string content, CancellationToken cancellationToken = default) + public async Task UpdateAsync(string content, CancellationToken cancellationToken = default) { - var request = new SessionPlanUpdateRequest { SessionId = _sessionId, Content = content }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.update", [request], cancellationToken); + var request = new PlanUpdateRequest { SessionId = _sessionId, Content = content }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.update", [request], cancellationToken); } /// Calls "session.plan.delete". - public async Task DeleteAsync(CancellationToken cancellationToken = default) + public async Task DeleteAsync(CancellationToken cancellationToken = default) { var request = new SessionPlanDeleteRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.delete", [request], cancellationToken); + await CopilotClient.InvokeRpcAsync(_rpc, "session.plan.delete", [request], cancellationToken); } } /// Provides session-scoped Workspace APIs. -public class WorkspaceApi +public sealed class WorkspaceApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2217,30 +2213,30 @@ internal WorkspaceApi(JsonRpc rpc, string sessionId) } /// Calls "session.workspace.listFiles". - public async Task ListFilesAsync(CancellationToken cancellationToken = default) + public async Task ListFilesAsync(CancellationToken cancellationToken = default) { var request = new SessionWorkspaceListFilesRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.listFiles", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.listFiles", [request], cancellationToken); } /// Calls "session.workspace.readFile". - public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) + public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) { - var request = new SessionWorkspaceReadFileRequest { SessionId = _sessionId, Path = path }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.readFile", [request], cancellationToken); + var request = new WorkspaceReadFileRequest { SessionId = _sessionId, Path = path }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.readFile", [request], cancellationToken); } /// Calls "session.workspace.createFile". - public async Task CreateFileAsync(string path, string content, CancellationToken cancellationToken = default) + public async Task CreateFileAsync(string path, string content, CancellationToken cancellationToken = default) { - var request = new SessionWorkspaceCreateFileRequest { SessionId = _sessionId, Path = path, Content = content }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.createFile", [request], cancellationToken); + var request = new WorkspaceCreateFileRequest { SessionId = _sessionId, Path = path, Content = content }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.createFile", [request], cancellationToken); } } /// Provides session-scoped Fleet APIs. [Experimental(Diagnostics.Experimental)] -public class FleetApi +public sealed class FleetApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2252,16 +2248,16 @@ internal FleetApi(JsonRpc rpc, string sessionId) } /// Calls "session.fleet.start". - public async Task StartAsync(string? prompt = null, CancellationToken cancellationToken = default) + public async Task StartAsync(string? prompt = null, CancellationToken cancellationToken = default) { - var request = new SessionFleetStartRequest { SessionId = _sessionId, Prompt = prompt }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.fleet.start", [request], cancellationToken); + var request = new FleetStartRequest { SessionId = _sessionId, Prompt = prompt }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.fleet.start", [request], cancellationToken); } } /// Provides session-scoped Agent APIs. [Experimental(Diagnostics.Experimental)] -public class AgentApi +public sealed class AgentApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2273,44 +2269,44 @@ internal AgentApi(JsonRpc rpc, string sessionId) } /// Calls "session.agent.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { var request = new SessionAgentListRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.list", [request], cancellationToken); } /// Calls "session.agent.getCurrent". - public async Task GetCurrentAsync(CancellationToken cancellationToken = default) + public async Task GetCurrentAsync(CancellationToken cancellationToken = default) { var request = new SessionAgentGetCurrentRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.getCurrent", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.getCurrent", [request], cancellationToken); } /// Calls "session.agent.select". - public async Task SelectAsync(string name, CancellationToken cancellationToken = default) + public async Task SelectAsync(string name, CancellationToken cancellationToken = default) { - var request = new SessionAgentSelectRequest { SessionId = _sessionId, Name = name }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.select", [request], cancellationToken); + var request = new AgentSelectRequest { SessionId = _sessionId, Name = name }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.select", [request], cancellationToken); } /// Calls "session.agent.deselect". - public async Task DeselectAsync(CancellationToken cancellationToken = default) + public async Task DeselectAsync(CancellationToken cancellationToken = default) { var request = new SessionAgentDeselectRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.deselect", [request], cancellationToken); + await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.deselect", [request], cancellationToken); } /// Calls "session.agent.reload". - public async Task ReloadAsync(CancellationToken cancellationToken = default) + public async Task ReloadAsync(CancellationToken cancellationToken = default) { var request = new SessionAgentReloadRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.reload", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.agent.reload", [request], cancellationToken); } } /// Provides session-scoped Skills APIs. [Experimental(Diagnostics.Experimental)] -public class SkillsApi +public sealed class SkillsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2322,37 +2318,37 @@ internal SkillsApi(JsonRpc rpc, string sessionId) } /// Calls "session.skills.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { var request = new SessionSkillsListRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.list", [request], cancellationToken); } /// Calls "session.skills.enable". - public async Task EnableAsync(string name, CancellationToken cancellationToken = default) + public async Task EnableAsync(string name, CancellationToken cancellationToken = default) { - var request = new SessionSkillsEnableRequest { SessionId = _sessionId, Name = name }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.enable", [request], cancellationToken); + var request = new SkillsEnableRequest { SessionId = _sessionId, Name = name }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.enable", [request], cancellationToken); } /// Calls "session.skills.disable". - public async Task DisableAsync(string name, CancellationToken cancellationToken = default) + public async Task DisableAsync(string name, CancellationToken cancellationToken = default) { - var request = new SessionSkillsDisableRequest { SessionId = _sessionId, Name = name }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.disable", [request], cancellationToken); + var request = new SkillsDisableRequest { SessionId = _sessionId, Name = name }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.disable", [request], 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 }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.reload", [request], cancellationToken); + await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.reload", [request], cancellationToken); } } /// Provides session-scoped Mcp APIs. [Experimental(Diagnostics.Experimental)] -public class McpApi +public sealed class McpApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2364,37 +2360,37 @@ internal McpApi(JsonRpc rpc, string sessionId) } /// Calls "session.mcp.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { var request = new SessionMcpListRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.list", [request], cancellationToken); } /// Calls "session.mcp.enable". - public async Task EnableAsync(string serverName, CancellationToken cancellationToken = default) + public async Task EnableAsync(string serverName, CancellationToken cancellationToken = default) { - var request = new SessionMcpEnableRequest { SessionId = _sessionId, ServerName = serverName }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.enable", [request], cancellationToken); + var request = new McpEnableRequest { SessionId = _sessionId, ServerName = serverName }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.enable", [request], cancellationToken); } /// Calls "session.mcp.disable". - public async Task DisableAsync(string serverName, CancellationToken cancellationToken = default) + public async Task DisableAsync(string serverName, CancellationToken cancellationToken = default) { - var request = new SessionMcpDisableRequest { SessionId = _sessionId, ServerName = serverName }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.disable", [request], cancellationToken); + var request = new McpDisableRequest { SessionId = _sessionId, ServerName = serverName }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.disable", [request], cancellationToken); } /// Calls "session.mcp.reload". - public async Task ReloadAsync(CancellationToken cancellationToken = default) + public async Task ReloadAsync(CancellationToken cancellationToken = default) { var request = new SessionMcpReloadRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.reload", [request], cancellationToken); + await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.reload", [request], cancellationToken); } } /// Provides session-scoped Plugins APIs. [Experimental(Diagnostics.Experimental)] -public class PluginsApi +public sealed class PluginsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2406,16 +2402,16 @@ internal PluginsApi(JsonRpc rpc, string sessionId) } /// Calls "session.plugins.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { var request = new SessionPluginsListRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.plugins.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.plugins.list", [request], cancellationToken); } } /// Provides session-scoped Extensions APIs. [Experimental(Diagnostics.Experimental)] -public class ExtensionsApi +public sealed class ExtensionsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2427,36 +2423,36 @@ internal ExtensionsApi(JsonRpc rpc, string sessionId) } /// Calls "session.extensions.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(CancellationToken cancellationToken = default) { var request = new SessionExtensionsListRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.list", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.list", [request], cancellationToken); } /// Calls "session.extensions.enable". - public async Task EnableAsync(string id, CancellationToken cancellationToken = default) + public async Task EnableAsync(string id, CancellationToken cancellationToken = default) { - var request = new SessionExtensionsEnableRequest { SessionId = _sessionId, Id = id }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.enable", [request], cancellationToken); + var request = new ExtensionsEnableRequest { SessionId = _sessionId, Id = id }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.enable", [request], cancellationToken); } /// Calls "session.extensions.disable". - public async Task DisableAsync(string id, CancellationToken cancellationToken = default) + public async Task DisableAsync(string id, CancellationToken cancellationToken = default) { - var request = new SessionExtensionsDisableRequest { SessionId = _sessionId, Id = id }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.disable", [request], cancellationToken); + var request = new ExtensionsDisableRequest { SessionId = _sessionId, Id = id }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.disable", [request], cancellationToken); } /// Calls "session.extensions.reload". - public async Task ReloadAsync(CancellationToken cancellationToken = default) + public async Task ReloadAsync(CancellationToken cancellationToken = default) { var request = new SessionExtensionsReloadRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.reload", [request], cancellationToken); + await CopilotClient.InvokeRpcAsync(_rpc, "session.extensions.reload", [request], cancellationToken); } } /// Provides session-scoped Tools APIs. -public class ToolsApi +public sealed class ToolsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2468,15 +2464,15 @@ internal ToolsApi(JsonRpc rpc, string sessionId) } /// Calls "session.tools.handlePendingToolCall". - public async Task HandlePendingToolCallAsync(string requestId, object? result = null, string? error = null, CancellationToken cancellationToken = default) + public async Task HandlePendingToolCallAsync(string requestId, object? result = null, string? error = null, CancellationToken cancellationToken = default) { - var request = new SessionToolsHandlePendingToolCallRequest { SessionId = _sessionId, RequestId = requestId, Result = result, Error = error }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.tools.handlePendingToolCall", [request], cancellationToken); + var request = new ToolsHandlePendingToolCallRequest { SessionId = _sessionId, RequestId = requestId, Result = result, Error = error }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.tools.handlePendingToolCall", [request], cancellationToken); } } /// Provides session-scoped Commands APIs. -public class CommandsApi +public sealed class CommandsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2488,15 +2484,15 @@ internal CommandsApi(JsonRpc rpc, string sessionId) } /// Calls "session.commands.handlePendingCommand". - public async Task HandlePendingCommandAsync(string requestId, string? error = null, CancellationToken cancellationToken = default) + public async Task HandlePendingCommandAsync(string requestId, string? error = null, CancellationToken cancellationToken = default) { - var request = new SessionCommandsHandlePendingCommandRequest { SessionId = _sessionId, RequestId = requestId, Error = error }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.commands.handlePendingCommand", [request], cancellationToken); + var request = new CommandsHandlePendingCommandRequest { SessionId = _sessionId, RequestId = requestId, Error = error }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.commands.handlePendingCommand", [request], cancellationToken); } } /// Provides session-scoped Ui APIs. -public class UiApi +public sealed class UiApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2508,22 +2504,22 @@ internal UiApi(JsonRpc rpc, string sessionId) } /// Calls "session.ui.elicitation". - public async Task ElicitationAsync(string message, SessionUiElicitationRequestRequestedSchema requestedSchema, CancellationToken cancellationToken = default) + public async Task ElicitationAsync(string message, UIElicitationSchema requestedSchema, CancellationToken cancellationToken = default) { - var request = new SessionUiElicitationRequest { SessionId = _sessionId, Message = message, RequestedSchema = requestedSchema }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.elicitation", [request], cancellationToken); + var request = new UIElicitationRequest { SessionId = _sessionId, Message = message, RequestedSchema = requestedSchema }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.elicitation", [request], cancellationToken); } /// Calls "session.ui.handlePendingElicitation". - public async Task HandlePendingElicitationAsync(string requestId, SessionUiHandlePendingElicitationRequestResult result, CancellationToken cancellationToken = default) + public async Task HandlePendingElicitationAsync(string requestId, UIElicitationResponse result, CancellationToken cancellationToken = default) { - var request = new SessionUiHandlePendingElicitationRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.handlePendingElicitation", [request], cancellationToken); + var request = new UIHandlePendingElicitationRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.ui.handlePendingElicitation", [request], cancellationToken); } } /// Provides session-scoped Permissions APIs. -public class PermissionsApi +public sealed class PermissionsApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2535,15 +2531,15 @@ internal PermissionsApi(JsonRpc rpc, string sessionId) } /// Calls "session.permissions.handlePendingPermissionRequest". - public async Task HandlePendingPermissionRequestAsync(string requestId, object result, CancellationToken cancellationToken = default) + public async Task HandlePendingPermissionRequestAsync(string requestId, object result, CancellationToken cancellationToken = default) { - var request = new SessionPermissionsHandlePendingPermissionRequestRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); + var request = new PermissionDecisionRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); } } /// Provides session-scoped Shell APIs. -public class ShellApi +public sealed class ShellApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2555,23 +2551,23 @@ internal ShellApi(JsonRpc rpc, string sessionId) } /// Calls "session.shell.exec". - public async Task ExecAsync(string command, string? cwd = null, double? timeout = null, CancellationToken cancellationToken = default) + public async Task ExecAsync(string command, string? cwd = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) { - var request = new SessionShellExecRequest { SessionId = _sessionId, Command = command, Cwd = cwd, Timeout = timeout }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.shell.exec", [request], cancellationToken); + var request = new ShellExecRequest { SessionId = _sessionId, Command = command, Cwd = cwd, Timeout = timeout }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.shell.exec", [request], cancellationToken); } /// Calls "session.shell.kill". - public async Task KillAsync(string processId, SessionShellKillRequestSignal? signal = null, CancellationToken cancellationToken = default) + public async Task KillAsync(string processId, ShellKillSignal? signal = null, CancellationToken cancellationToken = default) { - var request = new SessionShellKillRequest { SessionId = _sessionId, ProcessId = processId, Signal = signal }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.shell.kill", [request], cancellationToken); + var request = new ShellKillRequest { SessionId = _sessionId, ProcessId = processId, Signal = signal }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.shell.kill", [request], cancellationToken); } } /// Provides session-scoped History APIs. [Experimental(Diagnostics.Experimental)] -public class HistoryApi +public sealed class HistoryApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2583,23 +2579,23 @@ internal HistoryApi(JsonRpc rpc, string sessionId) } /// Calls "session.history.compact". - public async Task CompactAsync(CancellationToken cancellationToken = default) + public async Task CompactAsync(CancellationToken cancellationToken = default) { var request = new SessionHistoryCompactRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.history.compact", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.history.compact", [request], cancellationToken); } /// Calls "session.history.truncate". - public async Task TruncateAsync(string eventId, CancellationToken cancellationToken = default) + public async Task TruncateAsync(string eventId, CancellationToken cancellationToken = default) { - var request = new SessionHistoryTruncateRequest { SessionId = _sessionId, EventId = eventId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.history.truncate", [request], cancellationToken); + var request = new HistoryTruncateRequest { SessionId = _sessionId, EventId = eventId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.history.truncate", [request], cancellationToken); } } /// Provides session-scoped Usage APIs. [Experimental(Diagnostics.Experimental)] -public class UsageApi +public sealed class UsageApi { private readonly JsonRpc _rpc; private readonly string _sessionId; @@ -2611,10 +2607,10 @@ internal UsageApi(JsonRpc rpc, string sessionId) } /// Calls "session.usage.getMetrics". - public async Task GetMetricsAsync(CancellationToken cancellationToken = default) + public async Task GetMetricsAsync(CancellationToken cancellationToken = default) { var request = new SessionUsageGetMetricsRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.usage.getMetrics", [request], cancellationToken); + return await CopilotClient.InvokeRpcAsync(_rpc, "session.usage.getMetrics", [request], cancellationToken); } } @@ -2622,29 +2618,29 @@ public async Task GetMetricsAsync(CancellationToke public interface ISessionFsHandler { /// Handles "sessionFs.readFile". - Task ReadFileAsync(SessionFsReadFileParams request, CancellationToken cancellationToken = default); + Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.writeFile". - Task WriteFileAsync(SessionFsWriteFileParams request, CancellationToken cancellationToken = default); + Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.appendFile". - Task AppendFileAsync(SessionFsAppendFileParams request, CancellationToken cancellationToken = default); + Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.exists". - Task ExistsAsync(SessionFsExistsParams request, CancellationToken cancellationToken = default); + Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.stat". - Task StatAsync(SessionFsStatParams request, CancellationToken cancellationToken = default); + Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.mkdir". - Task MkdirAsync(SessionFsMkdirParams request, CancellationToken cancellationToken = default); + Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdir". - Task ReaddirAsync(SessionFsReaddirParams request, CancellationToken cancellationToken = default); + Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdirWithTypes". - Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesParams request, CancellationToken cancellationToken = default); + Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rm". - Task RmAsync(SessionFsRmParams request, CancellationToken cancellationToken = default); + Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rename". - Task RenameAsync(SessionFsRenameParams request, CancellationToken cancellationToken = default); + Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); } /// Provides all client session API handler groups for a session. -public class ClientSessionApiHandlers +public sealed class ClientSessionApiHandlers { /// Optional handler for SessionFs client session API methods. public ISessionFsHandler? SessionFs { get; set; } @@ -2660,7 +2656,7 @@ public static class ClientSessionApiRegistration /// public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func getHandlers) { - var registerSessionFsReadFileMethod = (Func>)(async (request, cancellationToken) => + var registerSessionFsReadFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2670,7 +2666,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsWriteFileMethod = (Func)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2680,7 +2676,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsAppendFileMethod = (Func)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2690,7 +2686,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsExistsMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2700,7 +2696,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsStatMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2710,7 +2706,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsMkdirMethod = (Func)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2720,7 +2716,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsReaddirMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2730,7 +2726,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsReaddirWithTypesMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2740,7 +2736,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsRmMethod = (Func)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2750,7 +2746,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsRenameMethod = (Func)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -2768,13 +2764,40 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncWorking directory and git context at session start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] - public SessionStartDataContext? Context { get; set; } + public StartContext? Context { get; set; } /// Whether the session was already in use by another client at start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1158,7 +1158,7 @@ public partial class SessionResumeData /// Updated working directory and git context at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] - public SessionResumeDataContext? Context { get; set; } + public ResumeContext? Context { get; set; } /// Whether the session was already in use by another client at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1308,7 +1308,7 @@ public partial class SessionPlanChangedData { /// The type of operation performed on the plan file. [JsonPropertyName("operation")] - public required SessionPlanChangedDataOperation Operation { get; set; } + public required PlanChangedOperation Operation { get; set; } } /// Workspace file change details including path and operation type. @@ -1320,7 +1320,7 @@ public partial class SessionWorkspaceFileChangedData /// Whether the file was newly created or updated. [JsonPropertyName("operation")] - public required SessionWorkspaceFileChangedDataOperation Operation { get; set; } + public required WorkspaceFileChangedOperation Operation { get; set; } } /// Session handoff metadata including source, context, and repository information. @@ -1332,12 +1332,12 @@ public partial class SessionHandoffData /// Origin type of the session being handed off. [JsonPropertyName("sourceType")] - public required SessionHandoffDataSourceType SourceType { get; set; } + public required HandoffSourceType SourceType { get; set; } /// Repository context for the handed-off session. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] - public SessionHandoffDataRepository? Repository { get; set; } + public HandoffRepository? Repository { get; set; } /// Additional context information for the handoff. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1413,7 +1413,7 @@ public partial class SessionShutdownData { /// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). [JsonPropertyName("shutdownType")] - public required SessionShutdownDataShutdownType ShutdownType { get; set; } + public required ShutdownType ShutdownType { get; set; } /// Error description when shutdownType is "error". [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1434,7 +1434,7 @@ public partial class SessionShutdownData /// Aggregate code change metrics for the session. [JsonPropertyName("codeChanges")] - public required SessionShutdownDataCodeChanges CodeChanges { get; set; } + public required ShutdownCodeChanges CodeChanges { get; set; } /// Per-model usage breakdown, keyed by model identifier. [JsonPropertyName("modelMetrics")] @@ -1486,7 +1486,7 @@ public partial class SessionContextChangedData /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] - public SessionStartDataContextHostType? HostType { get; set; } + public ContextChangedHostType? HostType { get; set; } /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1614,7 +1614,7 @@ public partial class SessionCompactionCompleteData /// Token usage breakdown for the compaction LLM call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("compactionTokensUsed")] - public SessionCompactionCompleteDataCompactionTokensUsed? CompactionTokensUsed { get; set; } + public CompactionCompleteCompactionTokensUsed? CompactionTokensUsed { get; set; } /// GitHub request tracing ID (x-github-request-id header) for the compaction LLM call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1666,7 +1666,7 @@ public partial class UserMessageData /// Files, selections, or GitHub references attached to the message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("attachments")] - public UserMessageDataAttachmentsItem[]? Attachments { get; set; } + public UserMessageAttachment[]? Attachments { get; set; } /// Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1676,7 +1676,7 @@ public partial class UserMessageData /// The agent mode that was active when this message was sent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("agentMode")] - public UserMessageDataAgentMode? AgentMode { get; set; } + public UserMessageAgentMode? AgentMode { get; set; } /// CAPI interaction ID for correlating this user message with its turn. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1756,7 +1756,7 @@ public partial class AssistantMessageData /// Tool invocations requested by the assistant in this message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolRequests")] - public AssistantMessageDataToolRequestsItem[]? ToolRequests { get; set; } + public AssistantMessageToolRequest[]? ToolRequests { get; set; } /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1851,6 +1851,11 @@ public partial class AssistantUsageData [JsonPropertyName("cacheWriteTokens")] public double? CacheWriteTokens { get; set; } + /// Number of output tokens used for reasoning (e.g., chain-of-thought). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningTokens")] + public double? ReasoningTokens { get; set; } + /// Model multiplier cost for billing purposes. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("cost")] @@ -1899,7 +1904,7 @@ public partial class AssistantUsageData /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("copilotUsage")] - public AssistantUsageDataCopilotUsage? CopilotUsage { get; set; } + public AssistantUsageCopilotUsage? CopilotUsage { get; set; } /// Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2017,12 +2022,12 @@ public partial class ToolExecutionCompleteData /// Tool execution result on success. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("result")] - public ToolExecutionCompleteDataResult? Result { get; set; } + public ToolExecutionCompleteResult? Result { get; set; } /// Error details when the tool execution failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] - public ToolExecutionCompleteDataError? Error { get; set; } + public ToolExecutionCompleteError? Error { get; set; } /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2228,7 +2233,7 @@ public partial class HookEndData /// Error details when the hook failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] - public HookEndDataError? Error { get; set; } + public HookEndError? Error { get; set; } } /// System or developer message content with role and optional template metadata. @@ -2240,7 +2245,7 @@ public partial class SystemMessageData /// Message role: "system" for system prompts, "developer" for developer-injected instructions. [JsonPropertyName("role")] - public required SystemMessageDataRole Role { get; set; } + public required SystemMessageRole Role { get; set; } /// Optional name identifier for the message source. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2250,7 +2255,7 @@ public partial class SystemMessageData /// Metadata about the prompt template and its construction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("metadata")] - public SystemMessageDataMetadata? Metadata { get; set; } + public SystemMessageMetadata? Metadata { get; set; } } /// System-generated notification for runtime events like background task completion. @@ -2262,7 +2267,7 @@ public partial class SystemNotificationData /// Structured metadata identifying what triggered this notification. [JsonPropertyName("kind")] - public required SystemNotificationDataKind Kind { get; set; } + public required SystemNotification Kind { get; set; } } /// Permission request notification requiring client approval with request details. @@ -2291,7 +2296,7 @@ public partial class PermissionCompletedData /// The result of the permission request. [JsonPropertyName("result")] - public required PermissionCompletedDataResult Result { get; set; } + public required PermissionCompletedResult Result { get; set; } } /// User input request notification with question and optional predefined choices. @@ -2363,12 +2368,12 @@ public partial class ElicitationRequestedData /// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mode")] - public ElicitationRequestedDataMode? Mode { get; set; } + public ElicitationRequestedMode? Mode { get; set; } /// JSON Schema describing the form fields to present to the user (form mode only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestedSchema")] - public ElicitationRequestedDataRequestedSchema? RequestedSchema { get; set; } + public ElicitationRequestedSchema? RequestedSchema { get; set; } /// URL to open in the user's browser (url mode only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2386,7 +2391,7 @@ public partial class ElicitationCompletedData /// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("action")] - public ElicitationCompletedDataAction? Action { get; set; } + public ElicitationCompletedAction? Action { get; set; } /// The submitted form data when action is 'accept'; keys match the requested schema fields. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2436,7 +2441,7 @@ public partial class McpOauthRequiredData /// Static OAuth client configuration, if the server specifies one. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("staticClientConfig")] - public McpOauthRequiredDataStaticClientConfig? StaticClientConfig { get; set; } + public McpOauthRequiredStaticClientConfig? StaticClientConfig { get; set; } } /// MCP OAuth request completion notification. @@ -2535,7 +2540,7 @@ public partial class CommandsChangedData { /// Current list of registered SDK commands. [JsonPropertyName("commands")] - public required CommandsChangedDataCommandsItem[] Commands { get; set; } + public required CommandsChangedCommand[] Commands { get; set; } } /// Session capability change notification. @@ -2544,7 +2549,7 @@ public partial class CapabilitiesChangedData /// UI capability changes. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ui")] - public CapabilitiesChangedDataUi? Ui { get; set; } + public CapabilitiesChangedUI? Ui { get; set; } } /// Plan approval request with plan content and available user actions. @@ -2617,7 +2622,7 @@ public partial class SessionSkillsLoadedData { /// Array of resolved skill metadata. [JsonPropertyName("skills")] - public required SessionSkillsLoadedDataSkillsItem[] Skills { get; set; } + public required SkillsLoadedSkill[] Skills { get; set; } } /// Event payload for . @@ -2625,7 +2630,7 @@ public partial class SessionCustomAgentsUpdatedData { /// Array of loaded custom agent metadata. [JsonPropertyName("agents")] - public required SessionCustomAgentsUpdatedDataAgentsItem[] Agents { get; set; } + public required CustomAgentsUpdatedAgent[] Agents { get; set; } /// Non-fatal warnings from agent loading. [JsonPropertyName("warnings")] @@ -2641,7 +2646,7 @@ public partial class SessionMcpServersLoadedData { /// Array of MCP server status summaries. [JsonPropertyName("servers")] - public required SessionMcpServersLoadedDataServersItem[] Servers { get; set; } + public required McpServersLoadedServer[] Servers { get; set; } } /// Event payload for . @@ -2653,7 +2658,7 @@ public partial class SessionMcpServerStatusChangedData /// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonPropertyName("status")] - public required SessionMcpServersLoadedDataServersItemStatus Status { get; set; } + public required McpServerStatusChangedStatus Status { get; set; } } /// Event payload for . @@ -2661,12 +2666,12 @@ public partial class SessionExtensionsLoadedData { /// Array of discovered extensions and their status. [JsonPropertyName("extensions")] - public required SessionExtensionsLoadedDataExtensionsItem[] Extensions { get; set; } + public required ExtensionsLoadedExtension[] Extensions { get; set; } } /// Working directory and git context at session start. -/// Nested data type for SessionStartDataContext. -public partial class SessionStartDataContext +/// Nested data type for StartContext. +public partial class StartContext { /// Current working directory path. [JsonPropertyName("cwd")] @@ -2685,7 +2690,7 @@ public partial class SessionStartDataContext /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] - public SessionStartDataContextHostType? HostType { get; set; } + public StartContextHostType? HostType { get; set; } /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2704,8 +2709,8 @@ public partial class SessionStartDataContext } /// Updated working directory and git context at resume time. -/// Nested data type for SessionResumeDataContext. -public partial class SessionResumeDataContext +/// Nested data type for ResumeContext. +public partial class ResumeContext { /// Current working directory path. [JsonPropertyName("cwd")] @@ -2724,7 +2729,7 @@ public partial class SessionResumeDataContext /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] - public SessionStartDataContextHostType? HostType { get; set; } + public ResumeContextHostType? HostType { get; set; } /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2743,8 +2748,8 @@ public partial class SessionResumeDataContext } /// Repository context for the handed-off session. -/// Nested data type for SessionHandoffDataRepository. -public partial class SessionHandoffDataRepository +/// Nested data type for HandoffRepository. +public partial class HandoffRepository { /// Repository owner (user or organization). [JsonPropertyName("owner")] @@ -2761,8 +2766,8 @@ public partial class SessionHandoffDataRepository } /// Aggregate code change metrics for the session. -/// Nested data type for SessionShutdownDataCodeChanges. -public partial class SessionShutdownDataCodeChanges +/// Nested data type for ShutdownCodeChanges. +public partial class ShutdownCodeChanges { /// Total number of lines added during the session. [JsonPropertyName("linesAdded")] @@ -2778,8 +2783,8 @@ public partial class SessionShutdownDataCodeChanges } /// Token usage breakdown for the compaction LLM call. -/// Nested data type for SessionCompactionCompleteDataCompactionTokensUsed. -public partial class SessionCompactionCompleteDataCompactionTokensUsed +/// Nested data type for CompactionCompleteCompactionTokensUsed. +public partial class CompactionCompleteCompactionTokensUsed { /// Input tokens consumed by the compaction LLM call. [JsonPropertyName("input")] @@ -2795,8 +2800,8 @@ public partial class SessionCompactionCompleteDataCompactionTokensUsed } /// Optional line range to scope the attachment to a specific section of the file. -/// Nested data type for UserMessageDataAttachmentsItemFileLineRange. -public partial class UserMessageDataAttachmentsItemFileLineRange +/// Nested data type for UserMessageAttachmentFileLineRange. +public partial class UserMessageAttachmentFileLineRange { /// Start line number (1-based). [JsonPropertyName("start")] @@ -2808,8 +2813,8 @@ public partial class UserMessageDataAttachmentsItemFileLineRange } /// File attachment. -/// The file variant of . -public partial class UserMessageDataAttachmentsItemFile : UserMessageDataAttachmentsItem +/// The file variant of . +public partial class UserMessageAttachmentFile : UserMessageAttachment { /// [JsonIgnore] @@ -2826,12 +2831,12 @@ public partial class UserMessageDataAttachmentsItemFile : UserMessageDataAttachm /// Optional line range to scope the attachment to a specific section of the file. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("lineRange")] - public UserMessageDataAttachmentsItemFileLineRange? LineRange { get; set; } + public UserMessageAttachmentFileLineRange? LineRange { get; set; } } /// Directory attachment. -/// The directory variant of . -public partial class UserMessageDataAttachmentsItemDirectory : UserMessageDataAttachmentsItem +/// The directory variant of . +public partial class UserMessageAttachmentDirectory : UserMessageAttachment { /// [JsonIgnore] @@ -2847,8 +2852,8 @@ public partial class UserMessageDataAttachmentsItemDirectory : UserMessageDataAt } /// Start position of the selection. -/// Nested data type for UserMessageDataAttachmentsItemSelectionSelectionStart. -public partial class UserMessageDataAttachmentsItemSelectionSelectionStart +/// Nested data type for UserMessageAttachmentSelectionDetailsStart. +public partial class UserMessageAttachmentSelectionDetailsStart { /// Start line number (0-based). [JsonPropertyName("line")] @@ -2860,8 +2865,8 @@ public partial class UserMessageDataAttachmentsItemSelectionSelectionStart } /// End position of the selection. -/// Nested data type for UserMessageDataAttachmentsItemSelectionSelectionEnd. -public partial class UserMessageDataAttachmentsItemSelectionSelectionEnd +/// Nested data type for UserMessageAttachmentSelectionDetailsEnd. +public partial class UserMessageAttachmentSelectionDetailsEnd { /// End line number (0-based). [JsonPropertyName("line")] @@ -2873,21 +2878,21 @@ public partial class UserMessageDataAttachmentsItemSelectionSelectionEnd } /// Position range of the selection within the file. -/// Nested data type for UserMessageDataAttachmentsItemSelectionSelection. -public partial class UserMessageDataAttachmentsItemSelectionSelection +/// Nested data type for UserMessageAttachmentSelectionDetails. +public partial class UserMessageAttachmentSelectionDetails { /// Start position of the selection. [JsonPropertyName("start")] - public required UserMessageDataAttachmentsItemSelectionSelectionStart Start { get; set; } + public required UserMessageAttachmentSelectionDetailsStart Start { get; set; } /// End position of the selection. [JsonPropertyName("end")] - public required UserMessageDataAttachmentsItemSelectionSelectionEnd End { get; set; } + public required UserMessageAttachmentSelectionDetailsEnd End { get; set; } } /// Code selection attachment from an editor. -/// The selection variant of . -public partial class UserMessageDataAttachmentsItemSelection : UserMessageDataAttachmentsItem +/// The selection variant of . +public partial class UserMessageAttachmentSelection : UserMessageAttachment { /// [JsonIgnore] @@ -2907,12 +2912,12 @@ public partial class UserMessageDataAttachmentsItemSelection : UserMessageDataAt /// Position range of the selection within the file. [JsonPropertyName("selection")] - public required UserMessageDataAttachmentsItemSelectionSelection Selection { get; set; } + public required UserMessageAttachmentSelectionDetails Selection { get; set; } } /// GitHub issue, pull request, or discussion reference. -/// The github_reference variant of . -public partial class UserMessageDataAttachmentsItemGithubReference : UserMessageDataAttachmentsItem +/// The github_reference variant of . +public partial class UserMessageAttachmentGithubReference : UserMessageAttachment { /// [JsonIgnore] @@ -2928,7 +2933,7 @@ public partial class UserMessageDataAttachmentsItemGithubReference : UserMessage /// Type of GitHub reference. [JsonPropertyName("referenceType")] - public required UserMessageDataAttachmentsItemGithubReferenceReferenceType ReferenceType { get; set; } + public required UserMessageAttachmentGithubReferenceType ReferenceType { get; set; } /// Current state of the referenced item (e.g., open, closed, merged). [JsonPropertyName("state")] @@ -2940,14 +2945,15 @@ public partial class UserMessageDataAttachmentsItemGithubReference : UserMessage } /// Blob attachment with inline base64-encoded data. -/// The blob variant of . -public partial class UserMessageDataAttachmentsItemBlob : UserMessageDataAttachmentsItem +/// The blob variant of . +public partial class UserMessageAttachmentBlob : UserMessageAttachment { /// [JsonIgnore] public override string Type => "blob"; /// Base64-encoded content. + [Base64String] [JsonPropertyName("data")] public required string Data { get; set; } @@ -2966,12 +2972,12 @@ public partial class UserMessageDataAttachmentsItemBlob : UserMessageDataAttachm [JsonPolymorphic( TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(UserMessageDataAttachmentsItemFile), "file")] -[JsonDerivedType(typeof(UserMessageDataAttachmentsItemDirectory), "directory")] -[JsonDerivedType(typeof(UserMessageDataAttachmentsItemSelection), "selection")] -[JsonDerivedType(typeof(UserMessageDataAttachmentsItemGithubReference), "github_reference")] -[JsonDerivedType(typeof(UserMessageDataAttachmentsItemBlob), "blob")] -public partial class UserMessageDataAttachmentsItem +[JsonDerivedType(typeof(UserMessageAttachmentFile), "file")] +[JsonDerivedType(typeof(UserMessageAttachmentDirectory), "directory")] +[JsonDerivedType(typeof(UserMessageAttachmentSelection), "selection")] +[JsonDerivedType(typeof(UserMessageAttachmentGithubReference), "github_reference")] +[JsonDerivedType(typeof(UserMessageAttachmentBlob), "blob")] +public partial class UserMessageAttachment { /// The type discriminator. [JsonPropertyName("type")] @@ -2980,8 +2986,8 @@ public partial class UserMessageDataAttachmentsItem /// A tool invocation request from the assistant. -/// Nested data type for AssistantMessageDataToolRequestsItem. -public partial class AssistantMessageDataToolRequestsItem +/// Nested data type for AssistantMessageToolRequest. +public partial class AssistantMessageToolRequest { /// Unique identifier for this tool call. [JsonPropertyName("toolCallId")] @@ -2999,7 +3005,7 @@ public partial class AssistantMessageDataToolRequestsItem /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("type")] - public AssistantMessageDataToolRequestsItemType? Type { get; set; } + public AssistantMessageToolRequestType? Type { get; set; } /// Human-readable display title for the tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3018,8 +3024,8 @@ public partial class AssistantMessageDataToolRequestsItem } /// Token usage detail for a single billing category. -/// Nested data type for AssistantUsageDataCopilotUsageTokenDetailsItem. -public partial class AssistantUsageDataCopilotUsageTokenDetailsItem +/// Nested data type for AssistantUsageCopilotUsageTokenDetail. +public partial class AssistantUsageCopilotUsageTokenDetail { /// Number of tokens in this billing batch. [JsonPropertyName("batchSize")] @@ -3039,12 +3045,12 @@ public partial class AssistantUsageDataCopilotUsageTokenDetailsItem } /// Per-request cost and usage data from the CAPI copilot_usage response field. -/// Nested data type for AssistantUsageDataCopilotUsage. -public partial class AssistantUsageDataCopilotUsage +/// Nested data type for AssistantUsageCopilotUsage. +public partial class AssistantUsageCopilotUsage { /// Itemized token usage breakdown. [JsonPropertyName("tokenDetails")] - public required AssistantUsageDataCopilotUsageTokenDetailsItem[] TokenDetails { get; set; } + public required AssistantUsageCopilotUsageTokenDetail[] TokenDetails { get; set; } /// Total cost in nano-AIU (AI Units) for this request. [JsonPropertyName("totalNanoAiu")] @@ -3052,8 +3058,8 @@ public partial class AssistantUsageDataCopilotUsage } /// Plain text content block. -/// The text variant of . -public partial class ToolExecutionCompleteDataResultContentsItemText : ToolExecutionCompleteDataResultContentsItem +/// The text variant of . +public partial class ToolExecutionCompleteContentText : ToolExecutionCompleteContent { /// [JsonIgnore] @@ -3065,8 +3071,8 @@ public partial class ToolExecutionCompleteDataResultContentsItemText : ToolExecu } /// Terminal/shell output content block with optional exit code and working directory. -/// The terminal variant of . -public partial class ToolExecutionCompleteDataResultContentsItemTerminal : ToolExecutionCompleteDataResultContentsItem +/// The terminal variant of . +public partial class ToolExecutionCompleteContentTerminal : ToolExecutionCompleteContent { /// [JsonIgnore] @@ -3088,14 +3094,15 @@ public partial class ToolExecutionCompleteDataResultContentsItemTerminal : ToolE } /// Image content block with base64-encoded data. -/// The image variant of . -public partial class ToolExecutionCompleteDataResultContentsItemImage : ToolExecutionCompleteDataResultContentsItem +/// The image variant of . +public partial class ToolExecutionCompleteContentImage : ToolExecutionCompleteContent { /// [JsonIgnore] public override string Type => "image"; /// Base64-encoded image data. + [Base64String] [JsonPropertyName("data")] public required string Data { get; set; } @@ -3105,14 +3112,15 @@ public partial class ToolExecutionCompleteDataResultContentsItemImage : ToolExec } /// Audio content block with base64-encoded data. -/// The audio variant of . -public partial class ToolExecutionCompleteDataResultContentsItemAudio : ToolExecutionCompleteDataResultContentsItem +/// The audio variant of . +public partial class ToolExecutionCompleteContentAudio : ToolExecutionCompleteContent { /// [JsonIgnore] public override string Type => "audio"; /// Base64-encoded audio data. + [Base64String] [JsonPropertyName("data")] public required string Data { get; set; } @@ -3122,8 +3130,8 @@ public partial class ToolExecutionCompleteDataResultContentsItemAudio : ToolExec } /// Icon image for a resource. -/// Nested data type for ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem. -public partial class ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem +/// Nested data type for ToolExecutionCompleteContentResourceLinkIcon. +public partial class ToolExecutionCompleteContentResourceLinkIcon { /// URL or path to the icon image. [JsonPropertyName("src")] @@ -3142,12 +3150,12 @@ public partial class ToolExecutionCompleteDataResultContentsItemResourceLinkIcon /// Theme variant this icon is intended for. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("theme")] - public ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme? Theme { get; set; } + public ToolExecutionCompleteContentResourceLinkIconTheme? Theme { get; set; } } /// Resource link content block referencing an external resource. -/// The resource_link variant of . -public partial class ToolExecutionCompleteDataResultContentsItemResourceLink : ToolExecutionCompleteDataResultContentsItem +/// The resource_link variant of . +public partial class ToolExecutionCompleteContentResourceLink : ToolExecutionCompleteContent { /// [JsonIgnore] @@ -3156,7 +3164,7 @@ public partial class ToolExecutionCompleteDataResultContentsItemResourceLink : T /// Icons associated with this resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("icons")] - public ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem[]? Icons { get; set; } + public ToolExecutionCompleteContentResourceLinkIcon[]? Icons { get; set; } /// Resource name identifier. [JsonPropertyName("name")] @@ -3188,8 +3196,8 @@ public partial class ToolExecutionCompleteDataResultContentsItemResourceLink : T } /// Embedded resource content block with inline text or binary data. -/// The resource variant of . -public partial class ToolExecutionCompleteDataResultContentsItemResource : ToolExecutionCompleteDataResultContentsItem +/// The resource variant of . +public partial class ToolExecutionCompleteContentResource : ToolExecutionCompleteContent { /// [JsonIgnore] @@ -3205,13 +3213,13 @@ public partial class ToolExecutionCompleteDataResultContentsItemResource : ToolE [JsonPolymorphic( TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemText), "text")] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemTerminal), "terminal")] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemImage), "image")] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemAudio), "audio")] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemResourceLink), "resource_link")] -[JsonDerivedType(typeof(ToolExecutionCompleteDataResultContentsItemResource), "resource")] -public partial class ToolExecutionCompleteDataResultContentsItem +[JsonDerivedType(typeof(ToolExecutionCompleteContentText), "text")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentTerminal), "terminal")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentImage), "image")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentAudio), "audio")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentResourceLink), "resource_link")] +[JsonDerivedType(typeof(ToolExecutionCompleteContentResource), "resource")] +public partial class ToolExecutionCompleteContent { /// The type discriminator. [JsonPropertyName("type")] @@ -3220,8 +3228,8 @@ public partial class ToolExecutionCompleteDataResultContentsItem /// Tool execution result on success. -/// Nested data type for ToolExecutionCompleteDataResult. -public partial class ToolExecutionCompleteDataResult +/// Nested data type for ToolExecutionCompleteResult. +public partial class ToolExecutionCompleteResult { /// Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency. [JsonPropertyName("content")] @@ -3235,12 +3243,12 @@ public partial class ToolExecutionCompleteDataResult /// Structured content blocks (text, images, audio, resources) returned by the tool in their native format. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("contents")] - public ToolExecutionCompleteDataResultContentsItem[]? Contents { get; set; } + public ToolExecutionCompleteContent[]? Contents { get; set; } } /// Error details when the tool execution failed. -/// Nested data type for ToolExecutionCompleteDataError. -public partial class ToolExecutionCompleteDataError +/// Nested data type for ToolExecutionCompleteError. +public partial class ToolExecutionCompleteError { /// Human-readable error message. [JsonPropertyName("message")] @@ -3253,8 +3261,8 @@ public partial class ToolExecutionCompleteDataError } /// Error details when the hook failed. -/// Nested data type for HookEndDataError. -public partial class HookEndDataError +/// Nested data type for HookEndError. +public partial class HookEndError { /// Human-readable error message. [JsonPropertyName("message")] @@ -3267,8 +3275,8 @@ public partial class HookEndDataError } /// Metadata about the prompt template and its construction. -/// Nested data type for SystemMessageDataMetadata. -public partial class SystemMessageDataMetadata +/// Nested data type for SystemMessageMetadata. +public partial class SystemMessageMetadata { /// Version identifier of the prompt template used. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3281,8 +3289,8 @@ public partial class SystemMessageDataMetadata public IDictionary? Variables { get; set; } } -/// The agent_completed variant of . -public partial class SystemNotificationDataKindAgentCompleted : SystemNotificationDataKind +/// The agent_completed variant of . +public partial class SystemNotificationAgentCompleted : SystemNotification { /// [JsonIgnore] @@ -3298,7 +3306,7 @@ public partial class SystemNotificationDataKindAgentCompleted : SystemNotificati /// Whether the agent completed successfully or failed. [JsonPropertyName("status")] - public required SystemNotificationDataKindAgentCompletedStatus Status { get; set; } + public required SystemNotificationAgentCompletedStatus Status { get; set; } /// Human-readable description of the agent task. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3311,8 +3319,8 @@ public partial class SystemNotificationDataKindAgentCompleted : SystemNotificati public string? Prompt { get; set; } } -/// The agent_idle variant of . -public partial class SystemNotificationDataKindAgentIdle : SystemNotificationDataKind +/// The agent_idle variant of . +public partial class SystemNotificationAgentIdle : SystemNotification { /// [JsonIgnore] @@ -3332,8 +3340,8 @@ public partial class SystemNotificationDataKindAgentIdle : SystemNotificationDat public string? Description { get; set; } } -/// The shell_completed variant of . -public partial class SystemNotificationDataKindShellCompleted : SystemNotificationDataKind +/// The shell_completed variant of . +public partial class SystemNotificationShellCompleted : SystemNotification { /// [JsonIgnore] @@ -3354,8 +3362,8 @@ public partial class SystemNotificationDataKindShellCompleted : SystemNotificati public string? Description { get; set; } } -/// The shell_detached_completed variant of . -public partial class SystemNotificationDataKindShellDetachedCompleted : SystemNotificationDataKind +/// The shell_detached_completed variant of . +public partial class SystemNotificationShellDetachedCompleted : SystemNotification { /// [JsonIgnore] @@ -3376,11 +3384,11 @@ public partial class SystemNotificationDataKindShellDetachedCompleted : SystemNo [JsonPolymorphic( TypeDiscriminatorPropertyName = "type", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(SystemNotificationDataKindAgentCompleted), "agent_completed")] -[JsonDerivedType(typeof(SystemNotificationDataKindAgentIdle), "agent_idle")] -[JsonDerivedType(typeof(SystemNotificationDataKindShellCompleted), "shell_completed")] -[JsonDerivedType(typeof(SystemNotificationDataKindShellDetachedCompleted), "shell_detached_completed")] -public partial class SystemNotificationDataKind +[JsonDerivedType(typeof(SystemNotificationAgentCompleted), "agent_completed")] +[JsonDerivedType(typeof(SystemNotificationAgentIdle), "agent_idle")] +[JsonDerivedType(typeof(SystemNotificationShellCompleted), "shell_completed")] +[JsonDerivedType(typeof(SystemNotificationShellDetachedCompleted), "shell_detached_completed")] +public partial class SystemNotification { /// The type discriminator. [JsonPropertyName("type")] @@ -3388,8 +3396,8 @@ public partial class SystemNotificationDataKind } -/// Nested data type for PermissionRequestShellCommandsItem. -public partial class PermissionRequestShellCommandsItem +/// Nested data type for PermissionRequestShellCommand. +public partial class PermissionRequestShellCommand { /// Command identifier (e.g., executable name). [JsonPropertyName("identifier")] @@ -3400,8 +3408,8 @@ public partial class PermissionRequestShellCommandsItem public required bool ReadOnly { get; set; } } -/// Nested data type for PermissionRequestShellPossibleUrlsItem. -public partial class PermissionRequestShellPossibleUrlsItem +/// Nested data type for PermissionRequestShellPossibleUrl. +public partial class PermissionRequestShellPossibleUrl { /// URL that may be accessed by the command. [JsonPropertyName("url")] @@ -3431,7 +3439,7 @@ public partial class PermissionRequestShell : PermissionRequest /// Parsed command identifiers found in the command text. [JsonPropertyName("commands")] - public required PermissionRequestShellCommandsItem[] Commands { get; set; } + public required PermissionRequestShellCommand[] Commands { get; set; } /// File paths that may be read or written by the command. [JsonPropertyName("possiblePaths")] @@ -3439,7 +3447,7 @@ public partial class PermissionRequestShell : PermissionRequest /// URLs that may be accessed by the command. [JsonPropertyName("possibleUrls")] - public required PermissionRequestShellPossibleUrlsItem[] PossibleUrls { get; set; } + public required PermissionRequestShellPossibleUrl[] PossibleUrls { get; set; } /// Whether the command includes a file write redirection (e.g., > or >>). [JsonPropertyName("hasWriteFileRedirection")] @@ -3685,17 +3693,17 @@ public partial class PermissionRequest /// The result of the permission request. -/// Nested data type for PermissionCompletedDataResult. -public partial class PermissionCompletedDataResult +/// Nested data type for PermissionCompletedResult. +public partial class PermissionCompletedResult { /// The outcome of the permission request. [JsonPropertyName("kind")] - public required PermissionCompletedDataResultKind Kind { get; set; } + public required PermissionCompletedKind Kind { get; set; } } /// JSON Schema describing the form fields to present to the user (form mode only). -/// Nested data type for ElicitationRequestedDataRequestedSchema. -public partial class ElicitationRequestedDataRequestedSchema +/// Nested data type for ElicitationRequestedSchema. +public partial class ElicitationRequestedSchema { /// Schema type indicator (always 'object'). [JsonPropertyName("type")] @@ -3712,8 +3720,8 @@ public partial class ElicitationRequestedDataRequestedSchema } /// Static OAuth client configuration, if the server specifies one. -/// Nested data type for McpOauthRequiredDataStaticClientConfig. -public partial class McpOauthRequiredDataStaticClientConfig +/// Nested data type for McpOauthRequiredStaticClientConfig. +public partial class McpOauthRequiredStaticClientConfig { /// OAuth client ID for the server. [JsonPropertyName("clientId")] @@ -3725,8 +3733,8 @@ public partial class McpOauthRequiredDataStaticClientConfig public bool? PublicClient { get; set; } } -/// Nested data type for CommandsChangedDataCommandsItem. -public partial class CommandsChangedDataCommandsItem +/// Nested data type for CommandsChangedCommand. +public partial class CommandsChangedCommand { /// Gets or sets the name value. [JsonPropertyName("name")] @@ -3739,8 +3747,8 @@ public partial class CommandsChangedDataCommandsItem } /// UI capability changes. -/// Nested data type for CapabilitiesChangedDataUi. -public partial class CapabilitiesChangedDataUi +/// Nested data type for CapabilitiesChangedUI. +public partial class CapabilitiesChangedUI { /// Whether elicitation is now supported. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3748,8 +3756,8 @@ public partial class CapabilitiesChangedDataUi public bool? Elicitation { get; set; } } -/// Nested data type for SessionSkillsLoadedDataSkillsItem. -public partial class SessionSkillsLoadedDataSkillsItem +/// Nested data type for SkillsLoadedSkill. +public partial class SkillsLoadedSkill { /// Unique identifier for the skill. [JsonPropertyName("name")] @@ -3777,8 +3785,8 @@ public partial class SessionSkillsLoadedDataSkillsItem public string? Path { get; set; } } -/// Nested data type for SessionCustomAgentsUpdatedDataAgentsItem. -public partial class SessionCustomAgentsUpdatedDataAgentsItem +/// Nested data type for CustomAgentsUpdatedAgent. +public partial class CustomAgentsUpdatedAgent { /// Unique identifier for the agent. [JsonPropertyName("id")] @@ -3814,8 +3822,8 @@ public partial class SessionCustomAgentsUpdatedDataAgentsItem public string? Model { get; set; } } -/// Nested data type for SessionMcpServersLoadedDataServersItem. -public partial class SessionMcpServersLoadedDataServersItem +/// Nested data type for McpServersLoadedServer. +public partial class McpServersLoadedServer { /// Server name (config key). [JsonPropertyName("name")] @@ -3823,7 +3831,7 @@ public partial class SessionMcpServersLoadedDataServersItem /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonPropertyName("status")] - public required SessionMcpServersLoadedDataServersItemStatus Status { get; set; } + public required McpServersLoadedServerStatus Status { get; set; } /// Configuration source: user, workspace, plugin, or builtin. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3836,8 +3844,8 @@ public partial class SessionMcpServersLoadedDataServersItem public string? Error { get; set; } } -/// Nested data type for SessionExtensionsLoadedDataExtensionsItem. -public partial class SessionExtensionsLoadedDataExtensionsItem +/// Nested data type for ExtensionsLoadedExtension. +public partial class ExtensionsLoadedExtension { /// Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper'). [JsonPropertyName("id")] @@ -3849,16 +3857,28 @@ public partial class SessionExtensionsLoadedDataExtensionsItem /// Discovery source. [JsonPropertyName("source")] - public required SessionExtensionsLoadedDataExtensionsItemSource Source { get; set; } + public required ExtensionsLoadedExtensionSource Source { get; set; } /// Current status: running, disabled, failed, or starting. [JsonPropertyName("status")] - public required SessionExtensionsLoadedDataExtensionsItemStatus Status { get; set; } + public required ExtensionsLoadedExtensionStatus Status { get; set; } +} + +/// Hosting platform type of the repository (github or ado). +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum StartContextHostType +{ + /// The github variant. + [JsonStringEnumMemberName("github")] + Github, + /// The ado variant. + [JsonStringEnumMemberName("ado")] + Ado, } /// Hosting platform type of the repository (github or ado). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionStartDataContextHostType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ResumeContextHostType { /// The github variant. [JsonStringEnumMemberName("github")] @@ -3869,8 +3889,8 @@ public enum SessionStartDataContextHostType } /// The type of operation performed on the plan file. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionPlanChangedDataOperation +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PlanChangedOperation { /// The create variant. [JsonStringEnumMemberName("create")] @@ -3884,8 +3904,8 @@ public enum SessionPlanChangedDataOperation } /// Whether the file was newly created or updated. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionWorkspaceFileChangedDataOperation +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum WorkspaceFileChangedOperation { /// The create variant. [JsonStringEnumMemberName("create")] @@ -3896,8 +3916,8 @@ public enum SessionWorkspaceFileChangedDataOperation } /// Origin type of the session being handed off. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionHandoffDataSourceType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum HandoffSourceType { /// The remote variant. [JsonStringEnumMemberName("remote")] @@ -3908,8 +3928,8 @@ public enum SessionHandoffDataSourceType } /// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionShutdownDataShutdownType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ShutdownType { /// The routine variant. [JsonStringEnumMemberName("routine")] @@ -3919,9 +3939,21 @@ public enum SessionShutdownDataShutdownType Error, } +/// Hosting platform type of the repository (github or ado). +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ContextChangedHostType +{ + /// The github variant. + [JsonStringEnumMemberName("github")] + Github, + /// The ado variant. + [JsonStringEnumMemberName("ado")] + Ado, +} + /// Type of GitHub reference. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum UserMessageDataAttachmentsItemGithubReferenceReferenceType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UserMessageAttachmentGithubReferenceType { /// The issue variant. [JsonStringEnumMemberName("issue")] @@ -3935,8 +3967,8 @@ public enum UserMessageDataAttachmentsItemGithubReferenceReferenceType } /// The agent mode that was active when this message was sent. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum UserMessageDataAgentMode +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UserMessageAgentMode { /// The interactive variant. [JsonStringEnumMemberName("interactive")] @@ -3953,8 +3985,8 @@ public enum UserMessageDataAgentMode } /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum AssistantMessageDataToolRequestsItemType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum AssistantMessageToolRequestType { /// The function variant. [JsonStringEnumMemberName("function")] @@ -3965,8 +3997,8 @@ public enum AssistantMessageDataToolRequestsItemType } /// Theme variant this icon is intended for. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ToolExecutionCompleteContentResourceLinkIconTheme { /// The light variant. [JsonStringEnumMemberName("light")] @@ -3977,8 +4009,8 @@ public enum ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemThem } /// Message role: "system" for system prompts, "developer" for developer-injected instructions. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SystemMessageDataRole +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SystemMessageRole { /// The system variant. [JsonStringEnumMemberName("system")] @@ -3989,8 +4021,8 @@ public enum SystemMessageDataRole } /// Whether the agent completed successfully or failed. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SystemNotificationDataKindAgentCompletedStatus +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SystemNotificationAgentCompletedStatus { /// The completed variant. [JsonStringEnumMemberName("completed")] @@ -4025,8 +4057,8 @@ public enum PermissionRequestMemoryDirection } /// The outcome of the permission request. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum PermissionCompletedDataResultKind +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionCompletedKind { /// The approved variant. [JsonStringEnumMemberName("approved")] @@ -4049,8 +4081,8 @@ public enum PermissionCompletedDataResultKind } /// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ElicitationRequestedDataMode +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ElicitationRequestedMode { /// The form variant. [JsonStringEnumMemberName("form")] @@ -4061,8 +4093,8 @@ public enum ElicitationRequestedDataMode } /// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ElicitationCompletedDataAction +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ElicitationCompletedAction { /// The accept variant. [JsonStringEnumMemberName("accept")] @@ -4076,8 +4108,32 @@ public enum ElicitationCompletedDataAction } /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionMcpServersLoadedDataServersItemStatus +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum McpServersLoadedServerStatus +{ + /// The connected variant. + [JsonStringEnumMemberName("connected")] + Connected, + /// The failed variant. + [JsonStringEnumMemberName("failed")] + Failed, + /// The needs-auth variant. + [JsonStringEnumMemberName("needs-auth")] + NeedsAuth, + /// The pending variant. + [JsonStringEnumMemberName("pending")] + Pending, + /// The disabled variant. + [JsonStringEnumMemberName("disabled")] + Disabled, + /// The not_configured variant. + [JsonStringEnumMemberName("not_configured")] + NotConfigured, +} + +/// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum McpServerStatusChangedStatus { /// The connected variant. [JsonStringEnumMemberName("connected")] @@ -4100,8 +4156,8 @@ public enum SessionMcpServersLoadedDataServersItemStatus } /// Discovery source. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionExtensionsLoadedDataExtensionsItemSource +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ExtensionsLoadedExtensionSource { /// The project variant. [JsonStringEnumMemberName("project")] @@ -4112,8 +4168,8 @@ public enum SessionExtensionsLoadedDataExtensionsItemSource } /// Current status: running, disabled, failed, or starting. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum SessionExtensionsLoadedDataExtensionsItemStatus +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ExtensionsLoadedExtensionStatus { /// The running variant. [JsonStringEnumMemberName("running")] @@ -4139,10 +4195,10 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(AssistantIntentData))] [JsonSerializable(typeof(AssistantIntentEvent))] [JsonSerializable(typeof(AssistantMessageData))] -[JsonSerializable(typeof(AssistantMessageDataToolRequestsItem))] [JsonSerializable(typeof(AssistantMessageDeltaData))] [JsonSerializable(typeof(AssistantMessageDeltaEvent))] [JsonSerializable(typeof(AssistantMessageEvent))] +[JsonSerializable(typeof(AssistantMessageToolRequest))] [JsonSerializable(typeof(AssistantReasoningData))] [JsonSerializable(typeof(AssistantReasoningDeltaData))] [JsonSerializable(typeof(AssistantReasoningDeltaEvent))] @@ -4153,50 +4209,55 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(AssistantTurnEndEvent))] [JsonSerializable(typeof(AssistantTurnStartData))] [JsonSerializable(typeof(AssistantTurnStartEvent))] +[JsonSerializable(typeof(AssistantUsageCopilotUsage))] +[JsonSerializable(typeof(AssistantUsageCopilotUsageTokenDetail))] [JsonSerializable(typeof(AssistantUsageData))] -[JsonSerializable(typeof(AssistantUsageDataCopilotUsage))] -[JsonSerializable(typeof(AssistantUsageDataCopilotUsageTokenDetailsItem))] [JsonSerializable(typeof(AssistantUsageEvent))] [JsonSerializable(typeof(CapabilitiesChangedData))] -[JsonSerializable(typeof(CapabilitiesChangedDataUi))] [JsonSerializable(typeof(CapabilitiesChangedEvent))] +[JsonSerializable(typeof(CapabilitiesChangedUI))] [JsonSerializable(typeof(CommandCompletedData))] [JsonSerializable(typeof(CommandCompletedEvent))] [JsonSerializable(typeof(CommandExecuteData))] [JsonSerializable(typeof(CommandExecuteEvent))] [JsonSerializable(typeof(CommandQueuedData))] [JsonSerializable(typeof(CommandQueuedEvent))] +[JsonSerializable(typeof(CommandsChangedCommand))] [JsonSerializable(typeof(CommandsChangedData))] -[JsonSerializable(typeof(CommandsChangedDataCommandsItem))] [JsonSerializable(typeof(CommandsChangedEvent))] +[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsed))] +[JsonSerializable(typeof(CustomAgentsUpdatedAgent))] [JsonSerializable(typeof(ElicitationCompletedData))] [JsonSerializable(typeof(ElicitationCompletedEvent))] [JsonSerializable(typeof(ElicitationRequestedData))] -[JsonSerializable(typeof(ElicitationRequestedDataRequestedSchema))] [JsonSerializable(typeof(ElicitationRequestedEvent))] +[JsonSerializable(typeof(ElicitationRequestedSchema))] [JsonSerializable(typeof(ExitPlanModeCompletedData))] [JsonSerializable(typeof(ExitPlanModeCompletedEvent))] [JsonSerializable(typeof(ExitPlanModeRequestedData))] [JsonSerializable(typeof(ExitPlanModeRequestedEvent))] +[JsonSerializable(typeof(ExtensionsLoadedExtension))] [JsonSerializable(typeof(ExternalToolCompletedData))] [JsonSerializable(typeof(ExternalToolCompletedEvent))] [JsonSerializable(typeof(ExternalToolRequestedData))] [JsonSerializable(typeof(ExternalToolRequestedEvent))] +[JsonSerializable(typeof(HandoffRepository))] [JsonSerializable(typeof(HookEndData))] -[JsonSerializable(typeof(HookEndDataError))] +[JsonSerializable(typeof(HookEndError))] [JsonSerializable(typeof(HookEndEvent))] [JsonSerializable(typeof(HookStartData))] [JsonSerializable(typeof(HookStartEvent))] [JsonSerializable(typeof(McpOauthCompletedData))] [JsonSerializable(typeof(McpOauthCompletedEvent))] [JsonSerializable(typeof(McpOauthRequiredData))] -[JsonSerializable(typeof(McpOauthRequiredDataStaticClientConfig))] [JsonSerializable(typeof(McpOauthRequiredEvent))] +[JsonSerializable(typeof(McpOauthRequiredStaticClientConfig))] +[JsonSerializable(typeof(McpServersLoadedServer))] [JsonSerializable(typeof(PendingMessagesModifiedData))] [JsonSerializable(typeof(PendingMessagesModifiedEvent))] [JsonSerializable(typeof(PermissionCompletedData))] -[JsonSerializable(typeof(PermissionCompletedDataResult))] [JsonSerializable(typeof(PermissionCompletedEvent))] +[JsonSerializable(typeof(PermissionCompletedResult))] [JsonSerializable(typeof(PermissionRequest))] [JsonSerializable(typeof(PermissionRequestCustomTool))] [JsonSerializable(typeof(PermissionRequestHook))] @@ -4204,12 +4265,13 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(PermissionRequestMemory))] [JsonSerializable(typeof(PermissionRequestRead))] [JsonSerializable(typeof(PermissionRequestShell))] -[JsonSerializable(typeof(PermissionRequestShellCommandsItem))] -[JsonSerializable(typeof(PermissionRequestShellPossibleUrlsItem))] +[JsonSerializable(typeof(PermissionRequestShellCommand))] +[JsonSerializable(typeof(PermissionRequestShellPossibleUrl))] [JsonSerializable(typeof(PermissionRequestUrl))] [JsonSerializable(typeof(PermissionRequestWrite))] [JsonSerializable(typeof(PermissionRequestedData))] [JsonSerializable(typeof(PermissionRequestedEvent))] +[JsonSerializable(typeof(ResumeContext))] [JsonSerializable(typeof(SamplingCompletedData))] [JsonSerializable(typeof(SamplingCompletedEvent))] [JsonSerializable(typeof(SamplingRequestedData))] @@ -4217,23 +4279,19 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(SessionBackgroundTasksChangedData))] [JsonSerializable(typeof(SessionBackgroundTasksChangedEvent))] [JsonSerializable(typeof(SessionCompactionCompleteData))] -[JsonSerializable(typeof(SessionCompactionCompleteDataCompactionTokensUsed))] [JsonSerializable(typeof(SessionCompactionCompleteEvent))] [JsonSerializable(typeof(SessionCompactionStartData))] [JsonSerializable(typeof(SessionCompactionStartEvent))] [JsonSerializable(typeof(SessionContextChangedData))] [JsonSerializable(typeof(SessionContextChangedEvent))] [JsonSerializable(typeof(SessionCustomAgentsUpdatedData))] -[JsonSerializable(typeof(SessionCustomAgentsUpdatedDataAgentsItem))] [JsonSerializable(typeof(SessionCustomAgentsUpdatedEvent))] [JsonSerializable(typeof(SessionErrorData))] [JsonSerializable(typeof(SessionErrorEvent))] [JsonSerializable(typeof(SessionEvent))] [JsonSerializable(typeof(SessionExtensionsLoadedData))] -[JsonSerializable(typeof(SessionExtensionsLoadedDataExtensionsItem))] [JsonSerializable(typeof(SessionExtensionsLoadedEvent))] [JsonSerializable(typeof(SessionHandoffData))] -[JsonSerializable(typeof(SessionHandoffDataRepository))] [JsonSerializable(typeof(SessionHandoffEvent))] [JsonSerializable(typeof(SessionIdleData))] [JsonSerializable(typeof(SessionIdleEvent))] @@ -4242,7 +4300,6 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(SessionMcpServerStatusChangedData))] [JsonSerializable(typeof(SessionMcpServerStatusChangedEvent))] [JsonSerializable(typeof(SessionMcpServersLoadedData))] -[JsonSerializable(typeof(SessionMcpServersLoadedDataServersItem))] [JsonSerializable(typeof(SessionMcpServersLoadedEvent))] [JsonSerializable(typeof(SessionModeChangedData))] [JsonSerializable(typeof(SessionModeChangedEvent))] @@ -4253,18 +4310,14 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(SessionRemoteSteerableChangedData))] [JsonSerializable(typeof(SessionRemoteSteerableChangedEvent))] [JsonSerializable(typeof(SessionResumeData))] -[JsonSerializable(typeof(SessionResumeDataContext))] [JsonSerializable(typeof(SessionResumeEvent))] [JsonSerializable(typeof(SessionShutdownData))] -[JsonSerializable(typeof(SessionShutdownDataCodeChanges))] [JsonSerializable(typeof(SessionShutdownEvent))] [JsonSerializable(typeof(SessionSkillsLoadedData))] -[JsonSerializable(typeof(SessionSkillsLoadedDataSkillsItem))] [JsonSerializable(typeof(SessionSkillsLoadedEvent))] [JsonSerializable(typeof(SessionSnapshotRewindData))] [JsonSerializable(typeof(SessionSnapshotRewindEvent))] [JsonSerializable(typeof(SessionStartData))] -[JsonSerializable(typeof(SessionStartDataContext))] [JsonSerializable(typeof(SessionStartEvent))] [JsonSerializable(typeof(SessionTaskCompleteData))] [JsonSerializable(typeof(SessionTaskCompleteEvent))] @@ -4280,8 +4333,11 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(SessionWarningEvent))] [JsonSerializable(typeof(SessionWorkspaceFileChangedData))] [JsonSerializable(typeof(SessionWorkspaceFileChangedEvent))] +[JsonSerializable(typeof(ShutdownCodeChanges))] [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] +[JsonSerializable(typeof(SkillsLoadedSkill))] +[JsonSerializable(typeof(StartContext))] [JsonSerializable(typeof(SubagentCompletedData))] [JsonSerializable(typeof(SubagentCompletedEvent))] [JsonSerializable(typeof(SubagentDeselectedData))] @@ -4293,27 +4349,27 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(SubagentStartedData))] [JsonSerializable(typeof(SubagentStartedEvent))] [JsonSerializable(typeof(SystemMessageData))] -[JsonSerializable(typeof(SystemMessageDataMetadata))] [JsonSerializable(typeof(SystemMessageEvent))] +[JsonSerializable(typeof(SystemMessageMetadata))] +[JsonSerializable(typeof(SystemNotification))] +[JsonSerializable(typeof(SystemNotificationAgentCompleted))] +[JsonSerializable(typeof(SystemNotificationAgentIdle))] [JsonSerializable(typeof(SystemNotificationData))] -[JsonSerializable(typeof(SystemNotificationDataKind))] -[JsonSerializable(typeof(SystemNotificationDataKindAgentCompleted))] -[JsonSerializable(typeof(SystemNotificationDataKindAgentIdle))] -[JsonSerializable(typeof(SystemNotificationDataKindShellCompleted))] -[JsonSerializable(typeof(SystemNotificationDataKindShellDetachedCompleted))] [JsonSerializable(typeof(SystemNotificationEvent))] +[JsonSerializable(typeof(SystemNotificationShellCompleted))] +[JsonSerializable(typeof(SystemNotificationShellDetachedCompleted))] +[JsonSerializable(typeof(ToolExecutionCompleteContent))] +[JsonSerializable(typeof(ToolExecutionCompleteContentAudio))] +[JsonSerializable(typeof(ToolExecutionCompleteContentImage))] +[JsonSerializable(typeof(ToolExecutionCompleteContentResource))] +[JsonSerializable(typeof(ToolExecutionCompleteContentResourceLink))] +[JsonSerializable(typeof(ToolExecutionCompleteContentResourceLinkIcon))] +[JsonSerializable(typeof(ToolExecutionCompleteContentTerminal))] +[JsonSerializable(typeof(ToolExecutionCompleteContentText))] [JsonSerializable(typeof(ToolExecutionCompleteData))] -[JsonSerializable(typeof(ToolExecutionCompleteDataError))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResult))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItem))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemAudio))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemImage))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResource))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResourceLink))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItem))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemTerminal))] -[JsonSerializable(typeof(ToolExecutionCompleteDataResultContentsItemText))] +[JsonSerializable(typeof(ToolExecutionCompleteError))] [JsonSerializable(typeof(ToolExecutionCompleteEvent))] +[JsonSerializable(typeof(ToolExecutionCompleteResult))] [JsonSerializable(typeof(ToolExecutionPartialResultData))] [JsonSerializable(typeof(ToolExecutionPartialResultEvent))] [JsonSerializable(typeof(ToolExecutionProgressData))] @@ -4326,17 +4382,17 @@ public enum SessionExtensionsLoadedDataExtensionsItemStatus [JsonSerializable(typeof(UserInputCompletedEvent))] [JsonSerializable(typeof(UserInputRequestedData))] [JsonSerializable(typeof(UserInputRequestedEvent))] +[JsonSerializable(typeof(UserMessageAttachment))] +[JsonSerializable(typeof(UserMessageAttachmentBlob))] +[JsonSerializable(typeof(UserMessageAttachmentDirectory))] +[JsonSerializable(typeof(UserMessageAttachmentFile))] +[JsonSerializable(typeof(UserMessageAttachmentFileLineRange))] +[JsonSerializable(typeof(UserMessageAttachmentGithubReference))] +[JsonSerializable(typeof(UserMessageAttachmentSelection))] +[JsonSerializable(typeof(UserMessageAttachmentSelectionDetails))] +[JsonSerializable(typeof(UserMessageAttachmentSelectionDetailsEnd))] +[JsonSerializable(typeof(UserMessageAttachmentSelectionDetailsStart))] [JsonSerializable(typeof(UserMessageData))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItem))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemBlob))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemDirectory))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemFile))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemFileLineRange))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemGithubReference))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelection))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelection))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionEnd))] -[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionStart))] [JsonSerializable(typeof(UserMessageEvent))] [JsonSerializable(typeof(JsonElement))] internal partial class SessionEventsJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 2a2778b3c..733b94a71 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -727,7 +727,7 @@ private async Task HandleElicitationRequestAsync(ElicitationContext context, str try { var result = await handler(context); - await Rpc.Ui.HandlePendingElicitationAsync(requestId, new SessionUiHandlePendingElicitationRequestResult + await Rpc.Ui.HandlePendingElicitationAsync(requestId, new UIElicitationResponse { Action = result.Action, Content = result.Content @@ -738,9 +738,9 @@ private async Task HandleElicitationRequestAsync(ElicitationContext context, str // User handler can throw any exception — attempt to cancel so the request doesn't hang. try { - await Rpc.Ui.HandlePendingElicitationAsync(requestId, new SessionUiHandlePendingElicitationRequestResult + await Rpc.Ui.HandlePendingElicitationAsync(requestId, new UIElicitationResponse { - Action = SessionUiElicitationResultAction.Cancel + Action = UIElicitationResponseAction.Cancel }); } catch (Exception innerEx) when (innerEx is IOException or ObjectDisposedException) @@ -771,7 +771,7 @@ private sealed class SessionUiApiImpl(CopilotSession session) : ISessionUiApi public async Task ElicitationAsync(ElicitationParams elicitationParams, CancellationToken cancellationToken) { session.AssertElicitation(); - var schema = new SessionUiElicitationRequestRequestedSchema + var schema = new UIElicitationSchema { Type = elicitationParams.RequestedSchema.Type, Properties = elicitationParams.RequestedSchema.Properties, @@ -784,7 +784,7 @@ public async Task ElicitationAsync(ElicitationParams elicitat public async Task ConfirmAsync(string message, CancellationToken cancellationToken) { session.AssertElicitation(); - var schema = new SessionUiElicitationRequestRequestedSchema + var schema = new UIElicitationSchema { Type = "object", Properties = new Dictionary @@ -794,7 +794,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat Required = ["confirmed"] }; var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); - if (result.Action == SessionUiElicitationResultAction.Accept + if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("confirmed", out var val)) { @@ -812,7 +812,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat public async Task SelectAsync(string message, string[] options, CancellationToken cancellationToken) { session.AssertElicitation(); - var schema = new SessionUiElicitationRequestRequestedSchema + var schema = new UIElicitationSchema { Type = "object", Properties = new Dictionary @@ -822,7 +822,7 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat Required = ["selection"] }; var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); - if (result.Action == SessionUiElicitationResultAction.Accept + if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("selection", out var val)) { @@ -847,14 +847,14 @@ public async Task ConfirmAsync(string message, CancellationToken cancellat if (options?.Format != null) field["format"] = options.Format; if (options?.Default != null) field["default"] = options.Default; - var schema = new SessionUiElicitationRequestRequestedSchema + var schema = new UIElicitationSchema { Type = "object", Properties = new Dictionary { ["value"] = field }, Required = ["value"] }; var result = await session.Rpc.Ui.ElicitationAsync(message, schema, cancellationToken); - if (result.Action == SessionUiElicitationResultAction.Accept + if (result.Action == UIElicitationResponseAction.Accept && result.Content != null && result.Content.TryGetValue("value", out var val)) { @@ -1135,12 +1135,12 @@ public Task SetModelAsync(string model, CancellationToken cancellationToken = de /// /// /// await session.LogAsync("Build completed successfully"); - /// await session.LogAsync("Disk space low", level: SessionLogRequestLevel.Warning); - /// await session.LogAsync("Connection failed", level: SessionLogRequestLevel.Error); + /// await session.LogAsync("Disk space low", level: SessionLogLevel.Warning); + /// await session.LogAsync("Connection failed", level: SessionLogLevel.Error); /// await session.LogAsync("Temporary status", ephemeral: true); /// /// - public async Task LogAsync(string message, SessionLogRequestLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) + public async Task LogAsync(string message, SessionLogLevel? level = null, bool? ephemeral = null, string? url = null, CancellationToken cancellationToken = default) { await Rpc.LogAsync(message, level, ephemeral, url, cancellationToken); } @@ -1219,7 +1219,7 @@ internal record SendMessageRequest { public string SessionId { get; init; } = string.Empty; public string Prompt { get; init; } = string.Empty; - public IList? Attachments { get; init; } + public IList? Attachments { get; init; } public string? Mode { get; init; } public string? Traceparent { get; init; } public string? Tracestate { get; init; } @@ -1261,7 +1261,7 @@ internal record SessionDestroyRequest [JsonSerializable(typeof(SendMessageResponse))] [JsonSerializable(typeof(SessionAbortRequest))] [JsonSerializable(typeof(SessionDestroyRequest))] - [JsonSerializable(typeof(UserMessageDataAttachmentsItem))] + [JsonSerializable(typeof(UserMessageAttachment))] [JsonSerializable(typeof(PreToolUseHookInput))] [JsonSerializable(typeof(PreToolUseHookOutput))] [JsonSerializable(typeof(PostToolUseHookInput))] diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 970d44f76..f88d84eb6 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -245,7 +245,7 @@ public sealed class SessionFsConfig /// /// Path conventions used by this filesystem provider. /// - public required SessionFsSetProviderRequestConventions Conventions { get; init; } + public required SessionFsSetProviderConventions Conventions { get; init; } } /// @@ -729,7 +729,7 @@ public class ElicitationResult /// /// User action: "accept" (submitted), "decline" (rejected), or "cancel" (dismissed). /// - public SessionUiElicitationResultAction Action { get; set; } + public UIElicitationResponseAction Action { get; set; } /// /// Form values submitted by the user (present when is Accept). @@ -828,7 +828,7 @@ public class ElicitationContext public ElicitationSchema? RequestedSchema { get; set; } /// Elicitation mode: "form" for structured input, "url" for browser redirect. - public ElicitationRequestedDataMode? Mode { get; set; } + public ElicitationRequestedMode? Mode { get; set; } /// The source that initiated the request (e.g., MCP server name). public string? ElicitationSource { get; set; } @@ -2156,7 +2156,7 @@ protected MessageOptions(MessageOptions? other) /// /// File or data attachments to include with the message. /// - public IList? Attachments { get; set; } + public IList? Attachments { get; set; } /// /// Interaction mode for the message (e.g., "plan", "edit"). /// diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index dcde71f99..39c42fb25 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -203,7 +203,7 @@ public void MessageOptions_Clone_CopiesAllProperties() var original = new MessageOptions { Prompt = "Hello", - Attachments = [new UserMessageDataAttachmentsItemFile { Path = "/test.txt", DisplayName = "test.txt" }], + Attachments = [new UserMessageAttachmentFile { Path = "/test.txt", DisplayName = "test.txt" }], Mode = "chat", }; @@ -219,12 +219,12 @@ public void MessageOptions_Clone_AttachmentsAreIndependent() { var original = new MessageOptions { - Attachments = [new UserMessageDataAttachmentsItemFile { Path = "/test.txt", DisplayName = "test.txt" }], + Attachments = [new UserMessageAttachmentFile { Path = "/test.txt", DisplayName = "test.txt" }], }; var clone = original.Clone(); - clone.Attachments!.Add(new UserMessageDataAttachmentsItemFile { Path = "/other.txt", DisplayName = "other.txt" }); + clone.Attachments!.Add(new UserMessageAttachmentFile { Path = "/other.txt", DisplayName = "other.txt" }); Assert.Single(original.Attachments!); } diff --git a/dotnet/test/ElicitationTests.cs b/dotnet/test/ElicitationTests.cs index f91fe2d19..881c67f6c 100644 --- a/dotnet/test/ElicitationTests.cs +++ b/dotnet/test/ElicitationTests.cs @@ -80,7 +80,7 @@ public async Task Sends_RequestElicitation_When_Handler_Provided() OnPermissionRequest = PermissionHandler.ApproveAll, OnElicitationRequest = _ => Task.FromResult(new ElicitationResult { - Action = SessionUiElicitationResultAction.Accept, + Action = UIElicitationResponseAction.Accept, Content = new Dictionary(), }), }); @@ -99,7 +99,7 @@ public async Task Session_With_ElicitationHandler_Reports_Elicitation_Capability OnPermissionRequest = PermissionHandler.ApproveAll, OnElicitationRequest = _ => Task.FromResult(new ElicitationResult { - Action = SessionUiElicitationResultAction.Accept, + Action = UIElicitationResponseAction.Accept, Content = new Dictionary(), }), }); @@ -194,17 +194,17 @@ public void ElicitationResult_Types_Are_Properly_Structured() { var result = new ElicitationResult { - Action = SessionUiElicitationResultAction.Accept, + Action = UIElicitationResponseAction.Accept, Content = new Dictionary { ["name"] = "Alice" }, }; - Assert.Equal(SessionUiElicitationResultAction.Accept, result.Action); + Assert.Equal(UIElicitationResponseAction.Accept, result.Action); Assert.NotNull(result.Content); Assert.Equal("Alice", result.Content!["name"]); var declined = new ElicitationResult { - Action = SessionUiElicitationResultAction.Decline, + Action = UIElicitationResponseAction.Decline, }; Assert.Null(declined.Content); } @@ -244,7 +244,7 @@ public void ElicitationContext_Has_All_Properties() ["color"] = new Dictionary { ["type"] = "string", ["enum"] = new[] { "red", "blue" } }, }, }, - Mode = ElicitationRequestedDataMode.Form, + Mode = ElicitationRequestedMode.Form, ElicitationSource = "mcp-server", Url = null, }; @@ -252,7 +252,7 @@ public void ElicitationContext_Has_All_Properties() Assert.Equal("session-42", context.SessionId); Assert.Equal("Pick a color", context.Message); Assert.NotNull(context.RequestedSchema); - Assert.Equal(ElicitationRequestedDataMode.Form, context.Mode); + Assert.Equal(ElicitationRequestedMode.Form, context.Mode); Assert.Equal("mcp-server", context.ElicitationSource); Assert.Null(context.Url); } @@ -262,7 +262,7 @@ public async Task Session_Config_OnElicitationRequest_Is_Cloned() { ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult { - Action = SessionUiElicitationResultAction.Cancel, + Action = UIElicitationResponseAction.Cancel, }); var config = new SessionConfig @@ -281,7 +281,7 @@ public void Resume_Config_OnElicitationRequest_Is_Cloned() { ElicitationHandler handler = _ => Task.FromResult(new ElicitationResult { - Action = SessionUiElicitationResultAction.Cancel, + Action = UIElicitationResponseAction.Cancel, }); var config = new ResumeSessionConfig diff --git a/dotnet/test/MultiClientCommandsElicitationTests.cs b/dotnet/test/MultiClientCommandsElicitationTests.cs index 3764fd184..c5571b43e 100644 --- a/dotnet/test/MultiClientCommandsElicitationTests.cs +++ b/dotnet/test/MultiClientCommandsElicitationTests.cs @@ -175,7 +175,7 @@ public async Task Capabilities_Changed_Fires_When_Second_Client_Joins_With_Elici OnPermissionRequest = PermissionHandler.ApproveAll, OnElicitationRequest = _ => Task.FromResult(new ElicitationResult { - Action = Rpc.SessionUiElicitationResultAction.Accept, + Action = Rpc.UIElicitationResponseAction.Accept, Content = new Dictionary(), }), DisableResume = true, @@ -229,7 +229,7 @@ public async Task Capabilities_Changed_Fires_When_Elicitation_Provider_Disconnec OnPermissionRequest = PermissionHandler.ApproveAll, OnElicitationRequest = _ => Task.FromResult(new ElicitationResult { - Action = Rpc.SessionUiElicitationResultAction.Accept, + Action = Rpc.UIElicitationResponseAction.Accept, Content = new Dictionary(), }), DisableResume = true, diff --git a/dotnet/test/MultiClientTests.cs b/dotnet/test/MultiClientTests.cs index 0f12a3cec..7dbed65fe 100644 --- a/dotnet/test/MultiClientTests.cs +++ b/dotnet/test/MultiClientTests.cs @@ -194,7 +194,7 @@ public async Task One_Client_Approves_Permission_And_Both_See_The_Result() foreach (var evt in client1Events.OfType() .Concat(client2Events.OfType())) { - Assert.Equal(PermissionCompletedDataResultKind.Approved, evt.Data.Result.Kind); + Assert.Equal(PermissionCompletedKind.Approved, evt.Data.Result.Kind); } await session2.DisposeAsync(); @@ -241,7 +241,7 @@ await session1.SendAndWaitAsync(new MessageOptions foreach (var evt in client1Events.OfType() .Concat(client2Events.OfType())) { - Assert.Equal(PermissionCompletedDataResultKind.DeniedInteractivelyByUser, evt.Data.Result.Kind); + Assert.Equal(PermissionCompletedKind.DeniedInteractivelyByUser, evt.Data.Result.Kind); } await session2.DisposeAsync(); diff --git a/dotnet/test/RpcTests.cs b/dotnet/test/RpcTests.cs index e041033bd..a978a5f3f 100644 --- a/dotnet/test/RpcTests.cs +++ b/dotnet/test/RpcTests.cs @@ -88,19 +88,17 @@ public async Task Should_Get_And_Set_Session_Mode() // Get initial mode (default should be interactive) var initial = await session.Rpc.Mode.GetAsync(); - Assert.Equal(SessionModeGetResultMode.Interactive, initial.Mode); + Assert.Equal(SessionMode.Interactive, initial); // Switch to plan mode - var planResult = await session.Rpc.Mode.SetAsync(SessionModeGetResultMode.Plan); - Assert.Equal(SessionModeGetResultMode.Plan, planResult.Mode); + await session.Rpc.Mode.SetAsync(SessionMode.Plan); // Verify mode persisted var afterPlan = await session.Rpc.Mode.GetAsync(); - Assert.Equal(SessionModeGetResultMode.Plan, afterPlan.Mode); + Assert.Equal(SessionMode.Plan, afterPlan); // Switch back to interactive - var interactiveResult = await session.Rpc.Mode.SetAsync(SessionModeGetResultMode.Interactive); - Assert.Equal(SessionModeGetResultMode.Interactive, interactiveResult.Mode); + await session.Rpc.Mode.SetAsync(SessionMode.Interactive); } [Fact] diff --git a/dotnet/test/SessionEventSerializationTests.cs b/dotnet/test/SessionEventSerializationTests.cs index e7be64422..476867a4d 100644 --- a/dotnet/test/SessionEventSerializationTests.cs +++ b/dotnet/test/SessionEventSerializationTests.cs @@ -24,12 +24,12 @@ public class SessionEventSerializationTests Content = "", ToolRequests = [ - new AssistantMessageDataToolRequestsItem + new AssistantMessageToolRequest { ToolCallId = "call-1", Name = "view", Arguments = ParseJsonElement("""{"path":"README.md"}"""), - Type = AssistantMessageDataToolRequestsItemType.Function, + Type = AssistantMessageToolRequestType.Function, }, ], }, @@ -61,7 +61,7 @@ public class SessionEventSerializationTests { ToolCallId = "call-1", Success = true, - Result = new ToolExecutionCompleteDataResult + Result = new ToolExecutionCompleteResult { Content = "ok", DetailedContent = "ok", @@ -83,11 +83,11 @@ public class SessionEventSerializationTests ParentId = Guid.Parse("88888888-8888-8888-8888-888888888888"), Data = new SessionShutdownData { - ShutdownType = SessionShutdownDataShutdownType.Routine, + ShutdownType = ShutdownType.Routine, TotalPremiumRequests = 1, TotalApiDurationMs = 100, SessionStartTime = 1773609948932, - CodeChanges = new SessionShutdownDataCodeChanges + CodeChanges = new ShutdownCodeChanges { LinesAdded = 1, LinesRemoved = 0, diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs index 202abf323..8c55b1120 100644 --- a/dotnet/test/SessionFsTests.cs +++ b/dotnet/test/SessionFsTests.cs @@ -17,7 +17,7 @@ public class SessionFsTests(E2ETestFixture fixture, ITestOutputHelper output) { InitialCwd = "/", SessionStatePath = "/session-state", - Conventions = SessionFsSetProviderRequestConventions.Posix, + Conventions = SessionFsSetProviderConventions.Posix, }; [Fact] @@ -369,27 +369,27 @@ private static string NormalizeRelativePathSegment(string segment, string paramN private sealed class TestSessionFsHandler(string sessionId, string rootDir) : ISessionFsHandler { - public async Task ReadFileAsync(SessionFsReadFileParams request, CancellationToken cancellationToken = default) + public async Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default) { var content = await File.ReadAllTextAsync(ResolvePath(request.Path), cancellationToken); return new SessionFsReadFileResult { Content = content }; } - public async Task WriteFileAsync(SessionFsWriteFileParams request, CancellationToken cancellationToken = default) + public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); await File.WriteAllTextAsync(fullPath, request.Content, cancellationToken); } - public async Task AppendFileAsync(SessionFsAppendFileParams request, CancellationToken cancellationToken = default) + public async Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); await File.AppendAllTextAsync(fullPath, request.Content, cancellationToken); } - public Task ExistsAsync(SessionFsExistsParams request, CancellationToken cancellationToken = default) + public Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); return Task.FromResult(new SessionFsExistsResult @@ -398,7 +398,7 @@ public Task ExistsAsync(SessionFsExistsParams request, Ca }); } - public Task StatAsync(SessionFsStatParams request, CancellationToken cancellationToken = default) + public Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); if (File.Exists(fullPath)) @@ -409,8 +409,8 @@ public Task StatAsync(SessionFsStatParams request, Cancella IsFile = true, IsDirectory = false, Size = info.Length, - Mtime = info.LastWriteTimeUtc.ToString("O"), - Birthtime = info.CreationTimeUtc.ToString("O"), + Mtime = info.LastWriteTimeUtc, + Birthtime = info.CreationTimeUtc, }); } @@ -425,18 +425,18 @@ public Task StatAsync(SessionFsStatParams request, Cancella IsFile = false, IsDirectory = true, Size = 0, - Mtime = dirInfo.LastWriteTimeUtc.ToString("O"), - Birthtime = dirInfo.CreationTimeUtc.ToString("O"), + Mtime = dirInfo.LastWriteTimeUtc, + Birthtime = dirInfo.CreationTimeUtc, }); } - public Task MkdirAsync(SessionFsMkdirParams request, CancellationToken cancellationToken = default) + public Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) { Directory.CreateDirectory(ResolvePath(request.Path)); return Task.CompletedTask; } - public Task ReaddirAsync(SessionFsReaddirParams request, CancellationToken cancellationToken = default) + public Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) { var entries = Directory .EnumerateFileSystemEntries(ResolvePath(request.Path)) @@ -448,21 +448,21 @@ public Task ReaddirAsync(SessionFsReaddirParams request, return Task.FromResult(new SessionFsReaddirResult { Entries = entries }); } - public Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesParams request, CancellationToken cancellationToken = default) + public Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default) { var entries = Directory .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(path => new Entry + .Select(path => new SessionFsReaddirWithTypesEntry { Name = Path.GetFileName(path), - Type = Directory.Exists(path) ? EntryType.Directory : EntryType.File, + Type = Directory.Exists(path) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, }) .ToList(); return Task.FromResult(new SessionFsReaddirWithTypesResult { Entries = entries }); } - public Task RmAsync(SessionFsRmParams request, CancellationToken cancellationToken = default) + public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); @@ -486,7 +486,7 @@ public Task RmAsync(SessionFsRmParams request, CancellationToken cancellationTok throw new FileNotFoundException($"Path does not exist: {request.Path}"); } - public Task RenameAsync(SessionFsRenameParams request, CancellationToken cancellationToken = default) + public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) { var src = ResolvePath(request.Src); var dest = ResolvePath(request.Dest); diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 5200d6de5..59c11a84f 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -529,8 +529,8 @@ public async Task Should_Log_Messages_At_Various_Levels() session.On(evt => events.Add(evt)); await session.LogAsync("Info message"); - await session.LogAsync("Warning message", level: SessionLogRequestLevel.Warning); - await session.LogAsync("Error message", level: SessionLogRequestLevel.Error); + await session.LogAsync("Warning message", level: SessionLogLevel.Warning); + await session.LogAsync("Error message", level: SessionLogLevel.Error); await session.LogAsync("Ephemeral message", ephemeral: true); // Poll until all 4 notification events arrive @@ -618,7 +618,7 @@ await session.SendAndWaitAsync(new MessageOptions Prompt = "Describe this image", Attachments = [ - new UserMessageDataAttachmentsItemBlob + new UserMessageAttachmentBlob { Data = pngBase64, MimeType = "image/png", diff --git a/go/client.go b/go/client.go index ebea33209..db8438041 100644 --- a/go/client.go +++ b/go/client.go @@ -63,7 +63,7 @@ func validateSessionFsConfig(config *SessionFsConfig) error { if config.SessionStatePath == "" { return errors.New("SessionFs.SessionStatePath is required") } - if config.Conventions != rpc.ConventionsPosix && config.Conventions != rpc.ConventionsWindows { + if config.Conventions != rpc.SessionFSSetProviderConventionsPosix && config.Conventions != rpc.SessionFSSetProviderConventionsWindows { return errors.New("SessionFs.Conventions must be either 'posix' or 'windows'") } return nil @@ -330,7 +330,7 @@ func (c *Client) Start(ctx context.Context) error { // If a session filesystem provider was configured, register it. if c.options.SessionFs != nil { - _, err := c.RPC.SessionFs.SetProvider(ctx, &rpc.SessionFSSetProviderParams{ + _, err := c.RPC.SessionFs.SetProvider(ctx, &rpc.SessionFSSetProviderRequest{ InitialCwd: c.options.SessionFs.InitialCwd, SessionStatePath: c.options.SessionFs.SessionStatePath, Conventions: c.options.SessionFs.Conventions, diff --git a/go/client_test.go b/go/client_test.go index 1b88eda20..091c31726 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -241,7 +241,7 @@ func TestClient_SessionFsConfig(t *testing.T) { NewClient(&ClientOptions{ SessionFs: &SessionFsConfig{ SessionStatePath: "/session-state", - Conventions: rpc.ConventionsPosix, + Conventions: rpc.SessionFSSetProviderConventionsPosix, }, }) }) @@ -261,7 +261,7 @@ func TestClient_SessionFsConfig(t *testing.T) { NewClient(&ClientOptions{ SessionFs: &SessionFsConfig{ InitialCwd: "/", - Conventions: rpc.ConventionsPosix, + Conventions: rpc.SessionFSSetProviderConventionsPosix, }, }) }) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 1bd2e8959..01a6a0811 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -637,7 +637,7 @@ type SessionStartData struct { // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Working directory and git context at session start - Context *SessionStartDataContext `json:"context,omitempty"` + Context *StartContext `json:"context,omitempty"` // Whether the session was already in use by another client at start time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Whether this session supports remote steering via Mission Control @@ -657,7 +657,7 @@ type SessionResumeData struct { // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Updated working directory and git context at resume time - Context *SessionResumeDataContext `json:"context,omitempty"` + Context *ResumeContext `json:"context,omitempty"` // Whether the session was already in use by another client at resume time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Whether this session supports remote steering via Mission Control @@ -759,7 +759,7 @@ func (*SessionModeChangedData) sessionEventData() {} // Plan file operation details indicating what changed type SessionPlanChangedData struct { // The type of operation performed on the plan file - Operation SessionPlanChangedDataOperation `json:"operation"` + Operation PlanChangedOperation `json:"operation"` } func (*SessionPlanChangedData) sessionEventData() {} @@ -769,7 +769,7 @@ type SessionWorkspaceFileChangedData struct { // Relative path within the session workspace files directory Path string `json:"path"` // Whether the file was newly created or updated - Operation SessionWorkspaceFileChangedDataOperation `json:"operation"` + Operation WorkspaceFileChangedOperation `json:"operation"` } func (*SessionWorkspaceFileChangedData) sessionEventData() {} @@ -779,9 +779,9 @@ type SessionHandoffData struct { // ISO 8601 timestamp when the handoff occurred HandoffTime time.Time `json:"handoffTime"` // Origin type of the session being handed off - SourceType SessionHandoffDataSourceType `json:"sourceType"` + SourceType HandoffSourceType `json:"sourceType"` // Repository context for the handed-off session - Repository *SessionHandoffDataRepository `json:"repository,omitempty"` + Repository *HandoffRepository `json:"repository,omitempty"` // Additional context information for the handoff Context *string `json:"context,omitempty"` // Summary of the work done in the source session @@ -829,7 +829,7 @@ func (*SessionSnapshotRewindData) sessionEventData() {} // Session termination metrics including usage statistics, code changes, and shutdown reason type SessionShutdownData struct { // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") - ShutdownType SessionShutdownDataShutdownType `json:"shutdownType"` + ShutdownType ShutdownType `json:"shutdownType"` // Error description when shutdownType is "error" ErrorReason *string `json:"errorReason,omitempty"` // Total number of premium API requests used during the session @@ -839,9 +839,9 @@ type SessionShutdownData struct { // Unix timestamp (milliseconds) when the session started SessionStartTime float64 `json:"sessionStartTime"` // Aggregate code change metrics for the session - CodeChanges SessionShutdownDataCodeChanges `json:"codeChanges"` + CodeChanges ShutdownCodeChanges `json:"codeChanges"` // Per-model usage breakdown, keyed by model identifier - ModelMetrics map[string]SessionShutdownDataModelMetricsValue `json:"modelMetrics"` + ModelMetrics map[string]ShutdownModelMetric `json:"modelMetrics"` // Model that was selected at the time of shutdown CurrentModel *string `json:"currentModel,omitempty"` // Total tokens in context window at shutdown @@ -865,7 +865,7 @@ type SessionContextChangedData struct { // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) - HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + HostType *ContextChangedHostType `json:"hostType,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -931,7 +931,7 @@ type SessionCompactionCompleteData struct { // File path where the checkpoint was stored CheckpointPath *string `json:"checkpointPath,omitempty"` // Token usage breakdown for the compaction LLM call - CompactionTokensUsed *SessionCompactionCompleteDataCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` + CompactionTokensUsed *CompactionCompleteCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` // GitHub request tracing ID (x-github-request-id header) for the compaction LLM call RequestID *string `json:"requestId,omitempty"` // Token count from system message(s) after compaction @@ -961,11 +961,11 @@ type UserMessageData struct { // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching TransformedContent *string `json:"transformedContent,omitempty"` // Files, selections, or GitHub references attached to the message - Attachments []UserMessageDataAttachmentsItem `json:"attachments,omitempty"` + Attachments []UserMessageAttachment `json:"attachments,omitempty"` // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) Source *string `json:"source,omitempty"` // The agent mode that was active when this message was sent - AgentMode *UserMessageDataAgentMode `json:"agentMode,omitempty"` + AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` // CAPI interaction ID for correlating this user message with its turn InteractionID *string `json:"interactionId,omitempty"` } @@ -1031,7 +1031,7 @@ type AssistantMessageData struct { // The assistant's text response content Content string `json:"content"` // Tool invocations requested by the assistant in this message - ToolRequests []AssistantMessageDataToolRequestsItem `json:"toolRequests,omitempty"` + ToolRequests []AssistantMessageToolRequest `json:"toolRequests,omitempty"` // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` // Readable reasoning text from the model's extended thinking @@ -1084,6 +1084,8 @@ type AssistantUsageData struct { CacheReadTokens *float64 `json:"cacheReadTokens,omitempty"` // Number of tokens written to prompt cache CacheWriteTokens *float64 `json:"cacheWriteTokens,omitempty"` + // Number of output tokens used for reasoning (e.g., chain-of-thought) + ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` // Model multiplier cost for billing purposes Cost *float64 `json:"cost,omitempty"` // Duration of the API call in milliseconds @@ -1101,9 +1103,9 @@ type AssistantUsageData struct { // Parent tool call ID when this usage originates from a sub-agent ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Per-quota resource usage snapshots, keyed by quota identifier - QuotaSnapshots map[string]AssistantUsageDataQuotaSnapshotsValue `json:"quotaSnapshots,omitempty"` + QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitempty"` // Per-request cost and usage data from the CAPI copilot_usage response field - CopilotUsage *AssistantUsageDataCopilotUsage `json:"copilotUsage,omitempty"` + CopilotUsage *AssistantUsageCopilotUsage `json:"copilotUsage,omitempty"` // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` } @@ -1181,9 +1183,9 @@ type ToolExecutionCompleteData struct { // Whether this tool call was explicitly requested by the user rather than the assistant IsUserRequested *bool `json:"isUserRequested,omitempty"` // Tool execution result on success - Result *ToolExecutionCompleteDataResult `json:"result,omitempty"` + Result *ToolExecutionCompleteResult `json:"result,omitempty"` // Error details when the tool execution failed - Error *ToolExecutionCompleteDataError `json:"error,omitempty"` + Error *ToolExecutionCompleteError `json:"error,omitempty"` // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent @@ -1309,7 +1311,7 @@ type HookEndData struct { // Whether the hook completed successfully Success bool `json:"success"` // Error details when the hook failed - Error *HookEndDataError `json:"error,omitempty"` + Error *HookEndError `json:"error,omitempty"` } func (*HookEndData) sessionEventData() {} @@ -1319,11 +1321,11 @@ type SystemMessageData struct { // The system or developer prompt text Content string `json:"content"` // Message role: "system" for system prompts, "developer" for developer-injected instructions - Role SystemMessageDataRole `json:"role"` + Role SystemMessageRole `json:"role"` // Optional name identifier for the message source Name *string `json:"name,omitempty"` // Metadata about the prompt template and its construction - Metadata *SystemMessageDataMetadata `json:"metadata,omitempty"` + Metadata *SystemMessageMetadata `json:"metadata,omitempty"` } func (*SystemMessageData) sessionEventData() {} @@ -1333,7 +1335,7 @@ type SystemNotificationData struct { // The notification text, typically wrapped in XML tags Content string `json:"content"` // Structured metadata identifying what triggered this notification - Kind SystemNotificationDataKind `json:"kind"` + Kind SystemNotification `json:"kind"` } func (*SystemNotificationData) sessionEventData() {} @@ -1343,7 +1345,7 @@ type PermissionRequestedData struct { // Unique identifier for this permission request; used to respond via session.respondToPermission() RequestID string `json:"requestId"` // Details of the permission being requested - PermissionRequest PermissionRequestedDataPermissionRequest `json:"permissionRequest"` + PermissionRequest PermissionRequest `json:"permissionRequest"` // When true, this permission was already resolved by a permissionRequest hook and requires no client action ResolvedByHook *bool `json:"resolvedByHook,omitempty"` } @@ -1355,7 +1357,7 @@ type PermissionCompletedData struct { // Request ID of the resolved permission request; clients should dismiss any UI for this request RequestID string `json:"requestId"` // The result of the permission request - Result PermissionCompletedDataResult `json:"result"` + Result PermissionCompletedResult `json:"result"` } func (*PermissionCompletedData) sessionEventData() {} @@ -1399,9 +1401,9 @@ type ElicitationRequestedData struct { // Message describing what information is needed from the user Message string `json:"message"` // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. - Mode *ElicitationRequestedDataMode `json:"mode,omitempty"` + Mode *ElicitationRequestedMode `json:"mode,omitempty"` // JSON Schema describing the form fields to present to the user (form mode only) - RequestedSchema *ElicitationRequestedDataRequestedSchema `json:"requestedSchema,omitempty"` + RequestedSchema *ElicitationRequestedSchema `json:"requestedSchema,omitempty"` // URL to open in the user's browser (url mode only) URL *string `json:"url,omitempty"` } @@ -1413,7 +1415,7 @@ type ElicitationCompletedData struct { // Request ID of the resolved elicitation request; clients should dismiss any UI for this request RequestID string `json:"requestId"` // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) - Action *ElicitationCompletedDataAction `json:"action,omitempty"` + Action *ElicitationCompletedAction `json:"action,omitempty"` // The submitted form data when action is 'accept'; keys match the requested schema fields Content map[string]any `json:"content,omitempty"` } @@ -1449,7 +1451,7 @@ type McpOauthRequiredData struct { // URL of the MCP server that requires OAuth ServerURL string `json:"serverUrl"` // Static OAuth client configuration, if the server specifies one - StaticClientConfig *McpOauthRequiredDataStaticClientConfig `json:"staticClientConfig,omitempty"` + StaticClientConfig *McpOauthRequiredStaticClientConfig `json:"staticClientConfig,omitempty"` } func (*McpOauthRequiredData) sessionEventData() {} @@ -1525,7 +1527,7 @@ func (*CommandCompletedData) sessionEventData() {} // SDK command registration change notification type CommandsChangedData struct { // Current list of registered SDK commands - Commands []CommandsChangedDataCommandsItem `json:"commands"` + Commands []CommandsChangedCommand `json:"commands"` } func (*CommandsChangedData) sessionEventData() {} @@ -1533,7 +1535,7 @@ func (*CommandsChangedData) sessionEventData() {} // Session capability change notification type CapabilitiesChangedData struct { // UI capability changes - UI *CapabilitiesChangedDataUI `json:"ui,omitempty"` + UI *CapabilitiesChangedUI `json:"ui,omitempty"` } func (*CapabilitiesChangedData) sessionEventData() {} @@ -1586,7 +1588,7 @@ func (*SessionBackgroundTasksChangedData) sessionEventData() {} // SessionSkillsLoadedData holds the payload for session.skills_loaded events. type SessionSkillsLoadedData struct { // Array of resolved skill metadata - Skills []SessionSkillsLoadedDataSkillsItem `json:"skills"` + Skills []SkillsLoadedSkill `json:"skills"` } func (*SessionSkillsLoadedData) sessionEventData() {} @@ -1594,7 +1596,7 @@ func (*SessionSkillsLoadedData) sessionEventData() {} // SessionCustomAgentsUpdatedData holds the payload for session.custom_agents_updated events. type SessionCustomAgentsUpdatedData struct { // Array of loaded custom agent metadata - Agents []SessionCustomAgentsUpdatedDataAgentsItem `json:"agents"` + Agents []CustomAgentsUpdatedAgent `json:"agents"` // Non-fatal warnings from agent loading Warnings []string `json:"warnings"` // Fatal errors from agent loading @@ -1606,7 +1608,7 @@ func (*SessionCustomAgentsUpdatedData) sessionEventData() {} // SessionMcpServersLoadedData holds the payload for session.mcp_servers_loaded events. type SessionMcpServersLoadedData struct { // Array of MCP server status summaries - Servers []SessionMcpServersLoadedDataServersItem `json:"servers"` + Servers []McpServersLoadedServer `json:"servers"` } func (*SessionMcpServersLoadedData) sessionEventData() {} @@ -1616,7 +1618,7 @@ type SessionMcpServerStatusChangedData struct { // Name of the MCP server whose status changed ServerName string `json:"serverName"` // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status SessionMcpServersLoadedDataServersItemStatus `json:"status"` + Status McpServerStatusChangedStatus `json:"status"` } func (*SessionMcpServerStatusChangedData) sessionEventData() {} @@ -1624,13 +1626,13 @@ func (*SessionMcpServerStatusChangedData) sessionEventData() {} // SessionExtensionsLoadedData holds the payload for session.extensions_loaded events. type SessionExtensionsLoadedData struct { // Array of discovered extensions and their status - Extensions []SessionExtensionsLoadedDataExtensionsItem `json:"extensions"` + Extensions []ExtensionsLoadedExtension `json:"extensions"` } func (*SessionExtensionsLoadedData) sessionEventData() {} // Working directory and git context at session start -type SessionStartDataContext struct { +type StartContext struct { // Current working directory path Cwd string `json:"cwd"` // Root directory of the git repository, resolved via git rev-parse @@ -1638,7 +1640,7 @@ type SessionStartDataContext struct { // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) - HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + HostType *StartContextHostType `json:"hostType,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -1648,7 +1650,7 @@ type SessionStartDataContext struct { } // Updated working directory and git context at resume time -type SessionResumeDataContext struct { +type ResumeContext struct { // Current working directory path Cwd string `json:"cwd"` // Root directory of the git repository, resolved via git rev-parse @@ -1656,7 +1658,7 @@ type SessionResumeDataContext struct { // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) - HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + HostType *ResumeContextHostType `json:"hostType,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -1666,7 +1668,7 @@ type SessionResumeDataContext struct { } // Repository context for the handed-off session -type SessionHandoffDataRepository struct { +type HandoffRepository struct { // Repository owner (user or organization) Owner string `json:"owner"` // Repository name @@ -1676,7 +1678,7 @@ type SessionHandoffDataRepository struct { } // Aggregate code change metrics for the session -type SessionShutdownDataCodeChanges struct { +type ShutdownCodeChanges struct { // Total number of lines added during the session LinesAdded float64 `json:"linesAdded"` // Total number of lines removed during the session @@ -1686,7 +1688,7 @@ type SessionShutdownDataCodeChanges struct { } // Request count and cost metrics -type SessionShutdownDataModelMetricsValueRequests struct { +type ShutdownModelMetricRequests struct { // Total number of API requests made to this model Count float64 `json:"count"` // Cumulative cost multiplier for requests to this model @@ -1694,7 +1696,7 @@ type SessionShutdownDataModelMetricsValueRequests struct { } // Token usage breakdown -type SessionShutdownDataModelMetricsValueUsage struct { +type ShutdownModelMetricUsage struct { // Total input tokens consumed across all requests to this model InputTokens float64 `json:"inputTokens"` // Total output tokens produced across all requests to this model @@ -1703,17 +1705,19 @@ type SessionShutdownDataModelMetricsValueUsage struct { CacheReadTokens float64 `json:"cacheReadTokens"` // Total tokens written to prompt cache across all requests CacheWriteTokens float64 `json:"cacheWriteTokens"` + // Total reasoning tokens produced across all requests to this model + ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` } -type SessionShutdownDataModelMetricsValue struct { +type ShutdownModelMetric struct { // Request count and cost metrics - Requests SessionShutdownDataModelMetricsValueRequests `json:"requests"` + Requests ShutdownModelMetricRequests `json:"requests"` // Token usage breakdown - Usage SessionShutdownDataModelMetricsValueUsage `json:"usage"` + Usage ShutdownModelMetricUsage `json:"usage"` } // Token usage breakdown for the compaction LLM call -type SessionCompactionCompleteDataCompactionTokensUsed struct { +type CompactionCompleteCompactionTokensUsed struct { // Input tokens consumed by the compaction LLM call Input float64 `json:"input"` // Output tokens produced by the compaction LLM call @@ -1723,7 +1727,7 @@ type SessionCompactionCompleteDataCompactionTokensUsed struct { } // Optional line range to scope the attachment to a specific section of the file -type UserMessageDataAttachmentsItemLineRange struct { +type UserMessageAttachmentFileLineRange struct { // Start line number (1-based) Start float64 `json:"start"` // End line number (1-based, inclusive) @@ -1731,7 +1735,7 @@ type UserMessageDataAttachmentsItemLineRange struct { } // Start position of the selection -type UserMessageDataAttachmentsItemSelectionStart struct { +type UserMessageAttachmentSelectionDetailsStart struct { // Start line number (0-based) Line float64 `json:"line"` // Start character offset within the line (0-based) @@ -1739,7 +1743,7 @@ type UserMessageDataAttachmentsItemSelectionStart struct { } // End position of the selection -type UserMessageDataAttachmentsItemSelectionEnd struct { +type UserMessageAttachmentSelectionDetailsEnd struct { // End line number (0-based) Line float64 `json:"line"` // End character offset within the line (0-based) @@ -1747,35 +1751,35 @@ type UserMessageDataAttachmentsItemSelectionEnd struct { } // Position range of the selection within the file -type UserMessageDataAttachmentsItemSelection struct { +type UserMessageAttachmentSelectionDetails struct { // Start position of the selection - Start UserMessageDataAttachmentsItemSelectionStart `json:"start"` + Start UserMessageAttachmentSelectionDetailsStart `json:"start"` // End position of the selection - End UserMessageDataAttachmentsItemSelectionEnd `json:"end"` + End UserMessageAttachmentSelectionDetailsEnd `json:"end"` } // A user message attachment — a file, directory, code selection, blob, or GitHub reference -type UserMessageDataAttachmentsItem struct { +type UserMessageAttachment struct { // Type discriminator - Type UserMessageDataAttachmentsItemType `json:"type"` + Type UserMessageAttachmentType `json:"type"` // Absolute file path Path *string `json:"path,omitempty"` // User-facing display name for the attachment DisplayName *string `json:"displayName,omitempty"` // Optional line range to scope the attachment to a specific section of the file - LineRange *UserMessageDataAttachmentsItemLineRange `json:"lineRange,omitempty"` + LineRange *UserMessageAttachmentFileLineRange `json:"lineRange,omitempty"` // Absolute path to the file containing the selection FilePath *string `json:"filePath,omitempty"` // The selected text content Text *string `json:"text,omitempty"` // Position range of the selection within the file - Selection *UserMessageDataAttachmentsItemSelection `json:"selection,omitempty"` + Selection *UserMessageAttachmentSelectionDetails `json:"selection,omitempty"` // Issue, pull request, or discussion number Number *float64 `json:"number,omitempty"` // Title of the referenced item Title *string `json:"title,omitempty"` // Type of GitHub reference - ReferenceType *UserMessageDataAttachmentsItemReferenceType `json:"referenceType,omitempty"` + ReferenceType *UserMessageAttachmentGithubReferenceType `json:"referenceType,omitempty"` // Current state of the referenced item (e.g., open, closed, merged) State *string `json:"state,omitempty"` // URL to the referenced item on GitHub @@ -1787,7 +1791,7 @@ type UserMessageDataAttachmentsItem struct { } // A tool invocation request from the assistant -type AssistantMessageDataToolRequestsItem struct { +type AssistantMessageToolRequest struct { // Unique identifier for this tool call ToolCallID string `json:"toolCallId"` // Name of the tool being invoked @@ -1795,7 +1799,7 @@ type AssistantMessageDataToolRequestsItem struct { // Arguments to pass to the tool, format depends on the tool Arguments any `json:"arguments,omitempty"` // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - Type *AssistantMessageDataToolRequestsItemType `json:"type,omitempty"` + Type *AssistantMessageToolRequestType `json:"type,omitempty"` // Human-readable display title for the tool ToolTitle *string `json:"toolTitle,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool @@ -1804,7 +1808,7 @@ type AssistantMessageDataToolRequestsItem struct { IntentionSummary *string `json:"intentionSummary,omitempty"` } -type AssistantUsageDataQuotaSnapshotsValue struct { +type AssistantUsageQuotaSnapshot struct { // Whether the user has an unlimited usage entitlement IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` // Total requests allowed by the entitlement @@ -1824,7 +1828,7 @@ type AssistantUsageDataQuotaSnapshotsValue struct { } // Token usage detail for a single billing category -type AssistantUsageDataCopilotUsageTokenDetailsItem struct { +type AssistantUsageCopilotUsageTokenDetail struct { // Number of tokens in this billing batch BatchSize float64 `json:"batchSize"` // Cost per batch of tokens @@ -1836,15 +1840,15 @@ type AssistantUsageDataCopilotUsageTokenDetailsItem struct { } // Per-request cost and usage data from the CAPI copilot_usage response field -type AssistantUsageDataCopilotUsage struct { +type AssistantUsageCopilotUsage struct { // Itemized token usage breakdown - TokenDetails []AssistantUsageDataCopilotUsageTokenDetailsItem `json:"tokenDetails"` + TokenDetails []AssistantUsageCopilotUsageTokenDetail `json:"tokenDetails"` // Total cost in nano-AIU (AI Units) for this request TotalNanoAiu float64 `json:"totalNanoAiu"` } // Icon image for a resource -type ToolExecutionCompleteDataResultContentsItemIconsItem struct { +type ToolExecutionCompleteContentResourceLinkIcon struct { // URL or path to the icon image Src string `json:"src"` // MIME type of the icon image @@ -1852,13 +1856,13 @@ type ToolExecutionCompleteDataResultContentsItemIconsItem struct { // Available icon sizes (e.g., ['16x16', '32x32']) Sizes []string `json:"sizes,omitempty"` // Theme variant this icon is intended for - Theme *ToolExecutionCompleteDataResultContentsItemIconsItemTheme `json:"theme,omitempty"` + Theme *ToolExecutionCompleteContentResourceLinkIconTheme `json:"theme,omitempty"` } // A content block within a tool result, which may be text, terminal output, image, audio, or a resource -type ToolExecutionCompleteDataResultContentsItem struct { +type ToolExecutionCompleteContent struct { // Type discriminator - Type ToolExecutionCompleteDataResultContentsItemType `json:"type"` + Type ToolExecutionCompleteContentType `json:"type"` // The text content Text *string `json:"text,omitempty"` // Process exit code, if the command has completed @@ -1870,7 +1874,7 @@ type ToolExecutionCompleteDataResultContentsItem struct { // MIME type of the image (e.g., image/png, image/jpeg) MIMEType *string `json:"mimeType,omitempty"` // Icons associated with this resource - Icons []ToolExecutionCompleteDataResultContentsItemIconsItem `json:"icons,omitempty"` + Icons []ToolExecutionCompleteContentResourceLinkIcon `json:"icons,omitempty"` // Resource name identifier Name *string `json:"name,omitempty"` // Human-readable display title for the resource @@ -1886,17 +1890,17 @@ type ToolExecutionCompleteDataResultContentsItem struct { } // Tool execution result on success -type ToolExecutionCompleteDataResult struct { +type ToolExecutionCompleteResult struct { // Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency Content string `json:"content"` // Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. DetailedContent *string `json:"detailedContent,omitempty"` // Structured content blocks (text, images, audio, resources) returned by the tool in their native format - Contents []ToolExecutionCompleteDataResultContentsItem `json:"contents,omitempty"` + Contents []ToolExecutionCompleteContent `json:"contents,omitempty"` } // Error details when the tool execution failed -type ToolExecutionCompleteDataError struct { +type ToolExecutionCompleteError struct { // Human-readable error message Message string `json:"message"` // Machine-readable error code @@ -1904,7 +1908,7 @@ type ToolExecutionCompleteDataError struct { } // Error details when the hook failed -type HookEndDataError struct { +type HookEndError struct { // Human-readable error message Message string `json:"message"` // Error stack trace, when available @@ -1912,7 +1916,7 @@ type HookEndDataError struct { } // Metadata about the prompt template and its construction -type SystemMessageDataMetadata struct { +type SystemMessageMetadata struct { // Version identifier of the prompt template used PromptVersion *string `json:"promptVersion,omitempty"` // Template variables used when constructing the prompt @@ -1920,15 +1924,15 @@ type SystemMessageDataMetadata struct { } // Structured metadata identifying what triggered this notification -type SystemNotificationDataKind struct { +type SystemNotification struct { // Type discriminator - Type SystemNotificationDataKindType `json:"type"` + Type SystemNotificationType `json:"type"` // Unique identifier of the background agent AgentID *string `json:"agentId,omitempty"` // Type of the agent (e.g., explore, task, general-purpose) AgentType *string `json:"agentType,omitempty"` // Whether the agent completed successfully or failed - Status *SystemNotificationDataKindStatus `json:"status,omitempty"` + Status *SystemNotificationAgentCompletedStatus `json:"status,omitempty"` // Human-readable description of the agent task Description *string `json:"description,omitempty"` // The full prompt given to the background agent @@ -1939,22 +1943,22 @@ type SystemNotificationDataKind struct { ExitCode *float64 `json:"exitCode,omitempty"` } -type PermissionRequestedDataPermissionRequestCommandsItem struct { +type PermissionRequestShellCommand struct { // Command identifier (e.g., executable name) Identifier string `json:"identifier"` // Whether this command is read-only (no side effects) ReadOnly bool `json:"readOnly"` } -type PermissionRequestedDataPermissionRequestPossibleUrlsItem struct { +type PermissionRequestShellPossibleUrl struct { // URL that may be accessed by the command URL string `json:"url"` } // Details of the permission being requested -type PermissionRequestedDataPermissionRequest struct { +type PermissionRequest struct { // Kind discriminator - Kind PermissionRequestedDataPermissionRequestKind `json:"kind"` + Kind PermissionRequestKind `json:"kind"` // Tool call ID that triggered this permission request ToolCallID *string `json:"toolCallId,omitempty"` // The complete shell command text to be executed @@ -1962,11 +1966,11 @@ type PermissionRequestedDataPermissionRequest struct { // Human-readable description of what the command intends to do Intention *string `json:"intention,omitempty"` // Parsed command identifiers found in the command text - Commands []PermissionRequestedDataPermissionRequestCommandsItem `json:"commands,omitempty"` + Commands []PermissionRequestShellCommand `json:"commands,omitempty"` // File paths that may be read or written by the command PossiblePaths []string `json:"possiblePaths,omitempty"` // URLs that may be accessed by the command - PossibleUrls []PermissionRequestedDataPermissionRequestPossibleUrlsItem `json:"possibleUrls,omitempty"` + PossibleUrls []PermissionRequestShellPossibleUrl `json:"possibleUrls,omitempty"` // Whether the command includes a file write redirection (e.g., > or >>) HasWriteFileRedirection *bool `json:"hasWriteFileRedirection,omitempty"` // Whether the UI can offer session-wide approval for this command pattern @@ -1994,7 +1998,7 @@ type PermissionRequestedDataPermissionRequest struct { // URL to be fetched URL *string `json:"url,omitempty"` // Whether this is a store or vote memory operation - Action *PermissionRequestedDataPermissionRequestAction `json:"action,omitempty"` + Action *PermissionRequestMemoryAction `json:"action,omitempty"` // Topic or subject of the memory (store only) Subject *string `json:"subject,omitempty"` // The fact being stored or voted on @@ -2002,7 +2006,7 @@ type PermissionRequestedDataPermissionRequest struct { // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` // Vote direction (vote only) - Direction *PermissionRequestedDataPermissionRequestDirection `json:"direction,omitempty"` + Direction *PermissionRequestMemoryDirection `json:"direction,omitempty"` // Reason for the vote (vote only) Reason *string `json:"reason,omitempty"` // Description of what the custom tool does @@ -2014,13 +2018,13 @@ type PermissionRequestedDataPermissionRequest struct { } // The result of the permission request -type PermissionCompletedDataResult struct { +type PermissionCompletedResult struct { // The outcome of the permission request - Kind PermissionCompletedDataResultKind `json:"kind"` + Kind PermissionCompletedKind `json:"kind"` } // JSON Schema describing the form fields to present to the user (form mode only) -type ElicitationRequestedDataRequestedSchema struct { +type ElicitationRequestedSchema struct { // Schema type indicator (always 'object') Type string `json:"type"` // Form field definitions, keyed by field name @@ -2030,25 +2034,25 @@ type ElicitationRequestedDataRequestedSchema struct { } // Static OAuth client configuration, if the server specifies one -type McpOauthRequiredDataStaticClientConfig struct { +type McpOauthRequiredStaticClientConfig struct { // OAuth client ID for the server ClientID string `json:"clientId"` // Whether this is a public OAuth client PublicClient *bool `json:"publicClient,omitempty"` } -type CommandsChangedDataCommandsItem struct { +type CommandsChangedCommand struct { Name string `json:"name"` Description *string `json:"description,omitempty"` } // UI capability changes -type CapabilitiesChangedDataUI struct { +type CapabilitiesChangedUI struct { // Whether elicitation is now supported Elicitation *bool `json:"elicitation,omitempty"` } -type SessionSkillsLoadedDataSkillsItem struct { +type SkillsLoadedSkill struct { // Unique identifier for the skill Name string `json:"name"` // Description of what the skill does @@ -2063,7 +2067,7 @@ type SessionSkillsLoadedDataSkillsItem struct { Path *string `json:"path,omitempty"` } -type SessionCustomAgentsUpdatedDataAgentsItem struct { +type CustomAgentsUpdatedAgent struct { // Unique identifier for the agent ID string `json:"id"` // Internal name of the agent @@ -2082,265 +2086,283 @@ type SessionCustomAgentsUpdatedDataAgentsItem struct { Model *string `json:"model,omitempty"` } -type SessionMcpServersLoadedDataServersItem struct { +type McpServersLoadedServer struct { // Server name (config key) Name string `json:"name"` // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status SessionMcpServersLoadedDataServersItemStatus `json:"status"` + Status McpServersLoadedServerStatus `json:"status"` // Configuration source: user, workspace, plugin, or builtin Source *string `json:"source,omitempty"` // Error message if the server failed to connect Error *string `json:"error,omitempty"` } -type SessionExtensionsLoadedDataExtensionsItem struct { +type ExtensionsLoadedExtension struct { // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') ID string `json:"id"` // Extension name (directory name) Name string `json:"name"` // Discovery source - Source SessionExtensionsLoadedDataExtensionsItemSource `json:"source"` + Source ExtensionsLoadedExtensionSource `json:"source"` // Current status: running, disabled, failed, or starting - Status SessionExtensionsLoadedDataExtensionsItemStatus `json:"status"` + Status ExtensionsLoadedExtensionStatus `json:"status"` } // Hosting platform type of the repository (github or ado) -type SessionStartDataContextHostType string +type StartContextHostType string const ( - SessionStartDataContextHostTypeGithub SessionStartDataContextHostType = "github" - SessionStartDataContextHostTypeAdo SessionStartDataContextHostType = "ado" + StartContextHostTypeGithub StartContextHostType = "github" + StartContextHostTypeAdo StartContextHostType = "ado" +) + +// Hosting platform type of the repository (github or ado) +type ResumeContextHostType string + +const ( + ResumeContextHostTypeGithub ResumeContextHostType = "github" + ResumeContextHostTypeAdo ResumeContextHostType = "ado" ) // The type of operation performed on the plan file -type SessionPlanChangedDataOperation string +type PlanChangedOperation string const ( - SessionPlanChangedDataOperationCreate SessionPlanChangedDataOperation = "create" - SessionPlanChangedDataOperationUpdate SessionPlanChangedDataOperation = "update" - SessionPlanChangedDataOperationDelete SessionPlanChangedDataOperation = "delete" + PlanChangedOperationCreate PlanChangedOperation = "create" + PlanChangedOperationUpdate PlanChangedOperation = "update" + PlanChangedOperationDelete PlanChangedOperation = "delete" ) // Whether the file was newly created or updated -type SessionWorkspaceFileChangedDataOperation string +type WorkspaceFileChangedOperation string const ( - SessionWorkspaceFileChangedDataOperationCreate SessionWorkspaceFileChangedDataOperation = "create" - SessionWorkspaceFileChangedDataOperationUpdate SessionWorkspaceFileChangedDataOperation = "update" + WorkspaceFileChangedOperationCreate WorkspaceFileChangedOperation = "create" + WorkspaceFileChangedOperationUpdate WorkspaceFileChangedOperation = "update" ) // Origin type of the session being handed off -type SessionHandoffDataSourceType string +type HandoffSourceType string const ( - SessionHandoffDataSourceTypeRemote SessionHandoffDataSourceType = "remote" - SessionHandoffDataSourceTypeLocal SessionHandoffDataSourceType = "local" + HandoffSourceTypeRemote HandoffSourceType = "remote" + HandoffSourceTypeLocal HandoffSourceType = "local" ) // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") -type SessionShutdownDataShutdownType string +type ShutdownType string + +const ( + ShutdownTypeRoutine ShutdownType = "routine" + ShutdownTypeError ShutdownType = "error" +) + +// Hosting platform type of the repository (github or ado) +type ContextChangedHostType string const ( - SessionShutdownDataShutdownTypeRoutine SessionShutdownDataShutdownType = "routine" - SessionShutdownDataShutdownTypeError SessionShutdownDataShutdownType = "error" + ContextChangedHostTypeGithub ContextChangedHostType = "github" + ContextChangedHostTypeAdo ContextChangedHostType = "ado" ) -// Type discriminator for UserMessageDataAttachmentsItem. -type UserMessageDataAttachmentsItemType string +// Type discriminator for UserMessageAttachment. +type UserMessageAttachmentType string const ( - UserMessageDataAttachmentsItemTypeFile UserMessageDataAttachmentsItemType = "file" - UserMessageDataAttachmentsItemTypeDirectory UserMessageDataAttachmentsItemType = "directory" - UserMessageDataAttachmentsItemTypeSelection UserMessageDataAttachmentsItemType = "selection" - UserMessageDataAttachmentsItemTypeGithubReference UserMessageDataAttachmentsItemType = "github_reference" - UserMessageDataAttachmentsItemTypeBlob UserMessageDataAttachmentsItemType = "blob" + UserMessageAttachmentTypeFile UserMessageAttachmentType = "file" + UserMessageAttachmentTypeDirectory UserMessageAttachmentType = "directory" + UserMessageAttachmentTypeSelection UserMessageAttachmentType = "selection" + UserMessageAttachmentTypeGithubReference UserMessageAttachmentType = "github_reference" + UserMessageAttachmentTypeBlob UserMessageAttachmentType = "blob" ) // Type of GitHub reference -type UserMessageDataAttachmentsItemReferenceType string +type UserMessageAttachmentGithubReferenceType string const ( - UserMessageDataAttachmentsItemReferenceTypeIssue UserMessageDataAttachmentsItemReferenceType = "issue" - UserMessageDataAttachmentsItemReferenceTypePr UserMessageDataAttachmentsItemReferenceType = "pr" - UserMessageDataAttachmentsItemReferenceTypeDiscussion UserMessageDataAttachmentsItemReferenceType = "discussion" + UserMessageAttachmentGithubReferenceTypeIssue UserMessageAttachmentGithubReferenceType = "issue" + UserMessageAttachmentGithubReferenceTypePr UserMessageAttachmentGithubReferenceType = "pr" + UserMessageAttachmentGithubReferenceTypeDiscussion UserMessageAttachmentGithubReferenceType = "discussion" ) // The agent mode that was active when this message was sent -type UserMessageDataAgentMode string +type UserMessageAgentMode string const ( - UserMessageDataAgentModeInteractive UserMessageDataAgentMode = "interactive" - UserMessageDataAgentModePlan UserMessageDataAgentMode = "plan" - UserMessageDataAgentModeAutopilot UserMessageDataAgentMode = "autopilot" - UserMessageDataAgentModeShell UserMessageDataAgentMode = "shell" + UserMessageAgentModeInteractive UserMessageAgentMode = "interactive" + UserMessageAgentModePlan UserMessageAgentMode = "plan" + UserMessageAgentModeAutopilot UserMessageAgentMode = "autopilot" + UserMessageAgentModeShell UserMessageAgentMode = "shell" ) // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. -type AssistantMessageDataToolRequestsItemType string +type AssistantMessageToolRequestType string const ( - AssistantMessageDataToolRequestsItemTypeFunction AssistantMessageDataToolRequestsItemType = "function" - AssistantMessageDataToolRequestsItemTypeCustom AssistantMessageDataToolRequestsItemType = "custom" + AssistantMessageToolRequestTypeFunction AssistantMessageToolRequestType = "function" + AssistantMessageToolRequestTypeCustom AssistantMessageToolRequestType = "custom" ) -// Type discriminator for ToolExecutionCompleteDataResultContentsItem. -type ToolExecutionCompleteDataResultContentsItemType string +// Type discriminator for ToolExecutionCompleteContent. +type ToolExecutionCompleteContentType string const ( - ToolExecutionCompleteDataResultContentsItemTypeText ToolExecutionCompleteDataResultContentsItemType = "text" - ToolExecutionCompleteDataResultContentsItemTypeTerminal ToolExecutionCompleteDataResultContentsItemType = "terminal" - ToolExecutionCompleteDataResultContentsItemTypeImage ToolExecutionCompleteDataResultContentsItemType = "image" - ToolExecutionCompleteDataResultContentsItemTypeAudio ToolExecutionCompleteDataResultContentsItemType = "audio" - ToolExecutionCompleteDataResultContentsItemTypeResourceLink ToolExecutionCompleteDataResultContentsItemType = "resource_link" - ToolExecutionCompleteDataResultContentsItemTypeResource ToolExecutionCompleteDataResultContentsItemType = "resource" + ToolExecutionCompleteContentTypeText ToolExecutionCompleteContentType = "text" + ToolExecutionCompleteContentTypeTerminal ToolExecutionCompleteContentType = "terminal" + ToolExecutionCompleteContentTypeImage ToolExecutionCompleteContentType = "image" + ToolExecutionCompleteContentTypeAudio ToolExecutionCompleteContentType = "audio" + ToolExecutionCompleteContentTypeResourceLink ToolExecutionCompleteContentType = "resource_link" + ToolExecutionCompleteContentTypeResource ToolExecutionCompleteContentType = "resource" ) // Theme variant this icon is intended for -type ToolExecutionCompleteDataResultContentsItemIconsItemTheme string +type ToolExecutionCompleteContentResourceLinkIconTheme string const ( - ToolExecutionCompleteDataResultContentsItemIconsItemThemeLight ToolExecutionCompleteDataResultContentsItemIconsItemTheme = "light" - ToolExecutionCompleteDataResultContentsItemIconsItemThemeDark ToolExecutionCompleteDataResultContentsItemIconsItemTheme = "dark" + ToolExecutionCompleteContentResourceLinkIconThemeLight ToolExecutionCompleteContentResourceLinkIconTheme = "light" + ToolExecutionCompleteContentResourceLinkIconThemeDark ToolExecutionCompleteContentResourceLinkIconTheme = "dark" ) // Message role: "system" for system prompts, "developer" for developer-injected instructions -type SystemMessageDataRole string +type SystemMessageRole string const ( - SystemMessageDataRoleSystem SystemMessageDataRole = "system" - SystemMessageDataRoleDeveloper SystemMessageDataRole = "developer" + SystemMessageRoleSystem SystemMessageRole = "system" + SystemMessageRoleDeveloper SystemMessageRole = "developer" ) -// Type discriminator for SystemNotificationDataKind. -type SystemNotificationDataKindType string +// Type discriminator for SystemNotification. +type SystemNotificationType string const ( - SystemNotificationDataKindTypeAgentCompleted SystemNotificationDataKindType = "agent_completed" - SystemNotificationDataKindTypeAgentIdle SystemNotificationDataKindType = "agent_idle" - SystemNotificationDataKindTypeShellCompleted SystemNotificationDataKindType = "shell_completed" - SystemNotificationDataKindTypeShellDetachedCompleted SystemNotificationDataKindType = "shell_detached_completed" + SystemNotificationTypeAgentCompleted SystemNotificationType = "agent_completed" + SystemNotificationTypeAgentIdle SystemNotificationType = "agent_idle" + SystemNotificationTypeShellCompleted SystemNotificationType = "shell_completed" + SystemNotificationTypeShellDetachedCompleted SystemNotificationType = "shell_detached_completed" ) // Whether the agent completed successfully or failed -type SystemNotificationDataKindStatus string +type SystemNotificationAgentCompletedStatus string const ( - SystemNotificationDataKindStatusCompleted SystemNotificationDataKindStatus = "completed" - SystemNotificationDataKindStatusFailed SystemNotificationDataKindStatus = "failed" + SystemNotificationAgentCompletedStatusCompleted SystemNotificationAgentCompletedStatus = "completed" + SystemNotificationAgentCompletedStatusFailed SystemNotificationAgentCompletedStatus = "failed" ) -// Kind discriminator for PermissionRequestedDataPermissionRequest. -type PermissionRequestedDataPermissionRequestKind string +// Kind discriminator for PermissionRequest. +type PermissionRequestKind string const ( - PermissionRequestedDataPermissionRequestKindShell PermissionRequestedDataPermissionRequestKind = "shell" - PermissionRequestedDataPermissionRequestKindWrite PermissionRequestedDataPermissionRequestKind = "write" - PermissionRequestedDataPermissionRequestKindRead PermissionRequestedDataPermissionRequestKind = "read" - PermissionRequestedDataPermissionRequestKindMcp PermissionRequestedDataPermissionRequestKind = "mcp" - PermissionRequestedDataPermissionRequestKindURL PermissionRequestedDataPermissionRequestKind = "url" - PermissionRequestedDataPermissionRequestKindMemory PermissionRequestedDataPermissionRequestKind = "memory" - PermissionRequestedDataPermissionRequestKindCustomTool PermissionRequestedDataPermissionRequestKind = "custom-tool" - PermissionRequestedDataPermissionRequestKindHook PermissionRequestedDataPermissionRequestKind = "hook" + PermissionRequestKindShell PermissionRequestKind = "shell" + PermissionRequestKindWrite PermissionRequestKind = "write" + PermissionRequestKindRead PermissionRequestKind = "read" + PermissionRequestKindMcp PermissionRequestKind = "mcp" + PermissionRequestKindURL PermissionRequestKind = "url" + PermissionRequestKindMemory PermissionRequestKind = "memory" + PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" + PermissionRequestKindHook PermissionRequestKind = "hook" ) // Whether this is a store or vote memory operation -type PermissionRequestedDataPermissionRequestAction string +type PermissionRequestMemoryAction string const ( - PermissionRequestedDataPermissionRequestActionStore PermissionRequestedDataPermissionRequestAction = "store" - PermissionRequestedDataPermissionRequestActionVote PermissionRequestedDataPermissionRequestAction = "vote" + PermissionRequestMemoryActionStore PermissionRequestMemoryAction = "store" + PermissionRequestMemoryActionVote PermissionRequestMemoryAction = "vote" ) // Vote direction (vote only) -type PermissionRequestedDataPermissionRequestDirection string +type PermissionRequestMemoryDirection string const ( - PermissionRequestedDataPermissionRequestDirectionUpvote PermissionRequestedDataPermissionRequestDirection = "upvote" - PermissionRequestedDataPermissionRequestDirectionDownvote PermissionRequestedDataPermissionRequestDirection = "downvote" + PermissionRequestMemoryDirectionUpvote PermissionRequestMemoryDirection = "upvote" + PermissionRequestMemoryDirectionDownvote PermissionRequestMemoryDirection = "downvote" ) // The outcome of the permission request -type PermissionCompletedDataResultKind string +type PermissionCompletedKind string const ( - PermissionCompletedDataResultKindApproved PermissionCompletedDataResultKind = "approved" - PermissionCompletedDataResultKindDeniedByRules PermissionCompletedDataResultKind = "denied-by-rules" - PermissionCompletedDataResultKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedDataResultKind = "denied-no-approval-rule-and-could-not-request-from-user" - PermissionCompletedDataResultKindDeniedInteractivelyByUser PermissionCompletedDataResultKind = "denied-interactively-by-user" - PermissionCompletedDataResultKindDeniedByContentExclusionPolicy PermissionCompletedDataResultKind = "denied-by-content-exclusion-policy" - PermissionCompletedDataResultKindDeniedByPermissionRequestHook PermissionCompletedDataResultKind = "denied-by-permission-request-hook" + PermissionCompletedKindApproved PermissionCompletedKind = "approved" + PermissionCompletedKindDeniedByRules PermissionCompletedKind = "denied-by-rules" + PermissionCompletedKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionCompletedKindDeniedInteractivelyByUser PermissionCompletedKind = "denied-interactively-by-user" + PermissionCompletedKindDeniedByContentExclusionPolicy PermissionCompletedKind = "denied-by-content-exclusion-policy" + PermissionCompletedKindDeniedByPermissionRequestHook PermissionCompletedKind = "denied-by-permission-request-hook" ) // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. -type ElicitationRequestedDataMode string +type ElicitationRequestedMode string const ( - ElicitationRequestedDataModeForm ElicitationRequestedDataMode = "form" - ElicitationRequestedDataModeURL ElicitationRequestedDataMode = "url" + ElicitationRequestedModeForm ElicitationRequestedMode = "form" + ElicitationRequestedModeURL ElicitationRequestedMode = "url" ) // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) -type ElicitationCompletedDataAction string +type ElicitationCompletedAction string const ( - ElicitationCompletedDataActionAccept ElicitationCompletedDataAction = "accept" - ElicitationCompletedDataActionDecline ElicitationCompletedDataAction = "decline" - ElicitationCompletedDataActionCancel ElicitationCompletedDataAction = "cancel" + ElicitationCompletedActionAccept ElicitationCompletedAction = "accept" + ElicitationCompletedActionDecline ElicitationCompletedAction = "decline" + ElicitationCompletedActionCancel ElicitationCompletedAction = "cancel" ) // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type SessionMcpServersLoadedDataServersItemStatus string +type McpServersLoadedServerStatus string + +const ( + McpServersLoadedServerStatusConnected McpServersLoadedServerStatus = "connected" + McpServersLoadedServerStatusFailed McpServersLoadedServerStatus = "failed" + McpServersLoadedServerStatusNeedsAuth McpServersLoadedServerStatus = "needs-auth" + McpServersLoadedServerStatusPending McpServersLoadedServerStatus = "pending" + McpServersLoadedServerStatusDisabled McpServersLoadedServerStatus = "disabled" + McpServersLoadedServerStatusNotConfigured McpServersLoadedServerStatus = "not_configured" +) + +// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured +type McpServerStatusChangedStatus string const ( - SessionMcpServersLoadedDataServersItemStatusConnected SessionMcpServersLoadedDataServersItemStatus = "connected" - SessionMcpServersLoadedDataServersItemStatusFailed SessionMcpServersLoadedDataServersItemStatus = "failed" - SessionMcpServersLoadedDataServersItemStatusNeedsAuth SessionMcpServersLoadedDataServersItemStatus = "needs-auth" - SessionMcpServersLoadedDataServersItemStatusPending SessionMcpServersLoadedDataServersItemStatus = "pending" - SessionMcpServersLoadedDataServersItemStatusDisabled SessionMcpServersLoadedDataServersItemStatus = "disabled" - SessionMcpServersLoadedDataServersItemStatusNotConfigured SessionMcpServersLoadedDataServersItemStatus = "not_configured" + McpServerStatusChangedStatusConnected McpServerStatusChangedStatus = "connected" + McpServerStatusChangedStatusFailed McpServerStatusChangedStatus = "failed" + McpServerStatusChangedStatusNeedsAuth McpServerStatusChangedStatus = "needs-auth" + McpServerStatusChangedStatusPending McpServerStatusChangedStatus = "pending" + McpServerStatusChangedStatusDisabled McpServerStatusChangedStatus = "disabled" + McpServerStatusChangedStatusNotConfigured McpServerStatusChangedStatus = "not_configured" ) // Discovery source -type SessionExtensionsLoadedDataExtensionsItemSource string +type ExtensionsLoadedExtensionSource string const ( - SessionExtensionsLoadedDataExtensionsItemSourceProject SessionExtensionsLoadedDataExtensionsItemSource = "project" - SessionExtensionsLoadedDataExtensionsItemSourceUser SessionExtensionsLoadedDataExtensionsItemSource = "user" + ExtensionsLoadedExtensionSourceProject ExtensionsLoadedExtensionSource = "project" + ExtensionsLoadedExtensionSourceUser ExtensionsLoadedExtensionSource = "user" ) // Current status: running, disabled, failed, or starting -type SessionExtensionsLoadedDataExtensionsItemStatus string +type ExtensionsLoadedExtensionStatus string const ( - SessionExtensionsLoadedDataExtensionsItemStatusRunning SessionExtensionsLoadedDataExtensionsItemStatus = "running" - SessionExtensionsLoadedDataExtensionsItemStatusDisabled SessionExtensionsLoadedDataExtensionsItemStatus = "disabled" - SessionExtensionsLoadedDataExtensionsItemStatusFailed SessionExtensionsLoadedDataExtensionsItemStatus = "failed" - SessionExtensionsLoadedDataExtensionsItemStatusStarting SessionExtensionsLoadedDataExtensionsItemStatus = "starting" + ExtensionsLoadedExtensionStatusRunning ExtensionsLoadedExtensionStatus = "running" + ExtensionsLoadedExtensionStatusDisabled ExtensionsLoadedExtensionStatus = "disabled" + ExtensionsLoadedExtensionStatusFailed ExtensionsLoadedExtensionStatus = "failed" + ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting" ) // Type aliases for convenience. type ( - PermissionRequest = PermissionRequestedDataPermissionRequest - PermissionRequestKind = PermissionRequestedDataPermissionRequestKind - PermissionRequestCommand = PermissionRequestedDataPermissionRequestCommandsItem - PossibleURL = PermissionRequestedDataPermissionRequestPossibleUrlsItem - Attachment = UserMessageDataAttachmentsItem - AttachmentType = UserMessageDataAttachmentsItemType + PermissionRequestCommand = PermissionRequestShellCommand + PossibleURL = PermissionRequestShellPossibleUrl + Attachment = UserMessageAttachment + AttachmentType = UserMessageAttachmentType ) // Constant aliases for convenience. const ( - AttachmentTypeFile = UserMessageDataAttachmentsItemTypeFile - AttachmentTypeDirectory = UserMessageDataAttachmentsItemTypeDirectory - AttachmentTypeSelection = UserMessageDataAttachmentsItemTypeSelection - AttachmentTypeGithubReference = UserMessageDataAttachmentsItemTypeGithubReference - AttachmentTypeBlob = UserMessageDataAttachmentsItemTypeBlob - PermissionRequestKindShell = PermissionRequestedDataPermissionRequestKindShell - PermissionRequestKindWrite = PermissionRequestedDataPermissionRequestKindWrite - PermissionRequestKindRead = PermissionRequestedDataPermissionRequestKindRead - PermissionRequestKindMcp = PermissionRequestedDataPermissionRequestKindMcp - PermissionRequestKindURL = PermissionRequestedDataPermissionRequestKindURL - PermissionRequestKindMemory = PermissionRequestedDataPermissionRequestKindMemory - PermissionRequestKindCustomTool = PermissionRequestedDataPermissionRequestKindCustomTool - PermissionRequestKindHook = PermissionRequestedDataPermissionRequestKindHook + AttachmentTypeFile = UserMessageAttachmentTypeFile + AttachmentTypeDirectory = UserMessageAttachmentTypeDirectory + AttachmentTypeSelection = UserMessageAttachmentTypeSelection + AttachmentTypeGithubReference = UserMessageAttachmentTypeGithubReference + AttachmentTypeBlob = UserMessageAttachmentTypeBlob ) diff --git a/go/internal/e2e/agent_and_compact_rpc_test.go b/go/internal/e2e/agent_and_compact_rpc_test.go index dca773b5b..d7dd4a3fa 100644 --- a/go/internal/e2e/agent_and_compact_rpc_test.go +++ b/go/internal/e2e/agent_and_compact_rpc_test.go @@ -136,7 +136,7 @@ func TestAgentSelectionRpc(t *testing.T) { } // Select the agent - selectResult, err := session.RPC.Agent.Select(t.Context(), &rpc.SessionAgentSelectParams{Name: "test-agent"}) + selectResult, err := session.RPC.Agent.Select(t.Context(), &rpc.AgentSelectRequest{Name: "test-agent"}) if err != nil { t.Fatalf("Failed to select agent: %v", err) } @@ -191,7 +191,7 @@ func TestAgentSelectionRpc(t *testing.T) { } // Select then deselect - _, err = session.RPC.Agent.Select(t.Context(), &rpc.SessionAgentSelectParams{Name: "test-agent"}) + _, err = session.RPC.Agent.Select(t.Context(), &rpc.AgentSelectRequest{Name: "test-agent"}) if err != nil { t.Fatalf("Failed to select agent: %v", err) } diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index e38649e86..5a79a7509 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -26,7 +26,7 @@ func TestRpc(t *testing.T) { t.Fatalf("Failed to start client: %v", err) } - result, err := client.RPC.Ping(t.Context(), &rpc.PingParams{Message: copilot.String("typed rpc test")}) + result, err := client.RPC.Ping(t.Context(), &rpc.PingRequest{Message: copilot.String("typed rpc test")}) if err != nil { t.Fatalf("Failed to call RPC.Ping: %v", err) } @@ -36,7 +36,7 @@ func TestRpc(t *testing.T) { } if result.Timestamp < 0 { - t.Errorf("Expected timestamp >= 0, got %f", result.Timestamp) + t.Errorf("Expected timestamp >= 0, got %d", result.Timestamp) } if err := client.Stop(); err != nil { @@ -170,7 +170,7 @@ func TestSessionRpc(t *testing.T) { // Switch to a different model with reasoning effort re := "high" - result, err := session.RPC.Model.SwitchTo(t.Context(), &rpc.SessionModelSwitchToParams{ + result, err := session.RPC.Model.SwitchTo(t.Context(), &rpc.ModelSwitchToRequest{ ModelID: "gpt-4.1", ReasoningEffort: &re, }) @@ -218,36 +218,30 @@ func TestSessionRpc(t *testing.T) { if err != nil { t.Fatalf("Failed to get mode: %v", err) } - if initial.Mode != rpc.ModeInteractive { - t.Errorf("Expected initial mode 'interactive', got %q", initial.Mode) + if *initial != rpc.SessionModeInteractive { + t.Errorf("Expected initial mode 'interactive', got %q", *initial) } // Switch to plan mode - planResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModePlan}) + _, err = session.RPC.Mode.Set(t.Context(), &rpc.ModeSetRequest{Mode: rpc.SessionModePlan}) if err != nil { t.Fatalf("Failed to set mode to plan: %v", err) } - if planResult.Mode != rpc.ModePlan { - t.Errorf("Expected mode 'plan', got %q", planResult.Mode) - } // Verify mode persisted afterPlan, err := session.RPC.Mode.Get(t.Context()) if err != nil { t.Fatalf("Failed to get mode after plan: %v", err) } - if afterPlan.Mode != rpc.ModePlan { - t.Errorf("Expected mode 'plan' after set, got %q", afterPlan.Mode) + if *afterPlan != rpc.SessionModePlan { + t.Errorf("Expected mode 'plan' after set, got %q", *afterPlan) } // Switch back to interactive - interactiveResult, err := session.RPC.Mode.Set(t.Context(), &rpc.SessionModeSetParams{Mode: rpc.ModeInteractive}) + _, err = session.RPC.Mode.Set(t.Context(), &rpc.ModeSetRequest{Mode: rpc.SessionModeInteractive}) if err != nil { t.Fatalf("Failed to set mode to interactive: %v", err) } - if interactiveResult.Mode != rpc.ModeInteractive { - t.Errorf("Expected mode 'interactive', got %q", interactiveResult.Mode) - } }) t.Run("should read, update, and delete plan", func(t *testing.T) { @@ -270,7 +264,7 @@ func TestSessionRpc(t *testing.T) { // Create/update plan planContent := "# Test Plan\n\n- Step 1\n- Step 2" - _, err = session.RPC.Plan.Update(t.Context(), &rpc.SessionPlanUpdateParams{Content: planContent}) + _, err = session.RPC.Plan.Update(t.Context(), &rpc.PlanUpdateRequest{Content: planContent}) if err != nil { t.Fatalf("Failed to update plan: %v", err) } @@ -323,7 +317,7 @@ func TestSessionRpc(t *testing.T) { // Create a file fileContent := "Hello, workspace!" - _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.SessionWorkspaceCreateFileParams{ + _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.WorkspaceCreateFileRequest{ Path: "test.txt", Content: fileContent, }) @@ -341,7 +335,7 @@ func TestSessionRpc(t *testing.T) { } // Read file - readResult, err := session.RPC.Workspace.ReadFile(t.Context(), &rpc.SessionWorkspaceReadFileParams{ + readResult, err := session.RPC.Workspace.ReadFile(t.Context(), &rpc.WorkspaceReadFileRequest{ Path: "test.txt", }) if err != nil { @@ -352,7 +346,7 @@ func TestSessionRpc(t *testing.T) { } // Create nested file - _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.SessionWorkspaceCreateFileParams{ + _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.WorkspaceCreateFileRequest{ Path: "subdir/nested.txt", Content: "Nested content", }) diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go index 4d006a856..7fba219f7 100644 --- a/go/internal/e2e/session_fs_test.go +++ b/go/internal/e2e/session_fs_test.go @@ -250,7 +250,7 @@ func TestSessionFs(t *testing.T) { var sessionFsConfig = &copilot.SessionFsConfig{ InitialCwd: "/", SessionStatePath: "/session-state", - Conventions: rpc.ConventionsPosix, + Conventions: rpc.SessionFSSetProviderConventionsPosix, } type testSessionFsHandler struct { @@ -258,7 +258,7 @@ type testSessionFsHandler struct { sessionID string } -func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileParams) (*rpc.SessionFSReadFileResult, error) { +func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileRequest) (*rpc.SessionFSReadFileResult, error) { content, err := os.ReadFile(providerPath(h.root, h.sessionID, request.Path)) if err != nil { return nil, err @@ -266,22 +266,22 @@ func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileParams) (* return &rpc.SessionFSReadFileResult{Content: string(content)}, nil } -func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileParams) error { +func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSWriteFileResult, error) { path := providerPath(h.root, h.sessionID, request.Path) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return err + return nil, err } mode := os.FileMode(0o666) if request.Mode != nil { mode = os.FileMode(uint32(*request.Mode)) } - return os.WriteFile(path, []byte(request.Content), mode) + return &rpc.SessionFSWriteFileResult{}, os.WriteFile(path, []byte(request.Content), mode) } -func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileParams) error { +func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSAppendFileResult, error) { path := providerPath(h.root, h.sessionID, request.Path) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return err + return nil, err } mode := os.FileMode(0o666) if request.Mode != nil { @@ -289,14 +289,17 @@ func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileParams } f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, mode) if err != nil { - return err + return nil, err } defer f.Close() _, err = f.WriteString(request.Content) - return err + if err != nil { + return nil, err + } + return &rpc.SessionFSAppendFileResult{}, nil } -func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsParams) (*rpc.SessionFSExistsResult, error) { +func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { _, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) if err == nil { return &rpc.SessionFSExistsResult{Exists: true}, nil @@ -307,34 +310,34 @@ func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsParams) (*rpc. return nil, err } -func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatParams) (*rpc.SessionFSStatResult, error) { +func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.SessionFSStatResult, error) { info, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) if err != nil { return nil, err } - ts := info.ModTime().UTC().Format(time.RFC3339) + ts := info.ModTime().UTC() return &rpc.SessionFSStatResult{ IsFile: !info.IsDir(), IsDirectory: info.IsDir(), - Size: float64(info.Size()), + Size: info.Size(), Mtime: ts, Birthtime: ts, }, nil } -func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirParams) error { +func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSMkdirResult, error) { path := providerPath(h.root, h.sessionID, request.Path) mode := os.FileMode(0o777) if request.Mode != nil { mode = os.FileMode(uint32(*request.Mode)) } if request.Recursive != nil && *request.Recursive { - return os.MkdirAll(path, mode) + return &rpc.SessionFSMkdirResult{}, os.MkdirAll(path, mode) } - return os.Mkdir(path, mode) + return &rpc.SessionFSMkdirResult{}, os.Mkdir(path, mode) } -func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirParams) (*rpc.SessionFSReaddirResult, error) { +func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) if err != nil { return nil, err @@ -346,18 +349,18 @@ func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirParams) (*rp return &rpc.SessionFSReaddirResult{Entries: names}, nil } -func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesParams) (*rpc.SessionFSReaddirWithTypesResult, error) { +func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesRequest) (*rpc.SessionFSReaddirWithTypesResult, error) { entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) if err != nil { return nil, err } - result := make([]rpc.Entry, 0, len(entries)) + result := make([]rpc.SessionFSReaddirWithTypesEntry, 0, len(entries)) for _, entry := range entries { - entryType := rpc.EntryTypeFile + entryType := rpc.SessionFSReaddirWithTypesEntryTypeFile if entry.IsDir() { - entryType = rpc.EntryTypeDirectory + entryType = rpc.SessionFSReaddirWithTypesEntryTypeDirectory } - result = append(result, rpc.Entry{ + result = append(result, rpc.SessionFSReaddirWithTypesEntry{ Name: entry.Name(), Type: entryType, }) @@ -365,28 +368,28 @@ func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWit return &rpc.SessionFSReaddirWithTypesResult{Entries: result}, nil } -func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmParams) error { +func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSRmResult, error) { path := providerPath(h.root, h.sessionID, request.Path) if request.Recursive != nil && *request.Recursive { err := os.RemoveAll(path) if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return nil + return &rpc.SessionFSRmResult{}, nil } - return err + return &rpc.SessionFSRmResult{}, err } err := os.Remove(path) if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return nil + return &rpc.SessionFSRmResult{}, nil } - return err + return &rpc.SessionFSRmResult{}, err } -func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameParams) error { +func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSRenameResult, error) { dest := providerPath(h.root, h.sessionID, request.Dest) if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { - return err + return nil, err } - return os.Rename( + return &rpc.SessionFSRenameResult{}, os.Rename( providerPath(h.root, h.sessionID, request.Src), dest, ) diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index 813036545..1fed130d3 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -1157,7 +1157,7 @@ func TestSessionLog(t *testing.T) { }) t.Run("should log warning message", func(t *testing.T) { - if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.LevelWarning}); err != nil { + if err := session.Log(t.Context(), "Warning message", &copilot.LogOptions{Level: rpc.SessionLogLevelWarning}); err != nil { t.Fatalf("Log failed: %v", err) } @@ -1172,7 +1172,7 @@ func TestSessionLog(t *testing.T) { }) t.Run("should log error message", func(t *testing.T) { - if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.LevelError}); err != nil { + if err := session.Log(t.Context(), "Error message", &copilot.LogOptions{Level: rpc.SessionLogLevelError}); err != nil { t.Fatalf("Log failed: %v", err) } diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 698b3e95e..75660a0e0 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -9,30 +9,31 @@ import ( "errors" "fmt" "github.com/github/copilot-sdk/go/internal/jsonrpc2" + "time" ) type PingResult struct { // Echoed message (or default greeting) Message string `json:"message"` // Server protocol version number - ProtocolVersion float64 `json:"protocolVersion"` + ProtocolVersion int64 `json:"protocolVersion"` // Server timestamp in milliseconds - Timestamp float64 `json:"timestamp"` + Timestamp int64 `json:"timestamp"` } -type PingParams struct { +type PingRequest struct { // Optional message to echo back Message *string `json:"message,omitempty"` } -type ModelsListResult struct { +type ModelList struct { // List of available models with full metadata Models []Model `json:"models"` } type Model struct { // Billing information - Billing *Billing `json:"billing,omitempty"` + Billing *ModelBilling `json:"billing,omitempty"` // Model capabilities and limits Capabilities ModelCapabilities `json:"capabilities"` // Default reasoning effort level (only present if model supports reasoning effort) @@ -42,13 +43,13 @@ type Model struct { // Display name Name string `json:"name"` // Policy state (if applicable) - Policy *Policy `json:"policy,omitempty"` + Policy *ModelPolicy `json:"policy,omitempty"` // Supported reasoning effort levels (only present if model supports reasoning effort) SupportedReasoningEfforts []string `json:"supportedReasoningEfforts,omitempty"` } // Billing information -type Billing struct { +type ModelBilling struct { // Billing cost multiplier relative to the base rate Multiplier float64 `json:"multiplier"` } @@ -64,11 +65,11 @@ type ModelCapabilities struct { // Token limits for prompts, outputs, and context window type ModelCapabilitiesLimits struct { // Maximum total context window size in tokens - MaxContextWindowTokens float64 `json:"max_context_window_tokens"` + MaxContextWindowTokens int64 `json:"max_context_window_tokens"` // Maximum number of output/completion tokens - MaxOutputTokens *float64 `json:"max_output_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` // Maximum number of prompt/input tokens - MaxPromptTokens *float64 `json:"max_prompt_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` // Vision-specific limits Vision *ModelCapabilitiesLimitsVision `json:"vision,omitempty"` } @@ -76,9 +77,9 @@ type ModelCapabilitiesLimits struct { // Vision-specific limits type ModelCapabilitiesLimitsVision struct { // Maximum image size in bytes - MaxPromptImageSize float64 `json:"max_prompt_image_size"` + MaxPromptImageSize int64 `json:"max_prompt_image_size"` // Maximum number of images per prompt - MaxPromptImages float64 `json:"max_prompt_images"` + MaxPromptImages int64 `json:"max_prompt_images"` // MIME types the model accepts SupportedMediaTypes []string `json:"supported_media_types"` } @@ -92,14 +93,14 @@ type ModelCapabilitiesSupports struct { } // Policy state (if applicable) -type Policy struct { +type ModelPolicy struct { // Current policy state for this model State string `json:"state"` // Usage terms or conditions for this model Terms string `json:"terms"` } -type ToolsListResult struct { +type ToolList struct { // List of available built-in tools with metadata Tools []Tool `json:"tools"` } @@ -118,7 +119,7 @@ type Tool struct { Parameters map[string]any `json:"parameters,omitempty"` } -type ToolsListParams struct { +type ToolsListRequest struct { // Optional model ID — when provided, the returned tool list reflects model-specific // overrides Model *string `json:"model,omitempty"` @@ -126,98 +127,110 @@ type ToolsListParams struct { type AccountGetQuotaResult struct { // Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) - QuotaSnapshots map[string]QuotaSnapshot `json:"quotaSnapshots"` + QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` } -type QuotaSnapshot struct { +type AccountQuotaSnapshot struct { // Number of requests included in the entitlement - EntitlementRequests float64 `json:"entitlementRequests"` + EntitlementRequests int64 `json:"entitlementRequests"` // Number of overage requests made this period - Overage float64 `json:"overage"` + Overage int64 `json:"overage"` // Whether pay-per-request usage is allowed when quota is exhausted OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` // Percentage of entitlement remaining RemainingPercentage float64 `json:"remainingPercentage"` // Date when the quota resets (ISO 8601) - ResetDate *string `json:"resetDate,omitempty"` + ResetDate *time.Time `json:"resetDate,omitempty"` // Number of requests used so far this period - UsedRequests float64 `json:"usedRequests"` + UsedRequests int64 `json:"usedRequests"` } -type MCPConfigListResult struct { +type MCPConfigList struct { // All MCP servers from user config, keyed by name - Servers map[string]ServerValue `json:"servers"` + Servers map[string]MCPConfigServer `json:"servers"` } // MCP server configuration (local/stdio or remote/http) -type ServerValue struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMappingUnion `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - Timeout *float64 `json:"timeout,omitempty"` +type MCPConfigServer struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` - Type *ServerType `json:"type,omitempty"` + Type *MCPConfigType `json:"type,omitempty"` Headers map[string]string `json:"headers,omitempty"` OauthClientID *string `json:"oauthClientId,omitempty"` OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` URL *string `json:"url,omitempty"` } -type MCPConfigAddParams struct { +type MCPConfigAddResult struct { +} + +type MCPConfigAddRequest struct { // MCP server configuration (local/stdio or remote/http) - Config MCPConfigAddParamsConfig `json:"config"` + Config MCPConfigAddConfig `json:"config"` // Unique name for the MCP server Name string `json:"name"` } // MCP server configuration (local/stdio or remote/http) -type MCPConfigAddParamsConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMappingUnion `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - Timeout *float64 `json:"timeout,omitempty"` +type MCPConfigAddConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` - Type *ServerType `json:"type,omitempty"` + Type *MCPConfigType `json:"type,omitempty"` Headers map[string]string `json:"headers,omitempty"` OauthClientID *string `json:"oauthClientId,omitempty"` OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` URL *string `json:"url,omitempty"` } -type MCPConfigUpdateParams struct { +type MCPConfigUpdateResult struct { +} + +type MCPConfigUpdateRequest struct { // MCP server configuration (local/stdio or remote/http) - Config MCPConfigUpdateParamsConfig `json:"config"` + Config MCPConfigUpdateConfig `json:"config"` // Name of the MCP server to update Name string `json:"name"` } // MCP server configuration (local/stdio or remote/http) -type MCPConfigUpdateParamsConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMappingUnion `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - Timeout *float64 `json:"timeout,omitempty"` +type MCPConfigUpdateConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` - Type *ServerType `json:"type,omitempty"` + Type *MCPConfigType `json:"type,omitempty"` Headers map[string]string `json:"headers,omitempty"` OauthClientID *string `json:"oauthClientId,omitempty"` OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` URL *string `json:"url,omitempty"` } -type MCPConfigRemoveParams struct { +type MCPConfigRemoveResult struct { +} + +type MCPConfigRemoveRequest struct { // Name of the MCP server to remove Name string `json:"name"` } @@ -233,12 +246,12 @@ type DiscoveredMCPServer struct { // Server name (config key) Name string `json:"name"` // Configuration source - Source ServerSource `json:"source"` - // Server type: local, stdio, http, or sse - Type *string `json:"type,omitempty"` + Source MCPServerSource `json:"source"` + // Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + Type *DiscoveredMCPServerType `json:"type,omitempty"` } -type MCPDiscoverParams struct { +type MCPDiscoverRequest struct { // Working directory used as context for discovery (e.g., plugin resolution) WorkingDirectory *string `json:"workingDirectory,omitempty"` } @@ -248,9 +261,9 @@ type SessionFSSetProviderResult struct { Success bool `json:"success"` } -type SessionFSSetProviderParams struct { +type SessionFSSetProviderRequest struct { // Path conventions used by this filesystem - Conventions Conventions `json:"conventions"` + Conventions SessionFSSetProviderConventions `json:"conventions"` // Initial working directory for sessions InitialCwd string `json:"initialCwd"` // Path within each session's SessionFs where the runtime stores files for that session @@ -263,8 +276,8 @@ type SessionsForkResult struct { SessionID string `json:"sessionId"` } -// Experimental: SessionsForkParams is part of an experimental API and may change or be removed. -type SessionsForkParams struct { +// Experimental: SessionsForkRequest is part of an experimental API and may change or be removed. +type SessionsForkRequest struct { // Source session ID to fork from SessionID string `json:"sessionId"` // Optional event ID boundary. When provided, the fork includes only events before this ID @@ -272,17 +285,17 @@ type SessionsForkParams struct { ToEventID *string `json:"toEventId,omitempty"` } -type SessionModelGetCurrentResult struct { +type CurrentModel struct { // Currently active model identifier ModelID *string `json:"modelId,omitempty"` } -type SessionModelSwitchToResult struct { +type ModelSwitchToResult struct { // Currently active model identifier after the switch ModelID *string `json:"modelId,omitempty"` } -type SessionModelSwitchToParams struct { +type ModelSwitchToRequest struct { // Override individual model capabilities resolved by the runtime ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` // Model identifier to switch to @@ -302,17 +315,17 @@ type ModelCapabilitiesOverride struct { // Token limits for prompts, outputs, and context window type ModelCapabilitiesOverrideLimits struct { // Maximum total context window size in tokens - MaxContextWindowTokens *float64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *float64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *float64 `json:"max_prompt_tokens,omitempty"` + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` Vision *ModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` } type ModelCapabilitiesOverrideLimitsVision struct { // Maximum image size in bytes - MaxPromptImageSize *float64 `json:"max_prompt_image_size,omitempty"` + MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` // Maximum number of images per prompt - MaxPromptImages *float64 `json:"max_prompt_images,omitempty"` + MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` // MIME types the model accepts SupportedMediaTypes []string `json:"supported_media_types,omitempty"` } @@ -323,22 +336,15 @@ type ModelCapabilitiesOverrideSupports struct { Vision *bool `json:"vision,omitempty"` } -type SessionModeGetResult struct { - // The current agent mode. - Mode Mode `json:"mode"` -} - -type SessionModeSetResult struct { - // The agent mode after switching. - Mode Mode `json:"mode"` +type ModeSetResult struct { } -type SessionModeSetParams struct { - // The mode to switch to. Valid values: "interactive", "plan", "autopilot". - Mode Mode `json:"mode"` +type ModeSetRequest struct { + // The agent mode. Valid values: "interactive", "plan", "autopilot". + Mode SessionMode `json:"mode"` } -type SessionPlanReadResult struct { +type PlanReadResult struct { // The content of the plan file, or null if it does not exist Content *string `json:"content"` // Whether the plan file exists in the workspace @@ -347,61 +353,61 @@ type SessionPlanReadResult struct { Path *string `json:"path"` } -type SessionPlanUpdateResult struct { +type PlanUpdateResult struct { } -type SessionPlanUpdateParams struct { +type PlanUpdateRequest struct { // The new content for the plan file Content string `json:"content"` } -type SessionPlanDeleteResult struct { +type PlanDeleteResult struct { } -type SessionWorkspaceListFilesResult struct { +type WorkspaceListFilesResult struct { // Relative file paths in the workspace files directory Files []string `json:"files"` } -type SessionWorkspaceReadFileResult struct { +type WorkspaceReadFileResult struct { // File content as a UTF-8 string Content string `json:"content"` } -type SessionWorkspaceReadFileParams struct { +type WorkspaceReadFileRequest struct { // Relative path within the workspace files directory Path string `json:"path"` } -type SessionWorkspaceCreateFileResult struct { +type WorkspaceCreateFileResult struct { } -type SessionWorkspaceCreateFileParams struct { +type WorkspaceCreateFileRequest struct { // File content to write as a UTF-8 string Content string `json:"content"` // Relative path within the workspace files directory Path string `json:"path"` } -// Experimental: SessionFleetStartResult is part of an experimental API and may change or be removed. -type SessionFleetStartResult struct { +// Experimental: FleetStartResult is part of an experimental API and may change or be removed. +type FleetStartResult struct { // Whether fleet mode was successfully activated Started bool `json:"started"` } -// Experimental: SessionFleetStartParams is part of an experimental API and may change or be removed. -type SessionFleetStartParams struct { +// Experimental: FleetStartRequest is part of an experimental API and may change or be removed. +type FleetStartRequest struct { // Optional user prompt to combine with fleet instructions Prompt *string `json:"prompt,omitempty"` } -// Experimental: SessionAgentListResult is part of an experimental API and may change or be removed. -type SessionAgentListResult struct { +// Experimental: AgentList is part of an experimental API and may change or be removed. +type AgentList struct { // Available custom agents - Agents []SessionAgentListResultAgent `json:"agents"` + Agents []Agent `json:"agents"` } -type SessionAgentListResultAgent struct { +type Agent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -410,13 +416,13 @@ type SessionAgentListResultAgent struct { Name string `json:"name"` } -// Experimental: SessionAgentGetCurrentResult is part of an experimental API and may change or be removed. -type SessionAgentGetCurrentResult struct { +// Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. +type AgentGetCurrentResult struct { // Currently selected custom agent, or null if using the default agent - Agent *SessionAgentGetCurrentResultAgent `json:"agent"` + Agent *AgentGetCurrentResultAgent `json:"agent"` } -type SessionAgentGetCurrentResultAgent struct { +type AgentGetCurrentResultAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -425,14 +431,14 @@ type SessionAgentGetCurrentResultAgent struct { Name string `json:"name"` } -// Experimental: SessionAgentSelectResult is part of an experimental API and may change or be removed. -type SessionAgentSelectResult struct { +// Experimental: AgentSelectResult is part of an experimental API and may change or be removed. +type AgentSelectResult struct { // The newly selected custom agent - Agent SessionAgentSelectResultAgent `json:"agent"` + Agent AgentSelectAgent `json:"agent"` } // The newly selected custom agent -type SessionAgentSelectResultAgent struct { +type AgentSelectAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -441,23 +447,23 @@ type SessionAgentSelectResultAgent struct { Name string `json:"name"` } -// Experimental: SessionAgentSelectParams is part of an experimental API and may change or be removed. -type SessionAgentSelectParams struct { +// Experimental: AgentSelectRequest is part of an experimental API and may change or be removed. +type AgentSelectRequest struct { // Name of the custom agent to select Name string `json:"name"` } -// Experimental: SessionAgentDeselectResult is part of an experimental API and may change or be removed. -type SessionAgentDeselectResult struct { +// Experimental: AgentDeselectResult is part of an experimental API and may change or be removed. +type AgentDeselectResult struct { } -// Experimental: SessionAgentReloadResult is part of an experimental API and may change or be removed. -type SessionAgentReloadResult struct { +// Experimental: AgentReloadResult is part of an experimental API and may change or be removed. +type AgentReloadResult struct { // Reloaded custom agents - Agents []SessionAgentReloadResultAgent `json:"agents"` + Agents []AgentReloadAgent `json:"agents"` } -type SessionAgentReloadResultAgent struct { +type AgentReloadAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -466,8 +472,8 @@ type SessionAgentReloadResultAgent struct { Name string `json:"name"` } -// Experimental: SessionSkillsListResult is part of an experimental API and may change or be removed. -type SessionSkillsListResult struct { +// Experimental: SkillList is part of an experimental API and may change or be removed. +type SkillList struct { // Available skills Skills []Skill `json:"skills"` } @@ -487,67 +493,67 @@ type Skill struct { UserInvocable bool `json:"userInvocable"` } -// Experimental: SessionSkillsEnableResult is part of an experimental API and may change or be removed. -type SessionSkillsEnableResult struct { +// Experimental: SkillsEnableResult is part of an experimental API and may change or be removed. +type SkillsEnableResult struct { } -// Experimental: SessionSkillsEnableParams is part of an experimental API and may change or be removed. -type SessionSkillsEnableParams struct { +// Experimental: SkillsEnableRequest is part of an experimental API and may change or be removed. +type SkillsEnableRequest struct { // Name of the skill to enable Name string `json:"name"` } -// Experimental: SessionSkillsDisableResult is part of an experimental API and may change or be removed. -type SessionSkillsDisableResult struct { +// Experimental: SkillsDisableResult is part of an experimental API and may change or be removed. +type SkillsDisableResult struct { } -// Experimental: SessionSkillsDisableParams is part of an experimental API and may change or be removed. -type SessionSkillsDisableParams struct { +// Experimental: SkillsDisableRequest is part of an experimental API and may change or be removed. +type SkillsDisableRequest struct { // Name of the skill to disable Name string `json:"name"` } -// Experimental: SessionSkillsReloadResult is part of an experimental API and may change or be removed. -type SessionSkillsReloadResult struct { +// Experimental: SkillsReloadResult is part of an experimental API and may change or be removed. +type SkillsReloadResult struct { } -type SessionMCPListResult struct { +type MCPServerList struct { // Configured MCP servers - Servers []ServerElement `json:"servers"` + Servers []MCPServer `json:"servers"` } -type ServerElement struct { +type MCPServer struct { // Error message if the server failed to connect Error *string `json:"error,omitempty"` // Server name (config key) Name string `json:"name"` // Configuration source: user, workspace, plugin, or builtin - Source *string `json:"source,omitempty"` + Source *MCPServerSource `json:"source,omitempty"` // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status ServerStatus `json:"status"` + Status MCPServerStatus `json:"status"` } -type SessionMCPEnableResult struct { +type MCPEnableResult struct { } -type SessionMCPEnableParams struct { +type MCPEnableRequest struct { // Name of the MCP server to enable ServerName string `json:"serverName"` } -type SessionMCPDisableResult struct { +type MCPDisableResult struct { } -type SessionMCPDisableParams struct { +type MCPDisableRequest struct { // Name of the MCP server to disable ServerName string `json:"serverName"` } -type SessionMCPReloadResult struct { +type MCPReloadResult struct { } -// Experimental: SessionPluginsListResult is part of an experimental API and may change or be removed. -type SessionPluginsListResult struct { +// Experimental: PluginList is part of an experimental API and may change or be removed. +type PluginList struct { // Installed plugins Plugins []PluginElement `json:"plugins"` } @@ -563,8 +569,8 @@ type PluginElement struct { Version *string `json:"version,omitempty"` } -// Experimental: SessionExtensionsListResult is part of an experimental API and may change or be removed. -type SessionExtensionsListResult struct { +// Experimental: ExtensionList is part of an experimental API and may change or be removed. +type ExtensionList struct { // Discovered extensions and their current status Extensions []Extension `json:"extensions"` } @@ -582,45 +588,45 @@ type Extension struct { Status ExtensionStatus `json:"status"` } -// Experimental: SessionExtensionsEnableResult is part of an experimental API and may change or be removed. -type SessionExtensionsEnableResult struct { +// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be removed. +type ExtensionsEnableResult struct { } -// Experimental: SessionExtensionsEnableParams is part of an experimental API and may change or be removed. -type SessionExtensionsEnableParams struct { +// Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be removed. +type ExtensionsEnableRequest struct { // Source-qualified extension ID to enable ID string `json:"id"` } -// Experimental: SessionExtensionsDisableResult is part of an experimental API and may change or be removed. -type SessionExtensionsDisableResult struct { +// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be removed. +type ExtensionsDisableResult struct { } -// Experimental: SessionExtensionsDisableParams is part of an experimental API and may change or be removed. -type SessionExtensionsDisableParams struct { +// Experimental: ExtensionsDisableRequest is part of an experimental API and may change or be removed. +type ExtensionsDisableRequest struct { // Source-qualified extension ID to disable ID string `json:"id"` } -// Experimental: SessionExtensionsReloadResult is part of an experimental API and may change or be removed. -type SessionExtensionsReloadResult struct { +// Experimental: ExtensionsReloadResult is part of an experimental API and may change or be removed. +type ExtensionsReloadResult struct { } -type SessionToolsHandlePendingToolCallResult struct { +type HandleToolCallResult struct { // Whether the tool call result was handled successfully Success bool `json:"success"` } -type SessionToolsHandlePendingToolCallParams struct { +type ToolsHandlePendingToolCallRequest struct { // Error message if the tool call failed Error *string `json:"error,omitempty"` // Request ID of the pending tool call RequestID string `json:"requestId"` // Tool call result (string or expanded result object) - Result *ResultUnion `json:"result"` + Result *ToolsHandlePendingToolCall `json:"result"` } -type ResultResult struct { +type ToolCallResult struct { // Error message if the tool call failed Error *string `json:"error,omitempty"` // Type of the tool result @@ -631,109 +637,102 @@ type ResultResult struct { ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } -type SessionCommandsHandlePendingCommandResult struct { +type CommandsHandlePendingCommandResult struct { // Whether the command was handled successfully Success bool `json:"success"` } -type SessionCommandsHandlePendingCommandParams struct { +type CommandsHandlePendingCommandRequest struct { // Error message if the command handler failed Error *string `json:"error,omitempty"` // Request ID from the command invocation event RequestID string `json:"requestId"` } -type SessionUIElicitationResult struct { +// The elicitation response (accept with form values, decline, or cancel) +type UIElicitationResponse struct { // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - Action Action `json:"action"` + Action UIElicitationResponseAction `json:"action"` // The form values submitted by the user (present when action is 'accept') - Content map[string]*Content `json:"content,omitempty"` + Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` } -type SessionUIElicitationParams struct { +type UIElicitationRequest struct { // Message describing what information is needed from the user Message string `json:"message"` // JSON Schema describing the form fields to present to the user - RequestedSchema RequestedSchema `json:"requestedSchema"` + RequestedSchema UIElicitationSchema `json:"requestedSchema"` } // JSON Schema describing the form fields to present to the user -type RequestedSchema struct { +type UIElicitationSchema struct { // Form field definitions, keyed by field name - Properties map[string]Property `json:"properties"` + Properties map[string]UIElicitationSchemaProperty `json:"properties"` // List of required field names Required []string `json:"required,omitempty"` // Schema type indicator (always 'object') Type RequestedSchemaType `json:"type"` } -type Property struct { - Default *Content `json:"default"` - Description *string `json:"description,omitempty"` - Enum []string `json:"enum,omitempty"` - EnumNames []string `json:"enumNames,omitempty"` - Title *string `json:"title,omitempty"` - Type PropertyType `json:"type"` - OneOf []OneOf `json:"oneOf,omitempty"` - Items *Items `json:"items,omitempty"` - MaxItems *float64 `json:"maxItems,omitempty"` - MinItems *float64 `json:"minItems,omitempty"` - Format *Format `json:"format,omitempty"` - MaxLength *float64 `json:"maxLength,omitempty"` - MinLength *float64 `json:"minLength,omitempty"` - Maximum *float64 `json:"maximum,omitempty"` - Minimum *float64 `json:"minimum,omitempty"` +type UIElicitationSchemaProperty struct { + Default *UIElicitationFieldValue `json:"default"` + Description *string `json:"description,omitempty"` + Enum []string `json:"enum,omitempty"` + EnumNames []string `json:"enumNames,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationSchemaPropertyNumberType `json:"type"` + OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf,omitempty"` + Items *UIElicitationArrayFieldItems `json:"items,omitempty"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Format *UIElicitationSchemaPropertyStringFormat `json:"format,omitempty"` + MaxLength *float64 `json:"maxLength,omitempty"` + MinLength *float64 `json:"minLength,omitempty"` + Maximum *float64 `json:"maximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty"` } -type Items struct { - Enum []string `json:"enum,omitempty"` - Type *ItemsType `json:"type,omitempty"` - AnyOf []AnyOf `json:"anyOf,omitempty"` +type UIElicitationArrayFieldItems struct { + Enum []string `json:"enum,omitempty"` + Type *ItemsType `json:"type,omitempty"` + AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` } -type AnyOf struct { +type UIElicitationArrayAnyOfFieldItemsAnyOf struct { Const string `json:"const"` Title string `json:"title"` } -type OneOf struct { +type UIElicitationStringOneOfFieldOneOf struct { Const string `json:"const"` Title string `json:"title"` } -type SessionUIHandlePendingElicitationResult struct { +type UIElicitationResult struct { // Whether the response was accepted. False if the request was already resolved by another // client. Success bool `json:"success"` } -type SessionUIHandlePendingElicitationParams struct { +type UIHandlePendingElicitationRequest struct { // The unique request ID from the elicitation.requested event RequestID string `json:"requestId"` // The elicitation response (accept with form values, decline, or cancel) - Result SessionUIHandlePendingElicitationParamsResult `json:"result"` -} - -// The elicitation response (accept with form values, decline, or cancel) -type SessionUIHandlePendingElicitationParamsResult struct { - // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - Action Action `json:"action"` - // The form values submitted by the user (present when action is 'accept') - Content map[string]*Content `json:"content,omitempty"` + Result UIElicitationResponse `json:"result"` } -type SessionPermissionsHandlePendingPermissionRequestResult struct { +type PermissionRequestResult struct { // Whether the permission request was handled successfully Success bool `json:"success"` } -type SessionPermissionsHandlePendingPermissionRequestParams struct { +type PermissionDecisionRequest struct { // Request ID of the pending permission request - RequestID string `json:"requestId"` - Result SessionPermissionsHandlePendingPermissionRequestParamsResult `json:"result"` + RequestID string `json:"requestId"` + Result PermissionDecision `json:"result"` } -type SessionPermissionsHandlePendingPermissionRequestParamsResult struct { +type PermissionDecision struct { // The permission request was approved // // Denied because approval rules explicitly blocked it @@ -760,93 +759,93 @@ type SessionPermissionsHandlePendingPermissionRequestParamsResult struct { Interrupt *bool `json:"interrupt,omitempty"` } -type SessionLogResult struct { +type LogResult struct { // The unique identifier of the emitted session event EventID string `json:"eventId"` } -type SessionLogParams struct { +type LogRequest struct { // When true, the message is transient and not persisted to the session event log on disk Ephemeral *bool `json:"ephemeral,omitempty"` // Log severity level. Determines how the message is displayed in the timeline. Defaults to // "info". - Level *Level `json:"level,omitempty"` + Level *SessionLogLevel `json:"level,omitempty"` // Human-readable message Message string `json:"message"` // Optional URL the user can open in their browser for more details URL *string `json:"url,omitempty"` } -type SessionShellExecResult struct { +type ShellExecResult struct { // Unique identifier for tracking streamed output ProcessID string `json:"processId"` } -type SessionShellExecParams struct { +type ShellExecRequest struct { // Shell command to execute Command string `json:"command"` // Working directory (defaults to session working directory) Cwd *string `json:"cwd,omitempty"` // Timeout in milliseconds (default: 30000) - Timeout *float64 `json:"timeout,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` } -type SessionShellKillResult struct { +type ShellKillResult struct { // Whether the signal was sent successfully Killed bool `json:"killed"` } -type SessionShellKillParams struct { +type ShellKillRequest struct { // Process identifier returned by shell.exec ProcessID string `json:"processId"` // Signal to send (default: SIGTERM) - Signal *Signal `json:"signal,omitempty"` + Signal *ShellKillSignal `json:"signal,omitempty"` } -// Experimental: SessionHistoryCompactResult is part of an experimental API and may change or be removed. -type SessionHistoryCompactResult struct { +// Experimental: HistoryCompactResult is part of an experimental API and may change or be removed. +type HistoryCompactResult struct { // Post-compaction context window usage breakdown - ContextWindow *ContextWindow `json:"contextWindow,omitempty"` + ContextWindow *HistoryCompactContextWindow `json:"contextWindow,omitempty"` // Number of messages removed during compaction - MessagesRemoved float64 `json:"messagesRemoved"` + MessagesRemoved int64 `json:"messagesRemoved"` // Whether compaction completed successfully Success bool `json:"success"` // Number of tokens freed by compaction - TokensRemoved float64 `json:"tokensRemoved"` + TokensRemoved int64 `json:"tokensRemoved"` } // Post-compaction context window usage breakdown -type ContextWindow struct { +type HistoryCompactContextWindow struct { // Token count from non-system messages (user, assistant, tool) - ConversationTokens *float64 `json:"conversationTokens,omitempty"` + ConversationTokens *int64 `json:"conversationTokens,omitempty"` // Current total tokens in the context window (system + conversation + tool definitions) - CurrentTokens float64 `json:"currentTokens"` + CurrentTokens int64 `json:"currentTokens"` // Current number of messages in the conversation - MessagesLength float64 `json:"messagesLength"` + MessagesLength int64 `json:"messagesLength"` // Token count from system message(s) - SystemTokens *float64 `json:"systemTokens,omitempty"` + SystemTokens *int64 `json:"systemTokens,omitempty"` // Maximum token count for the model's context window - TokenLimit float64 `json:"tokenLimit"` + TokenLimit int64 `json:"tokenLimit"` // Token count from tool definitions - ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` + ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` } -// Experimental: SessionHistoryTruncateResult is part of an experimental API and may change or be removed. -type SessionHistoryTruncateResult struct { +// Experimental: HistoryTruncateResult is part of an experimental API and may change or be removed. +type HistoryTruncateResult struct { // Number of events that were removed - EventsRemoved float64 `json:"eventsRemoved"` + EventsRemoved int64 `json:"eventsRemoved"` } -// Experimental: SessionHistoryTruncateParams is part of an experimental API and may change or be removed. -type SessionHistoryTruncateParams struct { +// Experimental: HistoryTruncateRequest is part of an experimental API and may change or be removed. +type HistoryTruncateRequest struct { // Event ID to truncate to. This event and all events after it are removed from the session. EventID string `json:"eventId"` } -// Experimental: SessionUsageGetMetricsResult is part of an experimental API and may change or be removed. -type SessionUsageGetMetricsResult struct { +// Experimental: UsageGetMetricsResult is part of an experimental API and may change or be removed. +type UsageGetMetricsResult struct { // Aggregated code change metrics - CodeChanges CodeChanges `json:"codeChanges"` + CodeChanges UsageMetricsCodeChanges `json:"codeChanges"` // Currently active model identifier CurrentModel *string `json:"currentModel,omitempty"` // Input tokens from the most recent main-agent API call @@ -854,7 +853,7 @@ type SessionUsageGetMetricsResult struct { // Output tokens from the most recent main-agent API call LastCallOutputTokens int64 `json:"lastCallOutputTokens"` // Per-model token and request metrics, keyed by model identifier - ModelMetrics map[string]ModelMetric `json:"modelMetrics"` + ModelMetrics map[string]UsageMetricsModelMetric `json:"modelMetrics"` // Session start timestamp (epoch milliseconds) SessionStartTime int64 `json:"sessionStartTime"` // Total time spent in model API calls (milliseconds) @@ -867,7 +866,7 @@ type SessionUsageGetMetricsResult struct { } // Aggregated code change metrics -type CodeChanges struct { +type UsageMetricsCodeChanges struct { // Number of distinct files modified FilesModifiedCount int64 `json:"filesModifiedCount"` // Total lines of code added @@ -876,15 +875,15 @@ type CodeChanges struct { LinesRemoved int64 `json:"linesRemoved"` } -type ModelMetric struct { +type UsageMetricsModelMetric struct { // Request count and cost metrics for this model - Requests Requests `json:"requests"` + Requests UsageMetricsModelMetricRequests `json:"requests"` // Token usage metrics for this model - Usage Usage `json:"usage"` + Usage UsageMetricsModelMetricUsage `json:"usage"` } // Request count and cost metrics for this model -type Requests struct { +type UsageMetricsModelMetricRequests struct { // User-initiated premium request cost (with multiplier applied) Cost float64 `json:"cost"` // Number of API requests made with this model @@ -892,7 +891,7 @@ type Requests struct { } // Token usage metrics for this model -type Usage struct { +type UsageMetricsModelMetricUsage struct { // Total tokens read from prompt cache CacheReadTokens int64 `json:"cacheReadTokens"` // Total tokens written to prompt cache @@ -901,6 +900,8 @@ type Usage struct { InputTokens int64 `json:"inputTokens"` // Total output tokens produced OutputTokens int64 `json:"outputTokens"` + // Total output tokens used for reasoning + ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } type SessionFSReadFileResult struct { @@ -908,29 +909,35 @@ type SessionFSReadFileResult struct { Content string `json:"content"` } -type SessionFSReadFileParams struct { +type SessionFSReadFileRequest struct { // Path using SessionFs conventions Path string `json:"path"` // Target session identifier SessionID string `json:"sessionId"` } -type SessionFSWriteFileParams struct { +type SessionFSWriteFileResult struct { +} + +type SessionFSWriteFileRequest struct { // Content to write Content string `json:"content"` // Optional POSIX-style mode for newly created files - Mode *float64 `json:"mode,omitempty"` + Mode *int64 `json:"mode,omitempty"` // Path using SessionFs conventions Path string `json:"path"` // Target session identifier SessionID string `json:"sessionId"` } -type SessionFSAppendFileParams struct { +type SessionFSAppendFileResult struct { +} + +type SessionFSAppendFileRequest struct { // Content to append Content string `json:"content"` // Optional POSIX-style mode for newly created files - Mode *float64 `json:"mode,omitempty"` + Mode *int64 `json:"mode,omitempty"` // Path using SessionFs conventions Path string `json:"path"` // Target session identifier @@ -942,7 +949,7 @@ type SessionFSExistsResult struct { Exists bool `json:"exists"` } -type SessionFSExistsParams struct { +type SessionFSExistsRequest struct { // Path using SessionFs conventions Path string `json:"path"` // Target session identifier @@ -951,27 +958,30 @@ type SessionFSExistsParams struct { type SessionFSStatResult struct { // ISO 8601 timestamp of creation - Birthtime string `json:"birthtime"` + Birthtime time.Time `json:"birthtime"` // Whether the path is a directory IsDirectory bool `json:"isDirectory"` // Whether the path is a file IsFile bool `json:"isFile"` // ISO 8601 timestamp of last modification - Mtime string `json:"mtime"` + Mtime time.Time `json:"mtime"` // File size in bytes - Size float64 `json:"size"` + Size int64 `json:"size"` } -type SessionFSStatParams struct { +type SessionFSStatRequest struct { // Path using SessionFs conventions Path string `json:"path"` // Target session identifier SessionID string `json:"sessionId"` } -type SessionFSMkdirParams struct { +type SessionFSMkdirResult struct { +} + +type SessionFSMkdirRequest struct { // Optional POSIX-style mode for newly created directories - Mode *float64 `json:"mode,omitempty"` + Mode *int64 `json:"mode,omitempty"` // Path using SessionFs conventions Path string `json:"path"` // Create parent directories as needed @@ -985,7 +995,7 @@ type SessionFSReaddirResult struct { Entries []string `json:"entries"` } -type SessionFSReaddirParams struct { +type SessionFSReaddirRequest struct { // Path using SessionFs conventions Path string `json:"path"` // Target session identifier @@ -994,24 +1004,27 @@ type SessionFSReaddirParams struct { type SessionFSReaddirWithTypesResult struct { // Directory entries with type information - Entries []Entry `json:"entries"` + Entries []SessionFSReaddirWithTypesEntry `json:"entries"` } -type Entry struct { +type SessionFSReaddirWithTypesEntry struct { // Entry name Name string `json:"name"` // Entry type - Type EntryType `json:"type"` + Type SessionFSReaddirWithTypesEntryType `json:"type"` } -type SessionFSReaddirWithTypesParams struct { +type SessionFSReaddirWithTypesRequest struct { // Path using SessionFs conventions Path string `json:"path"` // Target session identifier SessionID string `json:"sessionId"` } -type SessionFSRmParams struct { +type SessionFSRmResult struct { +} + +type SessionFSRmRequest struct { // Ignore errors if the path does not exist Force *bool `json:"force,omitempty"` // Path using SessionFs conventions @@ -1022,7 +1035,10 @@ type SessionFSRmParams struct { SessionID string `json:"sessionId"` } -type SessionFSRenameParams struct { +type SessionFSRenameResult struct { +} + +type SessionFSRenameRequest struct { // Destination path using SessionFs conventions Dest string `json:"dest"` // Target session identifier @@ -1031,64 +1047,72 @@ type SessionFSRenameParams struct { Src string `json:"src"` } -type FilterMappingEnum string +type MCPConfigFilterMappingString string const ( - FilterMappingEnumHiddenCharacters FilterMappingEnum = "hidden_characters" - FilterMappingEnumMarkdown FilterMappingEnum = "markdown" - FilterMappingEnumNone FilterMappingEnum = "none" + MCPConfigFilterMappingStringHiddenCharacters MCPConfigFilterMappingString = "hidden_characters" + MCPConfigFilterMappingStringMarkdown MCPConfigFilterMappingString = "markdown" + MCPConfigFilterMappingStringNone MCPConfigFilterMappingString = "none" ) -type ServerType string +type MCPConfigType string const ( - ServerTypeHTTP ServerType = "http" - ServerTypeLocal ServerType = "local" - ServerTypeSse ServerType = "sse" - ServerTypeStdio ServerType = "stdio" + MCPConfigTypeLocal MCPConfigType = "local" + MCPConfigTypeHTTP MCPConfigType = "http" + MCPConfigTypeSSE MCPConfigType = "sse" + MCPConfigTypeStdio MCPConfigType = "stdio" ) // Configuration source -type ServerSource string +// +// Configuration source: user, workspace, plugin, or builtin +type MCPServerSource string const ( - ServerSourceBuiltin ServerSource = "builtin" - ServerSourcePlugin ServerSource = "plugin" - ServerSourceUser ServerSource = "user" - ServerSourceWorkspace ServerSource = "workspace" + MCPServerSourceBuiltin MCPServerSource = "builtin" + MCPServerSourceUser MCPServerSource = "user" + MCPServerSourcePlugin MCPServerSource = "plugin" + MCPServerSourceWorkspace MCPServerSource = "workspace" +) + +// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) +type DiscoveredMCPServerType string + +const ( + DiscoveredMCPServerTypeHTTP DiscoveredMCPServerType = "http" + DiscoveredMCPServerTypeSSE DiscoveredMCPServerType = "sse" + DiscoveredMCPServerTypeStdio DiscoveredMCPServerType = "stdio" + DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" ) // Path conventions used by this filesystem -type Conventions string +type SessionFSSetProviderConventions string const ( - ConventionsPosix Conventions = "posix" - ConventionsWindows Conventions = "windows" + SessionFSSetProviderConventionsPosix SessionFSSetProviderConventions = "posix" + SessionFSSetProviderConventionsWindows SessionFSSetProviderConventions = "windows" ) -// The current agent mode. -// -// The agent mode after switching. -// -// The mode to switch to. Valid values: "interactive", "plan", "autopilot". -type Mode string +// The agent mode. Valid values: "interactive", "plan", "autopilot". +type SessionMode string const ( - ModeAutopilot Mode = "autopilot" - ModeInteractive Mode = "interactive" - ModePlan Mode = "plan" + SessionModeAutopilot SessionMode = "autopilot" + SessionModeInteractive SessionMode = "interactive" + SessionModePlan SessionMode = "plan" ) // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type ServerStatus string +type MCPServerStatus string const ( - ServerStatusConnected ServerStatus = "connected" - ServerStatusNeedsAuth ServerStatus = "needs-auth" - ServerStatusNotConfigured ServerStatus = "not_configured" - ServerStatusPending ServerStatus = "pending" - ServerStatusDisabled ServerStatus = "disabled" - ServerStatusFailed ServerStatus = "failed" + MCPServerStatusConnected MCPServerStatus = "connected" + MCPServerStatusDisabled MCPServerStatus = "disabled" + MCPServerStatusFailed MCPServerStatus = "failed" + MCPServerStatusNeedsAuth MCPServerStatus = "needs-auth" + MCPServerStatusNotConfigured MCPServerStatus = "not_configured" + MCPServerStatusPending MCPServerStatus = "pending" ) // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) @@ -1110,21 +1134,21 @@ const ( ) // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) -type Action string +type UIElicitationResponseAction string const ( - ActionAccept Action = "accept" - ActionCancel Action = "cancel" - ActionDecline Action = "decline" + UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" + UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" + UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" ) -type Format string +type UIElicitationSchemaPropertyStringFormat string const ( - FormatDate Format = "date" - FormatDateTime Format = "date-time" - FormatEmail Format = "email" - FormatURI Format = "uri" + UIElicitationSchemaPropertyStringFormatDate UIElicitationSchemaPropertyStringFormat = "date" + UIElicitationSchemaPropertyStringFormatDateTime UIElicitationSchemaPropertyStringFormat = "date-time" + UIElicitationSchemaPropertyStringFormatEmail UIElicitationSchemaPropertyStringFormat = "email" + UIElicitationSchemaPropertyStringFormatURI UIElicitationSchemaPropertyStringFormat = "uri" ) type ItemsType string @@ -1133,14 +1157,14 @@ const ( ItemsTypeString ItemsType = "string" ) -type PropertyType string +type UIElicitationSchemaPropertyNumberType string const ( - PropertyTypeArray PropertyType = "array" - PropertyTypeBoolean PropertyType = "boolean" - PropertyTypeString PropertyType = "string" - PropertyTypeInteger PropertyType = "integer" - PropertyTypeNumber PropertyType = "number" + UIElicitationSchemaPropertyNumberTypeArray UIElicitationSchemaPropertyNumberType = "array" + UIElicitationSchemaPropertyNumberTypeBoolean UIElicitationSchemaPropertyNumberType = "boolean" + UIElicitationSchemaPropertyNumberTypeInteger UIElicitationSchemaPropertyNumberType = "integer" + UIElicitationSchemaPropertyNumberTypeNumber UIElicitationSchemaPropertyNumberType = "number" + UIElicitationSchemaPropertyNumberTypeString UIElicitationSchemaPropertyNumberType = "string" ) type RequestedSchemaType string @@ -1162,43 +1186,43 @@ const ( // Log severity level. Determines how the message is displayed in the timeline. Defaults to // "info". -type Level string +type SessionLogLevel string const ( - LevelError Level = "error" - LevelInfo Level = "info" - LevelWarning Level = "warning" + SessionLogLevelError SessionLogLevel = "error" + SessionLogLevelInfo SessionLogLevel = "info" + SessionLogLevelWarning SessionLogLevel = "warning" ) // Signal to send (default: SIGTERM) -type Signal string +type ShellKillSignal string const ( - SignalSIGINT Signal = "SIGINT" - SignalSIGKILL Signal = "SIGKILL" - SignalSIGTERM Signal = "SIGTERM" + ShellKillSignalSIGINT ShellKillSignal = "SIGINT" + ShellKillSignalSIGKILL ShellKillSignal = "SIGKILL" + ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" ) // Entry type -type EntryType string +type SessionFSReaddirWithTypesEntryType string const ( - EntryTypeDirectory EntryType = "directory" - EntryTypeFile EntryType = "file" + SessionFSReaddirWithTypesEntryTypeDirectory SessionFSReaddirWithTypesEntryType = "directory" + SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" ) -type FilterMappingUnion struct { - Enum *FilterMappingEnum - EnumMap map[string]FilterMappingEnum +type MCPConfigFilterMapping struct { + Enum *MCPConfigFilterMappingString + EnumMap map[string]MCPConfigFilterMappingString } // Tool call result (string or expanded result object) -type ResultUnion struct { - ResultResult *ResultResult - String *string +type ToolsHandlePendingToolCall struct { + String *string + ToolCallResult *ToolCallResult } -type Content struct { +type UIElicitationFieldValue struct { Bool *bool Double *float64 String *string @@ -1211,12 +1235,12 @@ type serverApi struct { type ServerModelsApi serverApi -func (a *ServerModelsApi) List(ctx context.Context) (*ModelsListResult, error) { +func (a *ServerModelsApi) List(ctx context.Context) (*ModelList, error) { raw, err := a.client.Request("models.list", nil) if err != nil { return nil, err } - var result ModelsListResult + var result ModelList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1225,12 +1249,12 @@ func (a *ServerModelsApi) List(ctx context.Context) (*ModelsListResult, error) { type ServerToolsApi serverApi -func (a *ServerToolsApi) List(ctx context.Context, params *ToolsListParams) (*ToolsListResult, error) { +func (a *ServerToolsApi) List(ctx context.Context, params *ToolsListRequest) (*ToolList, error) { raw, err := a.client.Request("tools.list", params) if err != nil { return nil, err } - var result ToolsListResult + var result ToolList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1253,7 +1277,7 @@ func (a *ServerAccountApi) GetQuota(ctx context.Context) (*AccountGetQuotaResult type ServerMcpApi serverApi -func (a *ServerMcpApi) Discover(ctx context.Context, params *MCPDiscoverParams) (*MCPDiscoverResult, error) { +func (a *ServerMcpApi) Discover(ctx context.Context, params *MCPDiscoverRequest) (*MCPDiscoverResult, error) { raw, err := a.client.Request("mcp.discover", params) if err != nil { return nil, err @@ -1267,7 +1291,7 @@ func (a *ServerMcpApi) Discover(ctx context.Context, params *MCPDiscoverParams) type ServerSessionFsApi serverApi -func (a *ServerSessionFsApi) SetProvider(ctx context.Context, params *SessionFSSetProviderParams) (*SessionFSSetProviderResult, error) { +func (a *ServerSessionFsApi) SetProvider(ctx context.Context, params *SessionFSSetProviderRequest) (*SessionFSSetProviderResult, error) { raw, err := a.client.Request("sessionFs.setProvider", params) if err != nil { return nil, err @@ -1282,7 +1306,7 @@ func (a *ServerSessionFsApi) SetProvider(ctx context.Context, params *SessionFSS // Experimental: ServerSessionsApi contains experimental APIs that may change or be removed. type ServerSessionsApi serverApi -func (a *ServerSessionsApi) Fork(ctx context.Context, params *SessionsForkParams) (*SessionsForkResult, error) { +func (a *ServerSessionsApi) Fork(ctx context.Context, params *SessionsForkRequest) (*SessionsForkResult, error) { raw, err := a.client.Request("sessions.fork", params) if err != nil { return nil, err @@ -1306,7 +1330,7 @@ type ServerRpc struct { Sessions *ServerSessionsApi } -func (a *ServerRpc) Ping(ctx context.Context, params *PingParams) (*PingResult, error) { +func (a *ServerRpc) Ping(ctx context.Context, params *PingRequest) (*PingResult, error) { raw, err := a.common.client.Request("ping", params) if err != nil { return nil, err @@ -1337,20 +1361,20 @@ type sessionApi struct { type ModelApi sessionApi -func (a *ModelApi) GetCurrent(ctx context.Context) (*SessionModelGetCurrentResult, error) { +func (a *ModelApi) GetCurrent(ctx context.Context) (*CurrentModel, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.model.getCurrent", req) if err != nil { return nil, err } - var result SessionModelGetCurrentResult + var result CurrentModel if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ModelApi) SwitchTo(ctx context.Context, params *SessionModelSwitchToParams) (*SessionModelSwitchToResult, error) { +func (a *ModelApi) SwitchTo(ctx context.Context, params *ModelSwitchToRequest) (*ModelSwitchToResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["modelId"] = params.ModelID @@ -1365,7 +1389,7 @@ func (a *ModelApi) SwitchTo(ctx context.Context, params *SessionModelSwitchToPar if err != nil { return nil, err } - var result SessionModelSwitchToResult + var result ModelSwitchToResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1374,20 +1398,20 @@ func (a *ModelApi) SwitchTo(ctx context.Context, params *SessionModelSwitchToPar type ModeApi sessionApi -func (a *ModeApi) Get(ctx context.Context) (*SessionModeGetResult, error) { +func (a *ModeApi) Get(ctx context.Context) (*SessionMode, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.mode.get", req) if err != nil { return nil, err } - var result SessionModeGetResult + var result SessionMode if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ModeApi) Set(ctx context.Context, params *SessionModeSetParams) (*SessionModeSetResult, error) { +func (a *ModeApi) Set(ctx context.Context, params *ModeSetRequest) (*ModeSetResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["mode"] = params.Mode @@ -1396,7 +1420,7 @@ func (a *ModeApi) Set(ctx context.Context, params *SessionModeSetParams) (*Sessi if err != nil { return nil, err } - var result SessionModeSetResult + var result ModeSetResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1405,20 +1429,20 @@ func (a *ModeApi) Set(ctx context.Context, params *SessionModeSetParams) (*Sessi type PlanApi sessionApi -func (a *PlanApi) Read(ctx context.Context) (*SessionPlanReadResult, error) { +func (a *PlanApi) Read(ctx context.Context) (*PlanReadResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.plan.read", req) if err != nil { return nil, err } - var result SessionPlanReadResult + var result PlanReadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *PlanApi) Update(ctx context.Context, params *SessionPlanUpdateParams) (*SessionPlanUpdateResult, error) { +func (a *PlanApi) Update(ctx context.Context, params *PlanUpdateRequest) (*PlanUpdateResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["content"] = params.Content @@ -1427,20 +1451,20 @@ func (a *PlanApi) Update(ctx context.Context, params *SessionPlanUpdateParams) ( if err != nil { return nil, err } - var result SessionPlanUpdateResult + var result PlanUpdateResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *PlanApi) Delete(ctx context.Context) (*SessionPlanDeleteResult, error) { +func (a *PlanApi) Delete(ctx context.Context) (*PlanDeleteResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.plan.delete", req) if err != nil { return nil, err } - var result SessionPlanDeleteResult + var result PlanDeleteResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1449,20 +1473,20 @@ func (a *PlanApi) Delete(ctx context.Context) (*SessionPlanDeleteResult, error) type WorkspaceApi sessionApi -func (a *WorkspaceApi) ListFiles(ctx context.Context) (*SessionWorkspaceListFilesResult, error) { +func (a *WorkspaceApi) ListFiles(ctx context.Context) (*WorkspaceListFilesResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.workspace.listFiles", req) if err != nil { return nil, err } - var result SessionWorkspaceListFilesResult + var result WorkspaceListFilesResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *WorkspaceApi) ReadFile(ctx context.Context, params *SessionWorkspaceReadFileParams) (*SessionWorkspaceReadFileResult, error) { +func (a *WorkspaceApi) ReadFile(ctx context.Context, params *WorkspaceReadFileRequest) (*WorkspaceReadFileResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["path"] = params.Path @@ -1471,14 +1495,14 @@ func (a *WorkspaceApi) ReadFile(ctx context.Context, params *SessionWorkspaceRea if err != nil { return nil, err } - var result SessionWorkspaceReadFileResult + var result WorkspaceReadFileResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *WorkspaceApi) CreateFile(ctx context.Context, params *SessionWorkspaceCreateFileParams) (*SessionWorkspaceCreateFileResult, error) { +func (a *WorkspaceApi) CreateFile(ctx context.Context, params *WorkspaceCreateFileRequest) (*WorkspaceCreateFileResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["path"] = params.Path @@ -1488,7 +1512,7 @@ func (a *WorkspaceApi) CreateFile(ctx context.Context, params *SessionWorkspaceC if err != nil { return nil, err } - var result SessionWorkspaceCreateFileResult + var result WorkspaceCreateFileResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1498,7 +1522,7 @@ func (a *WorkspaceApi) CreateFile(ctx context.Context, params *SessionWorkspaceC // Experimental: FleetApi contains experimental APIs that may change or be removed. type FleetApi sessionApi -func (a *FleetApi) Start(ctx context.Context, params *SessionFleetStartParams) (*SessionFleetStartResult, error) { +func (a *FleetApi) Start(ctx context.Context, params *FleetStartRequest) (*FleetStartResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { if params.Prompt != nil { @@ -1509,7 +1533,7 @@ func (a *FleetApi) Start(ctx context.Context, params *SessionFleetStartParams) ( if err != nil { return nil, err } - var result SessionFleetStartResult + var result FleetStartResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1519,33 +1543,33 @@ func (a *FleetApi) Start(ctx context.Context, params *SessionFleetStartParams) ( // Experimental: AgentApi contains experimental APIs that may change or be removed. type AgentApi sessionApi -func (a *AgentApi) List(ctx context.Context) (*SessionAgentListResult, error) { +func (a *AgentApi) List(ctx context.Context) (*AgentList, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.agent.list", req) if err != nil { return nil, err } - var result SessionAgentListResult + var result AgentList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *AgentApi) GetCurrent(ctx context.Context) (*SessionAgentGetCurrentResult, error) { +func (a *AgentApi) GetCurrent(ctx context.Context) (*AgentGetCurrentResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.agent.getCurrent", req) if err != nil { return nil, err } - var result SessionAgentGetCurrentResult + var result AgentGetCurrentResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *AgentApi) Select(ctx context.Context, params *SessionAgentSelectParams) (*SessionAgentSelectResult, error) { +func (a *AgentApi) Select(ctx context.Context, params *AgentSelectRequest) (*AgentSelectResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["name"] = params.Name @@ -1554,33 +1578,33 @@ func (a *AgentApi) Select(ctx context.Context, params *SessionAgentSelectParams) if err != nil { return nil, err } - var result SessionAgentSelectResult + var result AgentSelectResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *AgentApi) Deselect(ctx context.Context) (*SessionAgentDeselectResult, error) { +func (a *AgentApi) Deselect(ctx context.Context) (*AgentDeselectResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.agent.deselect", req) if err != nil { return nil, err } - var result SessionAgentDeselectResult + var result AgentDeselectResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *AgentApi) Reload(ctx context.Context) (*SessionAgentReloadResult, error) { +func (a *AgentApi) Reload(ctx context.Context) (*AgentReloadResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.agent.reload", req) if err != nil { return nil, err } - var result SessionAgentReloadResult + var result AgentReloadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1590,20 +1614,20 @@ func (a *AgentApi) Reload(ctx context.Context) (*SessionAgentReloadResult, error // Experimental: SkillsApi contains experimental APIs that may change or be removed. type SkillsApi sessionApi -func (a *SkillsApi) List(ctx context.Context) (*SessionSkillsListResult, error) { +func (a *SkillsApi) List(ctx context.Context) (*SkillList, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.skills.list", req) if err != nil { return nil, err } - var result SessionSkillsListResult + var result SkillList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *SkillsApi) Enable(ctx context.Context, params *SessionSkillsEnableParams) (*SessionSkillsEnableResult, error) { +func (a *SkillsApi) Enable(ctx context.Context, params *SkillsEnableRequest) (*SkillsEnableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["name"] = params.Name @@ -1612,14 +1636,14 @@ func (a *SkillsApi) Enable(ctx context.Context, params *SessionSkillsEnableParam if err != nil { return nil, err } - var result SessionSkillsEnableResult + var result SkillsEnableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *SkillsApi) Disable(ctx context.Context, params *SessionSkillsDisableParams) (*SessionSkillsDisableResult, error) { +func (a *SkillsApi) Disable(ctx context.Context, params *SkillsDisableRequest) (*SkillsDisableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["name"] = params.Name @@ -1628,20 +1652,20 @@ func (a *SkillsApi) Disable(ctx context.Context, params *SessionSkillsDisablePar if err != nil { return nil, err } - var result SessionSkillsDisableResult + var result SkillsDisableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *SkillsApi) Reload(ctx context.Context) (*SessionSkillsReloadResult, error) { +func (a *SkillsApi) Reload(ctx context.Context) (*SkillsReloadResult, 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 SessionSkillsReloadResult + var result SkillsReloadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1651,20 +1675,20 @@ func (a *SkillsApi) Reload(ctx context.Context) (*SessionSkillsReloadResult, err // Experimental: McpApi contains experimental APIs that may change or be removed. type McpApi sessionApi -func (a *McpApi) List(ctx context.Context) (*SessionMCPListResult, error) { +func (a *McpApi) List(ctx context.Context) (*MCPServerList, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.list", req) if err != nil { return nil, err } - var result SessionMCPListResult + var result MCPServerList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpApi) Enable(ctx context.Context, params *SessionMCPEnableParams) (*SessionMCPEnableResult, error) { +func (a *McpApi) Enable(ctx context.Context, params *MCPEnableRequest) (*MCPEnableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -1673,14 +1697,14 @@ func (a *McpApi) Enable(ctx context.Context, params *SessionMCPEnableParams) (*S if err != nil { return nil, err } - var result SessionMCPEnableResult + var result MCPEnableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpApi) Disable(ctx context.Context, params *SessionMCPDisableParams) (*SessionMCPDisableResult, error) { +func (a *McpApi) Disable(ctx context.Context, params *MCPDisableRequest) (*MCPDisableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["serverName"] = params.ServerName @@ -1689,20 +1713,20 @@ func (a *McpApi) Disable(ctx context.Context, params *SessionMCPDisableParams) ( if err != nil { return nil, err } - var result SessionMCPDisableResult + var result MCPDisableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *McpApi) Reload(ctx context.Context) (*SessionMCPReloadResult, error) { +func (a *McpApi) Reload(ctx context.Context) (*MCPReloadResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.mcp.reload", req) if err != nil { return nil, err } - var result SessionMCPReloadResult + var result MCPReloadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1712,13 +1736,13 @@ func (a *McpApi) Reload(ctx context.Context) (*SessionMCPReloadResult, error) { // Experimental: PluginsApi contains experimental APIs that may change or be removed. type PluginsApi sessionApi -func (a *PluginsApi) List(ctx context.Context) (*SessionPluginsListResult, error) { +func (a *PluginsApi) List(ctx context.Context) (*PluginList, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.plugins.list", req) if err != nil { return nil, err } - var result SessionPluginsListResult + var result PluginList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1728,20 +1752,20 @@ func (a *PluginsApi) List(ctx context.Context) (*SessionPluginsListResult, error // Experimental: ExtensionsApi contains experimental APIs that may change or be removed. type ExtensionsApi sessionApi -func (a *ExtensionsApi) List(ctx context.Context) (*SessionExtensionsListResult, error) { +func (a *ExtensionsApi) List(ctx context.Context) (*ExtensionList, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.extensions.list", req) if err != nil { return nil, err } - var result SessionExtensionsListResult + var result ExtensionList if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ExtensionsApi) Enable(ctx context.Context, params *SessionExtensionsEnableParams) (*SessionExtensionsEnableResult, error) { +func (a *ExtensionsApi) Enable(ctx context.Context, params *ExtensionsEnableRequest) (*ExtensionsEnableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["id"] = params.ID @@ -1750,14 +1774,14 @@ func (a *ExtensionsApi) Enable(ctx context.Context, params *SessionExtensionsEna if err != nil { return nil, err } - var result SessionExtensionsEnableResult + var result ExtensionsEnableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ExtensionsApi) Disable(ctx context.Context, params *SessionExtensionsDisableParams) (*SessionExtensionsDisableResult, error) { +func (a *ExtensionsApi) Disable(ctx context.Context, params *ExtensionsDisableRequest) (*ExtensionsDisableResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["id"] = params.ID @@ -1766,20 +1790,20 @@ func (a *ExtensionsApi) Disable(ctx context.Context, params *SessionExtensionsDi if err != nil { return nil, err } - var result SessionExtensionsDisableResult + var result ExtensionsDisableResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ExtensionsApi) Reload(ctx context.Context) (*SessionExtensionsReloadResult, error) { +func (a *ExtensionsApi) Reload(ctx context.Context) (*ExtensionsReloadResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.extensions.reload", req) if err != nil { return nil, err } - var result SessionExtensionsReloadResult + var result ExtensionsReloadResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1788,7 +1812,7 @@ func (a *ExtensionsApi) Reload(ctx context.Context) (*SessionExtensionsReloadRes type ToolsApi sessionApi -func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *SessionToolsHandlePendingToolCallParams) (*SessionToolsHandlePendingToolCallResult, error) { +func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *ToolsHandlePendingToolCallRequest) (*HandleToolCallResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -1803,7 +1827,7 @@ func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *SessionToo if err != nil { return nil, err } - var result SessionToolsHandlePendingToolCallResult + var result HandleToolCallResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1812,7 +1836,7 @@ func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *SessionToo type CommandsApi sessionApi -func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *SessionCommandsHandlePendingCommandParams) (*SessionCommandsHandlePendingCommandResult, error) { +func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *CommandsHandlePendingCommandRequest) (*CommandsHandlePendingCommandResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -1824,7 +1848,7 @@ func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *SessionC if err != nil { return nil, err } - var result SessionCommandsHandlePendingCommandResult + var result CommandsHandlePendingCommandResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1833,7 +1857,7 @@ func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *SessionC type UIApi sessionApi -func (a *UIApi) Elicitation(ctx context.Context, params *SessionUIElicitationParams) (*SessionUIElicitationResult, error) { +func (a *UIApi) Elicitation(ctx context.Context, params *UIElicitationRequest) (*UIElicitationResponse, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["message"] = params.Message @@ -1843,14 +1867,14 @@ func (a *UIApi) Elicitation(ctx context.Context, params *SessionUIElicitationPar if err != nil { return nil, err } - var result SessionUIElicitationResult + var result UIElicitationResponse if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *UIApi) HandlePendingElicitation(ctx context.Context, params *SessionUIHandlePendingElicitationParams) (*SessionUIHandlePendingElicitationResult, error) { +func (a *UIApi) HandlePendingElicitation(ctx context.Context, params *UIHandlePendingElicitationRequest) (*UIElicitationResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -1860,7 +1884,7 @@ func (a *UIApi) HandlePendingElicitation(ctx context.Context, params *SessionUIH if err != nil { return nil, err } - var result SessionUIHandlePendingElicitationResult + var result UIElicitationResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1869,7 +1893,7 @@ func (a *UIApi) HandlePendingElicitation(ctx context.Context, params *SessionUIH type PermissionsApi sessionApi -func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, params *SessionPermissionsHandlePendingPermissionRequestParams) (*SessionPermissionsHandlePendingPermissionRequestResult, error) { +func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, params *PermissionDecisionRequest) (*PermissionRequestResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -1879,7 +1903,7 @@ func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, par if err != nil { return nil, err } - var result SessionPermissionsHandlePendingPermissionRequestResult + var result PermissionRequestResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1888,7 +1912,7 @@ func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, par type ShellApi sessionApi -func (a *ShellApi) Exec(ctx context.Context, params *SessionShellExecParams) (*SessionShellExecResult, error) { +func (a *ShellApi) Exec(ctx context.Context, params *ShellExecRequest) (*ShellExecResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["command"] = params.Command @@ -1903,14 +1927,14 @@ func (a *ShellApi) Exec(ctx context.Context, params *SessionShellExecParams) (*S if err != nil { return nil, err } - var result SessionShellExecResult + var result ShellExecResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *ShellApi) Kill(ctx context.Context, params *SessionShellKillParams) (*SessionShellKillResult, error) { +func (a *ShellApi) Kill(ctx context.Context, params *ShellKillRequest) (*ShellKillResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["processId"] = params.ProcessID @@ -1922,7 +1946,7 @@ func (a *ShellApi) Kill(ctx context.Context, params *SessionShellKillParams) (*S if err != nil { return nil, err } - var result SessionShellKillResult + var result ShellKillResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1932,20 +1956,20 @@ func (a *ShellApi) Kill(ctx context.Context, params *SessionShellKillParams) (*S // Experimental: HistoryApi contains experimental APIs that may change or be removed. type HistoryApi sessionApi -func (a *HistoryApi) Compact(ctx context.Context) (*SessionHistoryCompactResult, error) { +func (a *HistoryApi) Compact(ctx context.Context) (*HistoryCompactResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.history.compact", req) if err != nil { return nil, err } - var result SessionHistoryCompactResult + var result HistoryCompactResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *HistoryApi) Truncate(ctx context.Context, params *SessionHistoryTruncateParams) (*SessionHistoryTruncateResult, error) { +func (a *HistoryApi) Truncate(ctx context.Context, params *HistoryTruncateRequest) (*HistoryTruncateResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["eventId"] = params.EventID @@ -1954,7 +1978,7 @@ func (a *HistoryApi) Truncate(ctx context.Context, params *SessionHistoryTruncat if err != nil { return nil, err } - var result SessionHistoryTruncateResult + var result HistoryTruncateResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -1964,13 +1988,13 @@ func (a *HistoryApi) Truncate(ctx context.Context, params *SessionHistoryTruncat // Experimental: UsageApi contains experimental APIs that may change or be removed. type UsageApi sessionApi -func (a *UsageApi) GetMetrics(ctx context.Context) (*SessionUsageGetMetricsResult, error) { +func (a *UsageApi) GetMetrics(ctx context.Context) (*UsageGetMetricsResult, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.usage.getMetrics", req) if err != nil { return nil, err } - var result SessionUsageGetMetricsResult + var result UsageGetMetricsResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -2000,7 +2024,7 @@ type SessionRpc struct { Usage *UsageApi } -func (a *SessionRpc) Log(ctx context.Context, params *SessionLogParams) (*SessionLogResult, error) { +func (a *SessionRpc) Log(ctx context.Context, params *LogRequest) (*LogResult, error) { req := map[string]any{"sessionId": a.common.sessionID} if params != nil { req["message"] = params.Message @@ -2018,7 +2042,7 @@ func (a *SessionRpc) Log(ctx context.Context, params *SessionLogParams) (*Sessio if err != nil { return nil, err } - var result SessionLogResult + var result LogResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -2049,16 +2073,16 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { } type SessionFsHandler interface { - ReadFile(request *SessionFSReadFileParams) (*SessionFSReadFileResult, error) - WriteFile(request *SessionFSWriteFileParams) error - AppendFile(request *SessionFSAppendFileParams) error - Exists(request *SessionFSExistsParams) (*SessionFSExistsResult, error) - Stat(request *SessionFSStatParams) (*SessionFSStatResult, error) - Mkdir(request *SessionFSMkdirParams) error - Readdir(request *SessionFSReaddirParams) (*SessionFSReaddirResult, error) - ReaddirWithTypes(request *SessionFSReaddirWithTypesParams) (*SessionFSReaddirWithTypesResult, error) - Rm(request *SessionFSRmParams) error - Rename(request *SessionFSRenameParams) error + ReadFile(request *SessionFSReadFileRequest) (*SessionFSReadFileResult, error) + WriteFile(request *SessionFSWriteFileRequest) (*SessionFSWriteFileResult, error) + AppendFile(request *SessionFSAppendFileRequest) (*SessionFSAppendFileResult, error) + Exists(request *SessionFSExistsRequest) (*SessionFSExistsResult, error) + Stat(request *SessionFSStatRequest) (*SessionFSStatResult, error) + Mkdir(request *SessionFSMkdirRequest) (*SessionFSMkdirResult, error) + Readdir(request *SessionFSReaddirRequest) (*SessionFSReaddirResult, error) + ReaddirWithTypes(request *SessionFSReaddirWithTypesRequest) (*SessionFSReaddirWithTypesResult, error) + Rm(request *SessionFSRmRequest) (*SessionFSRmResult, error) + Rename(request *SessionFSRenameRequest) (*SessionFSRenameResult, error) } // ClientSessionApiHandlers provides all client session API handler groups for a session. @@ -2080,7 +2104,7 @@ func clientSessionHandlerError(err error) *jsonrpc2.Error { // RegisterClientSessionApiHandlers registers handlers for server-to-client session API calls. func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func(sessionID string) *ClientSessionApiHandlers) { client.SetRequestHandler("sessionFs.readFile", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSReadFileParams + var request SessionFSReadFileRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2099,7 +2123,7 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( return raw, nil }) client.SetRequestHandler("sessionFs.writeFile", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSWriteFileParams + var request SessionFSWriteFileRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2107,13 +2131,18 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( if handlers == nil || handlers.SessionFs == nil { return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} } - if err := handlers.SessionFs.WriteFile(&request); err != nil { + result, err := handlers.SessionFs.WriteFile(&request) + if err != nil { return nil, clientSessionHandlerError(err) } - return json.RawMessage("null"), nil + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil }) client.SetRequestHandler("sessionFs.appendFile", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSAppendFileParams + var request SessionFSAppendFileRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2121,13 +2150,18 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( if handlers == nil || handlers.SessionFs == nil { return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} } - if err := handlers.SessionFs.AppendFile(&request); err != nil { + result, err := handlers.SessionFs.AppendFile(&request) + if err != nil { return nil, clientSessionHandlerError(err) } - return json.RawMessage("null"), nil + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil }) client.SetRequestHandler("sessionFs.exists", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSExistsParams + var request SessionFSExistsRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2146,7 +2180,7 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( return raw, nil }) client.SetRequestHandler("sessionFs.stat", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSStatParams + var request SessionFSStatRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2165,7 +2199,7 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( return raw, nil }) client.SetRequestHandler("sessionFs.mkdir", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSMkdirParams + var request SessionFSMkdirRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2173,13 +2207,18 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( if handlers == nil || handlers.SessionFs == nil { return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} } - if err := handlers.SessionFs.Mkdir(&request); err != nil { + result, err := handlers.SessionFs.Mkdir(&request) + if err != nil { return nil, clientSessionHandlerError(err) } - return json.RawMessage("null"), nil + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil }) client.SetRequestHandler("sessionFs.readdir", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSReaddirParams + var request SessionFSReaddirRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2198,7 +2237,7 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( return raw, nil }) client.SetRequestHandler("sessionFs.readdirWithTypes", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSReaddirWithTypesParams + var request SessionFSReaddirWithTypesRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2217,7 +2256,7 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( return raw, nil }) client.SetRequestHandler("sessionFs.rm", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSRmParams + var request SessionFSRmRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2225,13 +2264,18 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( if handlers == nil || handlers.SessionFs == nil { return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} } - if err := handlers.SessionFs.Rm(&request); err != nil { + result, err := handlers.SessionFs.Rm(&request) + if err != nil { return nil, clientSessionHandlerError(err) } - return json.RawMessage("null"), nil + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil }) client.SetRequestHandler("sessionFs.rename", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { - var request SessionFSRenameParams + var request SessionFSRenameRequest if err := json.Unmarshal(params, &request); err != nil { return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} } @@ -2239,9 +2283,14 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( if handlers == nil || handlers.SessionFs == nil { return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} } - if err := handlers.SessionFs.Rename(&request); err != nil { + result, err := handlers.SessionFs.Rename(&request) + if err != nil { return nil, clientSessionHandlerError(err) } - return json.RawMessage("null"), nil + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil }) } diff --git a/go/rpc/result_union.go b/go/rpc/result_union.go index 6cd948b50..aabfe6553 100644 --- a/go/rpc/result_union.go +++ b/go/rpc/result_union.go @@ -2,33 +2,33 @@ package rpc import "encoding/json" -// MarshalJSON serializes ResultUnion as the appropriate JSON variant: -// a plain string when String is set, or the ResultResult object otherwise. +// MarshalJSON serializes ToolsHandlePendingToolCall as the appropriate JSON variant: +// a plain string when String is set, or the ToolCallResult object otherwise. // The generated struct has no custom marshaler, so without this the Go -// struct fields would serialize as {"ResultResult":...,"String":...} +// struct fields would serialize as {"ToolCallResult":...,"String":...} // instead of the union the server expects. -func (r ResultUnion) MarshalJSON() ([]byte, error) { +func (r ToolsHandlePendingToolCall) MarshalJSON() ([]byte, error) { if r.String != nil { return json.Marshal(*r.String) } - if r.ResultResult != nil { - return json.Marshal(*r.ResultResult) + if r.ToolCallResult != nil { + return json.Marshal(*r.ToolCallResult) } return []byte("null"), nil } -// UnmarshalJSON deserializes a JSON value into the appropriate ResultUnion variant. -func (r *ResultUnion) UnmarshalJSON(data []byte) error { +// UnmarshalJSON deserializes a JSON value into the appropriate ToolsHandlePendingToolCall variant. +func (r *ToolsHandlePendingToolCall) UnmarshalJSON(data []byte) error { // Try string first var s string if err := json.Unmarshal(data, &s); err == nil { r.String = &s return nil } - // Try ResultResult object - var rr ResultResult + // Try ToolCallResult object + var rr ToolCallResult if err := json.Unmarshal(data, &rr); err == nil { - r.ResultResult = &rr + r.ToolCallResult = &rr return nil } return nil diff --git a/go/session.go b/go/session.go index fde0d9875..a2e52e72c 100644 --- a/go/session.go +++ b/go/session.go @@ -533,7 +533,7 @@ func (s *Session) executeCommandAndRespond(requestID, commandName, command, args handler, ok := s.getCommandHandler(commandName) if !ok { errMsg := fmt.Sprintf("Unknown command: %s", commandName) - s.RPC.Commands.HandlePendingCommand(ctx, &rpc.SessionCommandsHandlePendingCommandParams{ + s.RPC.Commands.HandlePendingCommand(ctx, &rpc.CommandsHandlePendingCommandRequest{ RequestID: requestID, Error: &errMsg, }) @@ -549,14 +549,14 @@ func (s *Session) executeCommandAndRespond(requestID, commandName, command, args if err := handler(cmdCtx); err != nil { errMsg := err.Error() - s.RPC.Commands.HandlePendingCommand(ctx, &rpc.SessionCommandsHandlePendingCommandParams{ + s.RPC.Commands.HandlePendingCommand(ctx, &rpc.CommandsHandlePendingCommandRequest{ RequestID: requestID, Error: &errMsg, }) return } - s.RPC.Commands.HandlePendingCommand(ctx, &rpc.SessionCommandsHandlePendingCommandParams{ + s.RPC.Commands.HandlePendingCommand(ctx, &rpc.CommandsHandlePendingCommandRequest{ RequestID: requestID, }) } @@ -588,35 +588,35 @@ func (s *Session) handleElicitationRequest(elicitCtx ElicitationContext, request result, err := handler(elicitCtx) if err != nil { // Handler failed — attempt to cancel so the request doesn't hang. - s.RPC.UI.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ + s.RPC.UI.HandlePendingElicitation(ctx, &rpc.UIHandlePendingElicitationRequest{ RequestID: requestID, - Result: rpc.SessionUIHandlePendingElicitationParamsResult{ - Action: rpc.ActionCancel, + Result: rpc.UIElicitationResponse{ + Action: rpc.UIElicitationResponseActionCancel, }, }) return } - rpcContent := make(map[string]*rpc.Content) + rpcContent := make(map[string]*rpc.UIElicitationFieldValue) for k, v := range result.Content { rpcContent[k] = toRPCContent(v) } - s.RPC.UI.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ + s.RPC.UI.HandlePendingElicitation(ctx, &rpc.UIHandlePendingElicitationRequest{ RequestID: requestID, - Result: rpc.SessionUIHandlePendingElicitationParamsResult{ - Action: rpc.Action(result.Action), + Result: rpc.UIElicitationResponse{ + Action: rpc.UIElicitationResponseAction(result.Action), Content: rpcContent, }, }) } -// toRPCContent converts an arbitrary value to a *rpc.Content for elicitation responses. -func toRPCContent(v any) *rpc.Content { +// toRPCContent converts an arbitrary value to a *rpc.UIElicitationFieldValue for elicitation responses. +func toRPCContent(v any) *rpc.UIElicitationFieldValue { if v == nil { return nil } - c := &rpc.Content{} + c := &rpc.UIElicitationFieldValue{} switch val := v.(type) { case bool: c.Bool = &val @@ -679,11 +679,11 @@ func (s *Session) assertElicitation() error { } // Elicitation shows a generic elicitation dialog with a custom schema. -func (ui *SessionUI) Elicitation(ctx context.Context, message string, requestedSchema rpc.RequestedSchema) (*ElicitationResult, error) { +func (ui *SessionUI) Elicitation(ctx context.Context, message string, requestedSchema rpc.UIElicitationSchema) (*ElicitationResult, error) { if err := ui.session.assertElicitation(); err != nil { return nil, err } - rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: requestedSchema, }) @@ -699,14 +699,14 @@ func (ui *SessionUI) Confirm(ctx context.Context, message string) (bool, error) if err := ui.session.assertElicitation(); err != nil { return false, err } - defaultTrue := &rpc.Content{Bool: Bool(true)} - rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + defaultTrue := &rpc.UIElicitationFieldValue{Bool: Bool(true)} + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, - RequestedSchema: rpc.RequestedSchema{ + RequestedSchema: rpc.UIElicitationSchema{ Type: rpc.RequestedSchemaTypeObject, - Properties: map[string]rpc.Property{ + Properties: map[string]rpc.UIElicitationSchemaProperty{ "confirmed": { - Type: rpc.PropertyTypeBoolean, + Type: rpc.UIElicitationSchemaPropertyNumberTypeBoolean, Default: defaultTrue, }, }, @@ -716,7 +716,7 @@ func (ui *SessionUI) Confirm(ctx context.Context, message string) (bool, error) if err != nil { return false, err } - if rpcResult.Action == rpc.ActionAccept { + if rpcResult.Action == rpc.UIElicitationResponseActionAccept { if c, ok := rpcResult.Content["confirmed"]; ok && c != nil && c.Bool != nil { return *c.Bool, nil } @@ -730,13 +730,13 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin if err := ui.session.assertElicitation(); err != nil { return "", false, err } - rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, - RequestedSchema: rpc.RequestedSchema{ + RequestedSchema: rpc.UIElicitationSchema{ Type: rpc.RequestedSchemaTypeObject, - Properties: map[string]rpc.Property{ + Properties: map[string]rpc.UIElicitationSchemaProperty{ "selection": { - Type: rpc.PropertyTypeString, + Type: rpc.UIElicitationSchemaPropertyNumberTypeString, Enum: options, }, }, @@ -746,7 +746,7 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin if err != nil { return "", false, err } - if rpcResult.Action == rpc.ActionAccept { + if rpcResult.Action == rpc.UIElicitationResponseActionAccept { if c, ok := rpcResult.Content["selection"]; ok && c != nil && c.String != nil { return *c.String, true, nil } @@ -760,7 +760,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio if err := ui.session.assertElicitation(); err != nil { return "", false, err } - prop := rpc.Property{Type: rpc.PropertyTypeString} + prop := rpc.UIElicitationSchemaProperty{Type: rpc.UIElicitationSchemaPropertyNumberTypeString} if opts != nil { if opts.Title != "" { prop.Title = &opts.Title @@ -777,18 +777,18 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio prop.MaxLength = &f } if opts.Format != "" { - format := rpc.Format(opts.Format) + format := rpc.UIElicitationSchemaPropertyStringFormat(opts.Format) prop.Format = &format } if opts.Default != "" { - prop.Default = &rpc.Content{String: &opts.Default} + prop.Default = &rpc.UIElicitationFieldValue{String: &opts.Default} } } - rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, - RequestedSchema: rpc.RequestedSchema{ + RequestedSchema: rpc.UIElicitationSchema{ Type: rpc.RequestedSchemaTypeObject, - Properties: map[string]rpc.Property{ + Properties: map[string]rpc.UIElicitationSchemaProperty{ "value": prop, }, Required: []string{"value"}, @@ -797,7 +797,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio if err != nil { return "", false, err } - if rpcResult.Action == rpc.ActionAccept { + if rpcResult.Action == rpc.UIElicitationResponseActionAccept { if c, ok := rpcResult.Content["value"]; ok && c != nil && c.String != nil { return *c.String, true, nil } @@ -806,7 +806,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio } // fromRPCElicitationResult converts the RPC result to the SDK ElicitationResult. -func fromRPCElicitationResult(r *rpc.SessionUIElicitationResult) *ElicitationResult { +func fromRPCElicitationResult(r *rpc.UIElicitationResponse) *ElicitationResult { if r == nil { return nil } @@ -965,7 +965,7 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, defer func() { if r := recover(); r != nil { errMsg := fmt.Sprintf("tool panic: %v", r) - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ RequestID: requestID, Error: &errMsg, }) @@ -983,7 +983,7 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, result, err := handler(invocation) if err != nil { errMsg := err.Error() - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ RequestID: requestID, Error: &errMsg, }) @@ -1005,17 +1005,17 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, } } - rpcResult := rpc.ResultUnion{ - ResultResult: &rpc.ResultResult{ + rpcResult := rpc.ToolsHandlePendingToolCall{ + ToolCallResult: &rpc.ToolCallResult{ TextResultForLlm: textResultForLLM, ToolTelemetry: result.ToolTelemetry, ResultType: &effectiveResultType, }, } if result.Error != "" { - rpcResult.ResultResult.Error = &result.Error + rpcResult.ToolCallResult.Error = &result.Error } - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.SessionToolsHandlePendingToolCallParams{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ RequestID: requestID, Result: &rpcResult, }) @@ -1025,9 +1025,9 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, func (s *Session) executePermissionAndRespond(requestID string, permissionRequest PermissionRequest, handler PermissionHandlerFunc) { defer func() { if r := recover(); r != nil { - s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.SessionPermissionsHandlePendingPermissionRequestParams{ + s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, - Result: rpc.SessionPermissionsHandlePendingPermissionRequestParamsResult{ + Result: rpc.PermissionDecision{ Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) @@ -1040,9 +1040,9 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques result, err := handler(permissionRequest, invocation) if err != nil { - s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.SessionPermissionsHandlePendingPermissionRequestParams{ + s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, - Result: rpc.SessionPermissionsHandlePendingPermissionRequestParamsResult{ + Result: rpc.PermissionDecision{ Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) @@ -1052,9 +1052,9 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques return } - s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.SessionPermissionsHandlePendingPermissionRequestParams{ + s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, - Result: rpc.SessionPermissionsHandlePendingPermissionRequestParamsResult{ + Result: rpc.PermissionDecision{ Kind: rpc.Kind(result.Kind), Rules: result.Rules, Feedback: nil, @@ -1209,7 +1209,7 @@ type SetModelOptions struct { // log.Printf("Failed to set model: %v", err) // } func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOptions) error { - params := &rpc.SessionModelSwitchToParams{ModelID: model} + params := &rpc.ModelSwitchToRequest{ModelID: model} if opts != nil { params.ReasoningEffort = opts.ReasoningEffort params.ModelCapabilities = opts.ModelCapabilities @@ -1224,9 +1224,9 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti // LogOptions configures optional parameters for [Session.Log]. type LogOptions struct { - // Level sets the log severity. Valid values are [rpc.LevelInfo] (default), - // [rpc.LevelWarning], and [rpc.LevelError]. - Level rpc.Level + // Level sets the log severity. Valid values are [rpc.SessionLogLevelInfo] (default), + // [rpc.SessionLogLevelWarning], and [rpc.SessionLogLevelError]. + Level rpc.SessionLogLevel // Ephemeral marks the message as transient so it is not persisted // to the session event log on disk. When nil the server decides the // default; set to a non-nil value to explicitly control persistence. @@ -1245,12 +1245,12 @@ type LogOptions struct { // session.Log(ctx, "Processing started") // // // Warning with options -// session.Log(ctx, "Rate limit approaching", &copilot.LogOptions{Level: rpc.LevelWarning}) +// session.Log(ctx, "Rate limit approaching", &copilot.LogOptions{Level: rpc.SessionLogLevelWarning}) // // // Ephemeral message (not persisted) // session.Log(ctx, "Working...", &copilot.LogOptions{Ephemeral: copilot.Bool(true)}) func (s *Session) Log(ctx context.Context, message string, opts *LogOptions) error { - params := &rpc.SessionLogParams{Message: message} + params := &rpc.LogRequest{Message: message} if opts != nil { if opts.Level != "" { diff --git a/go/session_test.go b/go/session_test.go index 7f22028db..845b2107d 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -403,7 +403,7 @@ func TestSession_Capabilities(t *testing.T) { session.dispatchEvent(SessionEvent{ Type: SessionEventTypeCapabilitiesChanged, Data: &CapabilitiesChangedData{ - UI: &CapabilitiesChangedDataUI{Elicitation: &elicitTrue}, + UI: &CapabilitiesChangedUI{Elicitation: &elicitTrue}, }, }) @@ -420,7 +420,7 @@ func TestSession_Capabilities(t *testing.T) { session.dispatchEvent(SessionEvent{ Type: SessionEventTypeCapabilitiesChanged, Data: &CapabilitiesChangedData{ - UI: &CapabilitiesChangedDataUI{Elicitation: &elicitFalse}, + UI: &CapabilitiesChangedUI{Elicitation: &elicitFalse}, }, }) diff --git a/go/types.go b/go/types.go index 568bcc1b9..0e0370ed2 100644 --- a/go/types.go +++ b/go/types.go @@ -474,7 +474,7 @@ type SessionFsConfig struct { // session-scoped files such as events, checkpoints, and temp files. SessionStatePath string // Conventions identifies the path conventions used by this filesystem provider. - Conventions rpc.Conventions + Conventions rpc.SessionFSSetProviderConventions } // SessionConfig configures a new session diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 55c3a4f24..cc4407bbb 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.22", + "@github/copilot": "^1.0.26-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.22.tgz", - "integrity": "sha512-BR9oTJ1tQ51RV81xcxmlZe0zB3Tf8i/vFsKSTm2f5wRLJgtuVl2LgaFStoI/peTFcmgtZbhrqsnWTu5GkEPK5Q==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26-0.tgz", + "integrity": "sha512-MHeddlLZCi5OFeuzKRtj7kmJVm1o/teNwgrL5/FHU9x0H6VioG+KGlY6gd1H/cTJ763dtYQyACMPYFUNVVY52g==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.22", - "@github/copilot-darwin-x64": "1.0.22", - "@github/copilot-linux-arm64": "1.0.22", - "@github/copilot-linux-x64": "1.0.22", - "@github/copilot-win32-arm64": "1.0.22", - "@github/copilot-win32-x64": "1.0.22" + "@github/copilot-darwin-arm64": "1.0.26-0", + "@github/copilot-darwin-x64": "1.0.26-0", + "@github/copilot-linux-arm64": "1.0.26-0", + "@github/copilot-linux-x64": "1.0.26-0", + "@github/copilot-win32-arm64": "1.0.26-0", + "@github/copilot-win32-x64": "1.0.26-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.22.tgz", - "integrity": "sha512-cK42uX+oz46Cjsb7z+rdPw+DIGczfVSFWlc1WDcdVlwBW4cEfV0pzFXExpN1r1z179TFgAaVMbhkgLqhOZ/PeQ==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26-0.tgz", + "integrity": "sha512-C1GP4qrKjCjPoKr485o0IbcP3n1q/4LxKwAhpga0V+9ZHlvggZ58YB9AaUFySJ+Alpu1vBlw/FFpD9amroasvw==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.22.tgz", - "integrity": "sha512-Pmw0ipF+yeLbP6JctsEoMS2LUCpVdC2r557BnCoe48BN8lO8i9JLnkpuDDrJ1AZuCk1VjnujFKEQywOOdfVlpA==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26-0.tgz", + "integrity": "sha512-A/HSuoCe8i5+yc5yCi4ZMi6PQfOOExA0wwpN13zFKwmqDwdNdogb4/wX42DoGr7JwuOGhZSzXCEZirt/lqqxjQ==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.22.tgz", - "integrity": "sha512-WVgG67VmZgHoD7GMlkTxEVe1qK8k9Ek9A02/Da7obpsDdtBInt3nJTwBEgm4cNDM4XaenQH17/jmwVtTwXB6lw==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26-0.tgz", + "integrity": "sha512-goMPZkMi5dCqA1JHbgsxaUKOmtZ6juBAeUfVomtKmdKee1KC74TFXlEuP8qJMGkeug2yivPOptAfQQXSyJJnHw==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.22.tgz", - "integrity": "sha512-XRkHVFmdC7FMrczXOdPjbNKiknMr13asKtwJoErJO/Xdy4cmzKQHSvNsBk8VNrr7oyWrUcB1F6mbIxb2LFxPOw==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26-0.tgz", + "integrity": "sha512-oK6uQ0Q0ZUO9IM3B+KJb9wyRHG5ZGP5qoTOOTN7JcC+p8ZveNSGCAHUAtzLSflUREJUFYfRZauUKcfV31/Y2LA==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.22.tgz", - "integrity": "sha512-Ao6gv1f2ZV+HVlkB1MV7YFdCuaB3NcFCnNu0a6/WLl2ypsfP1vWosPPkIB32jQJeBkT9ku3exOZLRj+XC0P3Mg==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26-0.tgz", + "integrity": "sha512-VXwm8xryO3cUHydVkzmSzb0M3WonwGDHCcgwI2GGS2YkHB9VjmRbdpVeLYeDB5EzmyZLSd7Nr4+i2X0gsU93ow==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.22.tgz", - "integrity": "sha512-EppcL+3TpxC+X/eQEIYtkN0PaA3/cvtI9UJqldLIkKDPXNYk/0mw877Ru9ypRcBWBWokDN6iKIWk5IxYH+JIvg==", + "version": "1.0.26-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26-0.tgz", + "integrity": "sha512-+4IFUZbYSg5jxchEFdgVEgSDJzDE/P3nRDtEBcIhpYlVb7/zAw2JCkCJr+i4Aruo4zysJnEybL0wM3TpcWTt/g==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 6a0ef9567..f4a3a2188 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.22", + "@github/copilot": "^1.0.26-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 1733e5cd9..d8d4cceca 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -20,14 +20,14 @@ export interface PingResult { protocolVersion: number; } -export interface PingParams { +export interface PingRequest { /** * Optional message to echo back */ message?: string; } -export interface ModelsListResult { +export interface ModelList { /** * List of available models with full metadata */ @@ -77,59 +77,56 @@ export interface ModelsListResult { * Model capabilities and limits */ export interface ModelCapabilities { - supports: ModelCapabilitiesSupports; - limits: ModelCapabilitiesLimits; -} -/** - * Feature flags indicating what the model supports - */ -export interface ModelCapabilitiesSupports { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; -} -/** - * Token limits for prompts, outputs, and context window - */ -export interface ModelCapabilitiesLimits { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; /** - * Maximum number of output/completion tokens + * Feature flags indicating what the model supports */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens: number; - vision?: ModelCapabilitiesLimitsVision; -} -/** - * Vision-specific limits - */ -export interface ModelCapabilitiesLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; + supports: { + /** + * Whether this model supports vision/image input + */ + vision?: boolean; + /** + * Whether this model supports reasoning effort configuration + */ + reasoningEffort?: boolean; + }; /** - * Maximum image size in bytes + * Token limits for prompts, outputs, and context window */ - max_prompt_image_size: number; + limits: { + /** + * Maximum number of prompt/input tokens + */ + max_prompt_tokens?: number; + /** + * Maximum number of output/completion tokens + */ + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens: number; + /** + * Vision-specific limits + */ + vision?: { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; + }; + }; } -export interface ToolsListResult { +export interface ToolList { /** * List of available built-in tools with metadata */ @@ -159,7 +156,7 @@ export interface ToolsListResult { }[]; } -export interface ToolsListParams { +export interface ToolsListRequest { /** * Optional model ID — when provided, the returned tool list reflects model-specific overrides */ @@ -200,7 +197,7 @@ export interface AccountGetQuotaResult { }; } -export interface McpConfigListResult { +export interface McpConfigList { /** * All MCP servers from user config, keyed by name */ @@ -221,6 +218,9 @@ export interface McpConfigListResult { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; command: string; args: string[]; @@ -241,6 +241,9 @@ export interface McpConfigListResult { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; url: string; headers?: { @@ -252,7 +255,7 @@ export interface McpConfigListResult { }; } -export interface McpConfigAddParams { +export interface McpConfigAddRequest { /** * Unique name for the MCP server */ @@ -273,6 +276,9 @@ export interface McpConfigAddParams { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; command: string; args: string[]; @@ -293,6 +299,9 @@ export interface McpConfigAddParams { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; url: string; headers?: { @@ -303,7 +312,7 @@ export interface McpConfigAddParams { }; } -export interface McpConfigUpdateParams { +export interface McpConfigUpdateRequest { /** * Name of the MCP server to update */ @@ -324,6 +333,9 @@ export interface McpConfigUpdateParams { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; command: string; args: string[]; @@ -344,6 +356,9 @@ export interface McpConfigUpdateParams { [k: string]: "none" | "markdown" | "hidden_characters"; } | ("none" | "markdown" | "hidden_characters"); + /** + * Timeout in milliseconds for tool calls to this server. + */ timeout?: number; url: string; headers?: { @@ -354,7 +369,7 @@ export interface McpConfigUpdateParams { }; } -export interface McpConfigRemoveParams { +export interface McpConfigRemoveRequest { /** * Name of the MCP server to remove */ @@ -373,9 +388,9 @@ export interface DiscoveredMcpServer { */ name: string; /** - * Server type: local, stdio, http, or sse + * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) */ - type?: string; + type?: "stdio" | "http" | "sse" | "memory"; /** * Configuration source */ @@ -386,7 +401,7 @@ export interface DiscoveredMcpServer { enabled: boolean; } -export interface McpDiscoverParams { +export interface McpDiscoverRequest { /** * Working directory used as context for discovery (e.g., plugin resolution) */ @@ -400,7 +415,7 @@ export interface SessionFsSetProviderResult { success: boolean; } -export interface SessionFsSetProviderParams { +export interface SessionFsSetProviderRequest { /** * Initial working directory for sessions */ @@ -424,7 +439,7 @@ export interface SessionsForkResult { } /** @experimental */ -export interface SessionsForkParams { +export interface SessionsForkRequest { /** * Source session ID to fork from */ @@ -435,28 +450,28 @@ export interface SessionsForkParams { toEventId?: string; } -export interface SessionModelGetCurrentResult { +export interface CurrentModel { /** * Currently active model identifier */ modelId?: string; } -export interface SessionModelGetCurrentParams { +export interface SessionModelGetCurrentRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionModelSwitchToResult { +export interface ModelSwitchToResult { /** * Currently active model identifier after the switch */ modelId?: string; } -export interface SessionModelSwitchToParams { +export interface ModelSwitchToRequest { /** * Target session identifier */ @@ -475,76 +490,61 @@ export interface SessionModelSwitchToParams { * Override individual model capabilities resolved by the runtime */ export interface ModelCapabilitiesOverride { - supports?: ModelCapabilitiesOverrideSupports; - limits?: ModelCapabilitiesOverrideLimits; -} -/** - * Feature flags indicating what the model supports - */ -export interface ModelCapabilitiesOverrideSupports { - vision?: boolean; - reasoningEffort?: boolean; -} -/** - * Token limits for prompts, outputs, and context window - */ -export interface ModelCapabilitiesOverrideLimits { - max_prompt_tokens?: number; - max_output_tokens?: number; /** - * Maximum total context window size in tokens + * Feature flags indicating what the model supports */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesOverrideLimitsVision; -} -export interface ModelCapabilitiesOverrideLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; + supports?: { + vision?: boolean; + reasoningEffort?: boolean; + }; /** - * Maximum image size in bytes + * Token limits for prompts, outputs, and context window */ - max_prompt_image_size?: number; + limits?: { + max_prompt_tokens?: number; + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: { + /** + * MIME types the model accepts + */ + supported_media_types?: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images?: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size?: number; + }; + }; } -export interface SessionModeGetResult { - /** - * The current agent mode. - */ - mode: "interactive" | "plan" | "autopilot"; -} +/** + * The agent mode. Valid values: "interactive", "plan", "autopilot". + */ +export type SessionMode = "interactive" | "plan" | "autopilot"; -export interface SessionModeGetParams { +export interface SessionModeGetRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionModeSetResult { - /** - * The agent mode after switching. - */ - mode: "interactive" | "plan" | "autopilot"; -} - -export interface SessionModeSetParams { +export interface ModeSetRequest { /** * Target session identifier */ sessionId: string; - /** - * The mode to switch to. Valid values: "interactive", "plan", "autopilot". - */ - mode: "interactive" | "plan" | "autopilot"; + mode: SessionMode; } -export interface SessionPlanReadResult { +export interface PlanReadResult { /** * Whether the plan file exists in the workspace */ @@ -559,16 +559,14 @@ export interface SessionPlanReadResult { path: string | null; } -export interface SessionPlanReadParams { +export interface SessionPlanReadRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionPlanUpdateResult {} - -export interface SessionPlanUpdateParams { +export interface PlanUpdateRequest { /** * Target session identifier */ @@ -579,37 +577,35 @@ export interface SessionPlanUpdateParams { content: string; } -export interface SessionPlanDeleteResult {} - -export interface SessionPlanDeleteParams { +export interface SessionPlanDeleteRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionWorkspaceListFilesResult { +export interface WorkspaceListFilesResult { /** * Relative file paths in the workspace files directory */ files: string[]; } -export interface SessionWorkspaceListFilesParams { +export interface SessionWorkspaceListFilesRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionWorkspaceReadFileResult { +export interface WorkspaceReadFileResult { /** * File content as a UTF-8 string */ content: string; } -export interface SessionWorkspaceReadFileParams { +export interface WorkspaceReadFileRequest { /** * Target session identifier */ @@ -620,9 +616,7 @@ export interface SessionWorkspaceReadFileParams { path: string; } -export interface SessionWorkspaceCreateFileResult {} - -export interface SessionWorkspaceCreateFileParams { +export interface WorkspaceCreateFileRequest { /** * Target session identifier */ @@ -638,7 +632,7 @@ export interface SessionWorkspaceCreateFileParams { } /** @experimental */ -export interface SessionFleetStartResult { +export interface FleetStartResult { /** * Whether fleet mode was successfully activated */ @@ -646,7 +640,7 @@ export interface SessionFleetStartResult { } /** @experimental */ -export interface SessionFleetStartParams { +export interface FleetStartRequest { /** * Target session identifier */ @@ -658,7 +652,7 @@ export interface SessionFleetStartParams { } /** @experimental */ -export interface SessionAgentListResult { +export interface AgentList { /** * Available custom agents */ @@ -679,7 +673,7 @@ export interface SessionAgentListResult { } /** @experimental */ -export interface SessionAgentListParams { +export interface SessionAgentListRequest { /** * Target session identifier */ @@ -687,7 +681,7 @@ export interface SessionAgentListParams { } /** @experimental */ -export interface SessionAgentGetCurrentResult { +export interface AgentGetCurrentResult { /** * Currently selected custom agent, or null if using the default agent */ @@ -708,7 +702,7 @@ export interface SessionAgentGetCurrentResult { } /** @experimental */ -export interface SessionAgentGetCurrentParams { +export interface SessionAgentGetCurrentRequest { /** * Target session identifier */ @@ -716,7 +710,7 @@ export interface SessionAgentGetCurrentParams { } /** @experimental */ -export interface SessionAgentSelectResult { +export interface AgentSelectResult { /** * The newly selected custom agent */ @@ -737,7 +731,7 @@ export interface SessionAgentSelectResult { } /** @experimental */ -export interface SessionAgentSelectParams { +export interface AgentSelectRequest { /** * Target session identifier */ @@ -749,10 +743,7 @@ export interface SessionAgentSelectParams { } /** @experimental */ -export interface SessionAgentDeselectResult {} - -/** @experimental */ -export interface SessionAgentDeselectParams { +export interface SessionAgentDeselectRequest { /** * Target session identifier */ @@ -760,7 +751,7 @@ export interface SessionAgentDeselectParams { } /** @experimental */ -export interface SessionAgentReloadResult { +export interface AgentReloadResult { /** * Reloaded custom agents */ @@ -781,7 +772,7 @@ export interface SessionAgentReloadResult { } /** @experimental */ -export interface SessionAgentReloadParams { +export interface SessionAgentReloadRequest { /** * Target session identifier */ @@ -789,7 +780,7 @@ export interface SessionAgentReloadParams { } /** @experimental */ -export interface SessionSkillsListResult { +export interface SkillList { /** * Available skills */ @@ -822,7 +813,7 @@ export interface SessionSkillsListResult { } /** @experimental */ -export interface SessionSkillsListParams { +export interface SessionSkillsListRequest { /** * Target session identifier */ @@ -830,10 +821,7 @@ export interface SessionSkillsListParams { } /** @experimental */ -export interface SessionSkillsEnableResult {} - -/** @experimental */ -export interface SessionSkillsEnableParams { +export interface SkillsEnableRequest { /** * Target session identifier */ @@ -845,10 +833,7 @@ export interface SessionSkillsEnableParams { } /** @experimental */ -export interface SessionSkillsDisableResult {} - -/** @experimental */ -export interface SessionSkillsDisableParams { +export interface SkillsDisableRequest { /** * Target session identifier */ @@ -860,10 +845,7 @@ export interface SessionSkillsDisableParams { } /** @experimental */ -export interface SessionSkillsReloadResult {} - -/** @experimental */ -export interface SessionSkillsReloadParams { +export interface SessionSkillsReloadRequest { /** * Target session identifier */ @@ -871,7 +853,7 @@ export interface SessionSkillsReloadParams { } /** @experimental */ -export interface SessionMcpListResult { +export interface McpServerList { /** * Configured MCP servers */ @@ -887,7 +869,7 @@ export interface SessionMcpListResult { /** * Configuration source: user, workspace, plugin, or builtin */ - source?: string; + source?: "user" | "workspace" | "plugin" | "builtin"; /** * Error message if the server failed to connect */ @@ -896,7 +878,7 @@ export interface SessionMcpListResult { } /** @experimental */ -export interface SessionMcpListParams { +export interface SessionMcpListRequest { /** * Target session identifier */ @@ -904,10 +886,7 @@ export interface SessionMcpListParams { } /** @experimental */ -export interface SessionMcpEnableResult {} - -/** @experimental */ -export interface SessionMcpEnableParams { +export interface McpEnableRequest { /** * Target session identifier */ @@ -919,10 +898,7 @@ export interface SessionMcpEnableParams { } /** @experimental */ -export interface SessionMcpDisableResult {} - -/** @experimental */ -export interface SessionMcpDisableParams { +export interface McpDisableRequest { /** * Target session identifier */ @@ -934,10 +910,7 @@ export interface SessionMcpDisableParams { } /** @experimental */ -export interface SessionMcpReloadResult {} - -/** @experimental */ -export interface SessionMcpReloadParams { +export interface SessionMcpReloadRequest { /** * Target session identifier */ @@ -945,7 +918,7 @@ export interface SessionMcpReloadParams { } /** @experimental */ -export interface SessionPluginsListResult { +export interface PluginList { /** * Installed plugins */ @@ -970,7 +943,7 @@ export interface SessionPluginsListResult { } /** @experimental */ -export interface SessionPluginsListParams { +export interface SessionPluginsListRequest { /** * Target session identifier */ @@ -978,7 +951,7 @@ export interface SessionPluginsListParams { } /** @experimental */ -export interface SessionExtensionsListResult { +export interface ExtensionList { /** * Discovered extensions and their current status */ @@ -1007,7 +980,7 @@ export interface SessionExtensionsListResult { } /** @experimental */ -export interface SessionExtensionsListParams { +export interface SessionExtensionsListRequest { /** * Target session identifier */ @@ -1015,10 +988,7 @@ export interface SessionExtensionsListParams { } /** @experimental */ -export interface SessionExtensionsEnableResult {} - -/** @experimental */ -export interface SessionExtensionsEnableParams { +export interface ExtensionsEnableRequest { /** * Target session identifier */ @@ -1030,10 +1000,7 @@ export interface SessionExtensionsEnableParams { } /** @experimental */ -export interface SessionExtensionsDisableResult {} - -/** @experimental */ -export interface SessionExtensionsDisableParams { +export interface ExtensionsDisableRequest { /** * Target session identifier */ @@ -1045,24 +1012,21 @@ export interface SessionExtensionsDisableParams { } /** @experimental */ -export interface SessionExtensionsReloadResult {} - -/** @experimental */ -export interface SessionExtensionsReloadParams { +export interface SessionExtensionsReloadRequest { /** * Target session identifier */ sessionId: string; } -export interface SessionToolsHandlePendingToolCallResult { +export interface HandleToolCallResult { /** * Whether the tool call result was handled successfully */ success: boolean; } -export interface SessionToolsHandlePendingToolCallParams { +export interface ToolsHandlePendingToolCallRequest { /** * Target session identifier */ @@ -1074,42 +1038,41 @@ export interface SessionToolsHandlePendingToolCallParams { /** * Tool call result (string or expanded result object) */ - result?: - | string - | { - /** - * Text result to send back to the LLM - */ - textResultForLlm: string; - /** - * Type of the tool result - */ - resultType?: string; - /** - * Error message if the tool call failed - */ - error?: string; - /** - * Telemetry data from tool execution - */ - toolTelemetry?: { - [k: string]: unknown; - }; - }; + result?: string | ToolCallResult; /** * Error message if the tool call failed */ error?: string; } +export interface ToolCallResult { + /** + * Text result to send back to the LLM + */ + textResultForLlm: string; + /** + * Type of the tool result + */ + resultType?: string; + /** + * Error message if the tool call failed + */ + error?: string; + /** + * Telemetry data from tool execution + */ + toolTelemetry?: { + [k: string]: unknown; + }; +} -export interface SessionCommandsHandlePendingCommandResult { +export interface CommandsHandlePendingCommandResult { /** * Whether the command was handled successfully */ success: boolean; } -export interface SessionCommandsHandlePendingCommandParams { +export interface CommandsHandlePendingCommandRequest { /** * Target session identifier */ @@ -1124,20 +1087,26 @@ export interface SessionCommandsHandlePendingCommandParams { error?: string; } -export interface SessionUiElicitationResult { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - /** - * The form values submitted by the user (present when action is 'accept') - */ - content?: { - [k: string]: string | number | boolean | string[]; - }; +/** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + */ +export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; +export type UIElicitationFieldValue = string | number | boolean | string[]; +/** + * The elicitation response (accept with form values, decline, or cancel) + */ +export interface UIElicitationResponse { + action: UIElicitationResponseAction; + content?: UIElicitationResponseContent; +} +/** + * The form values submitted by the user (present when action is 'accept') + */ +export interface UIElicitationResponseContent { + [k: string]: UIElicitationFieldValue; } -export interface SessionUiElicitationParams { +export interface UIElicitationRequest { /** * Target session identifier */ @@ -1159,59 +1128,17 @@ export interface SessionUiElicitationParams { */ properties: { [k: string]: - | { - type: "string"; - title?: string; - description?: string; - enum: string[]; - enumNames?: string[]; - default?: string; - } - | { - type: "string"; - title?: string; - description?: string; - oneOf: { - const: string; - title: string; - }[]; - default?: string; - } - | { - type: "array"; - title?: string; - description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; - } - | { - type: "array"; - title?: string; - description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - title: string; - }[]; - }; - default?: string[]; - } + | UIElicitationStringEnumField + | UIElicitationStringOneOfField + | UIElicitationArrayEnumField + | UIElicitationArrayAnyOfField | { type: "boolean"; - title?: string; description?: string; default?: boolean; } | { type: "string"; - title?: string; description?: string; minLength?: number; maxLength?: number; @@ -1220,7 +1147,6 @@ export interface SessionUiElicitationParams { } | { type: "number" | "integer"; - title?: string; description?: string; minimum?: number; maximum?: number; @@ -1233,15 +1159,53 @@ export interface SessionUiElicitationParams { required?: string[]; }; } +export interface UIElicitationStringEnumField { + type: "string"; + description?: string; + enum: string[]; + enumNames?: string[]; + default?: string; +} +export interface UIElicitationStringOneOfField { + type: "string"; + description?: string; + oneOf: { + const: string; + }[]; + default?: string; +} +export interface UIElicitationArrayEnumField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + type: "string"; + enum: string[]; + }; + default?: string[]; +} +export interface UIElicitationArrayAnyOfField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + anyOf: { + const: string; + }[]; + }; + default?: string[]; +} -export interface SessionUiHandlePendingElicitationResult { +export interface UIElicitationResult { /** * Whether the response was accepted. False if the request was already resolved by another client. */ success: boolean; } -export interface SessionUiHandlePendingElicitationParams { +export interface UIHandlePendingElicitationRequest { /** * Target session identifier */ @@ -1250,31 +1214,79 @@ export interface SessionUiHandlePendingElicitationParams { * The unique request ID from the elicitation.requested event */ requestId: string; - /** - * The elicitation response (accept with form values, decline, or cancel) - */ - result: { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - /** - * The form values submitted by the user (present when action is 'accept') - */ - content?: { - [k: string]: string | number | boolean | string[]; - }; - }; + result: UIElicitationResponse; } -export interface SessionPermissionsHandlePendingPermissionRequestResult { +export interface PermissionRequestResult { /** * Whether the permission request was handled successfully */ success: boolean; } -export interface SessionPermissionsHandlePendingPermissionRequestParams { +export type PermissionDecision = + | { + /** + * The permission request was approved + */ + kind: "approved"; + } +| { + /** + * Denied because approval rules explicitly blocked it + */ + kind: "denied-by-rules"; + /** + * Rules that denied the request + */ + rules: unknown[]; + } + | { + /** + * Denied because no approval rule matched and user confirmation was unavailable + */ + kind: "denied-no-approval-rule-and-could-not-request-from-user"; + } + | { + /** + * Denied by the user during an interactive prompt + */ + kind: "denied-interactively-by-user"; + /** + * Optional feedback from the user explaining the denial + */ + feedback?: string; + } + | { + /** + * Denied by the organization's content exclusion policy + */ + kind: "denied-by-content-exclusion-policy"; + /** + * File path that triggered the exclusion + */ + path: string; + /** + * Human-readable explanation of why the path was excluded + */ + message: string; + } + | { + /** + * Denied by a permission request hook registered by an extension or plugin + */ + kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ + message?: string; + /** + * Whether to interrupt the current agent turn + */ + interrupt?: boolean; + }; + +export interface PermissionDecisionRequest { /** * Target session identifier */ @@ -1283,77 +1295,21 @@ export interface SessionPermissionsHandlePendingPermissionRequestParams { * Request ID of the pending permission request */ requestId: string; - result: - | { - /** - * The permission request was approved - */ - kind: "approved"; - } - | { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; + result: PermissionDecision; } -export interface SessionLogResult { +export interface LogResult { /** * The unique identifier of the emitted session event */ eventId: string; } -export interface SessionLogParams { +/** + * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + */ +export type SessionLogLevel = "info" | "warning" | "error"; +export interface LogRequest { /** * Target session identifier */ @@ -1362,10 +1318,7 @@ export interface SessionLogParams { * Human-readable message */ message: string; - /** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - */ - level?: "info" | "warning" | "error"; + level?: SessionLogLevel; /** * When true, the message is transient and not persisted to the session event log on disk */ @@ -1376,14 +1329,14 @@ export interface SessionLogParams { url?: string; } -export interface SessionShellExecResult { +export interface ShellExecResult { /** * Unique identifier for tracking streamed output */ processId: string; } -export interface SessionShellExecParams { +export interface ShellExecRequest { /** * Target session identifier */ @@ -1402,14 +1355,14 @@ export interface SessionShellExecParams { timeout?: number; } -export interface SessionShellKillResult { +export interface ShellKillResult { /** * Whether the signal was sent successfully */ killed: boolean; } -export interface SessionShellKillParams { +export interface ShellKillRequest { /** * Target session identifier */ @@ -1425,7 +1378,7 @@ export interface SessionShellKillParams { } /** @experimental */ -export interface SessionHistoryCompactResult { +export interface HistoryCompactResult { /** * Whether compaction completed successfully */ @@ -1470,7 +1423,7 @@ export interface SessionHistoryCompactResult { } /** @experimental */ -export interface SessionHistoryCompactParams { +export interface SessionHistoryCompactRequest { /** * Target session identifier */ @@ -1478,7 +1431,7 @@ export interface SessionHistoryCompactParams { } /** @experimental */ -export interface SessionHistoryTruncateResult { +export interface HistoryTruncateResult { /** * Number of events that were removed */ @@ -1486,7 +1439,7 @@ export interface SessionHistoryTruncateResult { } /** @experimental */ -export interface SessionHistoryTruncateParams { +export interface HistoryTruncateRequest { /** * Target session identifier */ @@ -1498,7 +1451,7 @@ export interface SessionHistoryTruncateParams { } /** @experimental */ -export interface SessionUsageGetMetricsResult { +export interface UsageGetMetricsResult { /** * Total user-initiated premium request cost across all models (may be fractional due to multipliers) */ @@ -1570,6 +1523,10 @@ export interface SessionUsageGetMetricsResult { * Total tokens written to prompt cache */ cacheWriteTokens: number; + /** + * Total output tokens used for reasoning + */ + reasoningTokens?: number; }; }; }; @@ -1588,7 +1545,7 @@ export interface SessionUsageGetMetricsResult { } /** @experimental */ -export interface SessionUsageGetMetricsParams { +export interface SessionUsageGetMetricsRequest { /** * Target session identifier */ @@ -1602,7 +1559,7 @@ export interface SessionFsReadFileResult { content: string; } -export interface SessionFsReadFileParams { +export interface SessionFsReadFileRequest { /** * Target session identifier */ @@ -1613,7 +1570,7 @@ export interface SessionFsReadFileParams { path: string; } -export interface SessionFsWriteFileParams { +export interface SessionFsWriteFileRequest { /** * Target session identifier */ @@ -1632,7 +1589,7 @@ export interface SessionFsWriteFileParams { mode?: number; } -export interface SessionFsAppendFileParams { +export interface SessionFsAppendFileRequest { /** * Target session identifier */ @@ -1658,7 +1615,7 @@ export interface SessionFsExistsResult { exists: boolean; } -export interface SessionFsExistsParams { +export interface SessionFsExistsRequest { /** * Target session identifier */ @@ -1692,7 +1649,7 @@ export interface SessionFsStatResult { birthtime: string; } -export interface SessionFsStatParams { +export interface SessionFsStatRequest { /** * Target session identifier */ @@ -1703,7 +1660,7 @@ export interface SessionFsStatParams { path: string; } -export interface SessionFsMkdirParams { +export interface SessionFsMkdirRequest { /** * Target session identifier */ @@ -1729,7 +1686,7 @@ export interface SessionFsReaddirResult { entries: string[]; } -export interface SessionFsReaddirParams { +export interface SessionFsReaddirRequest { /** * Target session identifier */ @@ -1756,7 +1713,7 @@ export interface SessionFsReaddirWithTypesResult { }[]; } -export interface SessionFsReaddirWithTypesParams { +export interface SessionFsReaddirWithTypesRequest { /** * Target session identifier */ @@ -1767,7 +1724,7 @@ export interface SessionFsReaddirWithTypesParams { path: string; } -export interface SessionFsRmParams { +export interface SessionFsRmRequest { /** * Target session identifier */ @@ -1786,7 +1743,7 @@ export interface SessionFsRmParams { force?: boolean; } -export interface SessionFsRenameParams { +export interface SessionFsRenameRequest { /** * Target session identifier */ @@ -1804,14 +1761,14 @@ export interface SessionFsRenameParams { /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { return { - ping: async (params: PingParams): Promise => + ping: async (params: PingRequest): Promise => connection.sendRequest("ping", params), models: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("models.list", {}), }, tools: { - list: async (params: ToolsListParams): Promise => + list: async (params: ToolsListRequest): Promise => connection.sendRequest("tools.list", params), }, account: { @@ -1820,25 +1777,25 @@ export function createServerRpc(connection: MessageConnection) { }, mcp: { config: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("mcp.config.list", {}), - add: async (params: McpConfigAddParams): Promise => + add: async (params: McpConfigAddRequest): Promise => connection.sendRequest("mcp.config.add", params), - update: async (params: McpConfigUpdateParams): Promise => + update: async (params: McpConfigUpdateRequest): Promise => connection.sendRequest("mcp.config.update", params), - remove: async (params: McpConfigRemoveParams): Promise => + remove: async (params: McpConfigRemoveRequest): Promise => connection.sendRequest("mcp.config.remove", params), }, - discover: async (params: McpDiscoverParams): Promise => + discover: async (params: McpDiscoverRequest): Promise => connection.sendRequest("mcp.discover", params), }, sessionFs: { - setProvider: async (params: SessionFsSetProviderParams): Promise => + setProvider: async (params: SessionFsSetProviderRequest): Promise => connection.sendRequest("sessionFs.setProvider", params), }, /** @experimental */ sessions: { - fork: async (params: SessionsForkParams): Promise => + fork: async (params: SessionsForkRequest): Promise => connection.sendRequest("sessions.fork", params), }, }; @@ -1848,125 +1805,125 @@ export function createServerRpc(connection: MessageConnection) { export function createSessionRpc(connection: MessageConnection, sessionId: string) { return { model: { - getCurrent: async (): Promise => + getCurrent: async (): Promise => connection.sendRequest("session.model.getCurrent", { sessionId }), - switchTo: async (params: Omit): Promise => + switchTo: async (params: Omit): Promise => connection.sendRequest("session.model.switchTo", { sessionId, ...params }), }, mode: { - get: async (): Promise => + get: async (): Promise => connection.sendRequest("session.mode.get", { sessionId }), - set: async (params: Omit): Promise => + set: async (params: Omit): Promise => connection.sendRequest("session.mode.set", { sessionId, ...params }), }, plan: { - read: async (): Promise => + read: async (): Promise => connection.sendRequest("session.plan.read", { sessionId }), - update: async (params: Omit): Promise => + update: async (params: Omit): Promise => connection.sendRequest("session.plan.update", { sessionId, ...params }), - delete: async (): Promise => + delete: async (): Promise => connection.sendRequest("session.plan.delete", { sessionId }), }, workspace: { - listFiles: async (): Promise => + listFiles: async (): Promise => connection.sendRequest("session.workspace.listFiles", { sessionId }), - readFile: async (params: Omit): Promise => + readFile: async (params: Omit): Promise => connection.sendRequest("session.workspace.readFile", { sessionId, ...params }), - createFile: async (params: Omit): Promise => + createFile: async (params: Omit): Promise => connection.sendRequest("session.workspace.createFile", { sessionId, ...params }), }, /** @experimental */ fleet: { - start: async (params: Omit): Promise => + start: async (params: Omit): Promise => connection.sendRequest("session.fleet.start", { sessionId, ...params }), }, /** @experimental */ agent: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("session.agent.list", { sessionId }), - getCurrent: async (): Promise => + getCurrent: async (): Promise => connection.sendRequest("session.agent.getCurrent", { sessionId }), - select: async (params: Omit): Promise => + select: async (params: Omit): Promise => connection.sendRequest("session.agent.select", { sessionId, ...params }), - deselect: async (): Promise => + deselect: async (): Promise => connection.sendRequest("session.agent.deselect", { sessionId }), - reload: async (): Promise => + reload: async (): Promise => connection.sendRequest("session.agent.reload", { sessionId }), }, /** @experimental */ skills: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("session.skills.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: Omit): Promise => connection.sendRequest("session.skills.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: Omit): Promise => connection.sendRequest("session.skills.disable", { sessionId, ...params }), - reload: async (): Promise => + reload: async (): Promise => connection.sendRequest("session.skills.reload", { sessionId }), }, /** @experimental */ mcp: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("session.mcp.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: Omit): Promise => connection.sendRequest("session.mcp.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: Omit): Promise => connection.sendRequest("session.mcp.disable", { sessionId, ...params }), - reload: async (): Promise => + reload: async (): Promise => connection.sendRequest("session.mcp.reload", { sessionId }), }, /** @experimental */ plugins: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("session.plugins.list", { sessionId }), }, /** @experimental */ extensions: { - list: async (): Promise => + list: async (): Promise => connection.sendRequest("session.extensions.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: Omit): Promise => connection.sendRequest("session.extensions.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: Omit): Promise => connection.sendRequest("session.extensions.disable", { sessionId, ...params }), - reload: async (): Promise => + reload: async (): Promise => connection.sendRequest("session.extensions.reload", { sessionId }), }, tools: { - handlePendingToolCall: async (params: Omit): Promise => + handlePendingToolCall: async (params: Omit): Promise => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params }), }, commands: { - handlePendingCommand: async (params: Omit): Promise => + handlePendingCommand: async (params: Omit): Promise => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params }), }, ui: { - elicitation: async (params: Omit): Promise => + elicitation: async (params: Omit): Promise => connection.sendRequest("session.ui.elicitation", { sessionId, ...params }), - handlePendingElicitation: async (params: Omit): Promise => + handlePendingElicitation: async (params: Omit): Promise => connection.sendRequest("session.ui.handlePendingElicitation", { sessionId, ...params }), }, permissions: { - handlePendingPermissionRequest: async (params: Omit): Promise => + handlePendingPermissionRequest: async (params: Omit): Promise => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params }), }, - log: async (params: Omit): Promise => + log: async (params: Omit): Promise => connection.sendRequest("session.log", { sessionId, ...params }), shell: { - exec: async (params: Omit): Promise => + exec: async (params: Omit): Promise => connection.sendRequest("session.shell.exec", { sessionId, ...params }), - kill: async (params: Omit): Promise => + kill: async (params: Omit): Promise => connection.sendRequest("session.shell.kill", { sessionId, ...params }), }, /** @experimental */ history: { - compact: async (): Promise => + compact: async (): Promise => connection.sendRequest("session.history.compact", { sessionId }), - truncate: async (params: Omit): Promise => + truncate: async (params: Omit): Promise => connection.sendRequest("session.history.truncate", { sessionId, ...params }), }, /** @experimental */ usage: { - getMetrics: async (): Promise => + getMetrics: async (): Promise => connection.sendRequest("session.usage.getMetrics", { sessionId }), }, }; @@ -1974,16 +1931,16 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** Handler for `sessionFs` client session API methods. */ export interface SessionFsHandler { - readFile(params: SessionFsReadFileParams): Promise; - writeFile(params: SessionFsWriteFileParams): Promise; - appendFile(params: SessionFsAppendFileParams): Promise; - exists(params: SessionFsExistsParams): Promise; - stat(params: SessionFsStatParams): Promise; - mkdir(params: SessionFsMkdirParams): Promise; - readdir(params: SessionFsReaddirParams): Promise; - readdirWithTypes(params: SessionFsReaddirWithTypesParams): Promise; - rm(params: SessionFsRmParams): Promise; - rename(params: SessionFsRenameParams): Promise; + readFile(params: SessionFsReadFileRequest): Promise; + writeFile(params: SessionFsWriteFileRequest): Promise; + appendFile(params: SessionFsAppendFileRequest): Promise; + exists(params: SessionFsExistsRequest): Promise; + stat(params: SessionFsStatRequest): Promise; + mkdir(params: SessionFsMkdirRequest): Promise; + readdir(params: SessionFsReaddirRequest): Promise; + readdirWithTypes(params: SessionFsReaddirWithTypesRequest): Promise; + rm(params: SessionFsRmRequest): Promise; + rename(params: SessionFsRenameRequest): Promise; } /** All client session API handler groups. */ @@ -2001,52 +1958,52 @@ export function registerClientSessionApiHandlers( connection: MessageConnection, getHandlers: (sessionId: string) => ClientSessionApiHandlers, ): void { - connection.onRequest("sessionFs.readFile", async (params: SessionFsReadFileParams) => { + connection.onRequest("sessionFs.readFile", async (params: SessionFsReadFileRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.readFile(params); }); - connection.onRequest("sessionFs.writeFile", async (params: SessionFsWriteFileParams) => { + connection.onRequest("sessionFs.writeFile", async (params: SessionFsWriteFileRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.writeFile(params); }); - connection.onRequest("sessionFs.appendFile", async (params: SessionFsAppendFileParams) => { + connection.onRequest("sessionFs.appendFile", async (params: SessionFsAppendFileRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.appendFile(params); }); - connection.onRequest("sessionFs.exists", async (params: SessionFsExistsParams) => { + connection.onRequest("sessionFs.exists", async (params: SessionFsExistsRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.exists(params); }); - connection.onRequest("sessionFs.stat", async (params: SessionFsStatParams) => { + connection.onRequest("sessionFs.stat", async (params: SessionFsStatRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.stat(params); }); - connection.onRequest("sessionFs.mkdir", async (params: SessionFsMkdirParams) => { + connection.onRequest("sessionFs.mkdir", async (params: SessionFsMkdirRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.mkdir(params); }); - connection.onRequest("sessionFs.readdir", async (params: SessionFsReaddirParams) => { + connection.onRequest("sessionFs.readdir", async (params: SessionFsReaddirRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.readdir(params); }); - connection.onRequest("sessionFs.readdirWithTypes", async (params: SessionFsReaddirWithTypesParams) => { + connection.onRequest("sessionFs.readdirWithTypes", async (params: SessionFsReaddirWithTypesRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.readdirWithTypes(params); }); - connection.onRequest("sessionFs.rm", async (params: SessionFsRmParams) => { + connection.onRequest("sessionFs.rm", async (params: SessionFsRmRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.rm(params); }); - connection.onRequest("sessionFs.rename", async (params: SessionFsRenameParams) => { + connection.onRequest("sessionFs.rename", async (params: SessionFsRenameRequest) => { const handler = getHandlers(params.sessionId).sessionFs; if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.rename(params); diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 7cfc60522..2a5b08b21 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -297,12 +297,7 @@ export type SessionEvent = /** * Session title change payload containing the new display title */ - data: { - /** - * The new display title for the session - */ - title: string; - }; + data: {}; } | { /** @@ -755,6 +750,10 @@ export type SessionEvent = * Total tokens written to prompt cache across all requests */ cacheWriteTokens: number; + /** + * Total reasoning tokens produced across all requests to this model + */ + reasoningTokens?: number; }; }; }; @@ -1176,10 +1175,6 @@ export type SessionEvent = * Issue, pull request, or discussion number */ number: number; - /** - * Title of the referenced item - */ - title: string; /** * Type of GitHub reference */ @@ -1588,6 +1583,10 @@ export type SessionEvent = * Number of tokens written to prompt cache */ cacheWriteTokens?: number; + /** + * Number of output tokens used for reasoning (e.g., chain-of-thought) + */ + reasoningTokens?: number; /** * Model multiplier cost for billing purposes */ @@ -2009,10 +2008,6 @@ export type SessionEvent = * Resource name identifier */ name: string; - /** - * Human-readable display title for the resource - */ - title?: string; /** * URI identifying the resource */ @@ -2042,35 +2037,7 @@ export type SessionEvent = /** * The embedded resource contents, either text or base64-encoded binary */ - resource: - | { - /** - * URI identifying the resource - */ - uri: string; - /** - * MIME type of the text content - */ - mimeType?: string; - /** - * Text content of the resource - */ - text: string; - } - | { - /** - * URI identifying the resource - */ - uri: string; - /** - * MIME type of the blob content - */ - mimeType?: string; - /** - * Base64-encoded binary content of the resource - */ - blob: string; - }; + resource: EmbeddedTextResourceContents | EmbeddedBlobResourceContents; } )[]; }; @@ -3764,3 +3731,32 @@ export type SessionEvent = }[]; }; }; + +export interface EmbeddedTextResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the text content + */ + mimeType?: string; + /** + * Text content of the resource + */ + text: string; +} +export interface EmbeddedBlobResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the blob content + */ + mimeType?: string; + /** + * Base64-encoded binary content of the resource + */ + blob: string; +} diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index cb8dd7ad2..1318b3df4 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -744,11 +744,9 @@ export interface PermissionRequest { [key: string]: unknown; } -import type { SessionPermissionsHandlePendingPermissionRequestParams } from "./generated/rpc.js"; +import type { PermissionDecisionRequest } from "./generated/rpc.js"; -export type PermissionRequestResult = - | SessionPermissionsHandlePendingPermissionRequestParams["result"] - | { kind: "no-result" }; +export type PermissionRequestResult = PermissionDecisionRequest["result"] | { kind: "no-result" }; export type PermissionHandler = ( request: PermissionRequest, diff --git a/nodejs/test/e2e/rpc.test.ts b/nodejs/test/e2e/rpc.test.ts index d4d732efd..bca4e8cd7 100644 --- a/nodejs/test/e2e/rpc.test.ts +++ b/nodejs/test/e2e/rpc.test.ts @@ -109,19 +109,21 @@ describe("Session RPC", async () => { // Get initial mode (default should be interactive) const initial = await session.rpc.mode.get(); - expect(initial.mode).toBe("interactive"); + expect(initial).toBe("interactive"); // Switch to plan mode - const planResult = await session.rpc.mode.set({ mode: "plan" }); - expect(planResult.mode).toBe("plan"); + await session.rpc.mode.set({ mode: "plan" }); // Verify mode persisted const afterPlan = await session.rpc.mode.get(); - expect(afterPlan.mode).toBe("plan"); + expect(afterPlan).toBe("plan"); // Switch back to interactive - const interactiveResult = await session.rpc.mode.set({ mode: "interactive" }); - expect(interactiveResult.mode).toBe("interactive"); + await session.rpc.mode.set({ mode: "interactive" }); + + // Verify switch back + const afterInteractive = await session.rpc.mode.get(); + expect(afterInteractive).toBe("interactive"); }); it("should read, update, and delete plan", async () => { diff --git a/python/copilot/client.py b/python/copilot/client.py index d260dcc91..c47acdf14 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -360,15 +360,13 @@ def to_dict(self) -> dict: class ModelSupports: """Model support flags""" - vision: bool + vision: bool = False reasoning_effort: bool = False # Whether this model supports reasoning effort @staticmethod def from_dict(obj: Any) -> ModelSupports: assert isinstance(obj, dict) - vision = obj.get("vision") - if vision is None: - raise ValueError("Missing required field 'vision' in ModelSupports") + vision = obj.get("vision", False) reasoning_effort = obj.get("reasoningEffort", False) return ModelSupports(vision=bool(vision), reasoning_effort=bool(reasoning_effort)) @@ -391,13 +389,8 @@ def from_dict(obj: Any) -> ModelCapabilities: assert isinstance(obj, dict) supports_dict = obj.get("supports") limits_dict = obj.get("limits") - if supports_dict is None or limits_dict is None: - raise ValueError( - f"Missing required fields in ModelCapabilities: supports={supports_dict}, " - f"limits={limits_dict}" - ) - supports = ModelSupports.from_dict(supports_dict) - limits = ModelLimits.from_dict(limits_dict) + supports = ModelSupports.from_dict(supports_dict) if supports_dict else ModelSupports() + limits = ModelLimits.from_dict(limits_dict) if limits_dict else ModelLimits() return ModelCapabilities(supports=supports, limits=limits) def to_dict(self) -> dict: @@ -762,23 +755,24 @@ def _get_bundled_cli_path() -> str | None: def _extract_transform_callbacks( - system_message: dict | None, -) -> tuple[dict | None, dict[str, SectionTransformFn] | None]: + system_message: SystemMessageConfig | dict[str, Any] | None, +) -> tuple[dict[str, Any] | None, dict[str, SectionTransformFn] | None]: """Extract function-valued actions from system message config. Returns a wire-safe payload (with callable actions replaced by ``"transform"``) and a dict of transform callbacks keyed by section ID. """ + wire_system_message = cast(dict[str, Any] | None, system_message) if ( - not system_message - or system_message.get("mode") != "customize" - or not system_message.get("sections") + not wire_system_message + or wire_system_message.get("mode") != "customize" + or not wire_system_message.get("sections") ): - return system_message, None + return wire_system_message, None callbacks: dict[str, SectionTransformFn] = {} - wire_sections: dict[str, dict] = {} - for section_id, override in system_message["sections"].items(): + wire_sections: dict[str, Any] = {} + for section_id, override in wire_system_message["sections"].items(): if not override: continue action = override.get("action") @@ -789,9 +783,9 @@ def _extract_transform_callbacks( wire_sections[section_id] = override if not callbacks: - return system_message, None + return wire_system_message, None - wire_payload = {**system_message, "sections": wire_sections} + wire_payload = {**wire_system_message, "sections": wire_sections} return wire_payload, callbacks @@ -1798,9 +1792,9 @@ async def list_models(self) -> list[ModelInfo]: # Use custom handler instead of CLI RPC result = self._on_list_models() if inspect.isawaitable(result): - models = await result + models = cast(list[ModelInfo], await result) else: - models = result + models = cast(list[ModelInfo], result) else: if not self._client: raise RuntimeError("Client not connected") diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 19265c557..b24f74e51 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -15,34 +15,26 @@ from dataclasses import dataclass from typing import Any, TypeVar, Callable, cast +from datetime import datetime from enum import Enum from uuid import UUID - +import dateutil.parser T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) - def from_str(x: Any) -> str: assert isinstance(x, str) return x - -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) - - -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) return x - def from_none(x: Any) -> Any: assert x is None return x - def from_union(fs, x): for f in fs: try: @@ -51,74 +43,73 @@ def from_union(fs, x): pass assert False +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + +def to_float(x: Any) -> float: + assert isinstance(x, (int, float)) + return x def from_list(f: Callable[[Any], T], x: Any) -> list[T]: assert isinstance(x, list) return [f(y) for y in x] - def to_class(c: type[T], x: Any) -> dict: assert isinstance(x, c) return cast(Any, x).to_dict() - def from_bool(x: Any) -> bool: assert isinstance(x, bool) return x - def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: assert isinstance(x, dict) return { k: f(v) for (k, v) in x.items() } +def from_datetime(x: Any) -> datetime: + return dateutil.parser.parse(x) def to_enum(c: type[EnumT], x: Any) -> EnumT: assert isinstance(x, c) return x.value - -def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x - - @dataclass class PingResult: message: str """Echoed message (or default greeting)""" - protocol_version: float + protocol_version: int """Server protocol version number""" - timestamp: float + timestamp: int """Server timestamp in milliseconds""" @staticmethod def from_dict(obj: Any) -> 'PingResult': assert isinstance(obj, dict) message = from_str(obj.get("message")) - protocol_version = from_float(obj.get("protocolVersion")) - timestamp = from_float(obj.get("timestamp")) + protocol_version = from_int(obj.get("protocolVersion")) + timestamp = from_int(obj.get("timestamp")) return PingResult(message, protocol_version, timestamp) def to_dict(self) -> dict: result: dict = {} result["message"] = from_str(self.message) - result["protocolVersion"] = to_float(self.protocol_version) - result["timestamp"] = to_float(self.timestamp) + result["protocolVersion"] = from_int(self.protocol_version) + result["timestamp"] = from_int(self.timestamp) return result - @dataclass -class PingParams: +class PingRequest: message: str | None = None """Optional message to echo back""" @staticmethod - def from_dict(obj: Any) -> 'PingParams': + def from_dict(obj: Any) -> 'PingRequest': assert isinstance(obj, dict) message = from_union([from_str, from_none], obj.get("message")) - return PingParams(message) + return PingRequest(message) def to_dict(self) -> dict: result: dict = {} @@ -126,34 +117,32 @@ def to_dict(self) -> dict: result["message"] = from_union([from_str, from_none], self.message) return result - @dataclass -class Billing: +class ModelBilling: """Billing information""" multiplier: float """Billing cost multiplier relative to the base rate""" @staticmethod - def from_dict(obj: Any) -> 'Billing': + def from_dict(obj: Any) -> 'ModelBilling': assert isinstance(obj, dict) multiplier = from_float(obj.get("multiplier")) - return Billing(multiplier) + return ModelBilling(multiplier) def to_dict(self) -> dict: result: dict = {} result["multiplier"] = to_float(self.multiplier) return result - @dataclass class ModelCapabilitiesLimitsVision: """Vision-specific limits""" - max_prompt_image_size: float + max_prompt_image_size: int """Maximum image size in bytes""" - max_prompt_images: float + max_prompt_images: int """Maximum number of images per prompt""" supported_media_types: list[str] @@ -162,30 +151,29 @@ class ModelCapabilitiesLimitsVision: @staticmethod def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': assert isinstance(obj, dict) - max_prompt_image_size = from_float(obj.get("max_prompt_image_size")) - max_prompt_images = from_float(obj.get("max_prompt_images")) + max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) + max_prompt_images = from_int(obj.get("max_prompt_images")) supported_media_types = from_list(from_str, obj.get("supported_media_types")) return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} - result["max_prompt_image_size"] = to_float(self.max_prompt_image_size) - result["max_prompt_images"] = to_float(self.max_prompt_images) + result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) + result["max_prompt_images"] = from_int(self.max_prompt_images) result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result - @dataclass class ModelCapabilitiesLimits: """Token limits for prompts, outputs, and context window""" - max_context_window_tokens: float + max_context_window_tokens: int """Maximum total context window size in tokens""" - max_output_tokens: float | None = None + max_output_tokens: int | None = None """Maximum number of output/completion tokens""" - max_prompt_tokens: float | None = None + max_prompt_tokens: int | None = None """Maximum number of prompt/input tokens""" vision: ModelCapabilitiesLimitsVision | None = None @@ -194,24 +182,23 @@ class ModelCapabilitiesLimits: @staticmethod def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': assert isinstance(obj, dict) - max_context_window_tokens = from_float(obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_float, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_float, from_none], obj.get("max_prompt_tokens")) + max_context_window_tokens = from_int(obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - result["max_context_window_tokens"] = to_float(self.max_context_window_tokens) + result["max_context_window_tokens"] = from_int(self.max_context_window_tokens) if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([to_float, from_none], self.max_output_tokens) + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([to_float, from_none], self.max_prompt_tokens) + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) if self.vision is not None: result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result - @dataclass class ModelCapabilitiesSupports: """Feature flags indicating what the model supports""" @@ -237,7 +224,6 @@ def to_dict(self) -> dict: result["vision"] = from_union([from_bool, from_none], self.vision) return result - @dataclass class ModelCapabilities: """Model capabilities and limits""" @@ -261,9 +247,8 @@ def to_dict(self) -> dict: result["supports"] = to_class(ModelCapabilitiesSupports, self.supports) return result - @dataclass -class Policy: +class ModelPolicy: """Policy state (if applicable)""" state: str @@ -273,11 +258,11 @@ class Policy: """Usage terms or conditions for this model""" @staticmethod - def from_dict(obj: Any) -> 'Policy': + def from_dict(obj: Any) -> 'ModelPolicy': assert isinstance(obj, dict) state = from_str(obj.get("state")) terms = from_str(obj.get("terms")) - return Policy(state, terms) + return ModelPolicy(state, terms) def to_dict(self) -> dict: result: dict = {} @@ -285,7 +270,6 @@ def to_dict(self) -> dict: result["terms"] = from_str(self.terms) return result - @dataclass class Model: capabilities: ModelCapabilities @@ -297,13 +281,13 @@ class Model: name: str """Display name""" - billing: Billing | None = None + billing: ModelBilling | None = None """Billing information""" default_reasoning_effort: str | None = None """Default reasoning effort level (only present if model supports reasoning effort)""" - policy: Policy | None = None + policy: ModelPolicy | None = None """Policy state (if applicable)""" supported_reasoning_efforts: list[str] | None = None @@ -315,9 +299,9 @@ def from_dict(obj: Any) -> 'Model': capabilities = ModelCapabilities.from_dict(obj.get("capabilities")) id = from_str(obj.get("id")) name = from_str(obj.get("name")) - billing = from_union([Billing.from_dict, from_none], obj.get("billing")) + billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) - policy = from_union([Policy.from_dict, from_none], obj.get("policy")) + policy = from_union([ModelPolicy.from_dict, from_none], obj.get("policy")) supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) return Model(capabilities, id, name, billing, default_reasoning_effort, policy, supported_reasoning_efforts) @@ -327,33 +311,31 @@ def to_dict(self) -> dict: result["id"] = from_str(self.id) result["name"] = from_str(self.name) if self.billing is not None: - result["billing"] = from_union([lambda x: to_class(Billing, x), from_none], self.billing) + result["billing"] = from_union([lambda x: to_class(ModelBilling, x), from_none], self.billing) if self.default_reasoning_effort is not None: result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) if self.policy is not None: - result["policy"] = from_union([lambda x: to_class(Policy, x), from_none], self.policy) + result["policy"] = from_union([lambda x: to_class(ModelPolicy, x), from_none], self.policy) if self.supported_reasoning_efforts is not None: result["supportedReasoningEfforts"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_reasoning_efforts) return result - @dataclass -class ModelsListResult: +class ModelList: models: list[Model] """List of available models with full metadata""" @staticmethod - def from_dict(obj: Any) -> 'ModelsListResult': + def from_dict(obj: Any) -> 'ModelList': assert isinstance(obj, dict) models = from_list(Model.from_dict, obj.get("models")) - return ModelsListResult(models) + return ModelList(models) def to_dict(self) -> dict: result: dict = {} result["models"] = from_list(lambda x: to_class(Model, x), self.models) return result - @dataclass class Tool: description: str @@ -394,36 +376,34 @@ def to_dict(self) -> dict: result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result - @dataclass -class ToolsListResult: +class ToolList: tools: list[Tool] """List of available built-in tools with metadata""" @staticmethod - def from_dict(obj: Any) -> 'ToolsListResult': + def from_dict(obj: Any) -> 'ToolList': assert isinstance(obj, dict) tools = from_list(Tool.from_dict, obj.get("tools")) - return ToolsListResult(tools) + return ToolList(tools) def to_dict(self) -> dict: result: dict = {} result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) return result - @dataclass -class ToolsListParams: +class ToolsListRequest: model: str | None = None """Optional model ID — when provided, the returned tool list reflects model-specific overrides """ @staticmethod - def from_dict(obj: Any) -> 'ToolsListParams': + def from_dict(obj: Any) -> 'ToolsListRequest': assert isinstance(obj, dict) model = from_union([from_str, from_none], obj.get("model")) - return ToolsListParams(model) + return ToolsListRequest(model) def to_dict(self) -> dict: result: dict = {} @@ -431,13 +411,12 @@ def to_dict(self) -> dict: result["model"] = from_union([from_str, from_none], self.model) return result - @dataclass -class QuotaSnapshot: - entitlement_requests: float +class AccountQuotaSnapshot: + entitlement_requests: int """Number of requests included in the entitlement""" - overage: float + overage: int """Number of overage requests made this period""" overage_allowed_with_exhausted_quota: bool @@ -446,102 +425,100 @@ class QuotaSnapshot: remaining_percentage: float """Percentage of entitlement remaining""" - used_requests: float + used_requests: int """Number of requests used so far this period""" - reset_date: str | None = None + reset_date: datetime | None = None """Date when the quota resets (ISO 8601)""" @staticmethod - def from_dict(obj: Any) -> 'QuotaSnapshot': + def from_dict(obj: Any) -> 'AccountQuotaSnapshot': assert isinstance(obj, dict) - entitlement_requests = from_float(obj.get("entitlementRequests")) - overage = from_float(obj.get("overage")) + entitlement_requests = from_int(obj.get("entitlementRequests")) + overage = from_int(obj.get("overage")) overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) remaining_percentage = from_float(obj.get("remainingPercentage")) - used_requests = from_float(obj.get("usedRequests")) - reset_date = from_union([from_str, from_none], obj.get("resetDate")) - return QuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) + used_requests = from_int(obj.get("usedRequests")) + reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) + return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = to_float(self.entitlement_requests) - result["overage"] = to_float(self.overage) + result["entitlementRequests"] = from_int(self.entitlement_requests) + result["overage"] = from_int(self.overage) result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usedRequests"] = to_float(self.used_requests) + result["usedRequests"] = from_int(self.used_requests) if self.reset_date is not None: - result["resetDate"] = from_union([from_str, from_none], self.reset_date) + result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) return result - @dataclass class AccountGetQuotaResult: - quota_snapshots: dict[str, QuotaSnapshot] + quota_snapshots: dict[str, AccountQuotaSnapshot] """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" @staticmethod def from_dict(obj: Any) -> 'AccountGetQuotaResult': assert isinstance(obj, dict) - quota_snapshots = from_dict(QuotaSnapshot.from_dict, obj.get("quotaSnapshots")) + quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) return AccountGetQuotaResult(quota_snapshots) def to_dict(self) -> dict: result: dict = {} - result["quotaSnapshots"] = from_dict(lambda x: to_class(QuotaSnapshot, x), self.quota_snapshots) + result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) return result - -class FilterMappingEnum(Enum): +class MCPConfigFilterMappingString(Enum): HIDDEN_CHARACTERS = "hidden_characters" MARKDOWN = "markdown" NONE = "none" - -class ServerType(Enum): +class MCPConfigType(Enum): HTTP = "http" LOCAL = "local" SSE = "sse" STDIO = "stdio" - @dataclass -class ServerValue: +class MCPConfigServer: """MCP server configuration (local/stdio or remote/http)""" args: list[str] | None = None command: str | None = None cwd: str | None = None env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingEnum] | FilterMappingEnum | None = None + filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None is_default_server: bool | None = None - timeout: float | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + tools: list[str] | None = None """Tools to include. Defaults to all tools if not specified.""" - type: ServerType | None = None + type: MCPConfigType | None = None headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ServerValue': + def from_dict(obj: Any) -> 'MCPConfigServer': assert isinstance(obj, dict) args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) command = from_union([from_str, from_none], obj.get("command")) cwd = from_union([from_str, from_none], obj.get("cwd")) env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingEnum, x), FilterMappingEnum, from_none], obj.get("filterMapping")) + filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_float, from_none], obj.get("timeout")) + timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([ServerType, from_none], obj.get("type")) + type = from_union([MCPConfigType, from_none], obj.get("type")) headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) url = from_union([from_str, from_none], obj.get("url")) - return ServerValue(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return MCPConfigServer(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} @@ -554,15 +531,15 @@ def to_dict(self) -> dict: if self.env is not None: result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingEnum, x), x), lambda x: to_enum(FilterMappingEnum, x), from_none], self.filter_mapping) + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) if self.is_default_server is not None: result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) + result["timeout"] = from_union([from_int, from_none], self.timeout) if self.tools is not None: result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ServerType, x), from_none], self.type) + result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) if self.headers is not None: result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) if self.oauth_client_id is not None: @@ -573,61 +550,61 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result - @dataclass -class MCPConfigListResult: - servers: dict[str, ServerValue] +class MCPConfigList: + servers: dict[str, MCPConfigServer] """All MCP servers from user config, keyed by name""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigListResult': + def from_dict(obj: Any) -> 'MCPConfigList': assert isinstance(obj, dict) - servers = from_dict(ServerValue.from_dict, obj.get("servers")) - return MCPConfigListResult(servers) + servers = from_dict(MCPConfigServer.from_dict, obj.get("servers")) + return MCPConfigList(servers) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_dict(lambda x: to_class(ServerValue, x), self.servers) + result["servers"] = from_dict(lambda x: to_class(MCPConfigServer, x), self.servers) return result - @dataclass -class MCPConfigAddParamsConfig: +class MCPConfigAddConfig: """MCP server configuration (local/stdio or remote/http)""" args: list[str] | None = None command: str | None = None cwd: str | None = None env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingEnum] | FilterMappingEnum | None = None + filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None is_default_server: bool | None = None - timeout: float | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + tools: list[str] | None = None """Tools to include. Defaults to all tools if not specified.""" - type: ServerType | None = None + type: MCPConfigType | None = None headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddParamsConfig': + def from_dict(obj: Any) -> 'MCPConfigAddConfig': assert isinstance(obj, dict) args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) command = from_union([from_str, from_none], obj.get("command")) cwd = from_union([from_str, from_none], obj.get("cwd")) env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingEnum, x), FilterMappingEnum, from_none], obj.get("filterMapping")) + filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_float, from_none], obj.get("timeout")) + timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([ServerType, from_none], obj.get("type")) + type = from_union([MCPConfigType, from_none], obj.get("type")) headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigAddParamsConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return MCPConfigAddConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} @@ -640,15 +617,15 @@ def to_dict(self) -> dict: if self.env is not None: result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingEnum, x), x), lambda x: to_enum(FilterMappingEnum, x), from_none], self.filter_mapping) + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) if self.is_default_server is not None: result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) + result["timeout"] = from_union([from_int, from_none], self.timeout) if self.tools is not None: result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ServerType, x), from_none], self.type) + result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) if self.headers is not None: result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) if self.oauth_client_id is not None: @@ -659,66 +636,66 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result - @dataclass -class MCPConfigAddParams: - config: MCPConfigAddParamsConfig +class MCPConfigAddRequest: + config: MCPConfigAddConfig """MCP server configuration (local/stdio or remote/http)""" name: str """Unique name for the MCP server""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddParams': + def from_dict(obj: Any) -> 'MCPConfigAddRequest': assert isinstance(obj, dict) - config = MCPConfigAddParamsConfig.from_dict(obj.get("config")) + config = MCPConfigAddConfig.from_dict(obj.get("config")) name = from_str(obj.get("name")) - return MCPConfigAddParams(config, name) + return MCPConfigAddRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigAddParamsConfig, self.config) + result["config"] = to_class(MCPConfigAddConfig, self.config) result["name"] = from_str(self.name) return result - @dataclass -class MCPConfigUpdateParamsConfig: +class MCPConfigUpdateConfig: """MCP server configuration (local/stdio or remote/http)""" args: list[str] | None = None command: str | None = None cwd: str | None = None env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingEnum] | FilterMappingEnum | None = None + filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None is_default_server: bool | None = None - timeout: float | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + tools: list[str] | None = None """Tools to include. Defaults to all tools if not specified.""" - type: ServerType | None = None + type: MCPConfigType | None = None headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateParamsConfig': + def from_dict(obj: Any) -> 'MCPConfigUpdateConfig': assert isinstance(obj, dict) args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) command = from_union([from_str, from_none], obj.get("command")) cwd = from_union([from_str, from_none], obj.get("cwd")) env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingEnum, x), FilterMappingEnum, from_none], obj.get("filterMapping")) + filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_float, from_none], obj.get("timeout")) + timeout = from_union([from_int, from_none], obj.get("timeout")) tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([ServerType, from_none], obj.get("type")) + type = from_union([MCPConfigType, from_none], obj.get("type")) headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigUpdateParamsConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return MCPConfigUpdateConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} @@ -731,15 +708,15 @@ def to_dict(self) -> dict: if self.env is not None: result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingEnum, x), x), lambda x: to_enum(FilterMappingEnum, x), from_none], self.filter_mapping) + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) if self.is_default_server is not None: result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) + result["timeout"] = from_union([from_int, from_none], self.timeout) if self.tools is not None: result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ServerType, x), from_none], self.type) + result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) if self.headers is not None: result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) if self.oauth_client_id is not None: @@ -750,54 +727,60 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result - @dataclass -class MCPConfigUpdateParams: - config: MCPConfigUpdateParamsConfig +class MCPConfigUpdateRequest: + config: MCPConfigUpdateConfig """MCP server configuration (local/stdio or remote/http)""" name: str """Name of the MCP server to update""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateParams': + def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': assert isinstance(obj, dict) - config = MCPConfigUpdateParamsConfig.from_dict(obj.get("config")) + config = MCPConfigUpdateConfig.from_dict(obj.get("config")) name = from_str(obj.get("name")) - return MCPConfigUpdateParams(config, name) + return MCPConfigUpdateRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigUpdateParamsConfig, self.config) + result["config"] = to_class(MCPConfigUpdateConfig, self.config) result["name"] = from_str(self.name) return result - @dataclass -class MCPConfigRemoveParams: +class MCPConfigRemoveRequest: name: str """Name of the MCP server to remove""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigRemoveParams': + def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) - return MCPConfigRemoveParams(name) + return MCPConfigRemoveRequest(name) def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) return result - -class ServerSource(Enum): - """Configuration source""" - +class MCPServerSource(Enum): + """Configuration source + + Configuration source: user, workspace, plugin, or builtin + """ BUILTIN = "builtin" PLUGIN = "plugin" USER = "user" WORKSPACE = "workspace" +class DiscoveredMCPServerType(Enum): + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + + HTTP = "http" + MEMORY = "memory" + SSE = "sse" + STDIO = "stdio" @dataclass class DiscoveredMCPServer: @@ -807,31 +790,30 @@ class DiscoveredMCPServer: name: str """Server name (config key)""" - source: ServerSource + source: MCPServerSource """Configuration source""" - type: str | None = None - """Server type: local, stdio, http, or sse""" + type: DiscoveredMCPServerType | None = None + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" @staticmethod def from_dict(obj: Any) -> 'DiscoveredMCPServer': assert isinstance(obj, dict) enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = ServerSource(obj.get("source")) - type = from_union([from_str, from_none], obj.get("type")) + source = MCPServerSource(obj.get("source")) + type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) return DiscoveredMCPServer(enabled, name, source, type) def to_dict(self) -> dict: result: dict = {} result["enabled"] = from_bool(self.enabled) result["name"] = from_str(self.name) - result["source"] = to_enum(ServerSource, self.source) + result["source"] = to_enum(MCPServerSource, self.source) if self.type is not None: - result["type"] = from_union([from_str, from_none], self.type) + result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) return result - @dataclass class MCPDiscoverResult: servers: list[DiscoveredMCPServer] @@ -848,17 +830,16 @@ def to_dict(self) -> dict: result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) return result - @dataclass -class MCPDiscoverParams: +class MCPDiscoverRequest: working_directory: str | None = None """Working directory used as context for discovery (e.g., plugin resolution)""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverParams': + def from_dict(obj: Any) -> 'MCPDiscoverRequest': assert isinstance(obj, dict) working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return MCPDiscoverParams(working_directory) + return MCPDiscoverRequest(working_directory) def to_dict(self) -> dict: result: dict = {} @@ -866,7 +847,6 @@ def to_dict(self) -> dict: result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) return result - @dataclass class SessionFSSetProviderResult: success: bool @@ -883,17 +863,15 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) return result - -class Conventions(Enum): +class SessionFSSetProviderConventions(Enum): """Path conventions used by this filesystem""" POSIX = "posix" WINDOWS = "windows" - @dataclass -class SessionFSSetProviderParams: - conventions: Conventions +class SessionFSSetProviderRequest: + conventions: SessionFSSetProviderConventions """Path conventions used by this filesystem""" initial_cwd: str @@ -903,21 +881,20 @@ class SessionFSSetProviderParams: """Path within each session's SessionFs where the runtime stores files for that session""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderParams': + def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': assert isinstance(obj, dict) - conventions = Conventions(obj.get("conventions")) + conventions = SessionFSSetProviderConventions(obj.get("conventions")) initial_cwd = from_str(obj.get("initialCwd")) session_state_path = from_str(obj.get("sessionStatePath")) - return SessionFSSetProviderParams(conventions, initial_cwd, session_state_path) + return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) def to_dict(self) -> dict: result: dict = {} - result["conventions"] = to_enum(Conventions, self.conventions) + result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) result["initialCwd"] = from_str(self.initial_cwd) result["sessionStatePath"] = from_str(self.session_state_path) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsForkResult: @@ -935,10 +912,9 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionsForkParams: +class SessionsForkRequest: session_id: str """Source session ID to fork from""" @@ -948,11 +924,11 @@ class SessionsForkParams: """ @staticmethod - def from_dict(obj: Any) -> 'SessionsForkParams': + def from_dict(obj: Any) -> 'SessionsForkRequest': assert isinstance(obj, dict) session_id = from_str(obj.get("sessionId")) to_event_id = from_union([from_str, from_none], obj.get("toEventId")) - return SessionsForkParams(session_id, to_event_id) + return SessionsForkRequest(session_id, to_event_id) def to_dict(self) -> dict: result: dict = {} @@ -961,17 +937,16 @@ def to_dict(self) -> dict: result["toEventId"] = from_union([from_str, from_none], self.to_event_id) return result - @dataclass -class SessionModelGetCurrentResult: +class CurrentModel: model_id: str | None = None """Currently active model identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionModelGetCurrentResult': + def from_dict(obj: Any) -> 'CurrentModel': assert isinstance(obj, dict) model_id = from_union([from_str, from_none], obj.get("modelId")) - return SessionModelGetCurrentResult(model_id) + return CurrentModel(model_id) def to_dict(self) -> dict: result: dict = {} @@ -979,17 +954,16 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result - @dataclass -class SessionModelSwitchToResult: +class ModelSwitchToResult: model_id: str | None = None """Currently active model identifier after the switch""" @staticmethod - def from_dict(obj: Any) -> 'SessionModelSwitchToResult': + def from_dict(obj: Any) -> 'ModelSwitchToResult': assert isinstance(obj, dict) model_id = from_union([from_str, from_none], obj.get("modelId")) - return SessionModelSwitchToResult(model_id) + return ModelSwitchToResult(model_id) def to_dict(self) -> dict: result: dict = {} @@ -997,13 +971,12 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result - @dataclass class ModelCapabilitiesOverrideLimitsVision: - max_prompt_image_size: float | None = None + max_prompt_image_size: int | None = None """Maximum image size in bytes""" - max_prompt_images: float | None = None + max_prompt_images: int | None = None """Maximum number of images per prompt""" supported_media_types: list[str] | None = None @@ -1012,55 +985,53 @@ class ModelCapabilitiesOverrideLimitsVision: @staticmethod def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimitsVision': assert isinstance(obj, dict) - max_prompt_image_size = from_union([from_float, from_none], obj.get("max_prompt_image_size")) - max_prompt_images = from_union([from_float, from_none], obj.get("max_prompt_images")) + max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) + max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) return ModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} if self.max_prompt_image_size is not None: - result["max_prompt_image_size"] = from_union([to_float, from_none], self.max_prompt_image_size) + result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) if self.max_prompt_images is not None: - result["max_prompt_images"] = from_union([to_float, from_none], self.max_prompt_images) + result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) if self.supported_media_types is not None: result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) return result - @dataclass class ModelCapabilitiesOverrideLimits: """Token limits for prompts, outputs, and context window""" - max_context_window_tokens: float | None = None + max_context_window_tokens: int | None = None """Maximum total context window size in tokens""" - max_output_tokens: float | None = None - max_prompt_tokens: float | None = None + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None vision: ModelCapabilitiesOverrideLimitsVision | None = None @staticmethod def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_float, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_float, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_float, from_none], obj.get("max_prompt_tokens")) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) vision = from_union([ModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([to_float, from_none], self.max_context_window_tokens) + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([to_float, from_none], self.max_output_tokens) + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([to_float, from_none], self.max_prompt_tokens) + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) if self.vision is not None: result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) return result - @dataclass class ModelCapabilitiesOverrideSupports: """Feature flags indicating what the model supports""" @@ -1083,7 +1054,6 @@ def to_dict(self) -> dict: result["vision"] = from_union([from_bool, from_none], self.vision) return result - @dataclass class ModelCapabilitiesOverride: """Override individual model capabilities resolved by the runtime""" @@ -1109,9 +1079,8 @@ def to_dict(self) -> dict: result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) return result - @dataclass -class SessionModelSwitchToParams: +class ModelSwitchToRequest: model_id: str """Model identifier to switch to""" @@ -1122,12 +1091,12 @@ class SessionModelSwitchToParams: """Reasoning effort level to use for the model""" @staticmethod - def from_dict(obj: Any) -> 'SessionModelSwitchToParams': + def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) model_id = from_str(obj.get("modelId")) model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) - return SessionModelSwitchToParams(model_id, model_capabilities, reasoning_effort) + return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort) def to_dict(self) -> dict: result: dict = {} @@ -1138,72 +1107,31 @@ def to_dict(self) -> dict: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) return result +class SessionMode(Enum): + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" -class Mode(Enum): - """The current agent mode. - - The agent mode after switching. - - The mode to switch to. Valid values: "interactive", "plan", "autopilot". - """ AUTOPILOT = "autopilot" INTERACTIVE = "interactive" PLAN = "plan" - -@dataclass -class SessionModeGetResult: - mode: Mode - """The current agent mode.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionModeGetResult': - assert isinstance(obj, dict) - mode = Mode(obj.get("mode")) - return SessionModeGetResult(mode) - - def to_dict(self) -> dict: - result: dict = {} - result["mode"] = to_enum(Mode, self.mode) - return result - - -@dataclass -class SessionModeSetResult: - mode: Mode - """The agent mode after switching.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionModeSetResult': - assert isinstance(obj, dict) - mode = Mode(obj.get("mode")) - return SessionModeSetResult(mode) - - def to_dict(self) -> dict: - result: dict = {} - result["mode"] = to_enum(Mode, self.mode) - return result - - @dataclass -class SessionModeSetParams: - mode: Mode - """The mode to switch to. Valid values: "interactive", "plan", "autopilot".""" +class ModeSetRequest: + mode: SessionMode + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" @staticmethod - def from_dict(obj: Any) -> 'SessionModeSetParams': + def from_dict(obj: Any) -> 'ModeSetRequest': assert isinstance(obj, dict) - mode = Mode(obj.get("mode")) - return SessionModeSetParams(mode) + mode = SessionMode(obj.get("mode")) + return ModeSetRequest(mode) def to_dict(self) -> dict: result: dict = {} - result["mode"] = to_enum(Mode, self.mode) + result["mode"] = to_enum(SessionMode, self.mode) return result - @dataclass -class SessionPlanReadResult: +class PlanReadResult: exists: bool """Whether the plan file exists in the workspace""" @@ -1214,12 +1142,12 @@ class SessionPlanReadResult: """Absolute file path of the plan file, or null if workspace is not enabled""" @staticmethod - def from_dict(obj: Any) -> 'SessionPlanReadResult': + def from_dict(obj: Any) -> 'PlanReadResult': assert isinstance(obj, dict) exists = from_bool(obj.get("exists")) content = from_union([from_none, from_str], obj.get("content")) path = from_union([from_none, from_str], obj.get("path")) - return SessionPlanReadResult(exists, content, path) + return PlanReadResult(exists, content, path) def to_dict(self) -> dict: result: dict = {} @@ -1228,113 +1156,72 @@ def to_dict(self) -> dict: result["path"] = from_union([from_none, from_str], self.path) return result - -@dataclass -class SessionPlanUpdateResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionPlanUpdateResult': - assert isinstance(obj, dict) - return SessionPlanUpdateResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass -class SessionPlanUpdateParams: +class PlanUpdateRequest: content: str """The new content for the plan file""" @staticmethod - def from_dict(obj: Any) -> 'SessionPlanUpdateParams': + def from_dict(obj: Any) -> 'PlanUpdateRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) - return SessionPlanUpdateParams(content) + return PlanUpdateRequest(content) def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) return result - -@dataclass -class SessionPlanDeleteResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionPlanDeleteResult': - assert isinstance(obj, dict) - return SessionPlanDeleteResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass -class SessionWorkspaceListFilesResult: +class WorkspaceListFilesResult: files: list[str] """Relative file paths in the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionWorkspaceListFilesResult': + def from_dict(obj: Any) -> 'WorkspaceListFilesResult': assert isinstance(obj, dict) files = from_list(from_str, obj.get("files")) - return SessionWorkspaceListFilesResult(files) + return WorkspaceListFilesResult(files) def to_dict(self) -> dict: result: dict = {} result["files"] = from_list(from_str, self.files) return result - @dataclass -class SessionWorkspaceReadFileResult: +class WorkspaceReadFileResult: content: str """File content as a UTF-8 string""" @staticmethod - def from_dict(obj: Any) -> 'SessionWorkspaceReadFileResult': + def from_dict(obj: Any) -> 'WorkspaceReadFileResult': assert isinstance(obj, dict) content = from_str(obj.get("content")) - return SessionWorkspaceReadFileResult(content) + return WorkspaceReadFileResult(content) def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) return result - @dataclass -class SessionWorkspaceReadFileParams: +class WorkspaceReadFileRequest: path: str """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionWorkspaceReadFileParams': + def from_dict(obj: Any) -> 'WorkspaceReadFileRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) - return SessionWorkspaceReadFileParams(path) + return WorkspaceReadFileRequest(path) def to_dict(self) -> dict: result: dict = {} result["path"] = from_str(self.path) return result - -@dataclass -class SessionWorkspaceCreateFileResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionWorkspaceCreateFileResult': - assert isinstance(obj, dict) - return SessionWorkspaceCreateFileResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass -class SessionWorkspaceCreateFileParams: +class WorkspaceCreateFileRequest: content: str """File content to write as a UTF-8 string""" @@ -1342,11 +1229,11 @@ class SessionWorkspaceCreateFileParams: """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionWorkspaceCreateFileParams': + def from_dict(obj: Any) -> 'WorkspaceCreateFileRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) path = from_str(obj.get("path")) - return SessionWorkspaceCreateFileParams(content, path) + return WorkspaceCreateFileRequest(content, path) def to_dict(self) -> dict: result: dict = {} @@ -1354,36 +1241,34 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFleetStartResult: +class FleetStartResult: started: bool """Whether fleet mode was successfully activated""" @staticmethod - def from_dict(obj: Any) -> 'SessionFleetStartResult': + def from_dict(obj: Any) -> 'FleetStartResult': assert isinstance(obj, dict) started = from_bool(obj.get("started")) - return SessionFleetStartResult(started) + return FleetStartResult(started) def to_dict(self) -> dict: result: dict = {} result["started"] = from_bool(self.started) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFleetStartParams: +class FleetStartRequest: prompt: str | None = None """Optional user prompt to combine with fleet instructions""" @staticmethod - def from_dict(obj: Any) -> 'SessionFleetStartParams': + def from_dict(obj: Any) -> 'FleetStartRequest': assert isinstance(obj, dict) prompt = from_union([from_str, from_none], obj.get("prompt")) - return SessionFleetStartParams(prompt) + return FleetStartRequest(prompt) def to_dict(self) -> dict: result: dict = {} @@ -1391,9 +1276,8 @@ def to_dict(self) -> dict: result["prompt"] = from_union([from_str, from_none], self.prompt) return result - @dataclass -class SessionAgentListResultAgent: +class Agent: description: str """Description of the agent's purpose""" @@ -1404,12 +1288,12 @@ class SessionAgentListResultAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentListResultAgent': + def from_dict(obj: Any) -> 'Agent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return SessionAgentListResultAgent(description, display_name, name) + return Agent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1418,27 +1302,25 @@ 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 SessionAgentListResult: - agents: list[SessionAgentListResultAgent] +class AgentList: + agents: list[Agent] """Available custom agents""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentListResult': + def from_dict(obj: Any) -> 'AgentList': assert isinstance(obj, dict) - agents = from_list(SessionAgentListResultAgent.from_dict, obj.get("agents")) - return SessionAgentListResult(agents) + agents = from_list(Agent.from_dict, obj.get("agents")) + return AgentList(agents) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(SessionAgentListResultAgent, x), self.agents) + result["agents"] = from_list(lambda x: to_class(Agent, x), self.agents) return result - @dataclass -class SessionAgentGetCurrentResultAgent: +class AgentGetCurrentResultAgent: description: str """Description of the agent's purpose""" @@ -1449,12 +1331,12 @@ class SessionAgentGetCurrentResultAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentGetCurrentResultAgent': + def from_dict(obj: Any) -> 'AgentGetCurrentResultAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return SessionAgentGetCurrentResultAgent(description, display_name, name) + return AgentGetCurrentResultAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1463,27 +1345,25 @@ 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 SessionAgentGetCurrentResult: - agent: SessionAgentGetCurrentResultAgent | None = None +class AgentGetCurrentResult: + agent: AgentGetCurrentResultAgent | None = None """Currently selected custom agent, or null if using the default agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentGetCurrentResult': + def from_dict(obj: Any) -> 'AgentGetCurrentResult': assert isinstance(obj, dict) - agent = from_union([SessionAgentGetCurrentResultAgent.from_dict, from_none], obj.get("agent")) - return SessionAgentGetCurrentResult(agent) + agent = from_union([AgentGetCurrentResultAgent.from_dict, from_none], obj.get("agent")) + return AgentGetCurrentResult(agent) def to_dict(self) -> dict: result: dict = {} - result["agent"] = from_union([lambda x: to_class(SessionAgentGetCurrentResultAgent, x), from_none], self.agent) + result["agent"] = from_union([lambda x: to_class(AgentGetCurrentResultAgent, x), from_none], self.agent) return result - @dataclass -class SessionAgentSelectResultAgent: +class AgentSelectAgent: """The newly selected custom agent""" description: str @@ -1496,12 +1376,12 @@ class SessionAgentSelectResultAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentSelectResultAgent': + def from_dict(obj: Any) -> 'AgentSelectAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return SessionAgentSelectResultAgent(description, display_name, name) + return AgentSelectAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1510,58 +1390,42 @@ 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 SessionAgentSelectResult: - agent: SessionAgentSelectResultAgent +class AgentSelectResult: + agent: AgentSelectAgent """The newly selected custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentSelectResult': + def from_dict(obj: Any) -> 'AgentSelectResult': assert isinstance(obj, dict) - agent = SessionAgentSelectResultAgent.from_dict(obj.get("agent")) - return SessionAgentSelectResult(agent) + agent = AgentSelectAgent.from_dict(obj.get("agent")) + return AgentSelectResult(agent) def to_dict(self) -> dict: result: dict = {} - result["agent"] = to_class(SessionAgentSelectResultAgent, self.agent) + result["agent"] = to_class(AgentSelectAgent, self.agent) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionAgentSelectParams: +class AgentSelectRequest: name: str """Name of the custom agent to select""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentSelectParams': + def from_dict(obj: Any) -> 'AgentSelectRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) - return SessionAgentSelectParams(name) + return AgentSelectRequest(name) def to_dict(self) -> dict: result: 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 SessionAgentDeselectResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionAgentDeselectResult': - assert isinstance(obj, dict) - return SessionAgentDeselectResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass -class SessionAgentReloadResultAgent: +class AgentReloadAgent: description: str """Description of the agent's purpose""" @@ -1572,12 +1436,12 @@ class SessionAgentReloadResultAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentReloadResultAgent': + def from_dict(obj: Any) -> 'AgentReloadAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return SessionAgentReloadResultAgent(description, display_name, name) + return AgentReloadAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1586,25 +1450,23 @@ 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 SessionAgentReloadResult: - agents: list[SessionAgentReloadResultAgent] +class AgentReloadResult: + agents: list[AgentReloadAgent] """Reloaded custom agents""" @staticmethod - def from_dict(obj: Any) -> 'SessionAgentReloadResult': + def from_dict(obj: Any) -> 'AgentReloadResult': assert isinstance(obj, dict) - agents = from_list(SessionAgentReloadResultAgent.from_dict, obj.get("agents")) - return SessionAgentReloadResult(agents) + agents = from_list(AgentReloadAgent.from_dict, obj.get("agents")) + return AgentReloadResult(agents) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(SessionAgentReloadResultAgent, x), self.agents) + result["agents"] = from_list(lambda x: to_class(AgentReloadAgent, x), self.agents) return result - @dataclass class Skill: description: str @@ -1647,101 +1509,58 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionSkillsListResult: +class SkillList: skills: list[Skill] """Available skills""" @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsListResult': + def from_dict(obj: Any) -> 'SkillList': assert isinstance(obj, dict) skills = from_list(Skill.from_dict, obj.get("skills")) - return SessionSkillsListResult(skills) + return SkillList(skills) def to_dict(self) -> dict: result: dict = {} result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills) return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionSkillsEnableResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsEnableResult': - assert isinstance(obj, dict) - return SessionSkillsEnableResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionSkillsEnableParams: +class SkillsEnableRequest: name: str """Name of the skill to enable""" @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsEnableParams': + def from_dict(obj: Any) -> 'SkillsEnableRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) - return SessionSkillsEnableParams(name) + return SkillsEnableRequest(name) def to_dict(self) -> dict: result: 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 SessionSkillsDisableResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsDisableResult': - assert isinstance(obj, dict) - return SessionSkillsDisableResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionSkillsDisableParams: +class SkillsDisableRequest: name: str """Name of the skill to disable""" @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsDisableParams': + def from_dict(obj: Any) -> 'SkillsDisableRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) - return SessionSkillsDisableParams(name) + return SkillsDisableRequest(name) def to_dict(self) -> dict: result: 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 SessionSkillsReloadResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionSkillsReloadResult': - assert isinstance(obj, dict) - return SessionSkillsReloadResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - -class ServerStatus(Enum): +class MCPServerStatus(Enum): """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" CONNECTED = "connected" @@ -1751,128 +1570,87 @@ class ServerStatus(Enum): NOT_CONFIGURED = "not_configured" PENDING = "pending" - @dataclass -class ServerElement: +class MCPServer: name: str """Server name (config key)""" - status: ServerStatus + status: MCPServerStatus """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" error: str | None = None """Error message if the server failed to connect""" - source: str | None = None + source: MCPServerSource | None = None """Configuration source: user, workspace, plugin, or builtin""" @staticmethod - def from_dict(obj: Any) -> 'ServerElement': + def from_dict(obj: Any) -> 'MCPServer': assert isinstance(obj, dict) name = from_str(obj.get("name")) - status = ServerStatus(obj.get("status")) + status = MCPServerStatus(obj.get("status")) error = from_union([from_str, from_none], obj.get("error")) - source = from_union([from_str, from_none], obj.get("source")) - return ServerElement(name, status, error, source) + source = from_union([MCPServerSource, from_none], obj.get("source")) + return MCPServer(name, status, error, source) def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) - result["status"] = to_enum(ServerStatus, self.status) + result["status"] = to_enum(MCPServerStatus, self.status) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) + result["source"] = from_union([lambda x: to_enum(MCPServerSource, x), from_none], self.source) return result - @dataclass -class SessionMCPListResult: - servers: list[ServerElement] +class MCPServerList: + servers: list[MCPServer] """Configured MCP servers""" @staticmethod - def from_dict(obj: Any) -> 'SessionMCPListResult': - assert isinstance(obj, dict) - servers = from_list(ServerElement.from_dict, obj.get("servers")) - return SessionMCPListResult(servers) - - def to_dict(self) -> dict: - result: dict = {} - result["servers"] = from_list(lambda x: to_class(ServerElement, x), self.servers) - return result - - -@dataclass -class SessionMCPEnableResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionMCPEnableResult': + def from_dict(obj: Any) -> 'MCPServerList': assert isinstance(obj, dict) - return SessionMCPEnableResult() + servers = from_list(MCPServer.from_dict, obj.get("servers")) + return MCPServerList(servers) def to_dict(self) -> dict: result: dict = {} + result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) return result - @dataclass -class SessionMCPEnableParams: +class MCPEnableRequest: server_name: str """Name of the MCP server to enable""" @staticmethod - def from_dict(obj: Any) -> 'SessionMCPEnableParams': + def from_dict(obj: Any) -> 'MCPEnableRequest': assert isinstance(obj, dict) server_name = from_str(obj.get("serverName")) - return SessionMCPEnableParams(server_name) + return MCPEnableRequest(server_name) def to_dict(self) -> dict: result: dict = {} result["serverName"] = from_str(self.server_name) return result - -@dataclass -class SessionMCPDisableResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionMCPDisableResult': - assert isinstance(obj, dict) - return SessionMCPDisableResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass -class SessionMCPDisableParams: +class MCPDisableRequest: server_name: str """Name of the MCP server to disable""" @staticmethod - def from_dict(obj: Any) -> 'SessionMCPDisableParams': + def from_dict(obj: Any) -> 'MCPDisableRequest': assert isinstance(obj, dict) server_name = from_str(obj.get("serverName")) - return SessionMCPDisableParams(server_name) + return MCPDisableRequest(server_name) def to_dict(self) -> dict: result: dict = {} result["serverName"] = from_str(self.server_name) return result - -@dataclass -class SessionMCPReloadResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionMCPReloadResult': - assert isinstance(obj, dict) - return SessionMCPReloadResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - @dataclass class Plugin: enabled: bool @@ -1905,32 +1683,29 @@ def to_dict(self) -> dict: result["version"] = from_union([from_str, from_none], self.version) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionPluginsListResult: +class PluginList: plugins: list[Plugin] """Installed plugins""" @staticmethod - def from_dict(obj: Any) -> 'SessionPluginsListResult': + def from_dict(obj: Any) -> 'PluginList': assert isinstance(obj, dict) plugins = from_list(Plugin.from_dict, obj.get("plugins")) - return SessionPluginsListResult(plugins) + return PluginList(plugins) def to_dict(self) -> dict: result: dict = {} result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) return result - class ExtensionSource(Enum): """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" PROJECT = "project" USER = "user" - class ExtensionStatus(Enum): """Current status: running, disabled, failed, or starting""" @@ -1939,7 +1714,6 @@ class ExtensionStatus(Enum): RUNNING = "running" STARTING = "starting" - @dataclass class Extension: id: str @@ -1977,124 +1751,80 @@ def to_dict(self) -> dict: result["pid"] = from_union([from_int, from_none], self.pid) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionExtensionsListResult: +class ExtensionList: extensions: list[Extension] """Discovered extensions and their current status""" @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsListResult': + def from_dict(obj: Any) -> 'ExtensionList': assert isinstance(obj, dict) extensions = from_list(Extension.from_dict, obj.get("extensions")) - return SessionExtensionsListResult(extensions) + return ExtensionList(extensions) def to_dict(self) -> dict: result: dict = {} result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionExtensionsEnableResult: +class ExtensionsEnableRequest: + id: str + """Source-qualified extension ID to enable""" + @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsEnableResult': + def from_dict(obj: Any) -> 'ExtensionsEnableRequest': assert isinstance(obj, dict) - return SessionExtensionsEnableResult() + id = from_str(obj.get("id")) + return ExtensionsEnableRequest(id) def to_dict(self) -> dict: result: dict = {} + result["id"] = from_str(self.id) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionExtensionsEnableParams: +class ExtensionsDisableRequest: id: str - """Source-qualified extension ID to enable""" + """Source-qualified extension ID to disable""" @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsEnableParams': + def from_dict(obj: Any) -> 'ExtensionsDisableRequest': assert isinstance(obj, dict) id = from_str(obj.get("id")) - return SessionExtensionsEnableParams(id) + return ExtensionsDisableRequest(id) def to_dict(self) -> dict: result: dict = {} result["id"] = from_str(self.id) return result - -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionExtensionsDisableResult: +class HandleToolCallResult: + success: bool + """Whether the tool call result was handled successfully""" + @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsDisableResult': + def from_dict(obj: Any) -> 'HandleToolCallResult': assert isinstance(obj, dict) - return SessionExtensionsDisableResult() + success = from_bool(obj.get("success")) + return HandleToolCallResult(success) def to_dict(self) -> dict: result: dict = {} + result["success"] = from_bool(self.success) return result - -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionExtensionsDisableParams: - id: str - """Source-qualified extension ID to disable""" +class ToolCallResult: + text_result_for_llm: str + """Text result to send back to the LLM""" - @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsDisableParams': - assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return SessionExtensionsDisableParams(id) - - def to_dict(self) -> dict: - result: dict = {} - result["id"] = from_str(self.id) - return result - - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionExtensionsReloadResult: - @staticmethod - def from_dict(obj: Any) -> 'SessionExtensionsReloadResult': - assert isinstance(obj, dict) - return SessionExtensionsReloadResult() - - def to_dict(self) -> dict: - result: dict = {} - return result - - -@dataclass -class SessionToolsHandlePendingToolCallResult: - success: bool - """Whether the tool call result was handled successfully""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionToolsHandlePendingToolCallResult': - assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return SessionToolsHandlePendingToolCallResult(success) - - def to_dict(self) -> dict: - result: dict = {} - result["success"] = from_bool(self.success) - return result - - -@dataclass -class ResultResult: - text_result_for_llm: str - """Text result to send back to the LLM""" - - error: str | None = None - """Error message if the tool call failed""" + error: str | None = None + """Error message if the tool call failed""" result_type: str | None = None """Type of the tool result""" @@ -2103,13 +1833,13 @@ class ResultResult: """Telemetry data from tool execution""" @staticmethod - def from_dict(obj: Any) -> 'ResultResult': + def from_dict(obj: Any) -> 'ToolCallResult': assert isinstance(obj, dict) text_result_for_llm = from_str(obj.get("textResultForLlm")) error = from_union([from_str, from_none], obj.get("error")) result_type = from_union([from_str, from_none], obj.get("resultType")) tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ResultResult(text_result_for_llm, error, result_type, tool_telemetry) + return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) def to_dict(self) -> dict: result: dict = {} @@ -2122,25 +1852,24 @@ def to_dict(self) -> dict: result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result - @dataclass -class SessionToolsHandlePendingToolCallParams: +class ToolsHandlePendingToolCallRequest: request_id: str """Request ID of the pending tool call""" error: str | None = None """Error message if the tool call failed""" - result: ResultResult | str | None = None + result: ToolCallResult | str | None = None """Tool call result (string or expanded result object)""" @staticmethod - def from_dict(obj: Any) -> 'SessionToolsHandlePendingToolCallParams': + def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) error = from_union([from_str, from_none], obj.get("error")) - result = from_union([ResultResult.from_dict, from_str, from_none], obj.get("result")) - return SessionToolsHandlePendingToolCallParams(request_id, error, result) + result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) + return ToolsHandlePendingToolCallRequest(request_id, error, result) def to_dict(self) -> dict: result: dict = {} @@ -2148,29 +1877,27 @@ def to_dict(self) -> dict: if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.result is not None: - result["result"] = from_union([lambda x: to_class(ResultResult, x), from_str, from_none], self.result) + result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) return result - @dataclass -class SessionCommandsHandlePendingCommandResult: +class CommandsHandlePendingCommandResult: success: bool """Whether the command was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'SessionCommandsHandlePendingCommandResult': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return SessionCommandsHandlePendingCommandResult(success) + return CommandsHandlePendingCommandResult(success) def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) return result - @dataclass -class SessionCommandsHandlePendingCommandParams: +class CommandsHandlePendingCommandRequest: request_id: str """Request ID from the command invocation event""" @@ -2178,11 +1905,11 @@ class SessionCommandsHandlePendingCommandParams: """Error message if the command handler failed""" @staticmethod - def from_dict(obj: Any) -> 'SessionCommandsHandlePendingCommandParams': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) error = from_union([from_str, from_none], obj.get("error")) - return SessionCommandsHandlePendingCommandParams(request_id, error) + return CommandsHandlePendingCommandRequest(request_id, error) def to_dict(self) -> dict: result: dict = {} @@ -2191,56 +1918,54 @@ def to_dict(self) -> dict: result["error"] = from_union([from_str, from_none], self.error) return result - -class Action(Enum): +class UIElicitationResponseAction(Enum): """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" ACCEPT = "accept" CANCEL = "cancel" DECLINE = "decline" - @dataclass -class SessionUIElicitationResult: - action: Action +class UIElicitationResponse: + """The elicitation response (accept with form values, decline, or cancel)""" + + action: UIElicitationResponseAction """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" content: dict[str, float | bool | list[str] | str] | None = None """The form values submitted by the user (present when action is 'accept')""" @staticmethod - def from_dict(obj: Any) -> 'SessionUIElicitationResult': + def from_dict(obj: Any) -> 'UIElicitationResponse': assert isinstance(obj, dict) - action = Action(obj.get("action")) + action = UIElicitationResponseAction(obj.get("action")) content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return SessionUIElicitationResult(action, content) + return UIElicitationResponse(action, content) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(Action, self.action) + result["action"] = to_enum(UIElicitationResponseAction, self.action) if self.content is not None: result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) return result - -class Format(Enum): +class UIElicitationSchemaPropertyStringFormat(Enum): DATE = "date" DATE_TIME = "date-time" EMAIL = "email" URI = "uri" - @dataclass -class AnyOf: +class UIElicitationArrayAnyOfFieldItemsAnyOf: const: str title: str @staticmethod - def from_dict(obj: Any) -> 'AnyOf': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItemsAnyOf': assert isinstance(obj, dict) const = from_str(obj.get("const")) title = from_str(obj.get("title")) - return AnyOf(const, title) + return UIElicitationArrayAnyOfFieldItemsAnyOf(const, title) def to_dict(self) -> dict: result: dict = {} @@ -2248,24 +1973,22 @@ def to_dict(self) -> dict: result["title"] = from_str(self.title) return result - class ItemsType(Enum): STRING = "string" - @dataclass -class Items: +class UIElicitationArrayFieldItems: enum: list[str] | None = None type: ItemsType | None = None - any_of: list[AnyOf] | None = None + any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] | None = None @staticmethod - def from_dict(obj: Any) -> 'Items': + def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': assert isinstance(obj, dict) enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) type = from_union([ItemsType, from_none], obj.get("type")) - any_of = from_union([lambda x: from_list(AnyOf.from_dict, x), from_none], obj.get("anyOf")) - return Items(enum, type, any_of) + any_of = from_union([lambda x: from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) + return UIElicitationArrayFieldItems(enum, type, any_of) def to_dict(self) -> dict: result: dict = {} @@ -2274,21 +1997,20 @@ def to_dict(self) -> dict: if self.type is not None: result["type"] = from_union([lambda x: to_enum(ItemsType, x), from_none], self.type) if self.any_of is not None: - result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(AnyOf, x), x), from_none], self.any_of) + result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) return result - @dataclass -class OneOf: +class UIElicitationStringOneOfFieldOneOf: const: str title: str @staticmethod - def from_dict(obj: Any) -> 'OneOf': + def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': assert isinstance(obj, dict) const = from_str(obj.get("const")) title = from_str(obj.get("title")) - return OneOf(const, title) + return UIElicitationStringOneOfFieldOneOf(const, title) def to_dict(self) -> dict: result: dict = {} @@ -2296,56 +2018,54 @@ def to_dict(self) -> dict: result["title"] = from_str(self.title) return result - -class PropertyType(Enum): +class UIElicitationSchemaPropertyNumberType(Enum): ARRAY = "array" BOOLEAN = "boolean" INTEGER = "integer" NUMBER = "number" STRING = "string" - @dataclass -class Property: - type: PropertyType +class UIElicitationSchemaProperty: + type: UIElicitationSchemaPropertyNumberType default: float | bool | list[str] | str | None = None description: str | None = None enum: list[str] | None = None enum_names: list[str] | None = None title: str | None = None - one_of: list[OneOf] | None = None - items: Items | None = None + one_of: list[UIElicitationStringOneOfFieldOneOf] | None = None + items: UIElicitationArrayFieldItems | None = None max_items: float | None = None min_items: float | None = None - format: Format | None = None + format: UIElicitationSchemaPropertyStringFormat | None = None max_length: float | None = None min_length: float | None = None maximum: float | None = None minimum: float | None = None @staticmethod - def from_dict(obj: Any) -> 'Property': + def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': assert isinstance(obj, dict) - type = PropertyType(obj.get("type")) + type = UIElicitationSchemaPropertyNumberType(obj.get("type")) default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) description = from_union([from_str, from_none], obj.get("description")) enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) title = from_union([from_str, from_none], obj.get("title")) - one_of = from_union([lambda x: from_list(OneOf.from_dict, x), from_none], obj.get("oneOf")) - items = from_union([Items.from_dict, from_none], obj.get("items")) + one_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("oneOf")) + items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) max_items = from_union([from_float, from_none], obj.get("maxItems")) min_items = from_union([from_float, from_none], obj.get("minItems")) - format = from_union([Format, from_none], obj.get("format")) + format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) max_length = from_union([from_float, from_none], obj.get("maxLength")) min_length = from_union([from_float, from_none], obj.get("minLength")) maximum = from_union([from_float, from_none], obj.get("maximum")) minimum = from_union([from_float, from_none], obj.get("minimum")) - return Property(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) + return UIElicitationSchemaProperty(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(PropertyType, self.type) + result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) if self.default is not None: result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) if self.description is not None: @@ -2357,15 +2077,15 @@ def to_dict(self) -> dict: if self.title is not None: result["title"] = from_union([from_str, from_none], self.title) if self.one_of is not None: - result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(OneOf, x), x), from_none], self.one_of) + result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.one_of) if self.items is not None: - result["items"] = from_union([lambda x: to_class(Items, x), from_none], self.items) + result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) if self.max_items is not None: result["maxItems"] = from_union([to_float, from_none], self.max_items) if self.min_items is not None: result["minItems"] = from_union([to_float, from_none], self.min_items) if self.format is not None: - result["format"] = from_union([lambda x: to_enum(Format, x), from_none], self.format) + result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) if self.max_length is not None: result["maxLength"] = from_union([to_float, from_none], self.max_length) if self.min_length is not None: @@ -2376,16 +2096,14 @@ def to_dict(self) -> dict: result["minimum"] = from_union([to_float, from_none], self.minimum) return result - class RequestedSchemaType(Enum): OBJECT = "object" - @dataclass -class RequestedSchema: +class UIElicitationSchema: """JSON Schema describing the form fields to present to the user""" - properties: dict[str, Property] + properties: dict[str, UIElicitationSchemaProperty] """Form field definitions, keyed by field name""" type: RequestedSchemaType @@ -2395,127 +2113,97 @@ class RequestedSchema: """List of required field names""" @staticmethod - def from_dict(obj: Any) -> 'RequestedSchema': + def from_dict(obj: Any) -> 'UIElicitationSchema': assert isinstance(obj, dict) - properties = from_dict(Property.from_dict, obj.get("properties")) + properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) type = RequestedSchemaType(obj.get("type")) required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) - return RequestedSchema(properties, type, required) + return UIElicitationSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} - result["properties"] = from_dict(lambda x: to_class(Property, x), self.properties) + result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) result["type"] = to_enum(RequestedSchemaType, self.type) if self.required is not None: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result - @dataclass -class SessionUIElicitationParams: +class UIElicitationRequest: message: str """Message describing what information is needed from the user""" - requested_schema: RequestedSchema + requested_schema: UIElicitationSchema """JSON Schema describing the form fields to present to the user""" @staticmethod - def from_dict(obj: Any) -> 'SessionUIElicitationParams': + def from_dict(obj: Any) -> 'UIElicitationRequest': assert isinstance(obj, dict) message = from_str(obj.get("message")) - requested_schema = RequestedSchema.from_dict(obj.get("requestedSchema")) - return SessionUIElicitationParams(message, requested_schema) + requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema")) + return UIElicitationRequest(message, requested_schema) def to_dict(self) -> dict: result: dict = {} result["message"] = from_str(self.message) - result["requestedSchema"] = to_class(RequestedSchema, self.requested_schema) + result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema) return result - @dataclass -class SessionUIHandlePendingElicitationResult: +class UIElicitationResult: success: bool """Whether the response was accepted. False if the request was already resolved by another client. """ @staticmethod - def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationResult': + def from_dict(obj: Any) -> 'UIElicitationResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return SessionUIHandlePendingElicitationResult(success) + return UIElicitationResult(success) def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) return result - @dataclass -class SessionUIHandlePendingElicitationParamsResult: - """The elicitation response (accept with form values, decline, or cancel)""" - - action: Action - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" - - content: dict[str, float | bool | list[str] | str] | None = None - """The form values submitted by the user (present when action is 'accept')""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationParamsResult': - assert isinstance(obj, dict) - action = Action(obj.get("action")) - content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return SessionUIHandlePendingElicitationParamsResult(action, content) - - def to_dict(self) -> dict: - result: dict = {} - result["action"] = to_enum(Action, self.action) - if self.content is not None: - result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) - return result - - -@dataclass -class SessionUIHandlePendingElicitationParams: +class UIHandlePendingElicitationRequest: request_id: str """The unique request ID from the elicitation.requested event""" - result: SessionUIHandlePendingElicitationParamsResult + result: UIElicitationResponse """The elicitation response (accept with form values, decline, or cancel)""" @staticmethod - def from_dict(obj: Any) -> 'SessionUIHandlePendingElicitationParams': + def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - result = SessionUIHandlePendingElicitationParamsResult.from_dict(obj.get("result")) - return SessionUIHandlePendingElicitationParams(request_id, result) + result = UIElicitationResponse.from_dict(obj.get("result")) + return UIHandlePendingElicitationRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - result["result"] = to_class(SessionUIHandlePendingElicitationParamsResult, self.result) + result["result"] = to_class(UIElicitationResponse, self.result) return result - @dataclass -class SessionPermissionsHandlePendingPermissionRequestResult: +class PermissionRequestResult: success: bool """Whether the permission request was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'SessionPermissionsHandlePendingPermissionRequestResult': + def from_dict(obj: Any) -> 'PermissionRequestResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return SessionPermissionsHandlePendingPermissionRequestResult(success) + return PermissionRequestResult(success) def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) return result - class Kind(Enum): APPROVED = "approved" DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" @@ -2524,9 +2212,8 @@ class Kind(Enum): DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - @dataclass -class SessionPermissionsHandlePendingPermissionRequestParamsResult: +class PermissionDecision: kind: Kind """The permission request was approved @@ -2558,7 +2245,7 @@ class SessionPermissionsHandlePendingPermissionRequestParamsResult: """Whether to interrupt the current agent turn""" @staticmethod - def from_dict(obj: Any) -> 'SessionPermissionsHandlePendingPermissionRequestParamsResult': + def from_dict(obj: Any) -> 'PermissionDecision': assert isinstance(obj, dict) kind = Kind(obj.get("kind")) rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) @@ -2566,7 +2253,7 @@ def from_dict(obj: Any) -> 'SessionPermissionsHandlePendingPermissionRequestPara message = from_union([from_str, from_none], obj.get("message")) path = from_union([from_str, from_none], obj.get("path")) interrupt = from_union([from_bool, from_none], obj.get("interrupt")) - return SessionPermissionsHandlePendingPermissionRequestParamsResult(kind, rules, feedback, message, path, interrupt) + return PermissionDecision(kind, rules, feedback, message, path, interrupt) def to_dict(self) -> dict: result: dict = {} @@ -2583,46 +2270,43 @@ def to_dict(self) -> dict: result["interrupt"] = from_union([from_bool, from_none], self.interrupt) return result - @dataclass -class SessionPermissionsHandlePendingPermissionRequestParams: +class PermissionDecisionRequest: request_id: str """Request ID of the pending permission request""" - result: SessionPermissionsHandlePendingPermissionRequestParamsResult + result: PermissionDecision @staticmethod - def from_dict(obj: Any) -> 'SessionPermissionsHandlePendingPermissionRequestParams': + def from_dict(obj: Any) -> 'PermissionDecisionRequest': assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - result = SessionPermissionsHandlePendingPermissionRequestParamsResult.from_dict(obj.get("result")) - return SessionPermissionsHandlePendingPermissionRequestParams(request_id, result) + result = PermissionDecision.from_dict(obj.get("result")) + return PermissionDecisionRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - result["result"] = to_class(SessionPermissionsHandlePendingPermissionRequestParamsResult, self.result) + result["result"] = to_class(PermissionDecision, self.result) return result - @dataclass -class SessionLogResult: +class LogResult: event_id: UUID """The unique identifier of the emitted session event""" @staticmethod - def from_dict(obj: Any) -> 'SessionLogResult': + def from_dict(obj: Any) -> 'LogResult': assert isinstance(obj, dict) event_id = UUID(obj.get("eventId")) - return SessionLogResult(event_id) + return LogResult(event_id) def to_dict(self) -> dict: result: dict = {} result["eventId"] = str(self.event_id) return result - -class Level(Enum): +class SessionLogLevel(Enum): """Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". """ @@ -2630,16 +2314,15 @@ class Level(Enum): INFO = "info" WARNING = "warning" - @dataclass -class SessionLogParams: +class LogRequest: message: str """Human-readable message""" ephemeral: bool | None = None """When true, the message is transient and not persisted to the session event log on disk""" - level: Level | None = None + level: SessionLogLevel | None = None """Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". """ @@ -2647,13 +2330,13 @@ class SessionLogParams: """Optional URL the user can open in their browser for more details""" @staticmethod - def from_dict(obj: Any) -> 'SessionLogParams': + def from_dict(obj: Any) -> 'LogRequest': assert isinstance(obj, dict) message = from_str(obj.get("message")) ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - level = from_union([Level, from_none], obj.get("level")) + level = from_union([SessionLogLevel, from_none], obj.get("level")) url = from_union([from_str, from_none], obj.get("url")) - return SessionLogParams(message, ephemeral, level, url) + return LogRequest(message, ephemeral, level, url) def to_dict(self) -> dict: result: dict = {} @@ -2661,47 +2344,45 @@ def to_dict(self) -> dict: if self.ephemeral is not None: result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) if self.level is not None: - result["level"] = from_union([lambda x: to_enum(Level, x), from_none], self.level) + result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) return result - @dataclass -class SessionShellExecResult: +class ShellExecResult: process_id: str """Unique identifier for tracking streamed output""" @staticmethod - def from_dict(obj: Any) -> 'SessionShellExecResult': + def from_dict(obj: Any) -> 'ShellExecResult': assert isinstance(obj, dict) process_id = from_str(obj.get("processId")) - return SessionShellExecResult(process_id) + return ShellExecResult(process_id) def to_dict(self) -> dict: result: dict = {} result["processId"] = from_str(self.process_id) return result - @dataclass -class SessionShellExecParams: +class ShellExecRequest: command: str """Shell command to execute""" cwd: str | None = None """Working directory (defaults to session working directory)""" - timeout: float | None = None + timeout: int | None = None """Timeout in milliseconds (default: 30000)""" @staticmethod - def from_dict(obj: Any) -> 'SessionShellExecParams': + def from_dict(obj: Any) -> 'ShellExecRequest': assert isinstance(obj, dict) command = from_str(obj.get("command")) cwd = from_union([from_str, from_none], obj.get("cwd")) - timeout = from_union([from_float, from_none], obj.get("timeout")) - return SessionShellExecParams(command, cwd, timeout) + timeout = from_union([from_int, from_none], obj.get("timeout")) + return ShellExecRequest(command, cwd, timeout) def to_dict(self) -> dict: result: dict = {} @@ -2709,177 +2390,169 @@ def to_dict(self) -> dict: if self.cwd is not None: result["cwd"] = from_union([from_str, from_none], self.cwd) if self.timeout is not None: - result["timeout"] = from_union([to_float, from_none], self.timeout) + result["timeout"] = from_union([from_int, from_none], self.timeout) return result - @dataclass -class SessionShellKillResult: +class ShellKillResult: killed: bool """Whether the signal was sent successfully""" @staticmethod - def from_dict(obj: Any) -> 'SessionShellKillResult': + def from_dict(obj: Any) -> 'ShellKillResult': assert isinstance(obj, dict) killed = from_bool(obj.get("killed")) - return SessionShellKillResult(killed) + return ShellKillResult(killed) def to_dict(self) -> dict: result: dict = {} result["killed"] = from_bool(self.killed) return result - -class Signal(Enum): +class ShellKillSignal(Enum): """Signal to send (default: SIGTERM)""" SIGINT = "SIGINT" SIGKILL = "SIGKILL" SIGTERM = "SIGTERM" - @dataclass -class SessionShellKillParams: +class ShellKillRequest: process_id: str """Process identifier returned by shell.exec""" - signal: Signal | None = None + signal: ShellKillSignal | None = None """Signal to send (default: SIGTERM)""" @staticmethod - def from_dict(obj: Any) -> 'SessionShellKillParams': + def from_dict(obj: Any) -> 'ShellKillRequest': assert isinstance(obj, dict) process_id = from_str(obj.get("processId")) - signal = from_union([Signal, from_none], obj.get("signal")) - return SessionShellKillParams(process_id, signal) + signal = from_union([ShellKillSignal, from_none], obj.get("signal")) + return ShellKillRequest(process_id, signal) def to_dict(self) -> dict: result: dict = {} result["processId"] = from_str(self.process_id) if self.signal is not None: - result["signal"] = from_union([lambda x: to_enum(Signal, x), from_none], self.signal) + result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) return result - @dataclass -class ContextWindow: +class HistoryCompactContextWindow: """Post-compaction context window usage breakdown""" - current_tokens: float + current_tokens: int """Current total tokens in the context window (system + conversation + tool definitions)""" - messages_length: float + messages_length: int """Current number of messages in the conversation""" - token_limit: float + token_limit: int """Maximum token count for the model's context window""" - conversation_tokens: float | None = None + conversation_tokens: int | None = None """Token count from non-system messages (user, assistant, tool)""" - system_tokens: float | None = None + system_tokens: int | None = None """Token count from system message(s)""" - tool_definitions_tokens: float | None = None + tool_definitions_tokens: int | None = None """Token count from tool definitions""" @staticmethod - def from_dict(obj: Any) -> 'ContextWindow': + def from_dict(obj: Any) -> 'HistoryCompactContextWindow': assert isinstance(obj, dict) - current_tokens = from_float(obj.get("currentTokens")) - messages_length = from_float(obj.get("messagesLength")) - token_limit = from_float(obj.get("tokenLimit")) - conversation_tokens = from_union([from_float, from_none], obj.get("conversationTokens")) - system_tokens = from_union([from_float, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_float, from_none], obj.get("toolDefinitionsTokens")) - return ContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + current_tokens = from_int(obj.get("currentTokens")) + messages_length = from_int(obj.get("messagesLength")) + token_limit = from_int(obj.get("tokenLimit")) + conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) + return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) def to_dict(self) -> dict: result: dict = {} - result["currentTokens"] = to_float(self.current_tokens) - result["messagesLength"] = to_float(self.messages_length) - result["tokenLimit"] = to_float(self.token_limit) + result["currentTokens"] = from_int(self.current_tokens) + result["messagesLength"] = from_int(self.messages_length) + result["tokenLimit"] = from_int(self.token_limit) if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([to_float, from_none], self.conversation_tokens) + result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) if self.system_tokens is not None: - result["systemTokens"] = from_union([to_float, from_none], self.system_tokens) + result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([to_float, from_none], self.tool_definitions_tokens) + result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionHistoryCompactResult: - messages_removed: float +class HistoryCompactResult: + messages_removed: int """Number of messages removed during compaction""" success: bool """Whether compaction completed successfully""" - tokens_removed: float + tokens_removed: int """Number of tokens freed by compaction""" - context_window: ContextWindow | None = None + context_window: HistoryCompactContextWindow | None = None """Post-compaction context window usage breakdown""" @staticmethod - def from_dict(obj: Any) -> 'SessionHistoryCompactResult': + def from_dict(obj: Any) -> 'HistoryCompactResult': assert isinstance(obj, dict) - messages_removed = from_float(obj.get("messagesRemoved")) + messages_removed = from_int(obj.get("messagesRemoved")) success = from_bool(obj.get("success")) - tokens_removed = from_float(obj.get("tokensRemoved")) - context_window = from_union([ContextWindow.from_dict, from_none], obj.get("contextWindow")) - return SessionHistoryCompactResult(messages_removed, success, tokens_removed, context_window) + tokens_removed = from_int(obj.get("tokensRemoved")) + context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) + return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) def to_dict(self) -> dict: result: dict = {} - result["messagesRemoved"] = to_float(self.messages_removed) + result["messagesRemoved"] = from_int(self.messages_removed) result["success"] = from_bool(self.success) - result["tokensRemoved"] = to_float(self.tokens_removed) + result["tokensRemoved"] = from_int(self.tokens_removed) if self.context_window is not None: - result["contextWindow"] = from_union([lambda x: to_class(ContextWindow, x), from_none], self.context_window) + result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionHistoryTruncateResult: - events_removed: float +class HistoryTruncateResult: + events_removed: int """Number of events that were removed""" @staticmethod - def from_dict(obj: Any) -> 'SessionHistoryTruncateResult': + def from_dict(obj: Any) -> 'HistoryTruncateResult': assert isinstance(obj, dict) - events_removed = from_float(obj.get("eventsRemoved")) - return SessionHistoryTruncateResult(events_removed) + events_removed = from_int(obj.get("eventsRemoved")) + return HistoryTruncateResult(events_removed) def to_dict(self) -> dict: result: dict = {} - result["eventsRemoved"] = to_float(self.events_removed) + result["eventsRemoved"] = from_int(self.events_removed) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionHistoryTruncateParams: +class HistoryTruncateRequest: event_id: str """Event ID to truncate to. This event and all events after it are removed from the session.""" @staticmethod - def from_dict(obj: Any) -> 'SessionHistoryTruncateParams': + def from_dict(obj: Any) -> 'HistoryTruncateRequest': assert isinstance(obj, dict) event_id = from_str(obj.get("eventId")) - return SessionHistoryTruncateParams(event_id) + return HistoryTruncateRequest(event_id) def to_dict(self) -> dict: result: dict = {} result["eventId"] = from_str(self.event_id) return result - @dataclass -class CodeChanges: +class UsageMetricsCodeChanges: """Aggregated code change metrics""" files_modified_count: int @@ -2892,12 +2565,12 @@ class CodeChanges: """Total lines of code removed""" @staticmethod - def from_dict(obj: Any) -> 'CodeChanges': + def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': assert isinstance(obj, dict) files_modified_count = from_int(obj.get("filesModifiedCount")) lines_added = from_int(obj.get("linesAdded")) lines_removed = from_int(obj.get("linesRemoved")) - return CodeChanges(files_modified_count, lines_added, lines_removed) + return UsageMetricsCodeChanges(files_modified_count, lines_added, lines_removed) def to_dict(self) -> dict: result: dict = {} @@ -2906,9 +2579,8 @@ def to_dict(self) -> dict: result["linesRemoved"] = from_int(self.lines_removed) return result - @dataclass -class Requests: +class UsageMetricsModelMetricRequests: """Request count and cost metrics for this model""" cost: float @@ -2918,11 +2590,11 @@ class Requests: """Number of API requests made with this model""" @staticmethod - def from_dict(obj: Any) -> 'Requests': + def from_dict(obj: Any) -> 'UsageMetricsModelMetricRequests': assert isinstance(obj, dict) cost = from_float(obj.get("cost")) count = from_int(obj.get("count")) - return Requests(cost, count) + return UsageMetricsModelMetricRequests(cost, count) def to_dict(self) -> dict: result: dict = {} @@ -2930,9 +2602,8 @@ def to_dict(self) -> dict: result["count"] = from_int(self.count) return result - @dataclass -class Usage: +class UsageMetricsModelMetricUsage: """Token usage metrics for this model""" cache_read_tokens: int @@ -2947,14 +2618,18 @@ class Usage: output_tokens: int """Total output tokens produced""" + reasoning_tokens: int | None = None + """Total output tokens used for reasoning""" + @staticmethod - def from_dict(obj: Any) -> 'Usage': + def from_dict(obj: Any) -> 'UsageMetricsModelMetricUsage': assert isinstance(obj, dict) cache_read_tokens = from_int(obj.get("cacheReadTokens")) cache_write_tokens = from_int(obj.get("cacheWriteTokens")) input_tokens = from_int(obj.get("inputTokens")) output_tokens = from_int(obj.get("outputTokens")) - return Usage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens) + reasoning_tokens = from_union([from_int, from_none], obj.get("reasoningTokens")) + return UsageMetricsModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) def to_dict(self) -> dict: result: dict = {} @@ -2962,35 +2637,35 @@ def to_dict(self) -> dict: result["cacheWriteTokens"] = from_int(self.cache_write_tokens) result["inputTokens"] = from_int(self.input_tokens) result["outputTokens"] = from_int(self.output_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) return result - @dataclass -class ModelMetric: - requests: Requests +class UsageMetricsModelMetric: + requests: UsageMetricsModelMetricRequests """Request count and cost metrics for this model""" - usage: Usage + usage: UsageMetricsModelMetricUsage """Token usage metrics for this model""" @staticmethod - def from_dict(obj: Any) -> 'ModelMetric': + def from_dict(obj: Any) -> 'UsageMetricsModelMetric': assert isinstance(obj, dict) - requests = Requests.from_dict(obj.get("requests")) - usage = Usage.from_dict(obj.get("usage")) - return ModelMetric(requests, usage) + requests = UsageMetricsModelMetricRequests.from_dict(obj.get("requests")) + usage = UsageMetricsModelMetricUsage.from_dict(obj.get("usage")) + return UsageMetricsModelMetric(requests, usage) def to_dict(self) -> dict: result: dict = {} - result["requests"] = to_class(Requests, self.requests) - result["usage"] = to_class(Usage, self.usage) + result["requests"] = to_class(UsageMetricsModelMetricRequests, self.requests) + result["usage"] = to_class(UsageMetricsModelMetricUsage, self.usage) return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionUsageGetMetricsResult: - code_changes: CodeChanges +class UsageGetMetricsResult: + code_changes: UsageMetricsCodeChanges """Aggregated code change metrics""" last_call_input_tokens: int @@ -2999,7 +2674,7 @@ class SessionUsageGetMetricsResult: last_call_output_tokens: int """Output tokens from the most recent main-agent API call""" - model_metrics: dict[str, ModelMetric] + model_metrics: dict[str, UsageMetricsModelMetric] """Per-model token and request metrics, keyed by model identifier""" session_start_time: int @@ -3019,25 +2694,25 @@ class SessionUsageGetMetricsResult: """Currently active model identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionUsageGetMetricsResult': + def from_dict(obj: Any) -> 'UsageGetMetricsResult': assert isinstance(obj, dict) - code_changes = CodeChanges.from_dict(obj.get("codeChanges")) + code_changes = UsageMetricsCodeChanges.from_dict(obj.get("codeChanges")) last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) - model_metrics = from_dict(ModelMetric.from_dict, obj.get("modelMetrics")) + model_metrics = from_dict(UsageMetricsModelMetric.from_dict, obj.get("modelMetrics")) session_start_time = from_int(obj.get("sessionStartTime")) total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) total_user_requests = from_int(obj.get("totalUserRequests")) current_model = from_union([from_str, from_none], obj.get("currentModel")) - return SessionUsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) + return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) def to_dict(self) -> dict: result: dict = {} - result["codeChanges"] = to_class(CodeChanges, self.code_changes) + result["codeChanges"] = to_class(UsageMetricsCodeChanges, self.code_changes) result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) - result["modelMetrics"] = from_dict(lambda x: to_class(ModelMetric, x), self.model_metrics) + result["modelMetrics"] = from_dict(lambda x: to_class(UsageMetricsModelMetric, x), self.model_metrics) result["sessionStartTime"] = from_int(self.session_start_time) result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) @@ -3046,7 +2721,6 @@ def to_dict(self) -> dict: result["currentModel"] = from_union([from_str, from_none], self.current_model) return result - @dataclass class SessionFSReadFileResult: content: str @@ -3063,9 +2737,8 @@ def to_dict(self) -> dict: result["content"] = from_str(self.content) return result - @dataclass -class SessionFSReadFileParams: +class SessionFSReadFileRequest: path: str """Path using SessionFs conventions""" @@ -3073,11 +2746,11 @@ class SessionFSReadFileParams: """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileParams': + def from_dict(obj: Any) -> 'SessionFSReadFileRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - return SessionFSReadFileParams(path, session_id) + return SessionFSReadFileRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} @@ -3085,9 +2758,8 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - @dataclass -class SessionFSWriteFileParams: +class SessionFSWriteFileRequest: content: str """Content to write""" @@ -3097,17 +2769,17 @@ class SessionFSWriteFileParams: session_id: str """Target session identifier""" - mode: float | None = None + mode: int | None = None """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSWriteFileParams': + def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - mode = from_union([from_float, from_none], obj.get("mode")) - return SessionFSWriteFileParams(content, path, session_id, mode) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSWriteFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} @@ -3115,12 +2787,11 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) result["sessionId"] = from_str(self.session_id) if self.mode is not None: - result["mode"] = from_union([to_float, from_none], self.mode) + result["mode"] = from_union([from_int, from_none], self.mode) return result - @dataclass -class SessionFSAppendFileParams: +class SessionFSAppendFileRequest: content: str """Content to append""" @@ -3130,17 +2801,17 @@ class SessionFSAppendFileParams: session_id: str """Target session identifier""" - mode: float | None = None + mode: int | None = None """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSAppendFileParams': + def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - mode = from_union([from_float, from_none], obj.get("mode")) - return SessionFSAppendFileParams(content, path, session_id, mode) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSAppendFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} @@ -3148,10 +2819,9 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) result["sessionId"] = from_str(self.session_id) if self.mode is not None: - result["mode"] = from_union([to_float, from_none], self.mode) + result["mode"] = from_union([from_int, from_none], self.mode) return result - @dataclass class SessionFSExistsResult: exists: bool @@ -3168,9 +2838,8 @@ def to_dict(self) -> dict: result["exists"] = from_bool(self.exists) return result - @dataclass -class SessionFSExistsParams: +class SessionFSExistsRequest: path: str """Path using SessionFs conventions""" @@ -3178,11 +2847,11 @@ class SessionFSExistsParams: """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsParams': + def from_dict(obj: Any) -> 'SessionFSExistsRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - return SessionFSExistsParams(path, session_id) + return SessionFSExistsRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} @@ -3190,10 +2859,9 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - @dataclass class SessionFSStatResult: - birthtime: str + birthtime: datetime """ISO 8601 timestamp of creation""" is_directory: bool @@ -3202,34 +2870,33 @@ class SessionFSStatResult: is_file: bool """Whether the path is a file""" - mtime: str + mtime: datetime """ISO 8601 timestamp of last modification""" - size: float + size: int """File size in bytes""" @staticmethod def from_dict(obj: Any) -> 'SessionFSStatResult': assert isinstance(obj, dict) - birthtime = from_str(obj.get("birthtime")) + birthtime = from_datetime(obj.get("birthtime")) is_directory = from_bool(obj.get("isDirectory")) is_file = from_bool(obj.get("isFile")) - mtime = from_str(obj.get("mtime")) - size = from_float(obj.get("size")) + mtime = from_datetime(obj.get("mtime")) + size = from_int(obj.get("size")) return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size) def to_dict(self) -> dict: result: dict = {} - result["birthtime"] = from_str(self.birthtime) + result["birthtime"] = self.birthtime.isoformat() result["isDirectory"] = from_bool(self.is_directory) result["isFile"] = from_bool(self.is_file) - result["mtime"] = from_str(self.mtime) - result["size"] = to_float(self.size) + result["mtime"] = self.mtime.isoformat() + result["size"] = from_int(self.size) return result - @dataclass -class SessionFSStatParams: +class SessionFSStatRequest: path: str """Path using SessionFs conventions""" @@ -3237,11 +2904,11 @@ class SessionFSStatParams: """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatParams': + def from_dict(obj: Any) -> 'SessionFSStatRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - return SessionFSStatParams(path, session_id) + return SessionFSStatRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} @@ -3249,41 +2916,39 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - @dataclass -class SessionFSMkdirParams: +class SessionFSMkdirRequest: path: str """Path using SessionFs conventions""" session_id: str """Target session identifier""" - mode: float | None = None + mode: int | None = None """Optional POSIX-style mode for newly created directories""" recursive: bool | None = None """Create parent directories as needed""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSMkdirParams': + def from_dict(obj: Any) -> 'SessionFSMkdirRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - mode = from_union([from_float, from_none], obj.get("mode")) + mode = from_union([from_int, from_none], obj.get("mode")) recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSMkdirParams(path, session_id, mode, recursive) + return SessionFSMkdirRequest(path, session_id, mode, recursive) def to_dict(self) -> dict: result: dict = {} result["path"] = from_str(self.path) result["sessionId"] = from_str(self.session_id) if self.mode is not None: - result["mode"] = from_union([to_float, from_none], self.mode) + result["mode"] = from_union([from_int, from_none], self.mode) if self.recursive is not None: result["recursive"] = from_union([from_bool, from_none], self.recursive) return result - @dataclass class SessionFSReaddirResult: entries: list[str] @@ -3300,9 +2965,8 @@ def to_dict(self) -> dict: result["entries"] = from_list(from_str, self.entries) return result - @dataclass -class SessionFSReaddirParams: +class SessionFSReaddirRequest: path: str """Path using SessionFs conventions""" @@ -3310,11 +2974,11 @@ class SessionFSReaddirParams: """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirParams': + def from_dict(obj: Any) -> 'SessionFSReaddirRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirParams(path, session_id) + return SessionFSReaddirRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} @@ -3322,55 +2986,51 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - -class EntryType(Enum): +class SessionFSReaddirWithTypesEntryType(Enum): """Entry type""" DIRECTORY = "directory" FILE = "file" - @dataclass -class Entry: +class SessionFSReaddirWithTypesEntry: name: str """Entry name""" - type: EntryType + type: SessionFSReaddirWithTypesEntryType """Entry type""" @staticmethod - def from_dict(obj: Any) -> 'Entry': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': assert isinstance(obj, dict) name = from_str(obj.get("name")) - type = EntryType(obj.get("type")) - return Entry(name, type) + type = SessionFSReaddirWithTypesEntryType(obj.get("type")) + return SessionFSReaddirWithTypesEntry(name, type) def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) - result["type"] = to_enum(EntryType, self.type) + result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) return result - @dataclass class SessionFSReaddirWithTypesResult: - entries: list[Entry] + entries: list[SessionFSReaddirWithTypesEntry] """Directory entries with type information""" @staticmethod def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': assert isinstance(obj, dict) - entries = from_list(Entry.from_dict, obj.get("entries")) + entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) return SessionFSReaddirWithTypesResult(entries) def to_dict(self) -> dict: result: dict = {} - result["entries"] = from_list(lambda x: to_class(Entry, x), self.entries) + result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) return result - @dataclass -class SessionFSReaddirWithTypesParams: +class SessionFSReaddirWithTypesRequest: path: str """Path using SessionFs conventions""" @@ -3378,11 +3038,11 @@ class SessionFSReaddirWithTypesParams: """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesParams': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirWithTypesParams(path, session_id) + return SessionFSReaddirWithTypesRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} @@ -3390,9 +3050,8 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result - @dataclass -class SessionFSRmParams: +class SessionFSRmRequest: path: str """Path using SessionFs conventions""" @@ -3406,13 +3065,13 @@ class SessionFSRmParams: """Remove directories and their contents recursively""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSRmParams': + def from_dict(obj: Any) -> 'SessionFSRmRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) session_id = from_str(obj.get("sessionId")) force = from_union([from_bool, from_none], obj.get("force")) recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSRmParams(path, session_id, force, recursive) + return SessionFSRmRequest(path, session_id, force, recursive) def to_dict(self) -> dict: result: dict = {} @@ -3424,9 +3083,8 @@ def to_dict(self) -> dict: result["recursive"] = from_union([from_bool, from_none], self.recursive) return result - @dataclass -class SessionFSRenameParams: +class SessionFSRenameRequest: dest: str """Destination path using SessionFs conventions""" @@ -3437,12 +3095,12 @@ class SessionFSRenameParams: """Source path using SessionFs conventions""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSRenameParams': + def from_dict(obj: Any) -> 'SessionFSRenameRequest': assert isinstance(obj, dict) dest = from_str(obj.get("dest")) session_id = from_str(obj.get("sessionId")) src = from_str(obj.get("src")) - return SessionFSRenameParams(dest, session_id, src) + return SessionFSRenameRequest(dest, session_id, src) def to_dict(self) -> dict: result: dict = {} @@ -3451,749 +3109,479 @@ def to_dict(self) -> dict: result["src"] = from_str(self.src) return result - def ping_result_from_dict(s: Any) -> PingResult: return PingResult.from_dict(s) - def ping_result_to_dict(x: PingResult) -> Any: return to_class(PingResult, x) +def ping_request_from_dict(s: Any) -> PingRequest: + return PingRequest.from_dict(s) -def ping_params_from_dict(s: Any) -> PingParams: - return PingParams.from_dict(s) - - -def ping_params_to_dict(x: PingParams) -> Any: - return to_class(PingParams, x) - - -def models_list_result_from_dict(s: Any) -> ModelsListResult: - return ModelsListResult.from_dict(s) - - -def models_list_result_to_dict(x: ModelsListResult) -> Any: - return to_class(ModelsListResult, x) - - -def tools_list_result_from_dict(s: Any) -> ToolsListResult: - return ToolsListResult.from_dict(s) - +def ping_request_to_dict(x: PingRequest) -> Any: + return to_class(PingRequest, x) -def tools_list_result_to_dict(x: ToolsListResult) -> Any: - return to_class(ToolsListResult, x) +def model_list_from_dict(s: Any) -> ModelList: + return ModelList.from_dict(s) +def model_list_to_dict(x: ModelList) -> Any: + return to_class(ModelList, x) -def tools_list_params_from_dict(s: Any) -> ToolsListParams: - return ToolsListParams.from_dict(s) +def tool_list_from_dict(s: Any) -> ToolList: + return ToolList.from_dict(s) +def tool_list_to_dict(x: ToolList) -> Any: + return to_class(ToolList, x) -def tools_list_params_to_dict(x: ToolsListParams) -> Any: - return to_class(ToolsListParams, x) +def tools_list_request_from_dict(s: Any) -> ToolsListRequest: + return ToolsListRequest.from_dict(s) +def tools_list_request_to_dict(x: ToolsListRequest) -> Any: + return to_class(ToolsListRequest, x) def account_get_quota_result_from_dict(s: Any) -> AccountGetQuotaResult: return AccountGetQuotaResult.from_dict(s) - def account_get_quota_result_to_dict(x: AccountGetQuotaResult) -> Any: return to_class(AccountGetQuotaResult, x) +def mcp_config_list_from_dict(s: Any) -> MCPConfigList: + return MCPConfigList.from_dict(s) -def mcp_config_list_result_from_dict(s: Any) -> MCPConfigListResult: - return MCPConfigListResult.from_dict(s) - - -def mcp_config_list_result_to_dict(x: MCPConfigListResult) -> Any: - return to_class(MCPConfigListResult, x) - - -def mcp_config_add_params_from_dict(s: Any) -> MCPConfigAddParams: - return MCPConfigAddParams.from_dict(s) - +def mcp_config_list_to_dict(x: MCPConfigList) -> Any: + return to_class(MCPConfigList, x) -def mcp_config_add_params_to_dict(x: MCPConfigAddParams) -> Any: - return to_class(MCPConfigAddParams, x) +def mcp_config_add_request_from_dict(s: Any) -> MCPConfigAddRequest: + return MCPConfigAddRequest.from_dict(s) +def mcp_config_add_request_to_dict(x: MCPConfigAddRequest) -> Any: + return to_class(MCPConfigAddRequest, x) -def mcp_config_update_params_from_dict(s: Any) -> MCPConfigUpdateParams: - return MCPConfigUpdateParams.from_dict(s) +def mcp_config_update_request_from_dict(s: Any) -> MCPConfigUpdateRequest: + return MCPConfigUpdateRequest.from_dict(s) +def mcp_config_update_request_to_dict(x: MCPConfigUpdateRequest) -> Any: + return to_class(MCPConfigUpdateRequest, x) -def mcp_config_update_params_to_dict(x: MCPConfigUpdateParams) -> Any: - return to_class(MCPConfigUpdateParams, x) - - -def mcp_config_remove_params_from_dict(s: Any) -> MCPConfigRemoveParams: - return MCPConfigRemoveParams.from_dict(s) - - -def mcp_config_remove_params_to_dict(x: MCPConfigRemoveParams) -> Any: - return to_class(MCPConfigRemoveParams, x) +def mcp_config_remove_request_from_dict(s: Any) -> MCPConfigRemoveRequest: + return MCPConfigRemoveRequest.from_dict(s) +def mcp_config_remove_request_to_dict(x: MCPConfigRemoveRequest) -> Any: + return to_class(MCPConfigRemoveRequest, x) def mcp_discover_result_from_dict(s: Any) -> MCPDiscoverResult: return MCPDiscoverResult.from_dict(s) - def mcp_discover_result_to_dict(x: MCPDiscoverResult) -> Any: return to_class(MCPDiscoverResult, x) +def mcp_discover_request_from_dict(s: Any) -> MCPDiscoverRequest: + return MCPDiscoverRequest.from_dict(s) -def mcp_discover_params_from_dict(s: Any) -> MCPDiscoverParams: - return MCPDiscoverParams.from_dict(s) - - -def mcp_discover_params_to_dict(x: MCPDiscoverParams) -> Any: - return to_class(MCPDiscoverParams, x) - +def mcp_discover_request_to_dict(x: MCPDiscoverRequest) -> Any: + return to_class(MCPDiscoverRequest, x) def session_fs_set_provider_result_from_dict(s: Any) -> SessionFSSetProviderResult: return SessionFSSetProviderResult.from_dict(s) - def session_fs_set_provider_result_to_dict(x: SessionFSSetProviderResult) -> Any: return to_class(SessionFSSetProviderResult, x) +def session_fs_set_provider_request_from_dict(s: Any) -> SessionFSSetProviderRequest: + return SessionFSSetProviderRequest.from_dict(s) -def session_fs_set_provider_params_from_dict(s: Any) -> SessionFSSetProviderParams: - return SessionFSSetProviderParams.from_dict(s) - - -def session_fs_set_provider_params_to_dict(x: SessionFSSetProviderParams) -> Any: - return to_class(SessionFSSetProviderParams, x) - +def session_fs_set_provider_request_to_dict(x: SessionFSSetProviderRequest) -> Any: + return to_class(SessionFSSetProviderRequest, x) def sessions_fork_result_from_dict(s: Any) -> SessionsForkResult: return SessionsForkResult.from_dict(s) - def sessions_fork_result_to_dict(x: SessionsForkResult) -> Any: return to_class(SessionsForkResult, x) +def sessions_fork_request_from_dict(s: Any) -> SessionsForkRequest: + return SessionsForkRequest.from_dict(s) -def sessions_fork_params_from_dict(s: Any) -> SessionsForkParams: - return SessionsForkParams.from_dict(s) - - -def sessions_fork_params_to_dict(x: SessionsForkParams) -> Any: - return to_class(SessionsForkParams, x) - - -def session_model_get_current_result_from_dict(s: Any) -> SessionModelGetCurrentResult: - return SessionModelGetCurrentResult.from_dict(s) - - -def session_model_get_current_result_to_dict(x: SessionModelGetCurrentResult) -> Any: - return to_class(SessionModelGetCurrentResult, x) - - -def session_model_switch_to_result_from_dict(s: Any) -> SessionModelSwitchToResult: - return SessionModelSwitchToResult.from_dict(s) - - -def session_model_switch_to_result_to_dict(x: SessionModelSwitchToResult) -> Any: - return to_class(SessionModelSwitchToResult, x) - - -def session_model_switch_to_params_from_dict(s: Any) -> SessionModelSwitchToParams: - return SessionModelSwitchToParams.from_dict(s) - - -def session_model_switch_to_params_to_dict(x: SessionModelSwitchToParams) -> Any: - return to_class(SessionModelSwitchToParams, x) - - -def session_mode_get_result_from_dict(s: Any) -> SessionModeGetResult: - return SessionModeGetResult.from_dict(s) - - -def session_mode_get_result_to_dict(x: SessionModeGetResult) -> Any: - return to_class(SessionModeGetResult, x) - - -def session_mode_set_result_from_dict(s: Any) -> SessionModeSetResult: - return SessionModeSetResult.from_dict(s) - - -def session_mode_set_result_to_dict(x: SessionModeSetResult) -> Any: - return to_class(SessionModeSetResult, x) +def sessions_fork_request_to_dict(x: SessionsForkRequest) -> Any: + return to_class(SessionsForkRequest, x) +def current_model_from_dict(s: Any) -> CurrentModel: + return CurrentModel.from_dict(s) -def session_mode_set_params_from_dict(s: Any) -> SessionModeSetParams: - return SessionModeSetParams.from_dict(s) +def current_model_to_dict(x: CurrentModel) -> Any: + return to_class(CurrentModel, x) +def model_switch_to_result_from_dict(s: Any) -> ModelSwitchToResult: + return ModelSwitchToResult.from_dict(s) -def session_mode_set_params_to_dict(x: SessionModeSetParams) -> Any: - return to_class(SessionModeSetParams, x) +def model_switch_to_result_to_dict(x: ModelSwitchToResult) -> Any: + return to_class(ModelSwitchToResult, x) +def model_switch_to_request_from_dict(s: Any) -> ModelSwitchToRequest: + return ModelSwitchToRequest.from_dict(s) -def session_plan_read_result_from_dict(s: Any) -> SessionPlanReadResult: - return SessionPlanReadResult.from_dict(s) +def model_switch_to_request_to_dict(x: ModelSwitchToRequest) -> Any: + return to_class(ModelSwitchToRequest, x) +def session_mode_from_dict(s: Any) -> SessionMode: + return SessionMode(s) -def session_plan_read_result_to_dict(x: SessionPlanReadResult) -> Any: - return to_class(SessionPlanReadResult, x) +def session_mode_to_dict(x: SessionMode) -> Any: + return to_enum(SessionMode, x) +def mode_set_request_from_dict(s: Any) -> ModeSetRequest: + return ModeSetRequest.from_dict(s) -def session_plan_update_result_from_dict(s: Any) -> SessionPlanUpdateResult: - return SessionPlanUpdateResult.from_dict(s) +def mode_set_request_to_dict(x: ModeSetRequest) -> Any: + return to_class(ModeSetRequest, x) +def plan_read_result_from_dict(s: Any) -> PlanReadResult: + return PlanReadResult.from_dict(s) -def session_plan_update_result_to_dict(x: SessionPlanUpdateResult) -> Any: - return to_class(SessionPlanUpdateResult, x) +def plan_read_result_to_dict(x: PlanReadResult) -> Any: + return to_class(PlanReadResult, x) +def plan_update_request_from_dict(s: Any) -> PlanUpdateRequest: + return PlanUpdateRequest.from_dict(s) -def session_plan_update_params_from_dict(s: Any) -> SessionPlanUpdateParams: - return SessionPlanUpdateParams.from_dict(s) +def plan_update_request_to_dict(x: PlanUpdateRequest) -> Any: + return to_class(PlanUpdateRequest, x) +def workspace_list_files_result_from_dict(s: Any) -> WorkspaceListFilesResult: + return WorkspaceListFilesResult.from_dict(s) -def session_plan_update_params_to_dict(x: SessionPlanUpdateParams) -> Any: - return to_class(SessionPlanUpdateParams, x) +def workspace_list_files_result_to_dict(x: WorkspaceListFilesResult) -> Any: + return to_class(WorkspaceListFilesResult, x) +def workspace_read_file_result_from_dict(s: Any) -> WorkspaceReadFileResult: + return WorkspaceReadFileResult.from_dict(s) -def session_plan_delete_result_from_dict(s: Any) -> SessionPlanDeleteResult: - return SessionPlanDeleteResult.from_dict(s) +def workspace_read_file_result_to_dict(x: WorkspaceReadFileResult) -> Any: + return to_class(WorkspaceReadFileResult, x) +def workspace_read_file_request_from_dict(s: Any) -> WorkspaceReadFileRequest: + return WorkspaceReadFileRequest.from_dict(s) -def session_plan_delete_result_to_dict(x: SessionPlanDeleteResult) -> Any: - return to_class(SessionPlanDeleteResult, x) +def workspace_read_file_request_to_dict(x: WorkspaceReadFileRequest) -> Any: + return to_class(WorkspaceReadFileRequest, x) +def workspace_create_file_request_from_dict(s: Any) -> WorkspaceCreateFileRequest: + return WorkspaceCreateFileRequest.from_dict(s) -def session_workspace_list_files_result_from_dict(s: Any) -> SessionWorkspaceListFilesResult: - return SessionWorkspaceListFilesResult.from_dict(s) +def workspace_create_file_request_to_dict(x: WorkspaceCreateFileRequest) -> Any: + return to_class(WorkspaceCreateFileRequest, x) +def fleet_start_result_from_dict(s: Any) -> FleetStartResult: + return FleetStartResult.from_dict(s) -def session_workspace_list_files_result_to_dict(x: SessionWorkspaceListFilesResult) -> Any: - return to_class(SessionWorkspaceListFilesResult, x) +def fleet_start_result_to_dict(x: FleetStartResult) -> Any: + return to_class(FleetStartResult, x) +def fleet_start_request_from_dict(s: Any) -> FleetStartRequest: + return FleetStartRequest.from_dict(s) -def session_workspace_read_file_result_from_dict(s: Any) -> SessionWorkspaceReadFileResult: - return SessionWorkspaceReadFileResult.from_dict(s) +def fleet_start_request_to_dict(x: FleetStartRequest) -> Any: + return to_class(FleetStartRequest, x) +def agent_list_from_dict(s: Any) -> AgentList: + return AgentList.from_dict(s) -def session_workspace_read_file_result_to_dict(x: SessionWorkspaceReadFileResult) -> Any: - return to_class(SessionWorkspaceReadFileResult, x) +def agent_list_to_dict(x: AgentList) -> Any: + return to_class(AgentList, x) +def agent_get_current_result_from_dict(s: Any) -> AgentGetCurrentResult: + return AgentGetCurrentResult.from_dict(s) -def session_workspace_read_file_params_from_dict(s: Any) -> SessionWorkspaceReadFileParams: - return SessionWorkspaceReadFileParams.from_dict(s) +def agent_get_current_result_to_dict(x: AgentGetCurrentResult) -> Any: + return to_class(AgentGetCurrentResult, x) +def agent_select_result_from_dict(s: Any) -> AgentSelectResult: + return AgentSelectResult.from_dict(s) -def session_workspace_read_file_params_to_dict(x: SessionWorkspaceReadFileParams) -> Any: - return to_class(SessionWorkspaceReadFileParams, x) +def agent_select_result_to_dict(x: AgentSelectResult) -> Any: + return to_class(AgentSelectResult, x) +def agent_select_request_from_dict(s: Any) -> AgentSelectRequest: + return AgentSelectRequest.from_dict(s) -def session_workspace_create_file_result_from_dict(s: Any) -> SessionWorkspaceCreateFileResult: - return SessionWorkspaceCreateFileResult.from_dict(s) +def agent_select_request_to_dict(x: AgentSelectRequest) -> Any: + return to_class(AgentSelectRequest, x) +def agent_reload_result_from_dict(s: Any) -> AgentReloadResult: + return AgentReloadResult.from_dict(s) -def session_workspace_create_file_result_to_dict(x: SessionWorkspaceCreateFileResult) -> Any: - return to_class(SessionWorkspaceCreateFileResult, x) +def agent_reload_result_to_dict(x: AgentReloadResult) -> Any: + return to_class(AgentReloadResult, x) +def skill_list_from_dict(s: Any) -> SkillList: + return SkillList.from_dict(s) -def session_workspace_create_file_params_from_dict(s: Any) -> SessionWorkspaceCreateFileParams: - return SessionWorkspaceCreateFileParams.from_dict(s) +def skill_list_to_dict(x: SkillList) -> Any: + return to_class(SkillList, x) +def skills_enable_request_from_dict(s: Any) -> SkillsEnableRequest: + return SkillsEnableRequest.from_dict(s) -def session_workspace_create_file_params_to_dict(x: SessionWorkspaceCreateFileParams) -> Any: - return to_class(SessionWorkspaceCreateFileParams, x) +def skills_enable_request_to_dict(x: SkillsEnableRequest) -> Any: + return to_class(SkillsEnableRequest, x) +def skills_disable_request_from_dict(s: Any) -> SkillsDisableRequest: + return SkillsDisableRequest.from_dict(s) -def session_fleet_start_result_from_dict(s: Any) -> SessionFleetStartResult: - return SessionFleetStartResult.from_dict(s) +def skills_disable_request_to_dict(x: SkillsDisableRequest) -> Any: + return to_class(SkillsDisableRequest, x) +def mcp_server_list_from_dict(s: Any) -> MCPServerList: + return MCPServerList.from_dict(s) -def session_fleet_start_result_to_dict(x: SessionFleetStartResult) -> Any: - return to_class(SessionFleetStartResult, x) +def mcp_server_list_to_dict(x: MCPServerList) -> Any: + return to_class(MCPServerList, x) +def mcp_enable_request_from_dict(s: Any) -> MCPEnableRequest: + return MCPEnableRequest.from_dict(s) -def session_fleet_start_params_from_dict(s: Any) -> SessionFleetStartParams: - return SessionFleetStartParams.from_dict(s) +def mcp_enable_request_to_dict(x: MCPEnableRequest) -> Any: + return to_class(MCPEnableRequest, x) +def mcp_disable_request_from_dict(s: Any) -> MCPDisableRequest: + return MCPDisableRequest.from_dict(s) -def session_fleet_start_params_to_dict(x: SessionFleetStartParams) -> Any: - return to_class(SessionFleetStartParams, x) +def mcp_disable_request_to_dict(x: MCPDisableRequest) -> Any: + return to_class(MCPDisableRequest, x) +def plugin_list_from_dict(s: Any) -> PluginList: + return PluginList.from_dict(s) -def session_agent_list_result_from_dict(s: Any) -> SessionAgentListResult: - return SessionAgentListResult.from_dict(s) +def plugin_list_to_dict(x: PluginList) -> Any: + return to_class(PluginList, x) +def extension_list_from_dict(s: Any) -> ExtensionList: + return ExtensionList.from_dict(s) -def session_agent_list_result_to_dict(x: SessionAgentListResult) -> Any: - return to_class(SessionAgentListResult, x) +def extension_list_to_dict(x: ExtensionList) -> Any: + return to_class(ExtensionList, x) +def extensions_enable_request_from_dict(s: Any) -> ExtensionsEnableRequest: + return ExtensionsEnableRequest.from_dict(s) -def session_agent_get_current_result_from_dict(s: Any) -> SessionAgentGetCurrentResult: - return SessionAgentGetCurrentResult.from_dict(s) +def extensions_enable_request_to_dict(x: ExtensionsEnableRequest) -> Any: + return to_class(ExtensionsEnableRequest, x) +def extensions_disable_request_from_dict(s: Any) -> ExtensionsDisableRequest: + return ExtensionsDisableRequest.from_dict(s) -def session_agent_get_current_result_to_dict(x: SessionAgentGetCurrentResult) -> Any: - return to_class(SessionAgentGetCurrentResult, x) +def extensions_disable_request_to_dict(x: ExtensionsDisableRequest) -> Any: + return to_class(ExtensionsDisableRequest, x) +def handle_tool_call_result_from_dict(s: Any) -> HandleToolCallResult: + return HandleToolCallResult.from_dict(s) -def session_agent_select_result_from_dict(s: Any) -> SessionAgentSelectResult: - return SessionAgentSelectResult.from_dict(s) +def handle_tool_call_result_to_dict(x: HandleToolCallResult) -> Any: + return to_class(HandleToolCallResult, x) +def tools_handle_pending_tool_call_request_from_dict(s: Any) -> ToolsHandlePendingToolCallRequest: + return ToolsHandlePendingToolCallRequest.from_dict(s) -def session_agent_select_result_to_dict(x: SessionAgentSelectResult) -> Any: - return to_class(SessionAgentSelectResult, x) +def tools_handle_pending_tool_call_request_to_dict(x: ToolsHandlePendingToolCallRequest) -> Any: + return to_class(ToolsHandlePendingToolCallRequest, x) +def commands_handle_pending_command_result_from_dict(s: Any) -> CommandsHandlePendingCommandResult: + return CommandsHandlePendingCommandResult.from_dict(s) -def session_agent_select_params_from_dict(s: Any) -> SessionAgentSelectParams: - return SessionAgentSelectParams.from_dict(s) +def commands_handle_pending_command_result_to_dict(x: CommandsHandlePendingCommandResult) -> Any: + return to_class(CommandsHandlePendingCommandResult, x) +def commands_handle_pending_command_request_from_dict(s: Any) -> CommandsHandlePendingCommandRequest: + return CommandsHandlePendingCommandRequest.from_dict(s) -def session_agent_select_params_to_dict(x: SessionAgentSelectParams) -> Any: - return to_class(SessionAgentSelectParams, x) +def commands_handle_pending_command_request_to_dict(x: CommandsHandlePendingCommandRequest) -> Any: + return to_class(CommandsHandlePendingCommandRequest, x) +def ui_elicitation_response_from_dict(s: Any) -> UIElicitationResponse: + return UIElicitationResponse.from_dict(s) -def session_agent_deselect_result_from_dict(s: Any) -> SessionAgentDeselectResult: - return SessionAgentDeselectResult.from_dict(s) +def ui_elicitation_response_to_dict(x: UIElicitationResponse) -> Any: + return to_class(UIElicitationResponse, x) +def ui_elicitation_request_from_dict(s: Any) -> UIElicitationRequest: + return UIElicitationRequest.from_dict(s) -def session_agent_deselect_result_to_dict(x: SessionAgentDeselectResult) -> Any: - return to_class(SessionAgentDeselectResult, x) +def ui_elicitation_request_to_dict(x: UIElicitationRequest) -> Any: + return to_class(UIElicitationRequest, x) +def ui_elicitation_result_from_dict(s: Any) -> UIElicitationResult: + return UIElicitationResult.from_dict(s) -def session_agent_reload_result_from_dict(s: Any) -> SessionAgentReloadResult: - return SessionAgentReloadResult.from_dict(s) +def ui_elicitation_result_to_dict(x: UIElicitationResult) -> Any: + return to_class(UIElicitationResult, x) +def ui_handle_pending_elicitation_request_from_dict(s: Any) -> UIHandlePendingElicitationRequest: + return UIHandlePendingElicitationRequest.from_dict(s) -def session_agent_reload_result_to_dict(x: SessionAgentReloadResult) -> Any: - return to_class(SessionAgentReloadResult, x) +def ui_handle_pending_elicitation_request_to_dict(x: UIHandlePendingElicitationRequest) -> Any: + return to_class(UIHandlePendingElicitationRequest, x) +def permission_request_result_from_dict(s: Any) -> PermissionRequestResult: + return PermissionRequestResult.from_dict(s) -def session_skills_list_result_from_dict(s: Any) -> SessionSkillsListResult: - return SessionSkillsListResult.from_dict(s) +def permission_request_result_to_dict(x: PermissionRequestResult) -> Any: + return to_class(PermissionRequestResult, x) +def permission_decision_request_from_dict(s: Any) -> PermissionDecisionRequest: + return PermissionDecisionRequest.from_dict(s) -def session_skills_list_result_to_dict(x: SessionSkillsListResult) -> Any: - return to_class(SessionSkillsListResult, x) +def permission_decision_request_to_dict(x: PermissionDecisionRequest) -> Any: + return to_class(PermissionDecisionRequest, x) +def log_result_from_dict(s: Any) -> LogResult: + return LogResult.from_dict(s) -def session_skills_enable_result_from_dict(s: Any) -> SessionSkillsEnableResult: - return SessionSkillsEnableResult.from_dict(s) +def log_result_to_dict(x: LogResult) -> Any: + return to_class(LogResult, x) +def log_request_from_dict(s: Any) -> LogRequest: + return LogRequest.from_dict(s) -def session_skills_enable_result_to_dict(x: SessionSkillsEnableResult) -> Any: - return to_class(SessionSkillsEnableResult, x) +def log_request_to_dict(x: LogRequest) -> Any: + return to_class(LogRequest, x) +def shell_exec_result_from_dict(s: Any) -> ShellExecResult: + return ShellExecResult.from_dict(s) -def session_skills_enable_params_from_dict(s: Any) -> SessionSkillsEnableParams: - return SessionSkillsEnableParams.from_dict(s) +def shell_exec_result_to_dict(x: ShellExecResult) -> Any: + return to_class(ShellExecResult, x) +def shell_exec_request_from_dict(s: Any) -> ShellExecRequest: + return ShellExecRequest.from_dict(s) -def session_skills_enable_params_to_dict(x: SessionSkillsEnableParams) -> Any: - return to_class(SessionSkillsEnableParams, x) +def shell_exec_request_to_dict(x: ShellExecRequest) -> Any: + return to_class(ShellExecRequest, x) +def shell_kill_result_from_dict(s: Any) -> ShellKillResult: + return ShellKillResult.from_dict(s) -def session_skills_disable_result_from_dict(s: Any) -> SessionSkillsDisableResult: - return SessionSkillsDisableResult.from_dict(s) +def shell_kill_result_to_dict(x: ShellKillResult) -> Any: + return to_class(ShellKillResult, x) +def shell_kill_request_from_dict(s: Any) -> ShellKillRequest: + return ShellKillRequest.from_dict(s) -def session_skills_disable_result_to_dict(x: SessionSkillsDisableResult) -> Any: - return to_class(SessionSkillsDisableResult, x) +def shell_kill_request_to_dict(x: ShellKillRequest) -> Any: + return to_class(ShellKillRequest, x) +def history_compact_result_from_dict(s: Any) -> HistoryCompactResult: + return HistoryCompactResult.from_dict(s) -def session_skills_disable_params_from_dict(s: Any) -> SessionSkillsDisableParams: - return SessionSkillsDisableParams.from_dict(s) +def history_compact_result_to_dict(x: HistoryCompactResult) -> Any: + return to_class(HistoryCompactResult, x) +def history_truncate_result_from_dict(s: Any) -> HistoryTruncateResult: + return HistoryTruncateResult.from_dict(s) -def session_skills_disable_params_to_dict(x: SessionSkillsDisableParams) -> Any: - return to_class(SessionSkillsDisableParams, x) +def history_truncate_result_to_dict(x: HistoryTruncateResult) -> Any: + return to_class(HistoryTruncateResult, x) +def history_truncate_request_from_dict(s: Any) -> HistoryTruncateRequest: + return HistoryTruncateRequest.from_dict(s) -def session_skills_reload_result_from_dict(s: Any) -> SessionSkillsReloadResult: - return SessionSkillsReloadResult.from_dict(s) +def history_truncate_request_to_dict(x: HistoryTruncateRequest) -> Any: + return to_class(HistoryTruncateRequest, x) +def usage_get_metrics_result_from_dict(s: Any) -> UsageGetMetricsResult: + return UsageGetMetricsResult.from_dict(s) -def session_skills_reload_result_to_dict(x: SessionSkillsReloadResult) -> Any: - return to_class(SessionSkillsReloadResult, x) - - -def session_mcp_list_result_from_dict(s: Any) -> SessionMCPListResult: - return SessionMCPListResult.from_dict(s) - - -def session_mcp_list_result_to_dict(x: SessionMCPListResult) -> Any: - return to_class(SessionMCPListResult, x) - - -def session_mcp_enable_result_from_dict(s: Any) -> SessionMCPEnableResult: - return SessionMCPEnableResult.from_dict(s) - - -def session_mcp_enable_result_to_dict(x: SessionMCPEnableResult) -> Any: - return to_class(SessionMCPEnableResult, x) - - -def session_mcp_enable_params_from_dict(s: Any) -> SessionMCPEnableParams: - return SessionMCPEnableParams.from_dict(s) - - -def session_mcp_enable_params_to_dict(x: SessionMCPEnableParams) -> Any: - return to_class(SessionMCPEnableParams, x) - - -def session_mcp_disable_result_from_dict(s: Any) -> SessionMCPDisableResult: - return SessionMCPDisableResult.from_dict(s) - - -def session_mcp_disable_result_to_dict(x: SessionMCPDisableResult) -> Any: - return to_class(SessionMCPDisableResult, x) - - -def session_mcp_disable_params_from_dict(s: Any) -> SessionMCPDisableParams: - return SessionMCPDisableParams.from_dict(s) - - -def session_mcp_disable_params_to_dict(x: SessionMCPDisableParams) -> Any: - return to_class(SessionMCPDisableParams, x) - - -def session_mcp_reload_result_from_dict(s: Any) -> SessionMCPReloadResult: - return SessionMCPReloadResult.from_dict(s) - - -def session_mcp_reload_result_to_dict(x: SessionMCPReloadResult) -> Any: - return to_class(SessionMCPReloadResult, x) - - -def session_plugins_list_result_from_dict(s: Any) -> SessionPluginsListResult: - return SessionPluginsListResult.from_dict(s) - - -def session_plugins_list_result_to_dict(x: SessionPluginsListResult) -> Any: - return to_class(SessionPluginsListResult, x) - - -def session_extensions_list_result_from_dict(s: Any) -> SessionExtensionsListResult: - return SessionExtensionsListResult.from_dict(s) - - -def session_extensions_list_result_to_dict(x: SessionExtensionsListResult) -> Any: - return to_class(SessionExtensionsListResult, x) - - -def session_extensions_enable_result_from_dict(s: Any) -> SessionExtensionsEnableResult: - return SessionExtensionsEnableResult.from_dict(s) - - -def session_extensions_enable_result_to_dict(x: SessionExtensionsEnableResult) -> Any: - return to_class(SessionExtensionsEnableResult, x) - - -def session_extensions_enable_params_from_dict(s: Any) -> SessionExtensionsEnableParams: - return SessionExtensionsEnableParams.from_dict(s) - - -def session_extensions_enable_params_to_dict(x: SessionExtensionsEnableParams) -> Any: - return to_class(SessionExtensionsEnableParams, x) - - -def session_extensions_disable_result_from_dict(s: Any) -> SessionExtensionsDisableResult: - return SessionExtensionsDisableResult.from_dict(s) - - -def session_extensions_disable_result_to_dict(x: SessionExtensionsDisableResult) -> Any: - return to_class(SessionExtensionsDisableResult, x) - - -def session_extensions_disable_params_from_dict(s: Any) -> SessionExtensionsDisableParams: - return SessionExtensionsDisableParams.from_dict(s) - - -def session_extensions_disable_params_to_dict(x: SessionExtensionsDisableParams) -> Any: - return to_class(SessionExtensionsDisableParams, x) - - -def session_extensions_reload_result_from_dict(s: Any) -> SessionExtensionsReloadResult: - return SessionExtensionsReloadResult.from_dict(s) - - -def session_extensions_reload_result_to_dict(x: SessionExtensionsReloadResult) -> Any: - return to_class(SessionExtensionsReloadResult, x) - - -def session_tools_handle_pending_tool_call_result_from_dict(s: Any) -> SessionToolsHandlePendingToolCallResult: - return SessionToolsHandlePendingToolCallResult.from_dict(s) - - -def session_tools_handle_pending_tool_call_result_to_dict(x: SessionToolsHandlePendingToolCallResult) -> Any: - return to_class(SessionToolsHandlePendingToolCallResult, x) - - -def session_tools_handle_pending_tool_call_params_from_dict(s: Any) -> SessionToolsHandlePendingToolCallParams: - return SessionToolsHandlePendingToolCallParams.from_dict(s) - - -def session_tools_handle_pending_tool_call_params_to_dict(x: SessionToolsHandlePendingToolCallParams) -> Any: - return to_class(SessionToolsHandlePendingToolCallParams, x) - - -def session_commands_handle_pending_command_result_from_dict(s: Any) -> SessionCommandsHandlePendingCommandResult: - return SessionCommandsHandlePendingCommandResult.from_dict(s) - - -def session_commands_handle_pending_command_result_to_dict(x: SessionCommandsHandlePendingCommandResult) -> Any: - return to_class(SessionCommandsHandlePendingCommandResult, x) - - -def session_commands_handle_pending_command_params_from_dict(s: Any) -> SessionCommandsHandlePendingCommandParams: - return SessionCommandsHandlePendingCommandParams.from_dict(s) - - -def session_commands_handle_pending_command_params_to_dict(x: SessionCommandsHandlePendingCommandParams) -> Any: - return to_class(SessionCommandsHandlePendingCommandParams, x) - - -def session_ui_elicitation_result_from_dict(s: Any) -> SessionUIElicitationResult: - return SessionUIElicitationResult.from_dict(s) - - -def session_ui_elicitation_result_to_dict(x: SessionUIElicitationResult) -> Any: - return to_class(SessionUIElicitationResult, x) - - -def session_ui_elicitation_params_from_dict(s: Any) -> SessionUIElicitationParams: - return SessionUIElicitationParams.from_dict(s) - - -def session_ui_elicitation_params_to_dict(x: SessionUIElicitationParams) -> Any: - return to_class(SessionUIElicitationParams, x) - - -def session_ui_handle_pending_elicitation_result_from_dict(s: Any) -> SessionUIHandlePendingElicitationResult: - return SessionUIHandlePendingElicitationResult.from_dict(s) - - -def session_ui_handle_pending_elicitation_result_to_dict(x: SessionUIHandlePendingElicitationResult) -> Any: - return to_class(SessionUIHandlePendingElicitationResult, x) - - -def session_ui_handle_pending_elicitation_params_from_dict(s: Any) -> SessionUIHandlePendingElicitationParams: - return SessionUIHandlePendingElicitationParams.from_dict(s) - - -def session_ui_handle_pending_elicitation_params_to_dict(x: SessionUIHandlePendingElicitationParams) -> Any: - return to_class(SessionUIHandlePendingElicitationParams, x) - - -def session_permissions_handle_pending_permission_request_result_from_dict(s: Any) -> SessionPermissionsHandlePendingPermissionRequestResult: - return SessionPermissionsHandlePendingPermissionRequestResult.from_dict(s) - - -def session_permissions_handle_pending_permission_request_result_to_dict(x: SessionPermissionsHandlePendingPermissionRequestResult) -> Any: - return to_class(SessionPermissionsHandlePendingPermissionRequestResult, x) - - -def session_permissions_handle_pending_permission_request_params_from_dict(s: Any) -> SessionPermissionsHandlePendingPermissionRequestParams: - return SessionPermissionsHandlePendingPermissionRequestParams.from_dict(s) - - -def session_permissions_handle_pending_permission_request_params_to_dict(x: SessionPermissionsHandlePendingPermissionRequestParams) -> Any: - return to_class(SessionPermissionsHandlePendingPermissionRequestParams, x) - - -def session_log_result_from_dict(s: Any) -> SessionLogResult: - return SessionLogResult.from_dict(s) - - -def session_log_result_to_dict(x: SessionLogResult) -> Any: - return to_class(SessionLogResult, x) - - -def session_log_params_from_dict(s: Any) -> SessionLogParams: - return SessionLogParams.from_dict(s) - - -def session_log_params_to_dict(x: SessionLogParams) -> Any: - return to_class(SessionLogParams, x) - - -def session_shell_exec_result_from_dict(s: Any) -> SessionShellExecResult: - return SessionShellExecResult.from_dict(s) - - -def session_shell_exec_result_to_dict(x: SessionShellExecResult) -> Any: - return to_class(SessionShellExecResult, x) - - -def session_shell_exec_params_from_dict(s: Any) -> SessionShellExecParams: - return SessionShellExecParams.from_dict(s) - - -def session_shell_exec_params_to_dict(x: SessionShellExecParams) -> Any: - return to_class(SessionShellExecParams, x) - - -def session_shell_kill_result_from_dict(s: Any) -> SessionShellKillResult: - return SessionShellKillResult.from_dict(s) - - -def session_shell_kill_result_to_dict(x: SessionShellKillResult) -> Any: - return to_class(SessionShellKillResult, x) - - -def session_shell_kill_params_from_dict(s: Any) -> SessionShellKillParams: - return SessionShellKillParams.from_dict(s) - - -def session_shell_kill_params_to_dict(x: SessionShellKillParams) -> Any: - return to_class(SessionShellKillParams, x) - - -def session_history_compact_result_from_dict(s: Any) -> SessionHistoryCompactResult: - return SessionHistoryCompactResult.from_dict(s) - - -def session_history_compact_result_to_dict(x: SessionHistoryCompactResult) -> Any: - return to_class(SessionHistoryCompactResult, x) - - -def session_history_truncate_result_from_dict(s: Any) -> SessionHistoryTruncateResult: - return SessionHistoryTruncateResult.from_dict(s) - - -def session_history_truncate_result_to_dict(x: SessionHistoryTruncateResult) -> Any: - return to_class(SessionHistoryTruncateResult, x) - - -def session_history_truncate_params_from_dict(s: Any) -> SessionHistoryTruncateParams: - return SessionHistoryTruncateParams.from_dict(s) - - -def session_history_truncate_params_to_dict(x: SessionHistoryTruncateParams) -> Any: - return to_class(SessionHistoryTruncateParams, x) - - -def session_usage_get_metrics_result_from_dict(s: Any) -> SessionUsageGetMetricsResult: - return SessionUsageGetMetricsResult.from_dict(s) - - -def session_usage_get_metrics_result_to_dict(x: SessionUsageGetMetricsResult) -> Any: - return to_class(SessionUsageGetMetricsResult, x) - +def usage_get_metrics_result_to_dict(x: UsageGetMetricsResult) -> Any: + return to_class(UsageGetMetricsResult, x) def session_fs_read_file_result_from_dict(s: Any) -> SessionFSReadFileResult: return SessionFSReadFileResult.from_dict(s) - def session_fs_read_file_result_to_dict(x: SessionFSReadFileResult) -> Any: return to_class(SessionFSReadFileResult, x) +def session_fs_read_file_request_from_dict(s: Any) -> SessionFSReadFileRequest: + return SessionFSReadFileRequest.from_dict(s) -def session_fs_read_file_params_from_dict(s: Any) -> SessionFSReadFileParams: - return SessionFSReadFileParams.from_dict(s) - +def session_fs_read_file_request_to_dict(x: SessionFSReadFileRequest) -> Any: + return to_class(SessionFSReadFileRequest, x) -def session_fs_read_file_params_to_dict(x: SessionFSReadFileParams) -> Any: - return to_class(SessionFSReadFileParams, x) +def session_fs_write_file_request_from_dict(s: Any) -> SessionFSWriteFileRequest: + return SessionFSWriteFileRequest.from_dict(s) +def session_fs_write_file_request_to_dict(x: SessionFSWriteFileRequest) -> Any: + return to_class(SessionFSWriteFileRequest, x) -def session_fs_write_file_params_from_dict(s: Any) -> SessionFSWriteFileParams: - return SessionFSWriteFileParams.from_dict(s) - - -def session_fs_write_file_params_to_dict(x: SessionFSWriteFileParams) -> Any: - return to_class(SessionFSWriteFileParams, x) - - -def session_fs_append_file_params_from_dict(s: Any) -> SessionFSAppendFileParams: - return SessionFSAppendFileParams.from_dict(s) - - -def session_fs_append_file_params_to_dict(x: SessionFSAppendFileParams) -> Any: - return to_class(SessionFSAppendFileParams, x) +def session_fs_append_file_request_from_dict(s: Any) -> SessionFSAppendFileRequest: + return SessionFSAppendFileRequest.from_dict(s) +def session_fs_append_file_request_to_dict(x: SessionFSAppendFileRequest) -> Any: + return to_class(SessionFSAppendFileRequest, x) def session_fs_exists_result_from_dict(s: Any) -> SessionFSExistsResult: return SessionFSExistsResult.from_dict(s) - def session_fs_exists_result_to_dict(x: SessionFSExistsResult) -> Any: return to_class(SessionFSExistsResult, x) +def session_fs_exists_request_from_dict(s: Any) -> SessionFSExistsRequest: + return SessionFSExistsRequest.from_dict(s) -def session_fs_exists_params_from_dict(s: Any) -> SessionFSExistsParams: - return SessionFSExistsParams.from_dict(s) - - -def session_fs_exists_params_to_dict(x: SessionFSExistsParams) -> Any: - return to_class(SessionFSExistsParams, x) - +def session_fs_exists_request_to_dict(x: SessionFSExistsRequest) -> Any: + return to_class(SessionFSExistsRequest, x) def session_fs_stat_result_from_dict(s: Any) -> SessionFSStatResult: return SessionFSStatResult.from_dict(s) - def session_fs_stat_result_to_dict(x: SessionFSStatResult) -> Any: return to_class(SessionFSStatResult, x) +def session_fs_stat_request_from_dict(s: Any) -> SessionFSStatRequest: + return SessionFSStatRequest.from_dict(s) -def session_fs_stat_params_from_dict(s: Any) -> SessionFSStatParams: - return SessionFSStatParams.from_dict(s) - - -def session_fs_stat_params_to_dict(x: SessionFSStatParams) -> Any: - return to_class(SessionFSStatParams, x) - - -def session_fs_mkdir_params_from_dict(s: Any) -> SessionFSMkdirParams: - return SessionFSMkdirParams.from_dict(s) +def session_fs_stat_request_to_dict(x: SessionFSStatRequest) -> Any: + return to_class(SessionFSStatRequest, x) +def session_fs_mkdir_request_from_dict(s: Any) -> SessionFSMkdirRequest: + return SessionFSMkdirRequest.from_dict(s) -def session_fs_mkdir_params_to_dict(x: SessionFSMkdirParams) -> Any: - return to_class(SessionFSMkdirParams, x) - +def session_fs_mkdir_request_to_dict(x: SessionFSMkdirRequest) -> Any: + return to_class(SessionFSMkdirRequest, x) def session_fs_readdir_result_from_dict(s: Any) -> SessionFSReaddirResult: return SessionFSReaddirResult.from_dict(s) - def session_fs_readdir_result_to_dict(x: SessionFSReaddirResult) -> Any: return to_class(SessionFSReaddirResult, x) +def session_fs_readdir_request_from_dict(s: Any) -> SessionFSReaddirRequest: + return SessionFSReaddirRequest.from_dict(s) -def session_fs_readdir_params_from_dict(s: Any) -> SessionFSReaddirParams: - return SessionFSReaddirParams.from_dict(s) - - -def session_fs_readdir_params_to_dict(x: SessionFSReaddirParams) -> Any: - return to_class(SessionFSReaddirParams, x) - +def session_fs_readdir_request_to_dict(x: SessionFSReaddirRequest) -> Any: + return to_class(SessionFSReaddirRequest, x) def session_fs_readdir_with_types_result_from_dict(s: Any) -> SessionFSReaddirWithTypesResult: return SessionFSReaddirWithTypesResult.from_dict(s) - def session_fs_readdir_with_types_result_to_dict(x: SessionFSReaddirWithTypesResult) -> Any: return to_class(SessionFSReaddirWithTypesResult, x) +def session_fs_readdir_with_types_request_from_dict(s: Any) -> SessionFSReaddirWithTypesRequest: + return SessionFSReaddirWithTypesRequest.from_dict(s) -def session_fs_readdir_with_types_params_from_dict(s: Any) -> SessionFSReaddirWithTypesParams: - return SessionFSReaddirWithTypesParams.from_dict(s) - - -def session_fs_readdir_with_types_params_to_dict(x: SessionFSReaddirWithTypesParams) -> Any: - return to_class(SessionFSReaddirWithTypesParams, x) - - -def session_fs_rm_params_from_dict(s: Any) -> SessionFSRmParams: - return SessionFSRmParams.from_dict(s) +def session_fs_readdir_with_types_request_to_dict(x: SessionFSReaddirWithTypesRequest) -> Any: + return to_class(SessionFSReaddirWithTypesRequest, x) +def session_fs_rm_request_from_dict(s: Any) -> SessionFSRmRequest: + return SessionFSRmRequest.from_dict(s) -def session_fs_rm_params_to_dict(x: SessionFSRmParams) -> Any: - return to_class(SessionFSRmParams, x) +def session_fs_rm_request_to_dict(x: SessionFSRmRequest) -> Any: + return to_class(SessionFSRmRequest, x) +def session_fs_rename_request_from_dict(s: Any) -> SessionFSRenameRequest: + return SessionFSRenameRequest.from_dict(s) -def session_fs_rename_params_from_dict(s: Any) -> SessionFSRenameParams: - return SessionFSRenameParams.from_dict(s) - - -def session_fs_rename_params_to_dict(x: SessionFSRenameParams) -> Any: - return to_class(SessionFSRenameParams, x) +def session_fs_rename_request_to_dict(x: SessionFSRenameRequest) -> Any: + return to_class(SessionFSRenameRequest, x) def _timeout_kwargs(timeout: float | None) -> dict: @@ -4202,22 +3590,43 @@ def _timeout_kwargs(timeout: float | None) -> dict: return {"timeout": timeout} return {} +def _patch_model_capabilities(data: dict) -> dict: + """Ensure model capabilities have required fields. + + TODO: Remove once the runtime schema correctly marks these fields as optional. + Some models (e.g. embedding models) may omit 'limits' or 'supports' in their + capabilities, or omit 'max_context_window_tokens' within limits. The generated + deserializer requires these fields, so we supply defaults here. + """ + for model in data.get("models", []): + caps = model.get("capabilities") + if caps is None: + model["capabilities"] = {"supports": {}, "limits": {"max_context_window_tokens": 0}} + continue + if "supports" not in caps: + caps["supports"] = {} + if "limits" not in caps: + caps["limits"] = {"max_context_window_tokens": 0} + elif "max_context_window_tokens" not in caps["limits"]: + caps["limits"]["max_context_window_tokens"] = 0 + return data + class ServerModelsApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def list(self, *, timeout: float | None = None) -> ModelsListResult: - return ModelsListResult.from_dict(await self._client.request("models.list", {}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> ModelList: + return ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list", {}, **_timeout_kwargs(timeout)))) class ServerToolsApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def list(self, params: ToolsListParams, *, timeout: float | None = None) -> ToolsListResult: + async def list(self, params: ToolsListRequest, *, timeout: float | None = None) -> ToolList: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} - return ToolsListResult.from_dict(await self._client.request("tools.list", params_dict, **_timeout_kwargs(timeout))) + return ToolList.from_dict(await self._client.request("tools.list", params_dict, **_timeout_kwargs(timeout))) class ServerAccountApi: @@ -4232,7 +3641,7 @@ class ServerMcpApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def discover(self, params: MCPDiscoverParams, *, timeout: float | None = None) -> MCPDiscoverResult: + async def discover(self, params: MCPDiscoverRequest, *, timeout: float | None = None) -> MCPDiscoverResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return MCPDiscoverResult.from_dict(await self._client.request("mcp.discover", params_dict, **_timeout_kwargs(timeout))) @@ -4241,7 +3650,7 @@ class ServerSessionFsApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def set_provider(self, params: SessionFSSetProviderParams, *, timeout: float | None = None) -> SessionFSSetProviderResult: + async def set_provider(self, params: SessionFSSetProviderRequest, *, timeout: float | None = None) -> SessionFSSetProviderResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SessionFSSetProviderResult.from_dict(await self._client.request("sessionFs.setProvider", params_dict, **_timeout_kwargs(timeout))) @@ -4251,7 +3660,7 @@ class ServerSessionsApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def fork(self, params: SessionsForkParams, *, timeout: float | None = None) -> SessionsForkResult: + async def fork(self, params: SessionsForkRequest, *, timeout: float | None = None) -> SessionsForkResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return SessionsForkResult.from_dict(await self._client.request("sessions.fork", params_dict, **_timeout_kwargs(timeout))) @@ -4267,7 +3676,7 @@ def __init__(self, client: "JsonRpcClient"): self.session_fs = ServerSessionFsApi(client) self.sessions = ServerSessionsApi(client) - async def ping(self, params: PingParams, *, timeout: float | None = None) -> PingResult: + async def ping(self, params: PingRequest, *, timeout: float | None = None) -> PingResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return PingResult.from_dict(await self._client.request("ping", params_dict, **_timeout_kwargs(timeout))) @@ -4277,13 +3686,13 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def get_current(self, *, timeout: float | None = None) -> SessionModelGetCurrentResult: - return SessionModelGetCurrentResult.from_dict(await self._client.request("session.model.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get_current(self, *, timeout: float | None = None) -> CurrentModel: + return CurrentModel.from_dict(await self._client.request("session.model.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def switch_to(self, params: SessionModelSwitchToParams, *, timeout: float | None = None) -> SessionModelSwitchToResult: + async def switch_to(self, params: ModelSwitchToRequest, *, timeout: float | None = None) -> ModelSwitchToResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionModelSwitchToResult.from_dict(await self._client.request("session.model.switchTo", params_dict, **_timeout_kwargs(timeout))) + return ModelSwitchToResult.from_dict(await self._client.request("session.model.switchTo", params_dict, **_timeout_kwargs(timeout))) class ModeApi: @@ -4291,13 +3700,13 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def get(self, *, timeout: float | None = None) -> SessionModeGetResult: - return SessionModeGetResult.from_dict(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + 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 set(self, params: SessionModeSetParams, *, timeout: float | None = None) -> SessionModeSetResult: + async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionModeSetResult.from_dict(await self._client.request("session.mode.set", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.mode.set", params_dict, **_timeout_kwargs(timeout)) class PlanApi: @@ -4305,16 +3714,16 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def read(self, *, timeout: float | None = None) -> SessionPlanReadResult: - return SessionPlanReadResult.from_dict(await self._client.request("session.plan.read", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def read(self, *, timeout: float | None = None) -> PlanReadResult: + return PlanReadResult.from_dict(await self._client.request("session.plan.read", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def update(self, params: SessionPlanUpdateParams, *, timeout: float | None = None) -> SessionPlanUpdateResult: + async def update(self, params: PlanUpdateRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionPlanUpdateResult.from_dict(await self._client.request("session.plan.update", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.plan.update", params_dict, **_timeout_kwargs(timeout)) - async def delete(self, *, timeout: float | None = None) -> SessionPlanDeleteResult: - return SessionPlanDeleteResult.from_dict(await self._client.request("session.plan.delete", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def delete(self, *, timeout: float | None = None) -> None: + await self._client.request("session.plan.delete", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) class WorkspaceApi: @@ -4322,18 +3731,18 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list_files(self, *, timeout: float | None = None) -> SessionWorkspaceListFilesResult: - return SessionWorkspaceListFilesResult.from_dict(await self._client.request("session.workspace.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list_files(self, *, timeout: float | None = None) -> WorkspaceListFilesResult: + return WorkspaceListFilesResult.from_dict(await self._client.request("session.workspace.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def read_file(self, params: SessionWorkspaceReadFileParams, *, timeout: float | None = None) -> SessionWorkspaceReadFileResult: + async def read_file(self, params: WorkspaceReadFileRequest, *, timeout: float | None = None) -> WorkspaceReadFileResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionWorkspaceReadFileResult.from_dict(await self._client.request("session.workspace.readFile", params_dict, **_timeout_kwargs(timeout))) + return WorkspaceReadFileResult.from_dict(await self._client.request("session.workspace.readFile", params_dict, **_timeout_kwargs(timeout))) - async def create_file(self, params: SessionWorkspaceCreateFileParams, *, timeout: float | None = None) -> SessionWorkspaceCreateFileResult: + async def create_file(self, params: WorkspaceCreateFileRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionWorkspaceCreateFileResult.from_dict(await self._client.request("session.workspace.createFile", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.workspace.createFile", params_dict, **_timeout_kwargs(timeout)) # Experimental: this API group is experimental and may change or be removed. @@ -4342,10 +3751,10 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def start(self, params: SessionFleetStartParams, *, timeout: float | None = None) -> SessionFleetStartResult: + async def start(self, params: FleetStartRequest, *, timeout: float | None = None) -> FleetStartResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionFleetStartResult.from_dict(await self._client.request("session.fleet.start", params_dict, **_timeout_kwargs(timeout))) + return FleetStartResult.from_dict(await self._client.request("session.fleet.start", params_dict, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -4354,22 +3763,22 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionAgentListResult: - return SessionAgentListResult.from_dict(await self._client.request("session.agent.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> AgentList: + return AgentList.from_dict(await self._client.request("session.agent.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def get_current(self, *, timeout: float | None = None) -> SessionAgentGetCurrentResult: - return SessionAgentGetCurrentResult.from_dict(await self._client.request("session.agent.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get_current(self, *, timeout: float | None = None) -> AgentGetCurrentResult: + return AgentGetCurrentResult.from_dict(await self._client.request("session.agent.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def select(self, params: SessionAgentSelectParams, *, timeout: float | None = None) -> SessionAgentSelectResult: + async def select(self, params: AgentSelectRequest, *, timeout: float | None = None) -> AgentSelectResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionAgentSelectResult.from_dict(await self._client.request("session.agent.select", params_dict, **_timeout_kwargs(timeout))) + return AgentSelectResult.from_dict(await self._client.request("session.agent.select", params_dict, **_timeout_kwargs(timeout))) - async def deselect(self, *, timeout: float | None = None) -> SessionAgentDeselectResult: - return SessionAgentDeselectResult.from_dict(await self._client.request("session.agent.deselect", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def deselect(self, *, timeout: float | None = None) -> None: + await self._client.request("session.agent.deselect", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) - async def reload(self, *, timeout: float | None = None) -> SessionAgentReloadResult: - return SessionAgentReloadResult.from_dict(await self._client.request("session.agent.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def reload(self, *, timeout: float | None = None) -> AgentReloadResult: + return AgentReloadResult.from_dict(await self._client.request("session.agent.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -4378,21 +3787,21 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionSkillsListResult: - return SessionSkillsListResult.from_dict(await self._client.request("session.skills.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> SkillList: + return SkillList.from_dict(await self._client.request("session.skills.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def enable(self, params: SessionSkillsEnableParams, *, timeout: float | None = None) -> SessionSkillsEnableResult: + async def enable(self, params: SkillsEnableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionSkillsEnableResult.from_dict(await self._client.request("session.skills.enable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.skills.enable", params_dict, **_timeout_kwargs(timeout)) - async def disable(self, params: SessionSkillsDisableParams, *, timeout: float | None = None) -> SessionSkillsDisableResult: + async def disable(self, params: SkillsDisableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionSkillsDisableResult.from_dict(await self._client.request("session.skills.disable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.skills.disable", params_dict, **_timeout_kwargs(timeout)) - async def reload(self, *, timeout: float | None = None) -> SessionSkillsReloadResult: - return SessionSkillsReloadResult.from_dict(await self._client.request("session.skills.reload", {"sessionId": self._session_id}, **_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)) # Experimental: this API group is experimental and may change or be removed. @@ -4401,21 +3810,21 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionMCPListResult: - return SessionMCPListResult.from_dict(await self._client.request("session.mcp.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> MCPServerList: + return MCPServerList.from_dict(await self._client.request("session.mcp.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def enable(self, params: SessionMCPEnableParams, *, timeout: float | None = None) -> SessionMCPEnableResult: + async def enable(self, params: MCPEnableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionMCPEnableResult.from_dict(await self._client.request("session.mcp.enable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.mcp.enable", params_dict, **_timeout_kwargs(timeout)) - async def disable(self, params: SessionMCPDisableParams, *, timeout: float | None = None) -> SessionMCPDisableResult: + async def disable(self, params: MCPDisableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionMCPDisableResult.from_dict(await self._client.request("session.mcp.disable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.mcp.disable", params_dict, **_timeout_kwargs(timeout)) - async def reload(self, *, timeout: float | None = None) -> SessionMCPReloadResult: - return SessionMCPReloadResult.from_dict(await self._client.request("session.mcp.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def reload(self, *, timeout: float | None = None) -> None: + await self._client.request("session.mcp.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) # Experimental: this API group is experimental and may change or be removed. @@ -4424,8 +3833,8 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionPluginsListResult: - return SessionPluginsListResult.from_dict(await self._client.request("session.plugins.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> PluginList: + return PluginList.from_dict(await self._client.request("session.plugins.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -4434,21 +3843,21 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list(self, *, timeout: float | None = None) -> SessionExtensionsListResult: - return SessionExtensionsListResult.from_dict(await self._client.request("session.extensions.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def list(self, *, timeout: float | None = None) -> ExtensionList: + return ExtensionList.from_dict(await self._client.request("session.extensions.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def enable(self, params: SessionExtensionsEnableParams, *, timeout: float | None = None) -> SessionExtensionsEnableResult: + async def enable(self, params: ExtensionsEnableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionExtensionsEnableResult.from_dict(await self._client.request("session.extensions.enable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.extensions.enable", params_dict, **_timeout_kwargs(timeout)) - async def disable(self, params: SessionExtensionsDisableParams, *, timeout: float | None = None) -> SessionExtensionsDisableResult: + async def disable(self, params: ExtensionsDisableRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionExtensionsDisableResult.from_dict(await self._client.request("session.extensions.disable", params_dict, **_timeout_kwargs(timeout))) + await self._client.request("session.extensions.disable", params_dict, **_timeout_kwargs(timeout)) - async def reload(self, *, timeout: float | None = None) -> SessionExtensionsReloadResult: - return SessionExtensionsReloadResult.from_dict(await self._client.request("session.extensions.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def reload(self, *, timeout: float | None = None) -> None: + await self._client.request("session.extensions.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) class ToolsApi: @@ -4456,10 +3865,10 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def handle_pending_tool_call(self, params: SessionToolsHandlePendingToolCallParams, *, timeout: float | None = None) -> SessionToolsHandlePendingToolCallResult: + async def handle_pending_tool_call(self, params: ToolsHandlePendingToolCallRequest, *, timeout: float | None = None) -> HandleToolCallResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionToolsHandlePendingToolCallResult.from_dict(await self._client.request("session.tools.handlePendingToolCall", params_dict, **_timeout_kwargs(timeout))) + return HandleToolCallResult.from_dict(await self._client.request("session.tools.handlePendingToolCall", params_dict, **_timeout_kwargs(timeout))) class CommandsApi: @@ -4467,10 +3876,10 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def handle_pending_command(self, params: SessionCommandsHandlePendingCommandParams, *, timeout: float | None = None) -> SessionCommandsHandlePendingCommandResult: + async def handle_pending_command(self, params: CommandsHandlePendingCommandRequest, *, timeout: float | None = None) -> CommandsHandlePendingCommandResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionCommandsHandlePendingCommandResult.from_dict(await self._client.request("session.commands.handlePendingCommand", params_dict, **_timeout_kwargs(timeout))) + return CommandsHandlePendingCommandResult.from_dict(await self._client.request("session.commands.handlePendingCommand", params_dict, **_timeout_kwargs(timeout))) class UiApi: @@ -4478,15 +3887,15 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def elicitation(self, params: SessionUIElicitationParams, *, timeout: float | None = None) -> SessionUIElicitationResult: + async def elicitation(self, params: UIElicitationRequest, *, timeout: float | None = None) -> UIElicitationResponse: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionUIElicitationResult.from_dict(await self._client.request("session.ui.elicitation", params_dict, **_timeout_kwargs(timeout))) + return UIElicitationResponse.from_dict(await self._client.request("session.ui.elicitation", params_dict, **_timeout_kwargs(timeout))) - async def handle_pending_elicitation(self, params: SessionUIHandlePendingElicitationParams, *, timeout: float | None = None) -> SessionUIHandlePendingElicitationResult: + async def handle_pending_elicitation(self, params: UIHandlePendingElicitationRequest, *, timeout: float | None = None) -> UIElicitationResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionUIHandlePendingElicitationResult.from_dict(await self._client.request("session.ui.handlePendingElicitation", params_dict, **_timeout_kwargs(timeout))) + return UIElicitationResult.from_dict(await self._client.request("session.ui.handlePendingElicitation", params_dict, **_timeout_kwargs(timeout))) class PermissionsApi: @@ -4494,10 +3903,10 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def handle_pending_permission_request(self, params: SessionPermissionsHandlePendingPermissionRequestParams, *, timeout: float | None = None) -> SessionPermissionsHandlePendingPermissionRequestResult: + async def handle_pending_permission_request(self, params: PermissionDecisionRequest, *, timeout: float | None = None) -> PermissionRequestResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionPermissionsHandlePendingPermissionRequestResult.from_dict(await self._client.request("session.permissions.handlePendingPermissionRequest", params_dict, **_timeout_kwargs(timeout))) + return PermissionRequestResult.from_dict(await self._client.request("session.permissions.handlePendingPermissionRequest", params_dict, **_timeout_kwargs(timeout))) class ShellApi: @@ -4505,15 +3914,15 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def exec(self, params: SessionShellExecParams, *, timeout: float | None = None) -> SessionShellExecResult: + async def exec(self, params: ShellExecRequest, *, timeout: float | None = None) -> ShellExecResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionShellExecResult.from_dict(await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout))) + return ShellExecResult.from_dict(await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout))) - async def kill(self, params: SessionShellKillParams, *, timeout: float | None = None) -> SessionShellKillResult: + async def kill(self, params: ShellKillRequest, *, timeout: float | None = None) -> ShellKillResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionShellKillResult.from_dict(await self._client.request("session.shell.kill", params_dict, **_timeout_kwargs(timeout))) + return ShellKillResult.from_dict(await self._client.request("session.shell.kill", params_dict, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -4522,13 +3931,13 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def compact(self, *, timeout: float | None = None) -> SessionHistoryCompactResult: - return SessionHistoryCompactResult.from_dict(await self._client.request("session.history.compact", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def compact(self, *, timeout: float | None = None) -> HistoryCompactResult: + return HistoryCompactResult.from_dict(await self._client.request("session.history.compact", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def truncate(self, params: SessionHistoryTruncateParams, *, timeout: float | None = None) -> SessionHistoryTruncateResult: + async def truncate(self, params: HistoryTruncateRequest, *, timeout: float | None = None) -> HistoryTruncateResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionHistoryTruncateResult.from_dict(await self._client.request("session.history.truncate", params_dict, **_timeout_kwargs(timeout))) + return HistoryTruncateResult.from_dict(await self._client.request("session.history.truncate", params_dict, **_timeout_kwargs(timeout))) # Experimental: this API group is experimental and may change or be removed. @@ -4537,8 +3946,8 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def get_metrics(self, *, timeout: float | None = None) -> SessionUsageGetMetricsResult: - return SessionUsageGetMetricsResult.from_dict(await self._client.request("session.usage.getMetrics", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get_metrics(self, *, timeout: float | None = None) -> UsageGetMetricsResult: + return UsageGetMetricsResult.from_dict(await self._client.request("session.usage.getMetrics", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) class SessionRpc: @@ -4564,32 +3973,32 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.history = HistoryApi(client, session_id) self.usage = UsageApi(client, session_id) - async def log(self, params: SessionLogParams, *, timeout: float | None = None) -> SessionLogResult: + async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return SessionLogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout))) + return LogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout))) class SessionFsHandler(Protocol): - async def read_file(self, params: SessionFSReadFileParams) -> SessionFSReadFileResult: + async def read_file(self, params: SessionFSReadFileRequest) -> SessionFSReadFileResult: pass - async def write_file(self, params: SessionFSWriteFileParams) -> None: + async def write_file(self, params: SessionFSWriteFileRequest) -> None: pass - async def append_file(self, params: SessionFSAppendFileParams) -> None: + async def append_file(self, params: SessionFSAppendFileRequest) -> None: pass - async def exists(self, params: SessionFSExistsParams) -> SessionFSExistsResult: + async def exists(self, params: SessionFSExistsRequest) -> SessionFSExistsResult: pass - async def stat(self, params: SessionFSStatParams) -> SessionFSStatResult: + async def stat(self, params: SessionFSStatRequest) -> SessionFSStatResult: pass - async def mkdir(self, params: SessionFSMkdirParams) -> None: + async def mkdir(self, params: SessionFSMkdirRequest) -> None: pass - async def readdir(self, params: SessionFSReaddirParams) -> SessionFSReaddirResult: + async def readdir(self, params: SessionFSReaddirRequest) -> SessionFSReaddirResult: pass - async def readdir_with_types(self, params: SessionFSReaddirWithTypesParams) -> SessionFSReaddirWithTypesResult: + async def readdir_with_types(self, params: SessionFSReaddirWithTypesRequest) -> SessionFSReaddirWithTypesResult: pass - async def rm(self, params: SessionFSRmParams) -> None: + async def rm(self, params: SessionFSRmRequest) -> None: pass - async def rename(self, params: SessionFSRenameParams) -> None: + async def rename(self, params: SessionFSRenameRequest) -> None: pass @dataclass @@ -4602,70 +4011,70 @@ def register_client_session_api_handlers( ) -> None: """Register client-session request handlers on a JSON-RPC connection.""" async def handle_session_fs_read_file(params: dict) -> dict | None: - request = SessionFSReadFileParams.from_dict(params) + request = SessionFSReadFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.read_file(request) return result.to_dict() client.set_request_handler("sessionFs.readFile", handle_session_fs_read_file) async def handle_session_fs_write_file(params: dict) -> dict | None: - request = SessionFSWriteFileParams.from_dict(params) + request = SessionFSWriteFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") await handler.write_file(request) return None client.set_request_handler("sessionFs.writeFile", handle_session_fs_write_file) async def handle_session_fs_append_file(params: dict) -> dict | None: - request = SessionFSAppendFileParams.from_dict(params) + request = SessionFSAppendFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") await handler.append_file(request) return None client.set_request_handler("sessionFs.appendFile", handle_session_fs_append_file) async def handle_session_fs_exists(params: dict) -> dict | None: - request = SessionFSExistsParams.from_dict(params) + request = SessionFSExistsRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.exists(request) return result.to_dict() client.set_request_handler("sessionFs.exists", handle_session_fs_exists) async def handle_session_fs_stat(params: dict) -> dict | None: - request = SessionFSStatParams.from_dict(params) + request = SessionFSStatRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.stat(request) return result.to_dict() client.set_request_handler("sessionFs.stat", handle_session_fs_stat) async def handle_session_fs_mkdir(params: dict) -> dict | None: - request = SessionFSMkdirParams.from_dict(params) + request = SessionFSMkdirRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") await handler.mkdir(request) return None client.set_request_handler("sessionFs.mkdir", handle_session_fs_mkdir) async def handle_session_fs_readdir(params: dict) -> dict | None: - request = SessionFSReaddirParams.from_dict(params) + request = SessionFSReaddirRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.readdir(request) return result.to_dict() client.set_request_handler("sessionFs.readdir", handle_session_fs_readdir) async def handle_session_fs_readdir_with_types(params: dict) -> dict | None: - request = SessionFSReaddirWithTypesParams.from_dict(params) + request = SessionFSReaddirWithTypesRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.readdir_with_types(request) return result.to_dict() client.set_request_handler("sessionFs.readdirWithTypes", handle_session_fs_readdir_with_types) async def handle_session_fs_rm(params: dict) -> dict | None: - request = SessionFSRmParams.from_dict(params) + request = SessionFSRmRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") await handler.rm(request) return None client.set_request_handler("sessionFs.rm", handle_session_fs_rm) async def handle_session_fs_rename(params: dict) -> dict | None: - request = SessionFSRenameParams.from_dict(params) + request = SessionFSRenameRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") await handler.rename(request) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 2c29c791a..2c1dbffb6 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -78,7 +78,7 @@ def from_int(x: Any) -> int: return x -class DataAction(Enum): +class ElicitationCompletedAction(Enum): """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) """ @@ -87,7 +87,7 @@ class DataAction(Enum): DECLINE = "decline" -class AgentMode(Enum): +class UserMessageAgentMode(Enum): """The agent mode that was active when this message was sent""" AUTOPILOT = "autopilot" @@ -97,7 +97,7 @@ class AgentMode(Enum): @dataclass -class Agent: +class CustomAgentsUpdatedAgent: description: str """Description of what the agent does""" @@ -123,7 +123,7 @@ class Agent: """Model override for this agent, if set""" @staticmethod - def from_dict(obj: Any) -> 'Agent': + def from_dict(obj: Any) -> 'CustomAgentsUpdatedAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) @@ -133,7 +133,7 @@ def from_dict(obj: Any) -> 'Agent': tools = from_list(from_str, obj.get("tools")) user_invocable = from_bool(obj.get("userInvocable")) model = from_union([from_str, from_none], obj.get("model")) - return Agent(description, display_name, id, name, source, tools, user_invocable, model) + return CustomAgentsUpdatedAgent(description, display_name, id, name, source, tools, user_invocable, model) def to_dict(self) -> dict: result: dict = {} @@ -150,7 +150,7 @@ def to_dict(self) -> dict: @dataclass -class LineRange: +class UserMessageAttachmentFileLineRange: """Optional line range to scope the attachment to a specific section of the file""" end: float @@ -160,11 +160,11 @@ class LineRange: """Start line number (1-based)""" @staticmethod - def from_dict(obj: Any) -> 'LineRange': + def from_dict(obj: Any) -> 'UserMessageAttachmentFileLineRange': assert isinstance(obj, dict) end = from_float(obj.get("end")) start = from_float(obj.get("start")) - return LineRange(end, start) + return UserMessageAttachmentFileLineRange(end, start) def to_dict(self) -> dict: result: dict = {} @@ -173,7 +173,7 @@ def to_dict(self) -> dict: return result -class ReferenceType(Enum): +class UserMessageAttachmentGithubReferenceType(Enum): """Type of GitHub reference""" DISCUSSION = "discussion" @@ -182,7 +182,7 @@ class ReferenceType(Enum): @dataclass -class End: +class UserMessageAttachmentSelectionDetailsEnd: """End position of the selection""" character: float @@ -192,11 +192,11 @@ class End: """End line number (0-based)""" @staticmethod - def from_dict(obj: Any) -> 'End': + def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetailsEnd': assert isinstance(obj, dict) character = from_float(obj.get("character")) line = from_float(obj.get("line")) - return End(character, line) + return UserMessageAttachmentSelectionDetailsEnd(character, line) def to_dict(self) -> dict: result: dict = {} @@ -206,7 +206,7 @@ def to_dict(self) -> dict: @dataclass -class Start: +class UserMessageAttachmentSelectionDetailsStart: """Start position of the selection""" character: float @@ -216,11 +216,11 @@ class Start: """Start line number (0-based)""" @staticmethod - def from_dict(obj: Any) -> 'Start': + def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetailsStart': assert isinstance(obj, dict) character = from_float(obj.get("character")) line = from_float(obj.get("line")) - return Start(character, line) + return UserMessageAttachmentSelectionDetailsStart(character, line) def to_dict(self) -> dict: result: dict = {} @@ -230,30 +230,30 @@ def to_dict(self) -> dict: @dataclass -class Selection: +class UserMessageAttachmentSelectionDetails: """Position range of the selection within the file""" - end: End + end: UserMessageAttachmentSelectionDetailsEnd """End position of the selection""" - start: Start + start: UserMessageAttachmentSelectionDetailsStart """Start position of the selection""" @staticmethod - def from_dict(obj: Any) -> 'Selection': + def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetails': assert isinstance(obj, dict) - end = End.from_dict(obj.get("end")) - start = Start.from_dict(obj.get("start")) - return Selection(end, start) + end = UserMessageAttachmentSelectionDetailsEnd.from_dict(obj.get("end")) + start = UserMessageAttachmentSelectionDetailsStart.from_dict(obj.get("start")) + return UserMessageAttachmentSelectionDetails(end, start) def to_dict(self) -> dict: result: dict = {} - result["end"] = to_class(End, self.end) - result["start"] = to_class(Start, self.start) + result["end"] = to_class(UserMessageAttachmentSelectionDetailsEnd, self.end) + result["start"] = to_class(UserMessageAttachmentSelectionDetailsStart, self.start) return result -class AttachmentType(Enum): +class UserMessageAttachmentType(Enum): BLOB = "blob" DIRECTORY = "directory" FILE = "file" @@ -262,7 +262,7 @@ class AttachmentType(Enum): @dataclass -class Attachment: +class UserMessageAttachment: """A user message attachment — a file, directory, code selection, blob, or GitHub reference File attachment @@ -275,7 +275,7 @@ class Attachment: Blob attachment with inline base64-encoded data """ - type: AttachmentType + type: UserMessageAttachmentType """Attachment type discriminator""" display_name: str | None = None @@ -283,7 +283,7 @@ class Attachment: User-facing display name for the selection """ - line_range: LineRange | None = None + line_range: UserMessageAttachmentFileLineRange | None = None """Optional line range to scope the attachment to a specific section of the file""" path: str | None = None @@ -294,7 +294,7 @@ class Attachment: file_path: str | None = None """Absolute path to the file containing the selection""" - selection: Selection | None = None + selection: UserMessageAttachmentSelectionDetails | None = None """Position range of the selection within the file""" text: str | None = None @@ -303,7 +303,7 @@ class Attachment: number: float | None = None """Issue, pull request, or discussion number""" - reference_type: ReferenceType | None = None + reference_type: UserMessageAttachmentGithubReferenceType | None = None """Type of GitHub reference""" state: str | None = None @@ -322,43 +322,43 @@ class Attachment: """MIME type of the inline data""" @staticmethod - def from_dict(obj: Any) -> 'Attachment': + def from_dict(obj: Any) -> 'UserMessageAttachment': assert isinstance(obj, dict) - type = AttachmentType(obj.get("type")) + type = UserMessageAttachmentType(obj.get("type")) display_name = from_union([from_str, from_none], obj.get("displayName")) - line_range = from_union([LineRange.from_dict, from_none], obj.get("lineRange")) + line_range = from_union([UserMessageAttachmentFileLineRange.from_dict, from_none], obj.get("lineRange")) path = from_union([from_str, from_none], obj.get("path")) file_path = from_union([from_str, from_none], obj.get("filePath")) - selection = from_union([Selection.from_dict, from_none], obj.get("selection")) + selection = from_union([UserMessageAttachmentSelectionDetails.from_dict, from_none], obj.get("selection")) text = from_union([from_str, from_none], obj.get("text")) number = from_union([from_float, from_none], obj.get("number")) - reference_type = from_union([ReferenceType, from_none], obj.get("referenceType")) + reference_type = from_union([UserMessageAttachmentGithubReferenceType, from_none], obj.get("referenceType")) state = from_union([from_str, from_none], obj.get("state")) title = from_union([from_str, from_none], obj.get("title")) url = from_union([from_str, from_none], obj.get("url")) data = from_union([from_str, from_none], obj.get("data")) mime_type = from_union([from_str, from_none], obj.get("mimeType")) - return Attachment(type, display_name, line_range, path, file_path, selection, text, number, reference_type, state, title, url, data, mime_type) + return UserMessageAttachment(type, display_name, line_range, path, file_path, selection, text, number, reference_type, state, title, url, data, mime_type) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(AttachmentType, self.type) + result["type"] = to_enum(UserMessageAttachmentType, self.type) if self.display_name is not None: result["displayName"] = from_union([from_str, from_none], self.display_name) if self.line_range is not None: - result["lineRange"] = from_union([lambda x: to_class(LineRange, x), from_none], self.line_range) + result["lineRange"] = from_union([lambda x: to_class(UserMessageAttachmentFileLineRange, x), from_none], self.line_range) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.file_path is not None: result["filePath"] = from_union([from_str, from_none], self.file_path) if self.selection is not None: - result["selection"] = from_union([lambda x: to_class(Selection, x), from_none], self.selection) + result["selection"] = from_union([lambda x: to_class(UserMessageAttachmentSelectionDetails, x), from_none], self.selection) if self.text is not None: result["text"] = from_union([from_str, from_none], self.text) if self.number is not None: result["number"] = from_union([to_float, from_none], self.number) if self.reference_type is not None: - result["referenceType"] = from_union([lambda x: to_enum(ReferenceType, x), from_none], self.reference_type) + result["referenceType"] = from_union([lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x), from_none], self.reference_type) if self.state is not None: result["state"] = from_union([from_str, from_none], self.state) if self.title is not None: @@ -373,7 +373,7 @@ def to_dict(self) -> dict: @dataclass -class CodeChanges: +class ShutdownCodeChanges: """Aggregate code change metrics for the session""" files_modified: list[str] @@ -386,12 +386,12 @@ class CodeChanges: """Total number of lines removed during the session""" @staticmethod - def from_dict(obj: Any) -> 'CodeChanges': + def from_dict(obj: Any) -> 'ShutdownCodeChanges': assert isinstance(obj, dict) files_modified = from_list(from_str, obj.get("filesModified")) lines_added = from_float(obj.get("linesAdded")) lines_removed = from_float(obj.get("linesRemoved")) - return CodeChanges(files_modified, lines_added, lines_removed) + return ShutdownCodeChanges(files_modified, lines_added, lines_removed) def to_dict(self) -> dict: result: dict = {} @@ -402,16 +402,16 @@ def to_dict(self) -> dict: @dataclass -class DataCommand: +class CommandsChangedCommand: name: str description: str | None = None @staticmethod - def from_dict(obj: Any) -> 'DataCommand': + def from_dict(obj: Any) -> 'CommandsChangedCommand': assert isinstance(obj, dict) name = from_str(obj.get("name")) description = from_union([from_str, from_none], obj.get("description")) - return DataCommand(name, description) + return CommandsChangedCommand(name, description) def to_dict(self) -> dict: result: dict = {} @@ -422,7 +422,7 @@ def to_dict(self) -> dict: @dataclass -class CompactionTokensUsed: +class CompactionCompleteCompactionTokensUsed: """Token usage breakdown for the compaction LLM call""" cached_input: float @@ -435,12 +435,12 @@ class CompactionTokensUsed: """Output tokens produced by the compaction LLM call""" @staticmethod - def from_dict(obj: Any) -> 'CompactionTokensUsed': + def from_dict(obj: Any) -> 'CompactionCompleteCompactionTokensUsed': assert isinstance(obj, dict) cached_input = from_float(obj.get("cachedInput")) input = from_float(obj.get("input")) output = from_float(obj.get("output")) - return CompactionTokensUsed(cached_input, input, output) + return CompactionCompleteCompactionTokensUsed(cached_input, input, output) def to_dict(self) -> dict: result: dict = {} @@ -450,7 +450,7 @@ def to_dict(self) -> dict: return result -class HostType(Enum): +class ContextChangedHostType(Enum): """Hosting platform type of the repository (github or ado)""" ADO = "ado" @@ -458,7 +458,7 @@ class HostType(Enum): @dataclass -class ContextClass: +class Context: """Working directory and git context at session start Updated working directory and git context at resume time @@ -478,7 +478,7 @@ class ContextClass: head_commit: str | None = None """Head commit of current git branch at session start time""" - host_type: HostType | None = None + host_type: ContextChangedHostType | None = None """Hosting platform type of the repository (github or ado)""" repository: str | None = None @@ -487,16 +487,16 @@ class ContextClass: """ @staticmethod - def from_dict(obj: Any) -> 'ContextClass': + def from_dict(obj: Any) -> 'Context': assert isinstance(obj, dict) cwd = from_str(obj.get("cwd")) base_commit = from_union([from_str, from_none], obj.get("baseCommit")) branch = from_union([from_str, from_none], obj.get("branch")) git_root = from_union([from_str, from_none], obj.get("gitRoot")) head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([HostType, from_none], obj.get("hostType")) + host_type = from_union([ContextChangedHostType, from_none], obj.get("hostType")) repository = from_union([from_str, from_none], obj.get("repository")) - return ContextClass(cwd, base_commit, branch, git_root, head_commit, host_type, repository) + return Context(cwd, base_commit, branch, git_root, head_commit, host_type, repository) def to_dict(self) -> dict: result: dict = {} @@ -510,14 +510,14 @@ def to_dict(self) -> dict: if self.head_commit is not None: result["headCommit"] = from_union([from_str, from_none], self.head_commit) if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + result["hostType"] = from_union([lambda x: to_enum(ContextChangedHostType, x), from_none], self.host_type) if self.repository is not None: result["repository"] = from_union([from_str, from_none], self.repository) return result @dataclass -class TokenDetail: +class AssistantUsageCopilotUsageTokenDetail: """Token usage detail for a single billing category""" batch_size: float @@ -533,13 +533,13 @@ class TokenDetail: """Token category (e.g., "input", "output")""" @staticmethod - def from_dict(obj: Any) -> 'TokenDetail': + def from_dict(obj: Any) -> 'AssistantUsageCopilotUsageTokenDetail': assert isinstance(obj, dict) batch_size = from_float(obj.get("batchSize")) cost_per_batch = from_float(obj.get("costPerBatch")) token_count = from_float(obj.get("tokenCount")) token_type = from_str(obj.get("tokenType")) - return TokenDetail(batch_size, cost_per_batch, token_count, token_type) + return AssistantUsageCopilotUsageTokenDetail(batch_size, cost_per_batch, token_count, token_type) def to_dict(self) -> dict: result: dict = {} @@ -551,31 +551,31 @@ def to_dict(self) -> dict: @dataclass -class CopilotUsage: +class AssistantUsageCopilotUsage: """Per-request cost and usage data from the CAPI copilot_usage response field""" - token_details: list[TokenDetail] + token_details: list[AssistantUsageCopilotUsageTokenDetail] """Itemized token usage breakdown""" total_nano_aiu: float """Total cost in nano-AIU (AI Units) for this request""" @staticmethod - def from_dict(obj: Any) -> 'CopilotUsage': + def from_dict(obj: Any) -> 'AssistantUsageCopilotUsage': assert isinstance(obj, dict) - token_details = from_list(TokenDetail.from_dict, obj.get("tokenDetails")) + token_details = from_list(AssistantUsageCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) total_nano_aiu = from_float(obj.get("totalNanoAiu")) - return CopilotUsage(token_details, total_nano_aiu) + return AssistantUsageCopilotUsage(token_details, total_nano_aiu) def to_dict(self) -> dict: result: dict = {} - result["tokenDetails"] = from_list(lambda x: to_class(TokenDetail, x), self.token_details) + result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageCopilotUsageTokenDetail, x), self.token_details) result["totalNanoAiu"] = to_float(self.total_nano_aiu) return result @dataclass -class ErrorClass: +class Error: """Error details when the tool execution failed Error details when the hook failed @@ -590,12 +590,12 @@ class ErrorClass: """Error stack trace, when available""" @staticmethod - def from_dict(obj: Any) -> 'ErrorClass': + def from_dict(obj: Any) -> 'Error': assert isinstance(obj, dict) message = from_str(obj.get("message")) code = from_union([from_str, from_none], obj.get("code")) stack = from_union([from_str, from_none], obj.get("stack")) - return ErrorClass(message, code, stack) + return Error(message, code, stack) def to_dict(self) -> dict: result: dict = {} @@ -607,14 +607,14 @@ def to_dict(self) -> dict: return result -class Source(Enum): +class ExtensionsLoadedExtensionSource(Enum): """Discovery source""" PROJECT = "project" USER = "user" -class ExtensionStatus(Enum): +class ExtensionsLoadedExtensionStatus(Enum): """Current status: running, disabled, failed, or starting""" DISABLED = "disabled" @@ -624,45 +624,45 @@ class ExtensionStatus(Enum): @dataclass -class Extension: +class ExtensionsLoadedExtension: id: str """Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper')""" name: str """Extension name (directory name)""" - source: Source + source: ExtensionsLoadedExtensionSource """Discovery source""" - status: ExtensionStatus + status: ExtensionsLoadedExtensionStatus """Current status: running, disabled, failed, or starting""" @staticmethod - def from_dict(obj: Any) -> 'Extension': + def from_dict(obj: Any) -> 'ExtensionsLoadedExtension': assert isinstance(obj, dict) id = from_str(obj.get("id")) name = from_str(obj.get("name")) - source = Source(obj.get("source")) - status = ExtensionStatus(obj.get("status")) - return Extension(id, name, source, status) + source = ExtensionsLoadedExtensionSource(obj.get("source")) + status = ExtensionsLoadedExtensionStatus(obj.get("status")) + return ExtensionsLoadedExtension(id, name, source, status) def to_dict(self) -> dict: result: dict = {} result["id"] = from_str(self.id) result["name"] = from_str(self.name) - result["source"] = to_enum(Source, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) + result["source"] = to_enum(ExtensionsLoadedExtensionSource, self.source) + result["status"] = to_enum(ExtensionsLoadedExtensionStatus, self.status) return result -class KindStatus(Enum): +class SystemNotificationAgentCompletedStatus(Enum): """Whether the agent completed successfully or failed""" COMPLETED = "completed" FAILED = "failed" -class KindType(Enum): +class SystemNotificationType(Enum): AGENT_COMPLETED = "agent_completed" AGENT_IDLE = "agent_idle" SHELL_COMPLETED = "shell_completed" @@ -670,10 +670,10 @@ class KindType(Enum): @dataclass -class KindClass: +class SystemNotification: """Structured metadata identifying what triggered this notification""" - type: KindType + type: SystemNotificationType agent_id: str | None = None """Unique identifier of the background agent""" @@ -688,7 +688,7 @@ class KindClass: prompt: str | None = None """The full prompt given to the background agent""" - status: KindStatus | None = None + status: SystemNotificationAgentCompletedStatus | None = None """Whether the agent completed successfully or failed""" exit_code: float | None = None @@ -701,21 +701,21 @@ class KindClass: """ @staticmethod - def from_dict(obj: Any) -> 'KindClass': + def from_dict(obj: Any) -> 'SystemNotification': assert isinstance(obj, dict) - type = KindType(obj.get("type")) + type = SystemNotificationType(obj.get("type")) agent_id = from_union([from_str, from_none], obj.get("agentId")) agent_type = from_union([from_str, from_none], obj.get("agentType")) description = from_union([from_str, from_none], obj.get("description")) prompt = from_union([from_str, from_none], obj.get("prompt")) - status = from_union([KindStatus, from_none], obj.get("status")) + status = from_union([SystemNotificationAgentCompletedStatus, from_none], obj.get("status")) exit_code = from_union([from_float, from_none], obj.get("exitCode")) shell_id = from_union([from_str, from_none], obj.get("shellId")) - return KindClass(type, agent_id, agent_type, description, prompt, status, exit_code, shell_id) + return SystemNotification(type, agent_id, agent_type, description, prompt, status, exit_code, shell_id) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(KindType, self.type) + result["type"] = to_enum(SystemNotificationType, self.type) if self.agent_id is not None: result["agentId"] = from_union([from_str, from_none], self.agent_id) if self.agent_type is not None: @@ -725,7 +725,7 @@ def to_dict(self) -> dict: if self.prompt is not None: result["prompt"] = from_union([from_str, from_none], self.prompt) if self.status is not None: - result["status"] = from_union([lambda x: to_enum(KindStatus, x), from_none], self.status) + result["status"] = from_union([lambda x: to_enum(SystemNotificationAgentCompletedStatus, x), from_none], self.status) if self.exit_code is not None: result["exitCode"] = from_union([to_float, from_none], self.exit_code) if self.shell_id is not None: @@ -734,7 +734,7 @@ def to_dict(self) -> dict: @dataclass -class Metadata: +class SystemMessageMetadata: """Metadata about the prompt template and its construction""" prompt_version: str | None = None @@ -744,11 +744,11 @@ class Metadata: """Template variables used when constructing the prompt""" @staticmethod - def from_dict(obj: Any) -> 'Metadata': + def from_dict(obj: Any) -> 'SystemMessageMetadata': assert isinstance(obj, dict) prompt_version = from_union([from_str, from_none], obj.get("promptVersion")) variables = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("variables")) - return Metadata(prompt_version, variables) + return SystemMessageMetadata(prompt_version, variables) def to_dict(self) -> dict: result: dict = {} @@ -759,7 +759,7 @@ def to_dict(self) -> dict: return result -class Mode(Enum): +class ElicitationRequestedMode(Enum): """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. """ @@ -768,7 +768,7 @@ class Mode(Enum): @dataclass -class Requests: +class ShutdownModelMetricRequests: """Request count and cost metrics""" cost: float @@ -778,11 +778,11 @@ class Requests: """Total number of API requests made to this model""" @staticmethod - def from_dict(obj: Any) -> 'Requests': + def from_dict(obj: Any) -> 'ShutdownModelMetricRequests': assert isinstance(obj, dict) cost = from_float(obj.get("cost")) count = from_float(obj.get("count")) - return Requests(cost, count) + return ShutdownModelMetricRequests(cost, count) def to_dict(self) -> dict: result: dict = {} @@ -792,7 +792,7 @@ def to_dict(self) -> dict: @dataclass -class Usage: +class ShutdownModelMetricUsage: """Token usage breakdown""" cache_read_tokens: float @@ -807,14 +807,18 @@ class Usage: output_tokens: float """Total output tokens produced across all requests to this model""" + reasoning_tokens: float | None = None + """Total reasoning tokens produced across all requests to this model""" + @staticmethod - def from_dict(obj: Any) -> 'Usage': + def from_dict(obj: Any) -> 'ShutdownModelMetricUsage': assert isinstance(obj, dict) cache_read_tokens = from_float(obj.get("cacheReadTokens")) cache_write_tokens = from_float(obj.get("cacheWriteTokens")) input_tokens = from_float(obj.get("inputTokens")) output_tokens = from_float(obj.get("outputTokens")) - return Usage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens) + reasoning_tokens = from_union([from_float, from_none], obj.get("reasoningTokens")) + return ShutdownModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) def to_dict(self) -> dict: result: dict = {} @@ -822,32 +826,34 @@ def to_dict(self) -> dict: result["cacheWriteTokens"] = to_float(self.cache_write_tokens) result["inputTokens"] = to_float(self.input_tokens) result["outputTokens"] = to_float(self.output_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([to_float, from_none], self.reasoning_tokens) return result @dataclass -class ModelMetric: - requests: Requests +class ShutdownModelMetric: + requests: ShutdownModelMetricRequests """Request count and cost metrics""" - usage: Usage + usage: ShutdownModelMetricUsage """Token usage breakdown""" @staticmethod - def from_dict(obj: Any) -> 'ModelMetric': + def from_dict(obj: Any) -> 'ShutdownModelMetric': assert isinstance(obj, dict) - requests = Requests.from_dict(obj.get("requests")) - usage = Usage.from_dict(obj.get("usage")) - return ModelMetric(requests, usage) + requests = ShutdownModelMetricRequests.from_dict(obj.get("requests")) + usage = ShutdownModelMetricUsage.from_dict(obj.get("usage")) + return ShutdownModelMetric(requests, usage) def to_dict(self) -> dict: result: dict = {} - result["requests"] = to_class(Requests, self.requests) - result["usage"] = to_class(Usage, self.usage) + result["requests"] = to_class(ShutdownModelMetricRequests, self.requests) + result["usage"] = to_class(ShutdownModelMetricUsage, self.usage) return result -class Operation(Enum): +class ChangedOperation(Enum): """The type of operation performed on the plan file Whether the file was newly created or updated @@ -857,7 +863,7 @@ class Operation(Enum): UPDATE = "update" -class PermissionRequestAction(Enum): +class PermissionRequestMemoryAction(Enum): """Whether this is a store or vote memory operation""" STORE = "store" @@ -865,7 +871,7 @@ class PermissionRequestAction(Enum): @dataclass -class PermissionRequestCommand: +class PermissionRequestShellCommand: identifier: str """Command identifier (e.g., executable name)""" @@ -873,11 +879,11 @@ class PermissionRequestCommand: """Whether this command is read-only (no side effects)""" @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestCommand': + def from_dict(obj: Any) -> 'PermissionRequestShellCommand': assert isinstance(obj, dict) identifier = from_str(obj.get("identifier")) read_only = from_bool(obj.get("readOnly")) - return PermissionRequestCommand(identifier, read_only) + return PermissionRequestShellCommand(identifier, read_only) def to_dict(self) -> dict: result: dict = {} @@ -886,14 +892,14 @@ def to_dict(self) -> dict: return result -class Direction(Enum): +class PermissionRequestMemoryDirection(Enum): """Vote direction (vote only)""" DOWNVOTE = "downvote" UPVOTE = "upvote" -class PermissionRequestKind(Enum): +class Kind(Enum): CUSTOM_TOOL = "custom-tool" HOOK = "hook" MCP = "mcp" @@ -905,15 +911,15 @@ class PermissionRequestKind(Enum): @dataclass -class PossibleURL: +class PermissionRequestShellPossibleURL: url: str """URL that may be accessed by the command""" @staticmethod - def from_dict(obj: Any) -> 'PossibleURL': + def from_dict(obj: Any) -> 'PermissionRequestShellPossibleURL': assert isinstance(obj, dict) url = from_str(obj.get("url")) - return PossibleURL(url) + return PermissionRequestShellPossibleURL(url) def to_dict(self) -> dict: result: dict = {} @@ -941,13 +947,13 @@ class PermissionRequest: Hook confirmation permission request """ - kind: PermissionRequestKind + kind: Kind """Permission kind discriminator""" can_offer_session_approval: bool | None = None """Whether the UI can offer session-wide approval for this command pattern""" - commands: list[PermissionRequestCommand] | None = None + commands: list[PermissionRequestShellCommand] | None = None """Parsed command identifiers found in the command text""" full_command_text: str | None = None @@ -968,7 +974,7 @@ class PermissionRequest: possible_paths: list[str] | None = None """File paths that may be read or written by the command""" - possible_urls: list[PossibleURL] | None = None + possible_urls: list[PermissionRequestShellPossibleURL] | None = None """URLs that may be accessed by the command""" tool_call_id: str | None = None @@ -1013,13 +1019,13 @@ class PermissionRequest: url: str | None = None """URL to be fetched""" - action: PermissionRequestAction | None = None + action: PermissionRequestMemoryAction | None = None """Whether this is a store or vote memory operation""" citations: str | None = None """Source references for the stored fact (store only)""" - direction: Direction | None = None + direction: PermissionRequestMemoryDirection | None = None """Vote direction (vote only)""" fact: str | None = None @@ -1043,14 +1049,14 @@ class PermissionRequest: @staticmethod def from_dict(obj: Any) -> 'PermissionRequest': assert isinstance(obj, dict) - kind = PermissionRequestKind(obj.get("kind")) + kind = Kind(obj.get("kind")) can_offer_session_approval = from_union([from_bool, from_none], obj.get("canOfferSessionApproval")) - commands = from_union([lambda x: from_list(PermissionRequestCommand.from_dict, x), from_none], obj.get("commands")) + commands = from_union([lambda x: from_list(PermissionRequestShellCommand.from_dict, x), from_none], obj.get("commands")) full_command_text = from_union([from_str, from_none], obj.get("fullCommandText")) has_write_file_redirection = from_union([from_bool, from_none], obj.get("hasWriteFileRedirection")) intention = from_union([from_str, from_none], obj.get("intention")) possible_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("possiblePaths")) - possible_urls = from_union([lambda x: from_list(PossibleURL.from_dict, x), from_none], obj.get("possibleUrls")) + possible_urls = from_union([lambda x: from_list(PermissionRequestShellPossibleURL.from_dict, x), from_none], obj.get("possibleUrls")) tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) warning = from_union([from_str, from_none], obj.get("warning")) diff = from_union([from_str, from_none], obj.get("diff")) @@ -1063,9 +1069,9 @@ def from_dict(obj: Any) -> 'PermissionRequest': tool_name = from_union([from_str, from_none], obj.get("toolName")) tool_title = from_union([from_str, from_none], obj.get("toolTitle")) url = from_union([from_str, from_none], obj.get("url")) - action = from_union([PermissionRequestAction, from_none], obj.get("action")) + action = from_union([PermissionRequestMemoryAction, from_none], obj.get("action")) citations = from_union([from_str, from_none], obj.get("citations")) - direction = from_union([Direction, from_none], obj.get("direction")) + direction = from_union([PermissionRequestMemoryDirection, from_none], obj.get("direction")) fact = from_union([from_str, from_none], obj.get("fact")) reason = from_union([from_str, from_none], obj.get("reason")) subject = from_union([from_str, from_none], obj.get("subject")) @@ -1076,11 +1082,11 @@ def from_dict(obj: Any) -> 'PermissionRequest': def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionRequestKind, self.kind) + result["kind"] = to_enum(Kind, self.kind) if self.can_offer_session_approval is not None: result["canOfferSessionApproval"] = from_union([from_bool, from_none], self.can_offer_session_approval) if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestCommand, x), x), from_none], self.commands) + result["commands"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x), from_none], self.commands) if self.full_command_text is not None: result["fullCommandText"] = from_union([from_str, from_none], self.full_command_text) if self.has_write_file_redirection is not None: @@ -1090,7 +1096,7 @@ def to_dict(self) -> dict: if self.possible_paths is not None: result["possiblePaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.possible_paths) if self.possible_urls is not None: - result["possibleUrls"] = from_union([lambda x: from_list(lambda x: to_class(PossibleURL, x), x), from_none], self.possible_urls) + result["possibleUrls"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x), from_none], self.possible_urls) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) if self.warning is not None: @@ -1116,11 +1122,11 @@ def to_dict(self) -> dict: if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) if self.action is not None: - result["action"] = from_union([lambda x: to_enum(PermissionRequestAction, x), from_none], self.action) + result["action"] = from_union([lambda x: to_enum(PermissionRequestMemoryAction, x), from_none], self.action) if self.citations is not None: result["citations"] = from_union([from_str, from_none], self.citations) if self.direction is not None: - result["direction"] = from_union([lambda x: to_enum(Direction, x), from_none], self.direction) + result["direction"] = from_union([lambda x: to_enum(PermissionRequestMemoryDirection, x), from_none], self.direction) if self.fact is not None: result["fact"] = from_union([from_str, from_none], self.fact) if self.reason is not None: @@ -1137,7 +1143,7 @@ def to_dict(self) -> dict: @dataclass -class QuotaSnapshot: +class AssistantUsageQuotaSnapshot: entitlement_requests: float """Total requests allowed by the entitlement""" @@ -1163,7 +1169,7 @@ class QuotaSnapshot: """Date when the quota resets""" @staticmethod - def from_dict(obj: Any) -> 'QuotaSnapshot': + def from_dict(obj: Any) -> 'AssistantUsageQuotaSnapshot': assert isinstance(obj, dict) entitlement_requests = from_float(obj.get("entitlementRequests")) is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) @@ -1173,7 +1179,7 @@ def from_dict(obj: Any) -> 'QuotaSnapshot': usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) used_requests = from_float(obj.get("usedRequests")) reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return QuotaSnapshot(entitlement_requests, is_unlimited_entitlement, overage, overage_allowed_with_exhausted_quota, remaining_percentage, usage_allowed_with_exhausted_quota, used_requests, reset_date) + return AssistantUsageQuotaSnapshot(entitlement_requests, is_unlimited_entitlement, overage, overage_allowed_with_exhausted_quota, remaining_percentage, usage_allowed_with_exhausted_quota, used_requests, reset_date) def to_dict(self) -> dict: result: dict = {} @@ -1190,7 +1196,7 @@ def to_dict(self) -> dict: @dataclass -class RepositoryClass: +class HandoffRepository: """Repository context for the handed-off session""" name: str @@ -1203,12 +1209,12 @@ class RepositoryClass: """Git branch name, if applicable""" @staticmethod - def from_dict(obj: Any) -> 'RepositoryClass': + def from_dict(obj: Any) -> 'HandoffRepository': assert isinstance(obj, dict) name = from_str(obj.get("name")) owner = from_str(obj.get("owner")) branch = from_union([from_str, from_none], obj.get("branch")) - return RepositoryClass(name, owner, branch) + return HandoffRepository(name, owner, branch) def to_dict(self) -> dict: result: dict = {} @@ -1224,7 +1230,7 @@ class RequestedSchemaType(Enum): @dataclass -class RequestedSchema: +class ElicitationRequestedSchema: """JSON Schema describing the form fields to present to the user (form mode only)""" properties: dict[str, Any] @@ -1237,12 +1243,12 @@ class RequestedSchema: """List of required field names""" @staticmethod - def from_dict(obj: Any) -> 'RequestedSchema': + def from_dict(obj: Any) -> 'ElicitationRequestedSchema': assert isinstance(obj, dict) properties = from_dict(lambda x: x, obj.get("properties")) type = RequestedSchemaType(obj.get("type")) required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) - return RequestedSchema(properties, type, required) + return ElicitationRequestedSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} @@ -1253,7 +1259,7 @@ def to_dict(self) -> dict: return result -class Theme(Enum): +class ToolExecutionCompleteContentResourceLinkIconTheme(Enum): """Theme variant this icon is intended for""" DARK = "dark" @@ -1261,7 +1267,7 @@ class Theme(Enum): @dataclass -class Icon: +class ToolExecutionCompleteContentResourceLinkIcon: """Icon image for a resource""" src: str @@ -1273,17 +1279,17 @@ class Icon: sizes: list[str] | None = None """Available icon sizes (e.g., ['16x16', '32x32'])""" - theme: Theme | None = None + theme: ToolExecutionCompleteContentResourceLinkIconTheme | None = None """Theme variant this icon is intended for""" @staticmethod - def from_dict(obj: Any) -> 'Icon': + def from_dict(obj: Any) -> 'ToolExecutionCompleteContentResourceLinkIcon': assert isinstance(obj, dict) src = from_str(obj.get("src")) mime_type = from_union([from_str, from_none], obj.get("mimeType")) sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) - theme = from_union([Theme, from_none], obj.get("theme")) - return Icon(src, mime_type, sizes, theme) + theme = from_union([ToolExecutionCompleteContentResourceLinkIconTheme, from_none], obj.get("theme")) + return ToolExecutionCompleteContentResourceLinkIcon(src, mime_type, sizes, theme) def to_dict(self) -> dict: result: dict = {} @@ -1293,12 +1299,12 @@ def to_dict(self) -> dict: if self.sizes is not None: result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) if self.theme is not None: - result["theme"] = from_union([lambda x: to_enum(Theme, x), from_none], self.theme) + result["theme"] = from_union([lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x), from_none], self.theme) return result @dataclass -class Resource: +class ToolExecutionCompleteContentResourceDetails: """The embedded resource contents, either text or base64-encoded binary""" uri: str @@ -1316,13 +1322,13 @@ class Resource: """Base64-encoded binary content of the resource""" @staticmethod - def from_dict(obj: Any) -> 'Resource': + def from_dict(obj: Any) -> 'ToolExecutionCompleteContentResourceDetails': assert isinstance(obj, dict) uri = from_str(obj.get("uri")) mime_type = from_union([from_str, from_none], obj.get("mimeType")) text = from_union([from_str, from_none], obj.get("text")) blob = from_union([from_str, from_none], obj.get("blob")) - return Resource(uri, mime_type, text, blob) + return ToolExecutionCompleteContentResourceDetails(uri, mime_type, text, blob) def to_dict(self) -> dict: result: dict = {} @@ -1336,7 +1342,7 @@ def to_dict(self) -> dict: return result -class ContentType(Enum): +class ToolExecutionCompleteContentType(Enum): AUDIO = "audio" IMAGE = "image" RESOURCE = "resource" @@ -1346,7 +1352,7 @@ class ContentType(Enum): @dataclass -class ContentElement: +class ToolExecutionCompleteContent: """A content block within a tool result, which may be text, terminal output, image, audio, or a resource @@ -1362,7 +1368,7 @@ class ContentElement: Embedded resource content block with inline text or binary data """ - type: ContentType + type: ToolExecutionCompleteContentType """Content block type discriminator""" text: str | None = None @@ -1391,7 +1397,7 @@ class ContentElement: description: str | None = None """Human-readable description of the resource""" - icons: list[Icon] | None = None + icons: list[ToolExecutionCompleteContentResourceLinkIcon] | None = None """Icons associated with this resource""" name: str | None = None @@ -1406,30 +1412,30 @@ class ContentElement: uri: str | None = None """URI identifying the resource""" - resource: Resource | None = None + resource: ToolExecutionCompleteContentResourceDetails | None = None """The embedded resource contents, either text or base64-encoded binary""" @staticmethod - def from_dict(obj: Any) -> 'ContentElement': + def from_dict(obj: Any) -> 'ToolExecutionCompleteContent': assert isinstance(obj, dict) - type = ContentType(obj.get("type")) + type = ToolExecutionCompleteContentType(obj.get("type")) text = from_union([from_str, from_none], obj.get("text")) cwd = from_union([from_str, from_none], obj.get("cwd")) exit_code = from_union([from_float, from_none], obj.get("exitCode")) data = from_union([from_str, from_none], obj.get("data")) mime_type = from_union([from_str, from_none], obj.get("mimeType")) description = from_union([from_str, from_none], obj.get("description")) - icons = from_union([lambda x: from_list(Icon.from_dict, x), from_none], obj.get("icons")) + icons = from_union([lambda x: from_list(ToolExecutionCompleteContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons")) name = from_union([from_str, from_none], obj.get("name")) size = from_union([from_float, from_none], obj.get("size")) title = from_union([from_str, from_none], obj.get("title")) uri = from_union([from_str, from_none], obj.get("uri")) - resource = from_union([Resource.from_dict, from_none], obj.get("resource")) - return ContentElement(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource) + resource = from_union([ToolExecutionCompleteContentResourceDetails.from_dict, from_none], obj.get("resource")) + return ToolExecutionCompleteContent(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(ContentType, self.type) + result["type"] = to_enum(ToolExecutionCompleteContentType, self.type) if self.text is not None: result["text"] = from_union([from_str, from_none], self.text) if self.cwd is not None: @@ -1443,7 +1449,7 @@ def to_dict(self) -> dict: if self.description is not None: result["description"] = from_union([from_str, from_none], self.description) if self.icons is not None: - result["icons"] = from_union([lambda x: from_list(lambda x: to_class(Icon, x), x), from_none], self.icons) + result["icons"] = from_union([lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContentResourceLinkIcon, x), x), from_none], self.icons) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) if self.size is not None: @@ -1453,11 +1459,11 @@ def to_dict(self) -> dict: if self.uri is not None: result["uri"] = from_union([from_str, from_none], self.uri) if self.resource is not None: - result["resource"] = from_union([lambda x: to_class(Resource, x), from_none], self.resource) + result["resource"] = from_union([lambda x: to_class(ToolExecutionCompleteContentResourceDetails, x), from_none], self.resource) return result -class ResultKind(Enum): +class PermissionCompletedKind(Enum): """The outcome of the permission request""" APPROVED = "approved" @@ -1478,7 +1484,7 @@ class Result: """Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency """ - contents: list[ContentElement] | None = None + contents: list[ToolExecutionCompleteContent] | None = None """Structured content blocks (text, images, audio, resources) returned by the tool in their native format """ @@ -1486,16 +1492,16 @@ class Result: """Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. """ - kind: ResultKind | None = None + kind: PermissionCompletedKind | None = None """The outcome of the permission request""" @staticmethod def from_dict(obj: Any) -> 'Result': assert isinstance(obj, dict) content = from_union([from_str, from_none], obj.get("content")) - contents = from_union([lambda x: from_list(ContentElement.from_dict, x), from_none], obj.get("contents")) + contents = from_union([lambda x: from_list(ToolExecutionCompleteContent.from_dict, x), from_none], obj.get("contents")) detailed_content = from_union([from_str, from_none], obj.get("detailedContent")) - kind = from_union([ResultKind, from_none], obj.get("kind")) + kind = from_union([PermissionCompletedKind, from_none], obj.get("kind")) return Result(content, contents, detailed_content, kind) def to_dict(self) -> dict: @@ -1503,22 +1509,22 @@ def to_dict(self) -> dict: if self.content is not None: result["content"] = from_union([from_str, from_none], self.content) if self.contents is not None: - result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ContentElement, x), x), from_none], self.contents) + result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContent, x), x), from_none], self.contents) if self.detailed_content is not None: result["detailedContent"] = from_union([from_str, from_none], self.detailed_content) if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(ResultKind, x), from_none], self.kind) + result["kind"] = from_union([lambda x: to_enum(PermissionCompletedKind, x), from_none], self.kind) return result -class Role(Enum): +class SystemMessageRole(Enum): """Message role: "system" for system prompts, "developer" for developer-injected instructions""" DEVELOPER = "developer" SYSTEM = "system" -class ServerStatus(Enum): +class MCPServerStatus(Enum): """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured New connection status: connected, failed, needs-auth, pending, disabled, or not_configured @@ -1532,11 +1538,11 @@ class ServerStatus(Enum): @dataclass -class Server: +class MCPServersLoadedServer: name: str """Server name (config key)""" - status: ServerStatus + status: MCPServerStatus """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" error: str | None = None @@ -1546,18 +1552,18 @@ class Server: """Configuration source: user, workspace, plugin, or builtin""" @staticmethod - def from_dict(obj: Any) -> 'Server': + def from_dict(obj: Any) -> 'MCPServersLoadedServer': assert isinstance(obj, dict) name = from_str(obj.get("name")) - status = ServerStatus(obj.get("status")) + status = MCPServerStatus(obj.get("status")) error = from_union([from_str, from_none], obj.get("error")) source = from_union([from_str, from_none], obj.get("source")) - return Server(name, status, error, source) + return MCPServersLoadedServer(name, status, error, source) def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) - result["status"] = to_enum(ServerStatus, self.status) + result["status"] = to_enum(MCPServerStatus, self.status) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.source is not None: @@ -1573,7 +1579,7 @@ class ShutdownType(Enum): @dataclass -class Skill: +class SkillsLoadedSkill: description: str """Description of what the skill does""" @@ -1593,7 +1599,7 @@ class Skill: """Absolute path to the skill file, if available""" @staticmethod - def from_dict(obj: Any) -> 'Skill': + def from_dict(obj: Any) -> 'SkillsLoadedSkill': assert isinstance(obj, dict) description = from_str(obj.get("description")) enabled = from_bool(obj.get("enabled")) @@ -1601,7 +1607,7 @@ def from_dict(obj: Any) -> 'Skill': source = from_str(obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) path = from_union([from_str, from_none], obj.get("path")) - return Skill(description, enabled, name, source, user_invocable, path) + return SkillsLoadedSkill(description, enabled, name, source, user_invocable, path) def to_dict(self) -> dict: result: dict = {} @@ -1615,7 +1621,7 @@ def to_dict(self) -> dict: return result -class SourceType(Enum): +class HandoffSourceType(Enum): """Origin type of the session being handed off""" LOCAL = "local" @@ -1623,7 +1629,7 @@ class SourceType(Enum): @dataclass -class StaticClientConfig: +class MCPOauthRequiredStaticClientConfig: """Static OAuth client configuration, if the server specifies one""" client_id: str @@ -1633,11 +1639,11 @@ class StaticClientConfig: """Whether this is a public OAuth client""" @staticmethod - def from_dict(obj: Any) -> 'StaticClientConfig': + def from_dict(obj: Any) -> 'MCPOauthRequiredStaticClientConfig': assert isinstance(obj, dict) client_id = from_str(obj.get("clientId")) public_client = from_union([from_bool, from_none], obj.get("publicClient")) - return StaticClientConfig(client_id, public_client) + return MCPOauthRequiredStaticClientConfig(client_id, public_client) def to_dict(self) -> dict: result: dict = {} @@ -1647,7 +1653,7 @@ def to_dict(self) -> dict: return result -class ToolRequestType(Enum): +class AssistantMessageToolRequestType(Enum): """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. """ @@ -1656,7 +1662,7 @@ class ToolRequestType(Enum): @dataclass -class ToolRequest: +class AssistantMessageToolRequest: """A tool invocation request from the assistant""" name: str @@ -1677,13 +1683,13 @@ class ToolRequest: tool_title: str | None = None """Human-readable display title for the tool""" - type: ToolRequestType | None = None + type: AssistantMessageToolRequestType | None = None """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. """ @staticmethod - def from_dict(obj: Any) -> 'ToolRequest': + def from_dict(obj: Any) -> 'AssistantMessageToolRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) tool_call_id = from_str(obj.get("toolCallId")) @@ -1691,8 +1697,8 @@ def from_dict(obj: Any) -> 'ToolRequest': intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) tool_title = from_union([from_str, from_none], obj.get("toolTitle")) - type = from_union([ToolRequestType, from_none], obj.get("type")) - return ToolRequest(name, tool_call_id, arguments, intention_summary, mcp_server_name, tool_title, type) + type = from_union([AssistantMessageToolRequestType, from_none], obj.get("type")) + return AssistantMessageToolRequest(name, tool_call_id, arguments, intention_summary, mcp_server_name, tool_title, type) def to_dict(self) -> dict: result: dict = {} @@ -1707,22 +1713,22 @@ def to_dict(self) -> dict: if self.tool_title is not None: result["toolTitle"] = from_union([from_str, from_none], self.tool_title) if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ToolRequestType, x), from_none], self.type) + result["type"] = from_union([lambda x: to_enum(AssistantMessageToolRequestType, x), from_none], self.type) return result @dataclass -class UI: +class CapabilitiesChangedUI: """UI capability changes""" elicitation: bool | None = None """Whether elicitation is now supported""" @staticmethod - def from_dict(obj: Any) -> 'UI': + def from_dict(obj: Any) -> 'CapabilitiesChangedUI': assert isinstance(obj, dict) elicitation = from_union([from_bool, from_none], obj.get("elicitation")) - return UI(elicitation) + return CapabilitiesChangedUI(elicitation) def to_dict(self) -> dict: result: dict = {} @@ -1878,7 +1884,7 @@ class Data: Whether the session was already in use by another client at resume time """ - context: ContextClass | str | None = None + context: Context | str | None = None """Working directory and git context at session start Updated working directory and git context at resume time @@ -1986,7 +1992,7 @@ class Data: previous_mode: str | None = None """Agent mode before the change (e.g., "interactive", "plan", "autopilot")""" - operation: Operation | None = None + operation: ChangedOperation | None = None """The type of operation performed on the plan file Whether the file was newly created or updated @@ -2006,13 +2012,13 @@ class Data: remote_session_id: str | None = None """Session ID of the remote session being handed off""" - repository: RepositoryClass | str | None = None + repository: HandoffRepository | str | None = None """Repository context for the handed-off session Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) """ - source_type: SourceType | None = None + source_type: HandoffSourceType | None = None """Origin type of the session being handed off""" summary: str | None = None @@ -2052,7 +2058,7 @@ class Data: up_to_event_id: str | None = None """Event ID that was rewound to; this event and all after it were removed""" - code_changes: CodeChanges | None = None + code_changes: ShutdownCodeChanges | None = None """Aggregate code change metrics for the session""" conversation_tokens: float | None = None @@ -2075,7 +2081,7 @@ class Data: error_reason: str | None = None """Error description when shutdownType is "error\"""" - model_metrics: dict[str, ModelMetric] | None = None + model_metrics: dict[str, ShutdownModelMetric] | None = None """Per-model usage breakdown, keyed by model identifier""" session_start_time: float | None = None @@ -2123,7 +2129,7 @@ class Data: head_commit: str | None = None """Head commit of current git branch at session start time""" - host_type: HostType | None = None + host_type: ContextChangedHostType | None = None """Hosting platform type of the repository (github or ado)""" is_initial: bool | None = None @@ -2138,10 +2144,10 @@ class Data: checkpoint_path: str | None = None """File path where the checkpoint was stored""" - compaction_tokens_used: CompactionTokensUsed | None = None + compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None """Token usage breakdown for the compaction LLM call""" - error: ErrorClass | str | None = None + error: Error | str | None = None """Error message if compaction failed Error details when the tool execution failed @@ -2229,10 +2235,10 @@ class Data: tokens_removed: float | None = None """Number of tokens removed during compaction""" - agent_mode: AgentMode | None = None + agent_mode: UserMessageAgentMode | None = None """The agent mode that was active when this message was sent""" - attachments: list[Attachment] | None = None + attachments: list[UserMessageAttachment] | None = None """Files, selections, or GitHub references attached to the message""" content: str | dict[str, float | bool | list[str] | str] | None = None @@ -2319,7 +2325,7 @@ class Data: reasoning_text: str | None = None """Readable reasoning text from the model's extended thinking""" - tool_requests: list[ToolRequest] | None = None + tool_requests: list[AssistantMessageToolRequest] | None = None """Tool invocations requested by the assistant in this message""" api_call_id: str | None = None @@ -2331,7 +2337,7 @@ class Data: cache_write_tokens: float | None = None """Number of tokens written to prompt cache""" - copilot_usage: CopilotUsage | None = None + copilot_usage: AssistantUsageCopilotUsage | None = None """Per-request cost and usage data from the CAPI copilot_usage response field""" cost: float | None = None @@ -2359,9 +2365,12 @@ class Data: Model used by the sub-agent (if any model calls succeeded before failure) """ - quota_snapshots: dict[str, QuotaSnapshot] | None = None + quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None """Per-quota resource usage snapshots, keyed by quota identifier""" + reasoning_tokens: float | None = None + """Number of output tokens used for reasoning (e.g., chain-of-thought)""" + ttft_ms: float | None = None """Time to first token in milliseconds. Only available for streaming requests""" @@ -2486,13 +2495,13 @@ class Data: output: Any = None """Output data produced by the hook""" - metadata: Metadata | None = None + metadata: SystemMessageMetadata | None = None """Metadata about the prompt template and its construction""" - role: Role | None = None + role: SystemMessageRole | None = None """Message role: "system" for system prompts, "developer" for developer-injected instructions""" - kind: KindClass | None = None + kind: SystemNotification | None = None """Structured metadata identifying what triggered this notification""" permission_request: PermissionRequest | None = None @@ -2520,14 +2529,14 @@ class Data: elicitation_source: str | None = None """The source that initiated the request (MCP server name, or absent for agent-initiated)""" - mode: Mode | None = None + mode: ElicitationRequestedMode | None = None """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. """ - requested_schema: RequestedSchema | None = None + requested_schema: ElicitationRequestedSchema | None = None """JSON Schema describing the form fields to present to the user (form mode only)""" - action: DataAction | None = None + action: ElicitationCompletedAction | None = None """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) """ @@ -2544,7 +2553,7 @@ class Data: server_url: str | None = None """URL of the MCP server that requires OAuth""" - static_client_config: StaticClientConfig | None = None + static_client_config: MCPOauthRequiredStaticClientConfig | None = None """Static OAuth client configuration, if the server specifies one""" traceparent: str | None = None @@ -2564,10 +2573,10 @@ class Data: command_name: str | None = None """Command name without leading /""" - commands: list[DataCommand] | None = None + commands: list[CommandsChangedCommand] | None = None """Current list of registered SDK commands""" - ui: UI | None = None + ui: CapabilitiesChangedUI | None = None """UI capability changes""" actions: list[str] | None = None @@ -2591,10 +2600,10 @@ class Data: selected_action: str | None = None """Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only')""" - skills: list[Skill] | None = None + skills: list[SkillsLoadedSkill] | None = None """Array of resolved skill metadata""" - agents: list[Agent] | None = None + agents: list[CustomAgentsUpdatedAgent] | None = None """Array of loaded custom agent metadata""" errors: list[str] | None = None @@ -2603,20 +2612,20 @@ class Data: warnings: list[str] | None = None """Non-fatal warnings from agent loading""" - servers: list[Server] | None = None + servers: list[MCPServersLoadedServer] | None = None """Array of MCP server status summaries""" - status: ServerStatus | None = None + status: MCPServerStatus | None = None """New connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - extensions: list[Extension] | None = None + extensions: list[ExtensionsLoadedExtension] | None = None """Array of discovered extensions and their status""" @staticmethod def from_dict(obj: Any) -> 'Data': assert isinstance(obj, dict) already_in_use = from_union([from_bool, from_none], obj.get("alreadyInUse")) - context = from_union([ContextClass.from_dict, from_str, from_none], obj.get("context")) + context = from_union([Context.from_dict, from_str, from_none], obj.get("context")) copilot_version = from_union([from_str, from_none], obj.get("copilotVersion")) producer = from_union([from_str, from_none], obj.get("producer")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) @@ -2642,13 +2651,13 @@ def from_dict(obj: Any) -> 'Data': previous_reasoning_effort = from_union([from_str, from_none], obj.get("previousReasoningEffort")) new_mode = from_union([from_str, from_none], obj.get("newMode")) previous_mode = from_union([from_str, from_none], obj.get("previousMode")) - operation = from_union([Operation, from_none], obj.get("operation")) + operation = from_union([ChangedOperation, from_none], obj.get("operation")) path = from_union([from_str, from_none], obj.get("path")) handoff_time = from_union([from_datetime, from_none], obj.get("handoffTime")) host = from_union([from_str, from_none], obj.get("host")) remote_session_id = from_union([from_str, from_none], obj.get("remoteSessionId")) - repository = from_union([RepositoryClass.from_dict, from_str, from_none], obj.get("repository")) - source_type = from_union([SourceType, from_none], obj.get("sourceType")) + repository = from_union([HandoffRepository.from_dict, from_str, from_none], obj.get("repository")) + source_type = from_union([HandoffSourceType, from_none], obj.get("sourceType")) summary = from_union([from_str, from_none], obj.get("summary")) messages_removed_during_truncation = from_union([from_float, from_none], obj.get("messagesRemovedDuringTruncation")) performed_by = from_union([from_str, from_none], obj.get("performedBy")) @@ -2660,12 +2669,12 @@ def from_dict(obj: Any) -> 'Data': tokens_removed_during_truncation = from_union([from_float, from_none], obj.get("tokensRemovedDuringTruncation")) events_removed = from_union([from_float, from_none], obj.get("eventsRemoved")) up_to_event_id = from_union([from_str, from_none], obj.get("upToEventId")) - code_changes = from_union([CodeChanges.from_dict, from_none], obj.get("codeChanges")) + code_changes = from_union([ShutdownCodeChanges.from_dict, from_none], obj.get("codeChanges")) conversation_tokens = from_union([from_float, from_none], obj.get("conversationTokens")) current_model = from_union([from_str, from_none], obj.get("currentModel")) current_tokens = from_union([from_float, from_none], obj.get("currentTokens")) error_reason = from_union([from_str, from_none], obj.get("errorReason")) - model_metrics = from_union([lambda x: from_dict(ModelMetric.from_dict, x), from_none], obj.get("modelMetrics")) + model_metrics = from_union([lambda x: from_dict(ShutdownModelMetric.from_dict, x), from_none], obj.get("modelMetrics")) session_start_time = from_union([from_float, from_none], obj.get("sessionStartTime")) shutdown_type = from_union([ShutdownType, from_none], obj.get("shutdownType")) system_tokens = from_union([from_float, from_none], obj.get("systemTokens")) @@ -2677,13 +2686,13 @@ def from_dict(obj: Any) -> 'Data': cwd = from_union([from_str, from_none], obj.get("cwd")) git_root = from_union([from_str, from_none], obj.get("gitRoot")) head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([HostType, from_none], obj.get("hostType")) + host_type = from_union([ContextChangedHostType, from_none], obj.get("hostType")) is_initial = from_union([from_bool, from_none], obj.get("isInitial")) messages_length = from_union([from_float, from_none], obj.get("messagesLength")) checkpoint_number = from_union([from_float, from_none], obj.get("checkpointNumber")) checkpoint_path = from_union([from_str, from_none], obj.get("checkpointPath")) - compaction_tokens_used = from_union([CompactionTokensUsed.from_dict, from_none], obj.get("compactionTokensUsed")) - error = from_union([ErrorClass.from_dict, from_str, from_none], obj.get("error")) + compaction_tokens_used = from_union([CompactionCompleteCompactionTokensUsed.from_dict, from_none], obj.get("compactionTokensUsed")) + error = from_union([Error.from_dict, from_str, from_none], obj.get("error")) messages_removed = from_union([from_float, from_none], obj.get("messagesRemoved")) post_compaction_tokens = from_union([from_float, from_none], obj.get("postCompactionTokens")) pre_compaction_messages_length = from_union([from_float, from_none], obj.get("preCompactionMessagesLength")) @@ -2692,8 +2701,8 @@ def from_dict(obj: Any) -> 'Data': success = from_union([from_bool, from_none], obj.get("success")) summary_content = from_union([from_str, from_none], obj.get("summaryContent")) tokens_removed = from_union([from_float, from_none], obj.get("tokensRemoved")) - agent_mode = from_union([AgentMode, from_none], obj.get("agentMode")) - attachments = from_union([lambda x: from_list(Attachment.from_dict, x), from_none], obj.get("attachments")) + agent_mode = from_union([UserMessageAgentMode, from_none], obj.get("agentMode")) + attachments = from_union([lambda x: from_list(UserMessageAttachment.from_dict, x), from_none], obj.get("attachments")) content = from_union([from_str, lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) interaction_id = from_union([from_str, from_none], obj.get("interactionId")) source = from_union([from_str, from_none], obj.get("source")) @@ -2710,18 +2719,19 @@ def from_dict(obj: Any) -> 'Data': phase = from_union([from_str, from_none], obj.get("phase")) reasoning_opaque = from_union([from_str, from_none], obj.get("reasoningOpaque")) reasoning_text = from_union([from_str, from_none], obj.get("reasoningText")) - tool_requests = from_union([lambda x: from_list(ToolRequest.from_dict, x), from_none], obj.get("toolRequests")) + tool_requests = from_union([lambda x: from_list(AssistantMessageToolRequest.from_dict, x), from_none], obj.get("toolRequests")) api_call_id = from_union([from_str, from_none], obj.get("apiCallId")) cache_read_tokens = from_union([from_float, from_none], obj.get("cacheReadTokens")) cache_write_tokens = from_union([from_float, from_none], obj.get("cacheWriteTokens")) - copilot_usage = from_union([CopilotUsage.from_dict, from_none], obj.get("copilotUsage")) + copilot_usage = from_union([AssistantUsageCopilotUsage.from_dict, from_none], obj.get("copilotUsage")) cost = from_union([from_float, from_none], obj.get("cost")) duration = from_union([from_float, from_none], obj.get("duration")) initiator = from_union([from_str, from_none], obj.get("initiator")) input_tokens = from_union([from_float, from_none], obj.get("inputTokens")) inter_token_latency_ms = from_union([from_float, from_none], obj.get("interTokenLatencyMs")) model = from_union([from_str, from_none], obj.get("model")) - quota_snapshots = from_union([lambda x: from_dict(QuotaSnapshot.from_dict, x), from_none], obj.get("quotaSnapshots")) + quota_snapshots = from_union([lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x), from_none], obj.get("quotaSnapshots")) + reasoning_tokens = from_union([from_float, from_none], obj.get("reasoningTokens")) ttft_ms = from_union([from_float, from_none], obj.get("ttftMs")) reason = from_union([from_str, from_none], obj.get("reason")) arguments = obj.get("arguments") @@ -2750,9 +2760,9 @@ def from_dict(obj: Any) -> 'Data': hook_type = from_union([from_str, from_none], obj.get("hookType")) input = obj.get("input") output = obj.get("output") - metadata = from_union([Metadata.from_dict, from_none], obj.get("metadata")) - role = from_union([Role, from_none], obj.get("role")) - kind = from_union([KindClass.from_dict, from_none], obj.get("kind")) + metadata = from_union([SystemMessageMetadata.from_dict, from_none], obj.get("metadata")) + role = from_union([SystemMessageRole, from_none], obj.get("role")) + kind = from_union([SystemNotification.from_dict, from_none], obj.get("kind")) permission_request = from_union([PermissionRequest.from_dict, from_none], obj.get("permissionRequest")) resolved_by_hook = from_union([from_bool, from_none], obj.get("resolvedByHook")) allow_freeform = from_union([from_bool, from_none], obj.get("allowFreeform")) @@ -2761,20 +2771,20 @@ def from_dict(obj: Any) -> 'Data': answer = from_union([from_str, from_none], obj.get("answer")) was_freeform = from_union([from_bool, from_none], obj.get("wasFreeform")) elicitation_source = from_union([from_str, from_none], obj.get("elicitationSource")) - mode = from_union([Mode, from_none], obj.get("mode")) - requested_schema = from_union([RequestedSchema.from_dict, from_none], obj.get("requestedSchema")) - action = from_union([DataAction, from_none], obj.get("action")) + mode = from_union([ElicitationRequestedMode, from_none], obj.get("mode")) + requested_schema = from_union([ElicitationRequestedSchema.from_dict, from_none], obj.get("requestedSchema")) + action = from_union([ElicitationCompletedAction, from_none], obj.get("action")) mcp_request_id = from_union([from_float, from_str, from_none], obj.get("mcpRequestId")) server_name = from_union([from_str, from_none], obj.get("serverName")) server_url = from_union([from_str, from_none], obj.get("serverUrl")) - static_client_config = from_union([StaticClientConfig.from_dict, from_none], obj.get("staticClientConfig")) + static_client_config = from_union([MCPOauthRequiredStaticClientConfig.from_dict, from_none], obj.get("staticClientConfig")) traceparent = from_union([from_str, from_none], obj.get("traceparent")) tracestate = from_union([from_str, from_none], obj.get("tracestate")) command = from_union([from_str, from_none], obj.get("command")) args = from_union([from_str, from_none], obj.get("args")) command_name = from_union([from_str, from_none], obj.get("commandName")) - commands = from_union([lambda x: from_list(DataCommand.from_dict, x), from_none], obj.get("commands")) - ui = from_union([UI.from_dict, from_none], obj.get("ui")) + commands = from_union([lambda x: from_list(CommandsChangedCommand.from_dict, x), from_none], obj.get("commands")) + ui = from_union([CapabilitiesChangedUI.from_dict, from_none], obj.get("ui")) actions = from_union([lambda x: from_list(from_str, x), from_none], obj.get("actions")) plan_content = from_union([from_str, from_none], obj.get("planContent")) recommended_action = from_union([from_str, from_none], obj.get("recommendedAction")) @@ -2782,21 +2792,21 @@ def from_dict(obj: Any) -> 'Data': auto_approve_edits = from_union([from_bool, from_none], obj.get("autoApproveEdits")) feedback = from_union([from_str, from_none], obj.get("feedback")) selected_action = from_union([from_str, from_none], obj.get("selectedAction")) - skills = from_union([lambda x: from_list(Skill.from_dict, x), from_none], obj.get("skills")) - agents = from_union([lambda x: from_list(Agent.from_dict, x), from_none], obj.get("agents")) + skills = from_union([lambda x: from_list(SkillsLoadedSkill.from_dict, x), from_none], obj.get("skills")) + agents = from_union([lambda x: from_list(CustomAgentsUpdatedAgent.from_dict, x), from_none], obj.get("agents")) errors = from_union([lambda x: from_list(from_str, x), from_none], obj.get("errors")) warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("warnings")) - servers = from_union([lambda x: from_list(Server.from_dict, x), from_none], obj.get("servers")) - status = from_union([ServerStatus, from_none], obj.get("status")) - extensions = from_union([lambda x: from_list(Extension.from_dict, x), from_none], obj.get("extensions")) - return Data(already_in_use, context, copilot_version, producer, reasoning_effort, remote_steerable, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, aborted, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, inter_token_latency_ms, model, quota_snapshots, ttft_ms, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, resolved_by_hook, allow_freeform, choices, question, answer, was_freeform, elicitation_source, mode, requested_schema, action, mcp_request_id, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, ui, actions, plan_content, recommended_action, approved, auto_approve_edits, feedback, selected_action, skills, agents, errors, warnings, servers, status, extensions) + servers = from_union([lambda x: from_list(MCPServersLoadedServer.from_dict, x), from_none], obj.get("servers")) + status = from_union([MCPServerStatus, from_none], obj.get("status")) + extensions = from_union([lambda x: from_list(ExtensionsLoadedExtension.from_dict, x), from_none], obj.get("extensions")) + return Data(already_in_use, context, copilot_version, producer, reasoning_effort, remote_steerable, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, aborted, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, inter_token_latency_ms, model, quota_snapshots, reasoning_tokens, ttft_ms, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, resolved_by_hook, allow_freeform, choices, question, answer, was_freeform, elicitation_source, mode, requested_schema, action, mcp_request_id, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, ui, actions, plan_content, recommended_action, approved, auto_approve_edits, feedback, selected_action, skills, agents, errors, warnings, servers, status, extensions) def to_dict(self) -> dict: result: dict = {} if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_bool, from_none], self.already_in_use) if self.context is not None: - result["context"] = from_union([lambda x: to_class(ContextClass, x), from_str, from_none], self.context) + result["context"] = from_union([lambda x: to_class(Context, x), from_str, from_none], self.context) if self.copilot_version is not None: result["copilotVersion"] = from_union([from_str, from_none], self.copilot_version) if self.producer is not None: @@ -2848,7 +2858,7 @@ def to_dict(self) -> dict: if self.previous_mode is not None: result["previousMode"] = from_union([from_str, from_none], self.previous_mode) if self.operation is not None: - result["operation"] = from_union([lambda x: to_enum(Operation, x), from_none], self.operation) + result["operation"] = from_union([lambda x: to_enum(ChangedOperation, x), from_none], self.operation) if self.path is not None: result["path"] = from_union([from_str, from_none], self.path) if self.handoff_time is not None: @@ -2858,9 +2868,9 @@ def to_dict(self) -> dict: if self.remote_session_id is not None: result["remoteSessionId"] = from_union([from_str, from_none], self.remote_session_id) if self.repository is not None: - result["repository"] = from_union([lambda x: to_class(RepositoryClass, x), from_str, from_none], self.repository) + result["repository"] = from_union([lambda x: to_class(HandoffRepository, x), from_str, from_none], self.repository) if self.source_type is not None: - result["sourceType"] = from_union([lambda x: to_enum(SourceType, x), from_none], self.source_type) + result["sourceType"] = from_union([lambda x: to_enum(HandoffSourceType, x), from_none], self.source_type) if self.summary is not None: result["summary"] = from_union([from_str, from_none], self.summary) if self.messages_removed_during_truncation is not None: @@ -2884,7 +2894,7 @@ def to_dict(self) -> dict: if self.up_to_event_id is not None: result["upToEventId"] = from_union([from_str, from_none], self.up_to_event_id) if self.code_changes is not None: - result["codeChanges"] = from_union([lambda x: to_class(CodeChanges, x), from_none], self.code_changes) + result["codeChanges"] = from_union([lambda x: to_class(ShutdownCodeChanges, x), from_none], self.code_changes) if self.conversation_tokens is not None: result["conversationTokens"] = from_union([to_float, from_none], self.conversation_tokens) if self.current_model is not None: @@ -2894,7 +2904,7 @@ def to_dict(self) -> dict: if self.error_reason is not None: result["errorReason"] = from_union([from_str, from_none], self.error_reason) if self.model_metrics is not None: - result["modelMetrics"] = from_union([lambda x: from_dict(lambda x: to_class(ModelMetric, x), x), from_none], self.model_metrics) + result["modelMetrics"] = from_union([lambda x: from_dict(lambda x: to_class(ShutdownModelMetric, x), x), from_none], self.model_metrics) if self.session_start_time is not None: result["sessionStartTime"] = from_union([to_float, from_none], self.session_start_time) if self.shutdown_type is not None: @@ -2918,7 +2928,7 @@ def to_dict(self) -> dict: if self.head_commit is not None: result["headCommit"] = from_union([from_str, from_none], self.head_commit) if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + result["hostType"] = from_union([lambda x: to_enum(ContextChangedHostType, x), from_none], self.host_type) if self.is_initial is not None: result["isInitial"] = from_union([from_bool, from_none], self.is_initial) if self.messages_length is not None: @@ -2928,9 +2938,9 @@ def to_dict(self) -> dict: if self.checkpoint_path is not None: result["checkpointPath"] = from_union([from_str, from_none], self.checkpoint_path) if self.compaction_tokens_used is not None: - result["compactionTokensUsed"] = from_union([lambda x: to_class(CompactionTokensUsed, x), from_none], self.compaction_tokens_used) + result["compactionTokensUsed"] = from_union([lambda x: to_class(CompactionCompleteCompactionTokensUsed, x), from_none], self.compaction_tokens_used) if self.error is not None: - result["error"] = from_union([lambda x: to_class(ErrorClass, x), from_str, from_none], self.error) + result["error"] = from_union([lambda x: to_class(Error, x), from_str, from_none], self.error) if self.messages_removed is not None: result["messagesRemoved"] = from_union([to_float, from_none], self.messages_removed) if self.post_compaction_tokens is not None: @@ -2948,9 +2958,9 @@ def to_dict(self) -> dict: if self.tokens_removed is not None: result["tokensRemoved"] = from_union([to_float, from_none], self.tokens_removed) if self.agent_mode is not None: - result["agentMode"] = from_union([lambda x: to_enum(AgentMode, x), from_none], self.agent_mode) + result["agentMode"] = from_union([lambda x: to_enum(UserMessageAgentMode, x), from_none], self.agent_mode) if self.attachments is not None: - result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(Attachment, x), x), from_none], self.attachments) + result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x), from_none], self.attachments) if self.content is not None: result["content"] = from_union([from_str, lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) if self.interaction_id is not None: @@ -2984,7 +2994,7 @@ def to_dict(self) -> dict: if self.reasoning_text is not None: result["reasoningText"] = from_union([from_str, from_none], self.reasoning_text) if self.tool_requests is not None: - result["toolRequests"] = from_union([lambda x: from_list(lambda x: to_class(ToolRequest, x), x), from_none], self.tool_requests) + result["toolRequests"] = from_union([lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x), from_none], self.tool_requests) if self.api_call_id is not None: result["apiCallId"] = from_union([from_str, from_none], self.api_call_id) if self.cache_read_tokens is not None: @@ -2992,7 +3002,7 @@ def to_dict(self) -> dict: if self.cache_write_tokens is not None: result["cacheWriteTokens"] = from_union([to_float, from_none], self.cache_write_tokens) if self.copilot_usage is not None: - result["copilotUsage"] = from_union([lambda x: to_class(CopilotUsage, x), from_none], self.copilot_usage) + result["copilotUsage"] = from_union([lambda x: to_class(AssistantUsageCopilotUsage, x), from_none], self.copilot_usage) if self.cost is not None: result["cost"] = from_union([to_float, from_none], self.cost) if self.duration is not None: @@ -3006,7 +3016,9 @@ def to_dict(self) -> dict: if self.model is not None: result["model"] = from_union([from_str, from_none], self.model) if self.quota_snapshots is not None: - result["quotaSnapshots"] = from_union([lambda x: from_dict(lambda x: to_class(QuotaSnapshot, x), x), from_none], self.quota_snapshots) + result["quotaSnapshots"] = from_union([lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x), from_none], self.quota_snapshots) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([to_float, from_none], self.reasoning_tokens) if self.ttft_ms is not None: result["ttftMs"] = from_union([to_float, from_none], self.ttft_ms) if self.reason is not None: @@ -3064,11 +3076,11 @@ def to_dict(self) -> dict: if self.output is not None: result["output"] = self.output if self.metadata is not None: - result["metadata"] = from_union([lambda x: to_class(Metadata, x), from_none], self.metadata) + result["metadata"] = from_union([lambda x: to_class(SystemMessageMetadata, x), from_none], self.metadata) if self.role is not None: - result["role"] = from_union([lambda x: to_enum(Role, x), from_none], self.role) + result["role"] = from_union([lambda x: to_enum(SystemMessageRole, x), from_none], self.role) if self.kind is not None: - result["kind"] = from_union([lambda x: to_class(KindClass, x), from_none], self.kind) + result["kind"] = from_union([lambda x: to_class(SystemNotification, x), from_none], self.kind) if self.permission_request is not None: result["permissionRequest"] = from_union([lambda x: to_class(PermissionRequest, x), from_none], self.permission_request) if self.resolved_by_hook is not None: @@ -3086,11 +3098,11 @@ def to_dict(self) -> dict: if self.elicitation_source is not None: result["elicitationSource"] = from_union([from_str, from_none], self.elicitation_source) if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode) + result["mode"] = from_union([lambda x: to_enum(ElicitationRequestedMode, x), from_none], self.mode) if self.requested_schema is not None: - result["requestedSchema"] = from_union([lambda x: to_class(RequestedSchema, x), from_none], self.requested_schema) + result["requestedSchema"] = from_union([lambda x: to_class(ElicitationRequestedSchema, x), from_none], self.requested_schema) if self.action is not None: - result["action"] = from_union([lambda x: to_enum(DataAction, x), from_none], self.action) + result["action"] = from_union([lambda x: to_enum(ElicitationCompletedAction, x), from_none], self.action) if self.mcp_request_id is not None: result["mcpRequestId"] = from_union([to_float, from_str, from_none], self.mcp_request_id) if self.server_name is not None: @@ -3098,7 +3110,7 @@ def to_dict(self) -> dict: if self.server_url is not None: result["serverUrl"] = from_union([from_str, from_none], self.server_url) if self.static_client_config is not None: - result["staticClientConfig"] = from_union([lambda x: to_class(StaticClientConfig, x), from_none], self.static_client_config) + result["staticClientConfig"] = from_union([lambda x: to_class(MCPOauthRequiredStaticClientConfig, x), from_none], self.static_client_config) if self.traceparent is not None: result["traceparent"] = from_union([from_str, from_none], self.traceparent) if self.tracestate is not None: @@ -3110,9 +3122,9 @@ def to_dict(self) -> dict: if self.command_name is not None: result["commandName"] = from_union([from_str, from_none], self.command_name) if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(DataCommand, x), x), from_none], self.commands) + result["commands"] = from_union([lambda x: from_list(lambda x: to_class(CommandsChangedCommand, x), x), from_none], self.commands) if self.ui is not None: - result["ui"] = from_union([lambda x: to_class(UI, x), from_none], self.ui) + result["ui"] = from_union([lambda x: to_class(CapabilitiesChangedUI, x), from_none], self.ui) if self.actions is not None: result["actions"] = from_union([lambda x: from_list(from_str, x), from_none], self.actions) if self.plan_content is not None: @@ -3128,19 +3140,19 @@ def to_dict(self) -> dict: if self.selected_action is not None: result["selectedAction"] = from_union([from_str, from_none], self.selected_action) if self.skills is not None: - result["skills"] = from_union([lambda x: from_list(lambda x: to_class(Skill, x), x), from_none], self.skills) + result["skills"] = from_union([lambda x: from_list(lambda x: to_class(SkillsLoadedSkill, x), x), from_none], self.skills) if self.agents is not None: - result["agents"] = from_union([lambda x: from_list(lambda x: to_class(Agent, x), x), from_none], self.agents) + result["agents"] = from_union([lambda x: from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), x), from_none], self.agents) if self.errors is not None: result["errors"] = from_union([lambda x: from_list(from_str, x), from_none], self.errors) if self.warnings is not None: result["warnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.warnings) if self.servers is not None: - result["servers"] = from_union([lambda x: from_list(lambda x: to_class(Server, x), x), from_none], self.servers) + result["servers"] = from_union([lambda x: from_list(lambda x: to_class(MCPServersLoadedServer, x), x), from_none], self.servers) if self.status is not None: - result["status"] = from_union([lambda x: to_enum(ServerStatus, x), from_none], self.status) + result["status"] = from_union([lambda x: to_enum(MCPServerStatus, x), from_none], self.status) if self.extensions is not None: - result["extensions"] = from_union([lambda x: from_list(lambda x: to_class(Extension, x), x), from_none], self.extensions) + result["extensions"] = from_union([lambda x: from_list(lambda x: to_class(ExtensionsLoadedExtension, x), x), from_none], self.extensions) return result diff --git a/python/copilot/session.py b/python/copilot/session.py index 45e8826b7..5edbe924b 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -22,30 +22,28 @@ from ._jsonrpc import JsonRpcError, ProcessExitedError from ._telemetry import get_trace_context, trace_context from .generated.rpc import ( - Action, ClientSessionApiHandlers, + CommandsHandlePendingCommandRequest, Kind, - Level, - Property, - PropertyType, - RequestedSchema, + LogRequest, + ModelSwitchToRequest, + PermissionDecision, + PermissionDecisionRequest, RequestedSchemaType, - ResultResult, - SessionCommandsHandlePendingCommandParams, SessionFsHandler, - SessionLogParams, - SessionModelSwitchToParams, - SessionPermissionsHandlePendingPermissionRequestParams, - SessionPermissionsHandlePendingPermissionRequestParamsResult, + SessionLogLevel, SessionRpc, - SessionToolsHandlePendingToolCallParams, - SessionUIElicitationParams, - SessionUIHandlePendingElicitationParams, - SessionUIHandlePendingElicitationParamsResult, -) -from .generated.rpc import ( - ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride, + ToolCallResult, + ToolsHandlePendingToolCallRequest, + UIElicitationRequest, + UIElicitationResponse, + UIElicitationResponseAction, + UIElicitationSchema, + UIElicitationSchemaProperty, + UIElicitationSchemaPropertyNumberType, + UIHandlePendingElicitationRequest, ) +from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride from .generated.session_events import ( PermissionRequest, SessionEvent, @@ -439,12 +437,12 @@ async def elicitation(self, params: ElicitationParams) -> ElicitationResult: """ self._session._assert_elicitation() rpc_result = await self._session.rpc.ui.elicitation( - SessionUIElicitationParams( + UIElicitationRequest( message=params["message"], - requested_schema=RequestedSchema.from_dict(params["requestedSchema"]), + requested_schema=UIElicitationSchema.from_dict(params["requestedSchema"]), ) ) - result: ElicitationResult = {"action": rpc_result.action.value} # type: ignore[typeddict-item] + result: ElicitationResult = {"action": rpc_result.action.value} if rpc_result.content is not None: result["content"] = rpc_result.content return result @@ -463,19 +461,22 @@ async def confirm(self, message: str) -> bool: """ self._session._assert_elicitation() rpc_result = await self._session.rpc.ui.elicitation( - SessionUIElicitationParams( + UIElicitationRequest( message=message, - requested_schema=RequestedSchema( + requested_schema=UIElicitationSchema( type=RequestedSchemaType.OBJECT, properties={ - "confirmed": Property(type=PropertyType.BOOLEAN, default=True), + "confirmed": UIElicitationSchemaProperty( + type=UIElicitationSchemaPropertyNumberType.BOOLEAN, + default=True, + ), }, required=["confirmed"], ), ) ) return ( - rpc_result.action == Action.ACCEPT + rpc_result.action == UIElicitationResponseAction.ACCEPT and rpc_result.content is not None and rpc_result.content.get("confirmed") is True ) @@ -495,19 +496,22 @@ async def select(self, message: str, options: list[str]) -> str | None: """ self._session._assert_elicitation() rpc_result = await self._session.rpc.ui.elicitation( - SessionUIElicitationParams( + UIElicitationRequest( message=message, - requested_schema=RequestedSchema( + requested_schema=UIElicitationSchema( type=RequestedSchemaType.OBJECT, properties={ - "selection": Property(type=PropertyType.STRING, enum=options), + "selection": UIElicitationSchemaProperty( + type=UIElicitationSchemaPropertyNumberType.STRING, + enum=options, + ), }, required=["selection"], ), ) ) if ( - rpc_result.action == Action.ACCEPT + rpc_result.action == UIElicitationResponseAction.ACCEPT and rpc_result.content is not None and rpc_result.content.get("selection") is not None ): @@ -535,9 +539,9 @@ async def input(self, message: str, options: InputOptions | None = None) -> str field[key] = options[key] rpc_result = await self._session.rpc.ui.elicitation( - SessionUIElicitationParams( + UIElicitationRequest( message=message, - requested_schema=RequestedSchema.from_dict( + requested_schema=UIElicitationSchema.from_dict( { "type": "object", "properties": {"value": field}, @@ -547,7 +551,7 @@ async def input(self, message: str, options: InputOptions | None = None) -> str ) ) if ( - rpc_result.action == Action.ACCEPT + rpc_result.action == UIElicitationResponseAction.ACCEPT and rpc_result.content is not None and rpc_result.content.get("value") is not None ): @@ -1345,19 +1349,19 @@ async def _execute_tool_and_respond( # failures send the full structured result to preserve metadata. if tool_result._from_exception: await self.rpc.tools.handle_pending_tool_call( - SessionToolsHandlePendingToolCallParams( + ToolsHandlePendingToolCallRequest( request_id=request_id, error=tool_result.error, ) ) else: await self.rpc.tools.handle_pending_tool_call( - SessionToolsHandlePendingToolCallParams( + ToolsHandlePendingToolCallRequest( request_id=request_id, - result=ResultResult( + result=ToolCallResult( text_result_for_llm=tool_result.text_result_for_llm, - result_type=tool_result.result_type, error=tool_result.error, + result_type=tool_result.result_type, tool_telemetry=tool_result.tool_telemetry, ), ) @@ -1365,7 +1369,7 @@ async def _execute_tool_and_respond( except Exception as exc: try: await self.rpc.tools.handle_pending_tool_call( - SessionToolsHandlePendingToolCallParams( + ToolsHandlePendingToolCallRequest( request_id=request_id, error=str(exc), ) @@ -1389,7 +1393,7 @@ async def _execute_permission_and_respond( if result.kind == "no-result": return - perm_result = SessionPermissionsHandlePendingPermissionRequestParamsResult( + perm_result = PermissionDecision( kind=Kind(result.kind), rules=result.rules, feedback=result.feedback, @@ -1398,7 +1402,7 @@ async def _execute_permission_and_respond( ) await self.rpc.permissions.handle_pending_permission_request( - SessionPermissionsHandlePendingPermissionRequestParams( + PermissionDecisionRequest( request_id=request_id, result=perm_result, ) @@ -1406,9 +1410,9 @@ async def _execute_permission_and_respond( except Exception: try: await self.rpc.permissions.handle_pending_permission_request( - SessionPermissionsHandlePendingPermissionRequestParams( + PermissionDecisionRequest( request_id=request_id, - result=SessionPermissionsHandlePendingPermissionRequestParamsResult( + result=PermissionDecision( kind=Kind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, ), ) @@ -1430,7 +1434,7 @@ async def _execute_command_and_respond( if not handler: try: await self.rpc.commands.handle_pending_command( - SessionCommandsHandlePendingCommandParams( + CommandsHandlePendingCommandRequest( request_id=request_id, error=f"Unknown command: {command_name}", ) @@ -1450,13 +1454,13 @@ async def _execute_command_and_respond( if inspect.isawaitable(result): await result await self.rpc.commands.handle_pending_command( - SessionCommandsHandlePendingCommandParams(request_id=request_id) + CommandsHandlePendingCommandRequest(request_id=request_id) ) except Exception as exc: message = str(exc) try: await self.rpc.commands.handle_pending_command( - SessionCommandsHandlePendingCommandParams( + CommandsHandlePendingCommandRequest( request_id=request_id, error=message, ) @@ -1484,12 +1488,12 @@ async def _handle_elicitation_request( result = await result result = cast(ElicitationResult, result) action_val = result.get("action", "cancel") - rpc_result = SessionUIHandlePendingElicitationParamsResult( - action=Action(action_val), + rpc_result = UIElicitationResponse( + action=UIElicitationResponseAction(action_val), content=result.get("content"), ) await self.rpc.ui.handle_pending_elicitation( - SessionUIHandlePendingElicitationParams( + UIHandlePendingElicitationRequest( request_id=request_id, result=rpc_result, ) @@ -1498,10 +1502,10 @@ async def _handle_elicitation_request( # Handler failed — attempt to cancel so the request doesn't hang try: await self.rpc.ui.handle_pending_elicitation( - SessionUIHandlePendingElicitationParams( + UIHandlePendingElicitationRequest( request_id=request_id, - result=SessionUIHandlePendingElicitationParamsResult( - action=Action.CANCEL, + result=UIElicitationResponse( + action=UIElicitationResponseAction.CANCEL, ), ) ) @@ -1939,7 +1943,7 @@ async def set_model( _capabilities_to_dict(model_capabilities) ) await self.rpc.model.switch_to( - SessionModelSwitchToParams( + ModelSwitchToRequest( model_id=model, reasoning_effort=reasoning_effort, model_capabilities=rpc_caps, @@ -1973,9 +1977,9 @@ async def log( >>> await session.log("Operation failed", level="error") >>> await session.log("Temporary status update", ephemeral=True) """ - params = SessionLogParams( + params = LogRequest( message=message, - level=Level(level) if level is not None else None, + level=SessionLogLevel(level) if level is not None else None, ephemeral=ephemeral, ) await self.rpc.log(params) diff --git a/python/e2e/test_agent_and_compact_rpc.py b/python/e2e/test_agent_and_compact_rpc.py index 047765641..1d1842fd0 100644 --- a/python/e2e/test_agent_and_compact_rpc.py +++ b/python/e2e/test_agent_and_compact_rpc.py @@ -4,7 +4,7 @@ from copilot import CopilotClient from copilot.client import SubprocessConfig -from copilot.generated.rpc import SessionAgentSelectParams +from copilot.generated.rpc import AgentSelectRequest from copilot.session import PermissionHandler from .testharness import CLI_PATH, E2ETestContext @@ -98,9 +98,7 @@ async def test_should_select_and_get_current_agent(self): ) # Select the agent - select_result = await session.rpc.agent.select( - SessionAgentSelectParams(name="test-agent") - ) + select_result = await session.rpc.agent.select(AgentSelectRequest(name="test-agent")) assert select_result.agent is not None assert select_result.agent.name == "test-agent" assert select_result.agent.display_name == "Test Agent" @@ -135,7 +133,7 @@ async def test_should_deselect_current_agent(self): ) # Select then deselect - await session.rpc.agent.select(SessionAgentSelectParams(name="test-agent")) + await session.rpc.agent.select(AgentSelectRequest(name="test-agent")) await session.rpc.agent.deselect() # Verify no agent is selected diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py index a86f874db..0d9f9a4eb 100644 --- a/python/e2e/test_rpc.py +++ b/python/e2e/test_rpc.py @@ -4,7 +4,7 @@ from copilot import CopilotClient from copilot.client import SubprocessConfig -from copilot.generated.rpc import PingParams +from copilot.generated.rpc import PingRequest from copilot.session import PermissionHandler from .testharness import CLI_PATH, E2ETestContext @@ -21,7 +21,7 @@ async def test_should_call_rpc_ping_with_typed_params(self): try: await client.start() - result = await client.rpc.ping(PingParams(message="typed rpc test")) + result = await client.rpc.ping(PingRequest(message="typed rpc test")) assert result.message == "pong: typed rpc test" assert isinstance(result.timestamp, (int, float)) @@ -91,7 +91,7 @@ async def test_should_call_session_rpc_model_get_current(self, ctx: E2ETestConte @pytest.mark.skip(reason="session.model.switchTo not yet implemented in CLI") async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext): """Test calling session.rpc.model.switchTo""" - from copilot.generated.rpc import SessionModelSwitchToParams + from copilot.generated.rpc import ModelSwitchToRequest session = await ctx.client.create_session( on_permission_request=PermissionHandler.approve_all, model="claude-sonnet-4.5" @@ -103,7 +103,7 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext # Switch to a different model with reasoning effort result = await session.rpc.model.switch_to( - SessionModelSwitchToParams(model_id="gpt-4.1", reasoning_effort="high") + ModelSwitchToRequest(model_id="gpt-4.1", reasoning_effort="high") ) assert result.model_id == "gpt-4.1" @@ -114,7 +114,7 @@ async def test_should_call_session_rpc_model_switch_to(self, ctx: E2ETestContext @pytest.mark.asyncio async def test_get_and_set_session_mode(self): """Test getting and setting session mode""" - from copilot.generated.rpc import Mode, SessionModeSetParams + from copilot.generated.rpc import ModeSetRequest, SessionMode client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True)) @@ -126,21 +126,17 @@ async def test_get_and_set_session_mode(self): # Get initial mode (default should be interactive) initial = await session.rpc.mode.get() - assert initial.mode == Mode.INTERACTIVE + assert initial == SessionMode.INTERACTIVE # Switch to plan mode - plan_result = await session.rpc.mode.set(SessionModeSetParams(mode=Mode.PLAN)) - assert plan_result.mode == Mode.PLAN + await session.rpc.mode.set(ModeSetRequest(mode=SessionMode.PLAN)) # Verify mode persisted after_plan = await session.rpc.mode.get() - assert after_plan.mode == Mode.PLAN + assert after_plan == SessionMode.PLAN # Switch back to interactive - interactive_result = await session.rpc.mode.set( - SessionModeSetParams(mode=Mode.INTERACTIVE) - ) - assert interactive_result.mode == Mode.INTERACTIVE + await session.rpc.mode.set(ModeSetRequest(mode=SessionMode.INTERACTIVE)) await session.disconnect() await client.stop() @@ -150,7 +146,7 @@ async def test_get_and_set_session_mode(self): @pytest.mark.asyncio async def test_read_update_and_delete_plan(self): """Test reading, updating, and deleting plan""" - from copilot.generated.rpc import SessionPlanUpdateParams + from copilot.generated.rpc import PlanUpdateRequest client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True)) @@ -167,7 +163,7 @@ async def test_read_update_and_delete_plan(self): # Create/update plan plan_content = "# Test Plan\n\n- Step 1\n- Step 2" - await session.rpc.plan.update(SessionPlanUpdateParams(content=plan_content)) + await session.rpc.plan.update(PlanUpdateRequest(content=plan_content)) # Verify plan exists and has correct content after_update = await session.rpc.plan.read() @@ -191,8 +187,8 @@ async def test_read_update_and_delete_plan(self): async def test_create_list_and_read_workspace_files(self): """Test creating, listing, and reading workspace files""" from copilot.generated.rpc import ( - SessionWorkspaceCreateFileParams, - SessionWorkspaceReadFileParams, + WorkspaceCreateFileRequest, + WorkspaceReadFileRequest, ) client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True)) @@ -210,7 +206,7 @@ async def test_create_list_and_read_workspace_files(self): # Create a file file_content = "Hello, workspace!" await session.rpc.workspace.create_file( - SessionWorkspaceCreateFileParams(content=file_content, path="test.txt") + WorkspaceCreateFileRequest(content=file_content, path="test.txt") ) # List files @@ -219,13 +215,13 @@ async def test_create_list_and_read_workspace_files(self): # Read file read_result = await session.rpc.workspace.read_file( - SessionWorkspaceReadFileParams(path="test.txt") + WorkspaceReadFileRequest(path="test.txt") ) assert read_result.content == file_content # Create nested file await session.rpc.workspace.create_file( - SessionWorkspaceCreateFileParams(content="Nested content", path="subdir/nested.txt") + WorkspaceCreateFileRequest(content="Nested content", path="subdir/nested.txt") ) after_nested = await session.rpc.workspace.list_files() diff --git a/python/test_commands_and_elicitation.py b/python/test_commands_and_elicitation.py index 9ee710fe0..6b8518e26 100644 --- a/python/test_commands_and_elicitation.py +++ b/python/test_commands_and_elicitation.py @@ -579,7 +579,7 @@ async def mock_request(method, params): from copilot.generated.session_events import ( Data, - RequestedSchema, + ElicitationRequestedSchema, RequestedSchemaType, SessionEvent, SessionEventType, @@ -589,7 +589,7 @@ async def mock_request(method, params): data=Data( request_id="req-schema-1", message="Fill in your details", - requested_schema=RequestedSchema( + requested_schema=ElicitationRequestedSchema( type=RequestedSchemaType.OBJECT, properties={ "name": {"type": "string"}, @@ -638,14 +638,14 @@ async def test_capabilities_changed_event_updates_session(self): session._set_capabilities({}) from copilot.generated.session_events import ( - UI, + CapabilitiesChangedUI, Data, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data(ui=UI(elicitation=True)), + data=Data(ui=CapabilitiesChangedUI(elicitation=True)), id="evt-cap-1", timestamp="2025-01-01T00:00:00Z", type=SessionEventType.CAPABILITIES_CHANGED, diff --git a/python/test_rpc_timeout.py b/python/test_rpc_timeout.py index 7fca7615b..b6f07caed 100644 --- a/python/test_rpc_timeout.py +++ b/python/test_rpc_timeout.py @@ -6,14 +6,14 @@ from copilot.generated.rpc import ( FleetApi, - Mode, + FleetStartRequest, ModeApi, + ModeSetRequest, PlanApi, ServerModelsApi, ServerToolsApi, - SessionFleetStartParams, - SessionModeSetParams, - ToolsListParams, + SessionMode, + ToolsListRequest, ) @@ -33,7 +33,7 @@ async def test_default_timeout_not_forwarded(self): client.request = AsyncMock(return_value={"started": True}) api = FleetApi(client, "sess-1") - await api.start(SessionFleetStartParams(prompt="go")) + await api.start(FleetStartRequest(prompt="go")) client.request.assert_called_once() _, kwargs = client.request.call_args @@ -45,7 +45,7 @@ async def test_custom_timeout_forwarded(self): client.request = AsyncMock(return_value={"started": True}) api = FleetApi(client, "sess-1") - await api.start(SessionFleetStartParams(prompt="go"), timeout=600.0) + await api.start(FleetStartRequest(prompt="go"), timeout=600.0) _, kwargs = client.request.call_args assert kwargs["timeout"] == 600.0 @@ -56,7 +56,7 @@ async def test_timeout_on_session_params_method(self): client.request = AsyncMock(return_value={"mode": "plan"}) api = ModeApi(client, "sess-1") - await api.set(SessionModeSetParams(mode=Mode.PLAN), timeout=120.0) + await api.set(ModeSetRequest(mode=SessionMode.PLAN), timeout=120.0) _, kwargs = client.request.call_args assert kwargs["timeout"] == 120.0 @@ -93,7 +93,7 @@ async def test_timeout_on_server_params_method(self): client.request = AsyncMock(return_value={"tools": []}) api = ServerToolsApi(client) - await api.list(ToolsListParams(), timeout=60.0) + await api.list(ToolsListRequest(), timeout=60.0) _, kwargs = client.request.call_args assert kwargs["timeout"] == 60.0 @@ -104,7 +104,7 @@ async def test_default_timeout_on_server_params_method(self): client.request = AsyncMock(return_value={"tools": []}) api = ServerToolsApi(client) - await api.list(ToolsListParams()) + await api.list(ToolsListRequest()) _, kwargs = client.request.call_args assert "timeout" not in kwargs diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index e6042eae5..96da352e8 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -12,13 +12,16 @@ import path from "path"; import { promisify } from "util"; import type { JSONSchema7 } from "json-schema"; import { - getSessionEventsSchemaPath, + cloneSchemaForCodegen, getApiSchemaPath, - writeGeneratedFile, - isRpcMethod, + getRpcSchemaTypeName, + getSessionEventsSchemaPath, isNodeFullyExperimental, - EXCLUDED_EVENT_TYPES, + isObjectSchema, + isVoidSchema, + isRpcMethod, REPO_ROOT, + writeGeneratedFile, type ApiSchema, type RpcMethod, } from "./utils.js"; @@ -229,10 +232,11 @@ function emitDataAnnotations(schema: JSONSchema7, indent: string): string[] { if (schema.exclusiveMaximum === true) namedArgs.push("MaximumIsExclusive = true"); const namedSuffix = namedArgs.length > 0 ? `, ${namedArgs.join(", ")}` : ""; if (schema.type === "integer") { - // Use Range(Type, string, string) overload since RangeAttribute has no long constructor + // Use Range(double, double) for AOT/trimming compatibility. + // The Range(Type, string, string) overload uses TypeConverter which triggers IL2026. const min = hasMin ? String(schema.minimum) : "long.MinValue"; const max = hasMax ? String(schema.maximum) : "long.MaxValue"; - attrs.push(`${indent}[Range(typeof(long), "${min}", "${max}"${namedSuffix})]`); + attrs.push(`${indent}[Range((double)${min}, (double)${max}${namedSuffix})]`); } else { const min = hasMin ? String(schema.minimum) : "double.MinValue"; const max = hasMax ? String(schema.maximum) : "double.MaxValue"; @@ -297,12 +301,10 @@ interface EventVariant { let generatedEnums = new Map(); -function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string): string { - const valuesKey = [...values].sort().join("|"); - for (const [, existing] of generatedEnums) { - if ([...existing.values].sort().join("|") === valuesKey) return existing.enumName; - } - const enumName = `${parentClassName}${propName}`; +function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string): string { + const enumName = explicitName ?? `${parentClassName}${propName}`; + const existing = generatedEnums.get(enumName); + if (existing) return existing.enumName; generatedEnums.set(enumName, { enumName, values }); const lines: string[] = []; @@ -336,8 +338,7 @@ function extractEventVariants(schema: JSONSchema7): EventVariant[] { dataSchema, dataDescription: dataSchema?.description, }; - }) - .filter((v) => !EXCLUDED_EVENT_TYPES.has(v.typeName)); + }); } /** @@ -515,7 +516,7 @@ function resolveSessionPropertyType( const variants = nonNull as JSONSchema7[]; const discriminatorInfo = findDiscriminator(variants); if (discriminatorInfo) { - const baseClassName = `${parentClassName}${propName}`; + const baseClassName = (propSchema.title as string) ?? `${parentClassName}${propName}`; const renamedBase = applyTypeRename(baseClassName); const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, knownTypes, nestedClasses, enumOutput, propSchema.description); nestedClasses.set(renamedBase, polymorphicCode); @@ -525,11 +526,11 @@ function resolveSessionPropertyType( return hasNull || !isRequired ? "object?" : "object"; } if (propSchema.enum && Array.isArray(propSchema.enum)) { - const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description); + const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined); return isRequired ? enumName : `${enumName}?`; } if (propSchema.type === "object" && propSchema.properties) { - const nestedClassName = `${parentClassName}${propName}`; + const nestedClassName = (propSchema.title as string) ?? `${parentClassName}${propName}`; nestedClasses.set(nestedClassName, generateNestedClass(nestedClassName, propSchema, knownTypes, nestedClasses, enumOutput)); return isRequired ? nestedClassName : `${nestedClassName}?`; } @@ -540,7 +541,7 @@ function resolveSessionPropertyType( const variants = items.anyOf.filter((v): v is JSONSchema7 => typeof v === "object"); const discriminatorInfo = findDiscriminator(variants); if (discriminatorInfo) { - const baseClassName = `${parentClassName}${propName}Item`; + const baseClassName = (items.title as string) ?? `${parentClassName}${propName}Item`; const renamedBase = applyTypeRename(baseClassName); const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, knownTypes, nestedClasses, enumOutput, items.description); nestedClasses.set(renamedBase, polymorphicCode); @@ -548,12 +549,12 @@ function resolveSessionPropertyType( } } if (items.type === "object" && items.properties) { - const itemClassName = `${parentClassName}${propName}Item`; + const itemClassName = (items.title as string) ?? `${parentClassName}${propName}Item`; nestedClasses.set(itemClassName, generateNestedClass(itemClassName, items, knownTypes, nestedClasses, enumOutput)); return isRequired ? `${itemClassName}[]` : `${itemClassName}[]?`; } if (items.enum && Array.isArray(items.enum)) { - const enumName = getOrCreateEnum(parentClassName, `${propName}Item`, items.enum as string[], enumOutput, items.description); + const enumName = getOrCreateEnum(parentClassName, `${propName}Item`, items.enum as string[], enumOutput, items.description, items.title as string | undefined); return isRequired ? `${enumName}[]` : `${enumName}[]?`; } const itemType = schemaTypeToCSharp(items, true, knownTypes); @@ -690,7 +691,7 @@ namespace GitHub.Copilot.SDK; export async function generateSessionEvents(schemaPath?: string): Promise { console.log("C#: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7); const code = generateSessionEventsCode(schema); const outPath = await writeGeneratedFile("dotnet/src/Generated/SessionEvents.cs", code); console.log(` ✓ ${outPath}`); @@ -702,6 +703,7 @@ export async function generateSessionEvents(schemaPath?: string): Promise // ══════════════════════════════════════════════════════════════════════════════ let emittedRpcClassSchemas = new Map(); +let emittedRpcEnumResultTypes = new Set(); let experimentalRpcTypes = new Set(); let rpcKnownTypes = new Map(); let rpcEnumOutput: string[] = []; @@ -714,12 +716,12 @@ function singularPascal(s: string): string { return p; } -function resultTypeName(rpcMethod: string): string { - return `${typeToClassName(rpcMethod)}Result`; +function resultTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.result, `${typeToClassName(method.rpcMethod)}Result`); } -function paramsTypeName(rpcMethod: string): string { - return `${typeToClassName(rpcMethod)}Params`; +function paramsTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.params, `${typeToClassName(method.rpcMethod)}Request`); } function stableStringify(value: unknown): string { @@ -744,7 +746,14 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam } // Handle enums (string unions like "interactive" | "plan" | "autopilot") if (schema.enum && Array.isArray(schema.enum)) { - const enumName = getOrCreateEnum(parentClassName, propName, schema.enum as string[], rpcEnumOutput, schema.description); + const enumName = getOrCreateEnum( + parentClassName, + propName, + schema.enum as string[], + rpcEnumOutput, + schema.description, + schema.title as string | undefined, + ); return isRequired ? enumName : `${enumName}?`; } if (schema.type === "object" && schema.properties) { @@ -759,13 +768,24 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam classes.push(emitRpcClass(itemClass, items, "public", classes)); return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; } + if (items.enum && Array.isArray(items.enum)) { + const itemEnum = getOrCreateEnum( + parentClassName, + `${propName}Item`, + items.enum as string[], + rpcEnumOutput, + items.description, + items.title as string | undefined, + ); + return isRequired ? `IList<${itemEnum}>` : `IList<${itemEnum}>?`; + } const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes); return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`; } if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") { const vs = schema.additionalProperties as JSONSchema7; if (vs.type === "object" && vs.properties) { - const valClass = `${parentClassName}${propName}Value`; + const valClass = (vs.title as string) ?? `${parentClassName}${propName}Value`; classes.push(emitRpcClass(valClass, vs, "public", classes)); return isRequired ? `IDictionary` : `IDictionary?`; } @@ -795,7 +815,7 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi if (experimentalRpcTypes.has(className)) { lines.push(`[Experimental(Diagnostics.Experimental)]`); } - lines.push(`${visibility} class ${className}`, `{`); + lines.push(`${visibility} sealed class ${className}`, `{`); const props = Object.entries(schema.properties || {}); for (let i = 0; i < props.length; i++) { @@ -832,6 +852,21 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi 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); + emittedRpcEnumResultTypes.add(enumName); + return enumName; + } + // For other non-object types, use the basic type mapping + return schemaTypeToCSharp(schema, true, rpcKnownTypes); +} + /** * Emit ServerRpc as an instance class (like SessionRpc but without sessionId). */ @@ -846,7 +881,7 @@ function emitServerRpcClasses(node: Record, classes: string[]): // ServerRpc class const srLines: string[] = []; srLines.push(`/// Provides server-scoped RPC methods (no session required).`); - srLines.push(`public class ServerRpc`); + srLines.push(`public sealed class ServerRpc`); srLines.push(`{`); srLines.push(` private readonly JsonRpc _rpc;`); srLines.push(""); @@ -890,7 +925,7 @@ function emitServerApiClass(className: string, node: Record, cl if (groupExperimental) { lines.push(`[Experimental(Diagnostics.Experimental)]`); } - lines.push(`public class ${className}`); + lines.push(`public sealed class ${className}`); lines.push(`{`); lines.push(` private readonly JsonRpc _rpc;`); lines.push(""); @@ -917,19 +952,23 @@ function emitServerInstanceMethod( groupExperimental: boolean ): void { const methodName = toPascalCase(name); - const resultClassName = `${typeToClassName(method.rpcMethod)}Result`; - if (method.stability === "experimental") { + let resultClassName = !isVoidSchema(method.result) ? resultTypeName(method) : ""; + if (!isVoidSchema(method.result) && method.stability === "experimental") { experimentalRpcTypes.add(resultClassName); } - const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); - if (resultClass) classes.push(resultClass); + if (isObjectSchema(method.result)) { + const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); + if (resultClass) classes.push(resultClass); + } else if (!isVoidSchema(method.result)) { + resultClassName = emitNonObjectResultType(resultClassName, method.result, classes); + } const paramEntries = method.params?.properties ? Object.entries(method.params.properties) : []; const requiredSet = new Set(method.params?.required || []); let requestClassName: string | null = null; if (paramEntries.length > 0) { - requestClassName = `${typeToClassName(method.rpcMethod)}Request`; + requestClassName = paramsTypeName(method); if (method.stability === "experimental") { experimentalRpcTypes.add(requestClassName); } @@ -951,10 +990,10 @@ function emitServerInstanceMethod( const isReq = requiredSet.has(pName); const jsonSchema = pSchema as JSONSchema7; let csType: string; - // If the property has an enum, resolve to the generated enum type + // If the property has an enum, resolve to the generated enum type by title if (jsonSchema.enum && Array.isArray(jsonSchema.enum) && requestClassName) { - const valuesKey = [...jsonSchema.enum].sort().join("|"); - const match = [...generatedEnums.values()].find((e) => [...e.values].sort().join("|") === valuesKey); + const enumTitle = (jsonSchema.title as string) ?? `${requestClassName}${toPascalCase(pName)}`; + const match = generatedEnums.get(enumTitle); csType = match ? (isReq ? match.enumName : `${match.enumName}?`) : schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes); } else { csType = schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes); @@ -964,13 +1003,22 @@ function emitServerInstanceMethod( } sigParams.push("CancellationToken cancellationToken = default"); - lines.push(`${indent}public async Task<${resultClassName}> ${methodName}Async(${sigParams.join(", ")})`); + const taskType = !isVoidSchema(method.result) ? `Task<${resultClassName}>` : "Task"; + lines.push(`${indent}public async ${taskType} ${methodName}Async(${sigParams.join(", ")})`); lines.push(`${indent}{`); if (requestClassName && bodyAssignments.length > 0) { lines.push(`${indent} var request = new ${requestClassName} { ${bodyAssignments.join(", ")} };`); - lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`); + if (!isVoidSchema(method.result)) { + lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`); + } else { + lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [request], cancellationToken);`); + } } else { - lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [], cancellationToken);`); + if (!isVoidSchema(method.result)) { + lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [], cancellationToken);`); + } else { + lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [], cancellationToken);`); + } } lines.push(`${indent}}`); } @@ -980,7 +1028,7 @@ function emitSessionRpcClasses(node: Record, classes: string[]) const groups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const topLevelMethods = Object.entries(node).filter(([, v]) => isRpcMethod(v)); - const srLines = [`/// Provides typed session-scoped RPC methods.`, `public class SessionRpc`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; + const srLines = [`/// Provides typed session-scoped RPC methods.`, `public sealed class SessionRpc`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; srLines.push(` internal SessionRpc(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`); for (const [groupName] of groups) srLines.push(` ${toPascalCase(groupName)} = new ${toPascalCase(groupName)}Api(rpc, sessionId);`); srLines.push(` }`); @@ -1004,12 +1052,16 @@ function emitSessionRpcClasses(node: Record, classes: string[]) function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean): void { const methodName = toPascalCase(key); - const resultClassName = `${typeToClassName(method.rpcMethod)}Result`; - if (method.stability === "experimental") { + let resultClassName = !isVoidSchema(method.result) ? resultTypeName(method) : ""; + if (!isVoidSchema(method.result) && method.stability === "experimental") { experimentalRpcTypes.add(resultClassName); } - const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); - if (resultClass) classes.push(resultClass); + if (isObjectSchema(method.result)) { + const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); + if (resultClass) classes.push(resultClass); + } else if (!isVoidSchema(method.result)) { + resultClassName = emitNonObjectResultType(resultClassName, method.result, classes); + } const paramEntries = (method.params?.properties ? Object.entries(method.params.properties) : []).filter(([k]) => k !== "sessionId"); const requiredSet = new Set(method.params?.required || []); @@ -1021,7 +1073,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas return aReq - bReq; }); - const requestClassName = `${typeToClassName(method.rpcMethod)}Request`; + const requestClassName = paramsTypeName(method); if (method.stability === "experimental") { experimentalRpcTypes.add(requestClassName); } @@ -1046,16 +1098,21 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas } sigParams.push("CancellationToken cancellationToken = default"); - lines.push(`${indent}public async Task<${resultClassName}> ${methodName}Async(${sigParams.join(", ")})`); + const taskType = !isVoidSchema(method.result) ? `Task<${resultClassName}>` : "Task"; + lines.push(`${indent}public async ${taskType} ${methodName}Async(${sigParams.join(", ")})`); lines.push(`${indent}{`, `${indent} var request = new ${requestClassName} { ${bodyAssignments.join(", ")} };`); - lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`); + if (!isVoidSchema(method.result)) { + lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`); + } else { + lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`); + } } function emitSessionApiClass(className: string, node: Record, classes: string[]): string { const displayName = className.replace(/Api$/, ""); const groupExperimental = isNodeFullyExperimental(node); const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : ""; - const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}public class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; + const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`, ` }`); for (const [key, value] of Object.entries(node)) { @@ -1095,13 +1152,17 @@ function emitClientSessionApiRegistration(clientSchema: Record, for (const { methods } of groups) { for (const method of methods) { - if (method.result) { - const resultClass = emitRpcClass(resultTypeName(method.rpcMethod), method.result, "public", classes); - if (resultClass) classes.push(resultClass); + if (!isVoidSchema(method.result)) { + if (isObjectSchema(method.result)) { + const resultClass = emitRpcClass(resultTypeName(method), method.result, "public", classes); + if (resultClass) classes.push(resultClass); + } else { + emitNonObjectResultType(resultTypeName(method), method.result, classes); + } } if (method.params?.properties && Object.keys(method.params.properties).length > 0) { - const paramsClass = emitRpcClass(paramsTypeName(method.rpcMethod), method.params, "public", classes); + const paramsClass = emitRpcClass(paramsTypeName(method), method.params, "public", classes); if (paramsClass) classes.push(paramsClass); } } @@ -1118,13 +1179,13 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(`{`); for (const method of methods) { const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; - const taskType = method.result ? `Task<${resultTypeName(method.rpcMethod)}>` : "Task"; + const taskType = !isVoidSchema(method.result) ? `Task<${resultTypeName(method)}>` : "Task"; lines.push(` /// Handles "${method.rpcMethod}".`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` [Experimental(Diagnostics.Experimental)]`); } if (hasParams) { - lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(${paramsTypeName(method.rpcMethod)} request, CancellationToken cancellationToken = default);`); + lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(${paramsTypeName(method)} request, CancellationToken cancellationToken = default);`); } else { lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(CancellationToken cancellationToken = default);`); } @@ -1134,7 +1195,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, } lines.push(`/// Provides all client session API handler groups for a session.`); - lines.push(`public class ClientSessionApiHandlers`); + lines.push(`public sealed class ClientSessionApiHandlers`); lines.push(`{`); for (const { groupName } of groups) { lines.push(` /// Optional handler for ${toPascalCase(groupName)} client session API methods.`); @@ -1160,8 +1221,8 @@ function emitClientSessionApiRegistration(clientSchema: Record, const handlerProperty = toPascalCase(groupName); const handlerMethod = clientHandlerMethodName(method.rpcMethod); const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; - const paramsClass = paramsTypeName(method.rpcMethod); - const taskType = method.result ? `Task<${resultTypeName(method.rpcMethod)}>` : "Task"; + const paramsClass = paramsTypeName(method); + const taskType = !isVoidSchema(method.result) ? `Task<${resultTypeName(method)}>` : "Task"; const registrationVar = `register${typeToClassName(method.rpcMethod)}Method`; if (hasParams) { @@ -1169,7 +1230,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(` {`); lines.push(` var handler = getHandlers(request.SessionId).${handlerProperty};`); lines.push(` if (handler is null) throw new InvalidOperationException($"No ${groupName} handler registered for session: {request.SessionId}");`); - if (method.result) { + if (!isVoidSchema(method.result)) { lines.push(` return await handler.${handlerMethod}(request, cancellationToken);`); } else { lines.push(` await handler.${handlerMethod}(request, cancellationToken);`); @@ -1193,6 +1254,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, function generateRpcCode(schema: ApiSchema): string { emittedRpcClassSchemas.clear(); + emittedRpcEnumResultTypes.clear(); experimentalRpcTypes.clear(); rpcKnownTypes.clear(); rpcEnumOutput = []; @@ -1237,7 +1299,7 @@ internal static class Diagnostics if (clientSessionParts.length > 0) lines.push(...clientSessionParts, ""); // Add JsonSerializerContext for AOT/trimming support - const typeNames = [...emittedRpcClassSchemas.keys()].sort(); + const typeNames = [...emittedRpcClassSchemas.keys(), ...emittedRpcEnumResultTypes].sort(); if (typeNames.length > 0) { lines.push(`[JsonSourceGenerationOptions(`); lines.push(` JsonSerializerDefaults.Web,`); @@ -1253,7 +1315,7 @@ internal static class Diagnostics export async function generateRpc(schemaPath?: string): Promise { console.log("C#: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); const code = generateRpcCode(schema); const outPath = await writeGeneratedFile("dotnet/src/Generated/Rpc.cs", code); console.log(` ✓ ${outPath}`); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 101702f18..980fb3b8e 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -12,10 +12,13 @@ import type { JSONSchema7 } from "json-schema"; import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from "quicktype-core"; import { promisify } from "util"; import { - EXCLUDED_EVENT_TYPES, + cloneSchemaForCodegen, getApiSchemaPath, + getRpcSchemaTypeName, getSessionEventsSchemaPath, + hoistTitledSchemas, isNodeFullyExperimental, + isVoidSchema, isRpcMethod, postProcessSchema, writeGeneratedFile, @@ -96,6 +99,59 @@ function postProcessEnumConstants(code: string): string { return code; } +function collapsePlaceholderGoStructs(code: string): string { + const structBlockRe = /((?:\/\/.*\r?\n)*)type\s+(\w+)\s+struct\s*\{[\s\S]*?^\}/gm; + const matches = [...code.matchAll(structBlockRe)].map((match) => ({ + fullBlock: match[0], + name: match[2], + normalizedBody: normalizeGoStructBlock(match[0], match[2]), + })); + const groups = new Map(); + + for (const match of matches) { + const group = groups.get(match.normalizedBody) ?? []; + group.push(match); + groups.set(match.normalizedBody, group); + } + + for (const group of groups.values()) { + if (group.length < 2) continue; + + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + if (!canonical) continue; + + for (const duplicate of group) { + if (duplicate.name === canonical) continue; + if (!isPlaceholderTypeName(duplicate.name)) continue; + + code = code.replace(duplicate.fullBlock, ""); + code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); + } + } + + return code.replace(/\n{3,}/g, "\n\n"); +} + +function normalizeGoStructBlock(block: string, name: string): string { + return block + .replace(/^\/\/.*\r?\n/gm, "") + .replace(new RegExp(`^type\\s+${name}\\s+struct\\s*\\{`, "m"), "type struct {") + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .join("\n"); +} + +function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { + const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); + if (specificNames.length === 0) return undefined; + return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; +} + +function isPlaceholderTypeName(name: string): boolean { + return name.endsWith("Class"); +} + /** * Extract a mapping from (structName, jsonFieldName) → goFieldName * so the wrapper code references the actual quicktype-generated field names. @@ -117,6 +173,14 @@ function extractFieldNames(qtCode: string): Map> { return result; } +function goResultTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.result, toPascalCase(method.rpcMethod) + "Result"); +} + +function goParamsTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.params, toPascalCase(method.rpcMethod) + "Request"); +} + async function formatGoFile(filePath: string): Promise { try { await execFileAsync("go", ["fmt", filePath]); @@ -150,7 +214,7 @@ interface GoEventVariant { interface GoCodegenCtx { structs: string[]; enums: string[]; - enumsByValues: Map; // sorted-values-key → enumName + enumsByName: Map; // enumName → enumName (dedup by type name, not values) generatedNames: Set; } @@ -171,8 +235,7 @@ function extractGoEventVariants(schema: JSONSchema7): GoEventVariant[] { dataSchema, dataDescription: dataSchema.description, }; - }) - .filter((v) => !EXCLUDED_EVENT_TYPES.has(v.typeName)); + }); } /** @@ -205,7 +268,8 @@ function findGoDiscriminator( } /** - * Get or create a Go enum type, deduplicating by value set. + * Get or create a Go enum type, deduplicating by type name (not by value set). + * Two enums with the same values but different names are distinct types. */ function getOrCreateGoEnum( enumName: string, @@ -213,8 +277,7 @@ function getOrCreateGoEnum( ctx: GoCodegenCtx, description?: string ): string { - const valuesKey = [...values].sort().join("|"); - const existing = ctx.enumsByValues.get(valuesKey); + const existing = ctx.enumsByName.get(enumName); if (existing) return existing; const lines: string[] = []; @@ -239,7 +302,7 @@ function getOrCreateGoEnum( } lines.push(`)`); - ctx.enumsByValues.set(valuesKey, enumName); + ctx.enumsByName.set(enumName, enumName); ctx.enums.push(lines.join("\n")); return enumName; } @@ -277,8 +340,9 @@ function resolveGoPropertyType( // Check for discriminated union const disc = findGoDiscriminator(nonNull); if (disc) { - emitGoFlatDiscriminatedUnion(nestedName, disc.property, disc.mapping, ctx, propSchema.description); - return isRequired && !hasNull ? nestedName : `*${nestedName}`; + const unionName = (propSchema.title as string) || nestedName; + emitGoFlatDiscriminatedUnion(unionName, disc.property, disc.mapping, ctx, propSchema.description); + return isRequired && !hasNull ? unionName : `*${unionName}`; } // Non-discriminated multi-type union → any return "any"; @@ -287,7 +351,7 @@ function resolveGoPropertyType( // Handle enum if (propSchema.enum && Array.isArray(propSchema.enum)) { - const enumType = getOrCreateGoEnum(nestedName, propSchema.enum as string[], ctx, propSchema.description); + const enumType = getOrCreateGoEnum((propSchema.title as string) || nestedName, propSchema.enum as string[], ctx, propSchema.description); return isRequired ? enumType : `*${enumType}`; } @@ -335,7 +399,7 @@ function resolveGoPropertyType( const itemVariants = (items.anyOf as JSONSchema7[]).filter((v) => v.type !== "null"); const disc = findGoDiscriminator(itemVariants); if (disc) { - const itemTypeName = nestedName + "Item"; + const itemTypeName = (items.title as string) || (nestedName + "Item"); emitGoFlatDiscriminatedUnion(itemTypeName, disc.property, disc.mapping, ctx, items.description); return `[]${itemTypeName}`; } @@ -349,8 +413,9 @@ function resolveGoPropertyType( // Object type if (type === "object" || (propSchema.properties && !type)) { if (propSchema.properties && Object.keys(propSchema.properties).length > 0) { - emitGoStruct(nestedName, propSchema, ctx); - return isRequired ? nestedName : `*${nestedName}`; + const structName = (propSchema.title as string) || nestedName; + emitGoStruct(structName, propSchema, ctx); + return isRequired ? structName : `*${structName}`; } if (propSchema.additionalProperties) { if ( @@ -359,8 +424,9 @@ function resolveGoPropertyType( ) { const ap = propSchema.additionalProperties as JSONSchema7; if (ap.type === "object" && ap.properties) { - emitGoStruct(nestedName + "Value", ap, ctx); - return `map[string]${nestedName}Value`; + const valueName = (ap.title as string) || `${nestedName}Value`; + emitGoStruct(valueName, ap, ctx); + return `map[string]${valueName}`; } const valueType = resolveGoPropertyType(ap, parentTypeName, jsonPropName + "Value", true, ctx); return `map[string]${valueType}`; @@ -512,7 +578,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { const ctx: GoCodegenCtx = { structs: [], enums: [], - enumsByValues: new Map(), + enumsByName: new Map(), generatedNames: new Set(), }; @@ -733,27 +799,17 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { // Type aliases for types referenced by non-generated SDK code under their short names. const TYPE_ALIASES: Record = { - PermissionRequest: "PermissionRequestedDataPermissionRequest", - PermissionRequestKind: "PermissionRequestedDataPermissionRequestKind", - PermissionRequestCommand: "PermissionRequestedDataPermissionRequestCommandsItem", - PossibleURL: "PermissionRequestedDataPermissionRequestPossibleUrlsItem", - Attachment: "UserMessageDataAttachmentsItem", - AttachmentType: "UserMessageDataAttachmentsItemType", + PermissionRequestCommand: "PermissionRequestShellCommand", + PossibleURL: "PermissionRequestShellPossibleUrl", + Attachment: "UserMessageAttachment", + AttachmentType: "UserMessageAttachmentType", }; const CONST_ALIASES: Record = { - AttachmentTypeFile: "UserMessageDataAttachmentsItemTypeFile", - AttachmentTypeDirectory: "UserMessageDataAttachmentsItemTypeDirectory", - AttachmentTypeSelection: "UserMessageDataAttachmentsItemTypeSelection", - AttachmentTypeGithubReference: "UserMessageDataAttachmentsItemTypeGithubReference", - AttachmentTypeBlob: "UserMessageDataAttachmentsItemTypeBlob", - PermissionRequestKindShell: "PermissionRequestedDataPermissionRequestKindShell", - PermissionRequestKindWrite: "PermissionRequestedDataPermissionRequestKindWrite", - PermissionRequestKindRead: "PermissionRequestedDataPermissionRequestKindRead", - PermissionRequestKindMcp: "PermissionRequestedDataPermissionRequestKindMcp", - PermissionRequestKindURL: "PermissionRequestedDataPermissionRequestKindURL", - PermissionRequestKindMemory: "PermissionRequestedDataPermissionRequestKindMemory", - PermissionRequestKindCustomTool: "PermissionRequestedDataPermissionRequestKindCustomTool", - PermissionRequestKindHook: "PermissionRequestedDataPermissionRequestKindHook", + AttachmentTypeFile: "UserMessageAttachmentTypeFile", + AttachmentTypeDirectory: "UserMessageAttachmentTypeDirectory", + AttachmentTypeSelection: "UserMessageAttachmentTypeSelection", + AttachmentTypeGithubReference: "UserMessageAttachmentTypeGithubReference", + AttachmentTypeBlob: "UserMessageAttachmentTypeBlob", }; out.push(`// Type aliases for convenience.`); out.push(`type (`); @@ -777,7 +833,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { console.log("Go: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7); const processed = postProcessSchema(schema); const code = generateGoSessionEventsCode(processed); @@ -794,7 +850,7 @@ async function generateRpc(schemaPath?: string): Promise { console.log("Go: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); const allMethods = [ ...collectRpcMethods(schema.server || {}), @@ -809,9 +865,11 @@ async function generateRpc(schemaPath?: string): Promise { }; for (const method of allMethods) { - const baseName = toPascalCase(method.rpcMethod); - if (method.result) { - combinedSchema.definitions![baseName + "Result"] = method.result; + if (isVoidSchema(method.result)) { + // Emit an empty struct for void results (forward-compatible with adding fields later) + combinedSchema.definitions![goResultTypeName(method)] = { type: "object", properties: {}, additionalProperties: false }; + } else { + combinedSchema.definitions![goResultTypeName(method)] = method.result; } if (method.params?.properties && Object.keys(method.params.properties).length > 0) { // For session methods, filter out sessionId from params type @@ -824,18 +882,26 @@ async function generateRpc(schemaPath?: string): Promise { required: method.params.required?.filter((r) => r !== "sessionId"), }; if (Object.keys(filtered.properties!).length > 0) { - combinedSchema.definitions![baseName + "Params"] = filtered; + combinedSchema.definitions![goParamsTypeName(method)] = filtered; } } else { - combinedSchema.definitions![baseName + "Params"] = method.params; + combinedSchema.definitions![goParamsTypeName(method)] = method.params; } } } + const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); + // Generate types via quicktype const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(combinedSchema.definitions!)) { - await schemaInput.addSource({ name, schema: JSON.stringify(def) }); + for (const [name, def] of Object.entries(rootDefinitions)) { + await schemaInput.addSource({ + name, + schema: JSON.stringify({ + ...def, + definitions: sharedDefinitions, + }), + }); } const inputData = new InputData(); @@ -849,15 +915,29 @@ async function generateRpc(schemaPath?: string): Promise { // Post-process quicktype output: fix enum constant names let qtCode = qtResult.lines.filter((l) => !l.startsWith("package ")).join("\n"); + // Extract any imports quicktype emitted (e.g., "time") and hoist them + const qtImports: string[] = []; + qtCode = qtCode.replace(/^import\s+"([^"]+)"\s*$/gm, (_match, imp) => { + qtImports.push(`"${imp}"`); + return ""; + }); + qtCode = qtCode.replace(/^import\s+\(([^)]*)\)\s*$/gm, (_match, block) => { + for (const line of block.split("\n")) { + const trimmed = line.trim(); + if (trimmed) qtImports.push(trimmed); + } + return ""; + }); qtCode = postProcessEnumConstants(qtCode); + qtCode = collapsePlaceholderGoStructs(qtCode); // Strip trailing whitespace from quicktype output (gofmt requirement) qtCode = qtCode.replace(/[ \t]+$/gm, ""); // Extract actual type names generated by quicktype (may differ from toPascalCase) const actualTypeNames = new Map(); - const structRe = /^type\s+(\w+)\s+struct\b/gm; + const typeRe = /^type\s+(\w+)\s+/gm; let sm; - while ((sm = structRe.exec(qtCode)) !== null) { + while ((sm = typeRe.exec(qtCode)) !== null) { actualTypeNames.set(sm[1].toLowerCase(), sm[1]); } const resolveType = (name: string): string => actualTypeNames.get(name.toLowerCase()) ?? name; @@ -869,10 +949,10 @@ async function generateRpc(schemaPath?: string): Promise { const experimentalTypeNames = new Set(); for (const method of allMethods) { if (method.stability !== "experimental") continue; - experimentalTypeNames.add(toPascalCase(method.rpcMethod) + "Result"); - const baseName = toPascalCase(method.rpcMethod); - if (combinedSchema.definitions![baseName + "Params"]) { - experimentalTypeNames.add(baseName + "Params"); + experimentalTypeNames.add(goResultTypeName(method)); + const paramsTypeName = goParamsTypeName(method); + if (rootDefinitions[paramsTypeName]) { + experimentalTypeNames.add(paramsTypeName); } } for (const typeName of experimentalTypeNames) { @@ -898,6 +978,10 @@ async function generateRpc(schemaPath?: string): Promise { imports.push(`"errors"`, `"fmt"`); } imports.push(`"github.com/github/copilot-sdk/go/internal/jsonrpc2"`); + // Add any imports hoisted from quicktype output + for (const qi of qtImports) { + if (!imports.includes(qi)) imports.push(qi); + } lines.push(`import (`); for (const imp of imports) { @@ -1004,13 +1088,13 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>, groupExperimental = false, isWrapper = false): void { const methodName = toPascalCase(name); - const resultType = resolveType(toPascalCase(method.rpcMethod) + "Result"); + const resultType = resolveType(goResultTypeName(method)); const paramProps = method.params?.properties || {}; const requiredParams = new Set(method.params?.required || []); const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; - const paramsType = hasParams ? resolveType(toPascalCase(method.rpcMethod) + "Params") : ""; + const paramsType = hasParams ? resolveType(goParamsTypeName(method)) : ""; // For wrapper-level methods, access fields through a.common; for service type aliases, use a directly const clientRef = isWrapper ? "a.common.client" : "a.client"; @@ -1103,13 +1187,9 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< if (method.stability === "experimental" && !groupExperimental) { lines.push(`\t// Experimental: ${clientHandlerMethodName(method.rpcMethod)} is an experimental API and may change or be removed in future versions.`); } - const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); - if (method.result) { - const resultType = resolveType(toPascalCase(method.rpcMethod) + "Result"); - lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (*${resultType}, error)`); - } else { - lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) error`); - } + const paramsType = resolveType(goParamsTypeName(method)); + const resultType = resolveType(goResultTypeName(method)); + lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (*${resultType}, error)`); } lines.push(`}`); lines.push(``); @@ -1140,7 +1220,7 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< for (const { groupName, methods } of groups) { const handlerField = toPascalCase(groupName); for (const method of methods) { - const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); + const paramsType = resolveType(goParamsTypeName(method)); lines.push(`\tclient.SetRequestHandler("${method.rpcMethod}", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {`); lines.push(`\t\tvar request ${paramsType}`); lines.push(`\t\tif err := json.Unmarshal(params, &request); err != nil {`); @@ -1150,22 +1230,15 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< lines.push(`\t\tif handlers == nil || handlers.${handlerField} == nil {`); lines.push(`\t\t\treturn nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No ${groupName} handler registered for session: %s", request.SessionID)}`); lines.push(`\t\t}`); - if (method.result) { - lines.push(`\t\tresult, err := handlers.${handlerField}.${clientHandlerMethodName(method.rpcMethod)}(&request)`); - lines.push(`\t\tif err != nil {`); - lines.push(`\t\t\treturn nil, clientSessionHandlerError(err)`); - lines.push(`\t\t}`); - lines.push(`\t\traw, err := json.Marshal(result)`); - lines.push(`\t\tif err != nil {`); - lines.push(`\t\t\treturn nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)}`); - lines.push(`\t\t}`); - lines.push(`\t\treturn raw, nil`); - } else { - lines.push(`\t\tif err := handlers.${handlerField}.${clientHandlerMethodName(method.rpcMethod)}(&request); err != nil {`); - lines.push(`\t\t\treturn nil, clientSessionHandlerError(err)`); - lines.push(`\t\t}`); - lines.push(`\t\treturn json.RawMessage("null"), nil`); - } + lines.push(`\t\tresult, err := handlers.${handlerField}.${clientHandlerMethodName(method.rpcMethod)}(&request)`); + lines.push(`\t\tif err != nil {`); + lines.push(`\t\t\treturn nil, clientSessionHandlerError(err)`); + lines.push(`\t\t}`); + lines.push(`\t\traw, err := json.Marshal(result)`); + lines.push(`\t\tif err != nil {`); + lines.push(`\t\t\treturn nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)}`); + lines.push(`\t\t}`); + lines.push(`\t\treturn raw, nil`); lines.push(`\t})`); } } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 2aa593c5d..f22a83ff9 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -10,12 +10,16 @@ import fs from "fs/promises"; import type { JSONSchema7 } from "json-schema"; import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from "quicktype-core"; import { + cloneSchemaForCodegen, getApiSchemaPath, + getRpcSchemaTypeName, getSessionEventsSchemaPath, + hoistTitledSchemas, + isObjectSchema, + isVoidSchema, isRpcMethod, postProcessSchema, writeGeneratedFile, - isRpcMethod, isNodeFullyExperimental, type ApiSchema, type RpcMethod, @@ -118,6 +122,59 @@ function modernizePython(code: string): string { return code; } +function collapsePlaceholderPythonDataclasses(code: string): string { + const classBlockRe = /(@dataclass\r?\nclass\s+(\w+):[\s\S]*?)(?=^@dataclass|^class\s+\w+|^def\s+\w+|\Z)/gm; + const matches = [...code.matchAll(classBlockRe)].map((match) => ({ + fullBlock: match[1], + name: match[2], + normalizedBody: normalizePythonDataclassBlock(match[1], match[2]), + })); + const groups = new Map(); + + for (const match of matches) { + const group = groups.get(match.normalizedBody) ?? []; + group.push(match); + groups.set(match.normalizedBody, group); + } + + for (const group of groups.values()) { + if (group.length < 2) continue; + + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + if (!canonical) continue; + + for (const duplicate of group) { + if (duplicate.name === canonical) continue; + if (!isPlaceholderTypeName(duplicate.name)) continue; + + code = code.replace(duplicate.fullBlock, ""); + code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); + } + } + + return code.replace(/\n{3,}/g, "\n\n"); +} + +function normalizePythonDataclassBlock(block: string, name: string): string { + return block + .replace(/^@dataclass\r?\nclass\s+\w+:/, "@dataclass\nclass:") + .replace(new RegExp(`\\b${name}\\b`, "g"), "SelfType") + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .join("\n"); +} + +function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { + const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); + if (specificNames.length === 0) return undefined; + return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; +} + +function isPlaceholderTypeName(name: string): boolean { + return name.endsWith("Class"); +} + function toSnakeCase(s: string): string { return s .replace(/([a-z])([A-Z])/g, "$1_$2") @@ -144,18 +201,34 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } +function pythonResultTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.result, toPascalCase(method.rpcMethod) + "Result"); +} + +function pythonParamsTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.params, toPascalCase(method.rpcMethod) + "Request"); +} + // ── Session Events ────────────────────────────────────────────────────────── async function generateSessionEvents(schemaPath?: string): Promise { console.log("Python: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7); const resolvedSchema = (schema.definitions?.SessionEvent as JSONSchema7) || schema; const processed = postProcessSchema(resolvedSchema); + // Hoist titled inline schemas (enums etc.) to definitions so quicktype + // uses the schema-defined names instead of its own structural heuristics. + const { rootDefinitions: hoistedRoots, sharedDefinitions } = hoistTitledSchemas({ SessionEvent: processed }); + const hoisted = hoistedRoots.SessionEvent; + if (Object.keys(sharedDefinitions).length > 0) { + hoisted.definitions = { ...hoisted.definitions, ...sharedDefinitions }; + } + const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - await schemaInput.addSource({ name: "SessionEvent", schema: JSON.stringify(processed) }); + await schemaInput.addSource({ name: "SessionEvent", schema: JSON.stringify(hoisted) }); const inputData = new InputData(); inputData.addInput(schemaInput); @@ -206,7 +279,7 @@ async function generateRpc(schemaPath?: string): Promise { console.log("Python: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema; + const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); const allMethods = [ ...collectRpcMethods(schema.server || {}), @@ -221,9 +294,8 @@ async function generateRpc(schemaPath?: string): Promise { }; for (const method of allMethods) { - const baseName = toPascalCase(method.rpcMethod); - if (method.result) { - combinedSchema.definitions![baseName + "Result"] = method.result; + if (!isVoidSchema(method.result)) { + combinedSchema.definitions![pythonResultTypeName(method)] = method.result; } if (method.params?.properties && Object.keys(method.params.properties).length > 0) { if (method.rpcMethod.startsWith("session.")) { @@ -235,18 +307,26 @@ async function generateRpc(schemaPath?: string): Promise { required: method.params.required?.filter((r) => r !== "sessionId"), }; if (Object.keys(filtered.properties!).length > 0) { - combinedSchema.definitions![baseName + "Params"] = filtered; + combinedSchema.definitions![pythonParamsTypeName(method)] = filtered; } } else { - combinedSchema.definitions![baseName + "Params"] = method.params; + combinedSchema.definitions![pythonParamsTypeName(method)] = method.params; } } } + const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); + // Generate types via quicktype const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(combinedSchema.definitions!)) { - await schemaInput.addSource({ name, schema: JSON.stringify(def) }); + for (const [name, def] of Object.entries(rootDefinitions)) { + await schemaInput.addSource({ + name, + schema: JSON.stringify({ + ...def, + definitions: sharedDefinitions, + }), + }); } const inputData = new InputData(); @@ -267,15 +347,16 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = typesCode.replace(/^(\s*)pass\n\n(\s*@staticmethod)/gm, "$2"); // Modernize to Python 3.11+ syntax typesCode = modernizePython(typesCode); + typesCode = collapsePlaceholderPythonDataclasses(typesCode); // Annotate experimental data types const experimentalTypeNames = new Set(); for (const method of allMethods) { if (method.stability !== "experimental") continue; - experimentalTypeNames.add(toPascalCase(method.rpcMethod) + "Result"); - const baseName = toPascalCase(method.rpcMethod); - if (combinedSchema.definitions![baseName + "Params"]) { - experimentalTypeNames.add(baseName + "Params"); + experimentalTypeNames.add(pythonResultTypeName(method)); + const paramsTypeName = pythonParamsTypeName(method); + if (rootDefinitions[paramsTypeName]) { + experimentalTypeNames.add(paramsTypeName); } } for (const typeName of experimentalTypeNames) { @@ -319,6 +400,27 @@ def _timeout_kwargs(timeout: float | None) -> dict: return {"timeout": timeout} return {} +def _patch_model_capabilities(data: dict) -> dict: + """Ensure model capabilities have required fields. + + TODO: Remove once the runtime schema correctly marks these fields as optional. + Some models (e.g. embedding models) may omit 'limits' or 'supports' in their + capabilities, or omit 'max_context_window_tokens' within limits. The generated + deserializer requires these fields, so we supply defaults here. + """ + for model in data.get("models", []): + caps = model.get("capabilities") + if caps is None: + model["capabilities"] = {"supports": {}, "limits": {"max_context_window_tokens": 0}} + continue + if "supports" not in caps: + caps["supports"] = {} + if "limits" not in caps: + caps["limits"] = {"max_context_window_tokens": 0} + elif "max_context_window_tokens" not in caps["limits"]: + caps["limits"]["max_context_window_tokens"] = 0 + return data + `); // Emit RPC wrapper classes @@ -332,7 +434,19 @@ def _timeout_kwargs(timeout: float | None) -> dict: emitClientSessionApiRegistration(lines, schema.clientSession, resolveType); } - const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", lines.join("\n")); + // Patch models.list to normalize capabilities before deserialization + let finalCode = lines.join("\n"); + finalCode = finalCode.replace( + `ModelList.from_dict(await self._client.request("models.list"`, + `ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list"`, + ); + // Close the extra paren opened by _patch_model_capabilities( + finalCode = finalCode.replace( + /(_patch_model_capabilities\(await self\._client\.request\("models\.list",\s*\{[^)]*\)[^)]*\))/, + "$1)", + ); + + const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", finalCode); console.log(` ✓ ${outPath}`); } @@ -402,12 +516,14 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false): void { const methodName = toSnakeCase(name); - const resultType = resolveType(toPascalCase(method.rpcMethod) + "Result"); + const hasResult = !isVoidSchema(method.result); + const resultType = hasResult ? resolveType(pythonResultTypeName(method)) : "None"; + const resultIsObject = isObjectSchema(method.result); const paramProps = method.params?.properties || {}; const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; - const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); + const paramsType = resolveType(pythonParamsTypeName(method)); // Build signature with typed params + optional timeout const sig = hasParams @@ -420,21 +536,40 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); } + // For object results use .from_dict(); for enums/primitives use direct construction + const deserialize = (expr: string) => resultIsObject ? `${resultType}.from_dict(${expr})` : `${resultType}(${expr})`; + // Build request body with proper serialization/deserialization if (isSession) { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); lines.push(` params_dict["sessionId"] = self._session_id`); - lines.push(` return ${resultType}.from_dict(await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout)))`); + if (hasResult) { + lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); + } else { + lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); + } } else { - lines.push(` return ${resultType}.from_dict(await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))`); + if (hasResult) { + lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`)}`); + } else { + lines.push(` await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`); + } } } else { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); - lines.push(` return ${resultType}.from_dict(await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout)))`); + if (hasResult) { + lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); + } else { + lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); + } } else { - lines.push(` return ${resultType}.from_dict(await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout)))`); + if (hasResult) { + lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`)}`); + } else { + lines.push(` await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`); + } } } lines.push(``); @@ -503,8 +638,8 @@ function emitClientSessionHandlerMethod( resolveType: (name: string) => string, groupExperimental = false ): void { - const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); - const resultType = method.result ? resolveType(toPascalCase(method.rpcMethod) + "Result") : "None"; + const paramsType = resolveType(pythonParamsTypeName(method)); + const resultType = !isVoidSchema(method.result) ? resolveType(pythonResultTypeName(method)) : "None"; lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); @@ -520,8 +655,8 @@ function emitClientSessionRegistrationMethod( resolveType: (name: string) => string ): void { const handlerVariableName = `handle_${toSnakeCase(groupName)}_${toSnakeCase(methodName)}`; - const paramsType = resolveType(toPascalCase(method.rpcMethod) + "Params"); - const resultType = method.result ? resolveType(toPascalCase(method.rpcMethod) + "Result") : null; + const paramsType = resolveType(pythonParamsTypeName(method)); + const resultType = !isVoidSchema(method.result) ? resolveType(pythonResultTypeName(method)) : null; const handlerField = toSnakeCase(groupName); const handlerMethod = toSnakeCase(methodName); @@ -533,7 +668,11 @@ function emitClientSessionRegistrationMethod( ); if (resultType) { lines.push(` result = await handler.${handlerMethod}(request)`); - lines.push(` return result.to_dict()`); + if (isObjectSchema(method.result)) { + lines.push(` return result.to_dict()`); + } else { + lines.push(` return result.value if hasattr(result, 'value') else result`); + } } else { lines.push(` await handler.${handlerMethod}(request)`); lines.push(` return None`); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index e5e82bdc6..7dfd5631f 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -10,22 +10,109 @@ import fs from "fs/promises"; import type { JSONSchema7 } from "json-schema"; import { compile } from "json-schema-to-typescript"; import { - getSessionEventsSchemaPath, getApiSchemaPath, + getRpcSchemaTypeName, + getSessionEventsSchemaPath, + isNodeFullyExperimental, + isRpcMethod, + isVoidSchema, postProcessSchema, + stripNonAnnotationTitles, writeGeneratedFile, - isRpcMethod, - isNodeFullyExperimental, type ApiSchema, type RpcMethod, } from "./utils.js"; -// ── Utilities ─────────────────────────────────────────────────────────────── - function toPascalCase(s: string): string { return s.charAt(0).toUpperCase() + s.slice(1); } +function appendUniqueExportBlocks(output: string[], compiled: string, seenBlocks: Map): void { + for (const block of splitExportBlocks(compiled)) { + const nameMatch = /^export\s+(?:interface|type)\s+(\w+)/m.exec(block); + if (!nameMatch) { + output.push(block); + continue; + } + + const name = nameMatch[1]; + const normalizedBlock = normalizeExportBlock(block); + const existing = seenBlocks.get(name); + if (existing) { + if (existing !== normalizedBlock) { + throw new Error(`Duplicate generated TypeScript declaration for "${name}" with different content.`); + } + continue; + } + + seenBlocks.set(name, normalizedBlock); + output.push(block); + } +} + +function splitExportBlocks(compiled: string): string[] { + const normalizedCompiled = compiled + .trim() + .replace(/;(export\s+(?:interface|type)\s+)/g, ";\n$1") + .replace(/}(export\s+(?:interface|type)\s+)/g, "}\n$1"); + const lines = normalizedCompiled.split(/\r?\n/); + const blocks: string[] = []; + let pending: string[] = []; + + for (let index = 0; index < lines.length;) { + const line = lines[index]; + if (!/^export\s+(?:interface|type)\s+\w+/.test(line)) { + pending.push(line); + index++; + continue; + } + + const blockLines = [...pending, line]; + pending = []; + let braceDepth = countBraces(line); + index++; + + if (braceDepth === 0 && line.trim().endsWith(";")) { + blocks.push(blockLines.join("\n").trim()); + continue; + } + + while (index < lines.length) { + const nextLine = lines[index]; + blockLines.push(nextLine); + braceDepth += countBraces(nextLine); + index++; + + const trimmed = nextLine.trim(); + if (braceDepth === 0 && (trimmed === "}" || trimmed.endsWith(";"))) { + break; + } + } + + blocks.push(blockLines.join("\n").trim()); + } + + return blocks; +} + +function countBraces(line: string): number { + let depth = 0; + for (const char of line) { + if (char === "{") depth++; + if (char === "}") depth--; + } + return depth; +} + +function normalizeExportBlock(block: string): string { + return block + .replace(/\/\*\*[\s\S]*?\*\//g, "") + .split(/\r?\n/) + .map((line) => line.trim()) + .filter((line) => line.length > 0) + .join("\n"); +} + function collectRpcMethods(node: Record): RpcMethod[] { const results: RpcMethod[] = []; for (const value of Object.values(node)) { @@ -45,7 +132,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; - const processed = postProcessSchema(schema); + const processed = postProcessSchema(stripNonAnnotationTitles(schema)); const ts = await compile(processed, "SessionEvent", { bannerComment: `/** @@ -62,12 +149,12 @@ async function generateSessionEvents(schemaPath?: string): Promise { // ── RPC Types ─────────────────────────────────────────────────────────────── -function resultTypeName(rpcMethod: string): string { - return rpcMethod.split(".").map(toPascalCase).join("") + "Result"; +function resultTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.result, method.rpcMethod.split(".").map(toPascalCase).join("") + "Result"); } -function paramsTypeName(rpcMethod: string): string { - return rpcMethod.split(".").map(toPascalCase).join("") + "Params"; +function paramsTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(method.params, method.rpcMethod.split(".").map(toPascalCase).join("") + "Request"); } async function generateRpc(schemaPath?: string): Promise { @@ -87,29 +174,30 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; const allMethods = [...collectRpcMethods(schema.server || {}), ...collectRpcMethods(schema.session || {})]; const clientSessionMethods = collectRpcMethods(schema.clientSession || {}); + const seenBlocks = new Map(); for (const method of [...allMethods, ...clientSessionMethods]) { - if (method.result) { - const compiled = await compile(method.result, resultTypeName(method.rpcMethod), { + if (!isVoidSchema(method.result)) { + const compiled = await compile(stripNonAnnotationTitles(method.result), resultTypeName(method), { bannerComment: "", additionalProperties: false, }); if (method.stability === "experimental") { lines.push("/** @experimental */"); } - lines.push(compiled.trim()); + appendUniqueExportBlocks(lines, compiled, seenBlocks); lines.push(""); } if (method.params?.properties && Object.keys(method.params.properties).length > 0) { - const paramsCompiled = await compile(method.params, paramsTypeName(method.rpcMethod), { + const paramsCompiled = await compile(stripNonAnnotationTitles(method.params), paramsTypeName(method), { bannerComment: "", additionalProperties: false, }); if (method.stability === "experimental") { lines.push("/** @experimental */"); } - lines.push(paramsCompiled.trim()); + appendUniqueExportBlocks(lines, paramsCompiled, seenBlocks); lines.push(""); } } @@ -149,8 +237,8 @@ function emitGroup(node: Record, indent: string, isSession: boo for (const [key, value] of Object.entries(node)) { if (isRpcMethod(value)) { const { rpcMethod, params } = value; - const resultType = value.result ? resultTypeName(rpcMethod) : "void"; - const paramsType = paramsTypeName(rpcMethod); + const resultType = !isVoidSchema(value.result) ? resultTypeName(value) : "void"; + const paramsType = paramsTypeName(value); const paramEntries = params?.properties ? Object.entries(params.properties).filter(([k]) => k !== "sessionId") : []; const hasParams = params?.properties && Object.keys(params.properties).length > 0; @@ -238,8 +326,8 @@ function emitClientSessionApiRegistration(clientSchema: Record) for (const method of methods) { const name = handlerMethodName(method.rpcMethod); const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; - const pType = hasParams ? paramsTypeName(method.rpcMethod) : ""; - const rType = method.result ? resultTypeName(method.rpcMethod) : "void"; + const pType = hasParams ? paramsTypeName(method) : ""; + const rType = !isVoidSchema(method.result) ? resultTypeName(method) : "void"; if (hasParams) { lines.push(` ${name}(params: ${pType}): Promise<${rType}>;`); @@ -276,7 +364,7 @@ function emitClientSessionApiRegistration(clientSchema: Record) for (const [groupName, methods] of groups) { for (const method of methods) { const name = handlerMethodName(method.rpcMethod); - const pType = paramsTypeName(method.rpcMethod); + const pType = paramsTypeName(method); const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; if (hasParams) { diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 1e95b4dd4..225e678b7 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -21,9 +21,6 @@ const __dirname = path.dirname(__filename); /** Root of the copilot-sdk repo */ export const REPO_ROOT = path.resolve(__dirname, "../.."); -/** Event types to exclude from generation (internal/legacy types) */ -export const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]); - // ── Schema paths ──────────────────────────────────────────────────────────── export async function getSessionEventsSchemaPath(): Promise { @@ -49,7 +46,7 @@ export async function getApiSchemaPath(cliArg?: string): Promise { /** * Post-process JSON Schema for quicktype compatibility. - * Converts boolean const values to enum, filters excluded event types. + * Converts boolean const values to enum. */ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { if (typeof schema !== "object" || schema === null) return schema; @@ -81,18 +78,9 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { if (processed[combiner]) { - processed[combiner] = processed[combiner]! - .filter((item) => { - if (typeof item !== "object") return true; - const typeConst = (item as JSONSchema7).properties?.type; - if (typeof typeConst === "object" && "const" in typeConst) { - return !EXCLUDED_EVENT_TYPES.has(typeConst.const as string); - } - return true; - }) - .map((item) => - typeof item === "object" ? postProcessSchema(item as JSONSchema7) : item - ) as JSONSchema7Definition[]; + processed[combiner] = processed[combiner]!.map((item) => + typeof item === "object" ? postProcessSchema(item as JSONSchema7) : item + ) as JSONSchema7Definition[]; } } @@ -129,6 +117,170 @@ export interface RpcMethod { stability?: string; } +export function getRpcSchemaTypeName(schema: JSONSchema7 | null | undefined, fallback: string): string { + if (typeof schema?.title === "string") return schema.title; + return fallback; +} + +/** + * Returns true if the schema represents an object with properties (i.e., a type that should + * be generated as a class/struct/dataclass). Returns false for enums, primitives, arrays, + * and other non-object schemas. + */ +export function isObjectSchema(schema: JSONSchema7 | null | undefined): boolean { + if (!schema) return false; + if (schema.type === "object" && schema.properties) return true; + return false; +} + +/** + * Returns true if the schema represents a void/null result (type: "null"). + * These carry a title for languages that need a named empty type (e.g., Go) + * but should be treated as void in other languages. + */ +export function isVoidSchema(schema: JSONSchema7 | null | undefined): boolean { + if (!schema) return true; + return schema.type === "null"; +} + +export function cloneSchemaForCodegen(value: T): T { + if (Array.isArray(value)) { + return value.map((item) => cloneSchemaForCodegen(item)) as T; + } + + if (value && typeof value === "object") { + const result: Record = {}; + for (const [key, child] of Object.entries(value as Record)) { + if (key === "titleSource") { + continue; + } + result[key] = cloneSchemaForCodegen(child); + } + + return result as T; + } + + return value; +} + +export function stripNonAnnotationTitles(value: T): T { + if (Array.isArray(value)) { + return value.map((item) => stripNonAnnotationTitles(item)) as T; + } + + if (value && typeof value === "object") { + const result: Record = {}; + const source = value as Record; + const keepTitle = typeof source.title === "string" && source.titleSource === "annotation"; + for (const [key, child] of Object.entries(source)) { + if (key === "titleSource") { + continue; + } + if (key === "title" && !keepTitle) { + continue; + } + result[key] = stripNonAnnotationTitles(child); + } + + return result as T; + } + + return value; +} + +export function hoistTitledSchemas( + rootDefinitions: Record +): { rootDefinitions: Record; sharedDefinitions: Record } { + const sharedDefinitions: Record = {}; + const processedRoots: Record = {}; + + for (const [rootName, definition] of Object.entries(rootDefinitions)) { + processedRoots[rootName] = visitSchema(definition, rootName, sharedDefinitions); + } + + return { rootDefinitions: processedRoots, sharedDefinitions }; +} + +function visitSchema( + schema: JSONSchema7, + rootName: string, + sharedDefinitions: Record +): JSONSchema7 { + const result: JSONSchema7 = { ...schema }; + + if (result.properties) { + result.properties = Object.fromEntries( + Object.entries(result.properties).map(([key, value]) => [ + key, + typeof value === "object" && value !== null && !Array.isArray(value) + ? visitSchema(value as JSONSchema7, rootName, sharedDefinitions) + : value, + ]) + ); + } + + if (result.items) { + if (Array.isArray(result.items)) { + result.items = result.items.map((item) => + typeof item === "object" && item !== null && !Array.isArray(item) + ? visitSchema(item as JSONSchema7, rootName, sharedDefinitions) + : item + ) as JSONSchema7Definition[]; + } else if (typeof result.items === "object" && result.items !== null) { + result.items = visitSchema(result.items as JSONSchema7, rootName, sharedDefinitions); + } + } + + if (typeof result.additionalProperties === "object" && result.additionalProperties !== null) { + result.additionalProperties = visitSchema(result.additionalProperties as JSONSchema7, rootName, sharedDefinitions); + } + + for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { + if (result[combiner]) { + result[combiner] = result[combiner]!.map((item) => + typeof item === "object" && item !== null && !Array.isArray(item) + ? visitSchema(item as JSONSchema7, rootName, sharedDefinitions) + : item + ) as JSONSchema7Definition[]; + } + } + + if (typeof result.title === "string" && result.title !== rootName) { + const existing = sharedDefinitions[result.title]; + if (existing) { + if (stableStringify(existing) !== stableStringify(result)) { + throw new Error(`Conflicting titled schemas for "${result.title}" while preparing quicktype inputs.`); + } + } else { + sharedDefinitions[result.title] = result; + } + return { $ref: `#/definitions/${result.title}`, description: result.description } as JSONSchema7; + } + + return result; +} + +function stableStringify(value: unknown): string { + return JSON.stringify(sortJsonValue(value)); +} + +function sortJsonValue(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(sortJsonValue); + } + + if (value && typeof value === "object") { + return Object.fromEntries( + Object.entries(value as Record) + .filter(([key]) => key !== "description" && key !== "titleSource") + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, child]) => [key, sortJsonValue(child)]) + ); + } + + return value; +} + export interface ApiSchema { server?: Record; session?: Record; diff --git a/test/scenarios/prompts/attachments/csharp/Program.cs b/test/scenarios/prompts/attachments/csharp/Program.cs index 357444a6f..272c89aab 100644 --- a/test/scenarios/prompts/attachments/csharp/Program.cs +++ b/test/scenarios/prompts/attachments/csharp/Program.cs @@ -24,7 +24,7 @@ Prompt = "What languages are listed in the attached file?", Attachments = [ - new UserMessageDataAttachmentsItemFile { Path = sampleFile, DisplayName = "sample-data.txt" }, + new UserMessageAttachmentFile { Path = sampleFile, DisplayName = "sample-data.txt" }, ], }); diff --git a/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml b/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml index fa5cf614a..a13727f0f 100644 --- a/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml +++ b/test/snapshots/agent_and_compact_rpc/should_compact_session_history_after_messages.yaml @@ -14,9 +14,9 @@ conversations: content: >- - The user asked a simple arithmetic question ("What is 2+2?") which was answered directly (4). No technical - work, code changes, or file modifications were requested or performed. This appears to be a minimal test or - verification exchange before any substantive work begins. + The user asked a simple arithmetic question: "What is 2+2?" This was answered directly (4). No technical work, + coding tasks, or file modifications were performed. This appears to have been a minimal test interaction with + no substantive goals or technical requirements. @@ -25,54 +25,39 @@ conversations: 1. The user asked "What is 2+2?" - Provided the answer: 4 - - No follow-up actions were needed + - No further actions were taken or requested - No work has been performed. No files were created, modified, or deleted. This was purely an informational - exchange with no code changes or system modifications. + No files were created, modified, or deleted. No technical tasks were performed. The conversation consisted + solely of answering a basic arithmetic question. - Current state: - - - Working directory: ${workdir} - - - Not a git repository - - - No files have been accessed or modified - - - No todos or plan have been created + Current state: No active work in progress. - - Environment is Windows (Windows_NT), requiring backslash path separators - - - Session workspace available for artifacts (e.g., plan.md) - - - SQL database available but not yet initialized (no tables created) - - - Available tools: git, curl, gh - - - No technical work has been performed yet, so no technical details have been discovered + No technical concepts, decisions, or issues were encountered in this conversation. This was a straightforward + arithmetic question with no technical context. - No files have been accessed or modified during this conversation. + No files were involved in this conversation. - No work is currently in progress. Awaiting user's next request for any substantive task. + No pending work or tasks. The user's question was fully addressed. Awaiting new requests or instructions. diff --git a/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml b/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml index 2b984d74c..d942e7ab1 100644 --- a/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml +++ b/test/snapshots/session_fs/should_succeed_with_compaction_while_using_sessionfs.yaml @@ -14,9 +14,9 @@ conversations: content: >- - The user asked a simple arithmetic question ("What is 2+2?"). This was a minimal interaction with no technical - work, coding tasks, or file modifications requested or performed. The conversation consisted solely of - providing a basic mathematical answer. + The user asked a simple arithmetic question ("What is 2+2?") which I answered correctly (4). No coding work, + file modifications, or technical implementation was requested or performed. This appears to be a minimal test + interaction before the conversation history is compacted. @@ -24,52 +24,46 @@ conversations: 1. The user asked "What is 2+2?" - - Provided the answer: 4 - - No further requests or actions were needed + - I provided the arithmetic answer: 4 + - No follow-up questions or additional requests were made - 2. The user requested a checkpoint summary - - Creating this summary to preserve conversation context before history compaction + 2. The user requested a detailed summary for conversation compaction + - Currently preparing this checkpoint summary - No files were created, modified, or deleted. No technical work was performed. The conversation consisted only - of answering a simple arithmetic question. + No files were created, modified, or deleted. No code changes were made. No tasks were assigned or completed + beyond answering a basic arithmetic question. - Current state: - - - No active tasks - - - No code changes - - - No systems or processes started + Current state: No active work in progress. The conversation consisted only of a single question and answer + exchange. - No technical work was performed during this conversation. No technical decisions, issues, or discoveries were - made. + No technical work was performed. No issues were encountered. No architectural decisions were made. No code was + explored or modified. - No files are relevant to this conversation, as no technical work was performed. + No files were accessed or are relevant to this conversation. - No pending work or next steps. The user's request (answering "2+2") has been completed. Awaiting further - instructions from the user. + No pending work. No tasks were assigned. The user may continue with new requests after the history compaction. - Simple arithmetic question answered + Answered arithmetic question From 672274331b539e629cd4ae660a21d670433720aa Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 14 Apr 2026 11:30:24 -0400 Subject: [PATCH 14/34] Generate dedicated Python session event types (#1063) * Generate dedicated Python session event types Align Python session event generation with the newer Go-style dedicated per-event payload model instead of the old merged quicktype Data shape. This updates the runtime/tests for typed payloads while preserving compatibility aliases and legacy Data behavior for existing callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review and CI follow-ups Fix the generated Python docstring escaping that CodeQL flagged, correct dotted-key normalization in the Data compatibility shim, update the stale Go local-cli docs snippet for the newer typed event API, and apply the Python formatter change required by CI. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Clarify typed Python event examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align Python generator formats Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix SDK test formatting Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use runtime checks in Python README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use isinstance in Python event examples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use isinstance for broadcast events Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Use match for Python event data Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python SDK Ruff failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Remove Python session event shims Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve Python event helper types Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Node.js lint and generated file drift Normalize trailing whitespace in pyDocstringLiteral to prevent cross-platform codegen drift, and reformat test file with Prettier. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Regenerate Python events and fix doc example - Regenerate session_events.py to match latest schema (removes session.import_legacy, adds reasoning_tokens to AssistantUsageData) - Update docs/setup/local-cli.md Python example to use match-based type narrowing with None check, consistent with python/README.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/setup/local-cli.md | 18 +- nodejs/test/python-codegen.test.ts | 329 + python/README.md | 59 +- python/copilot/client.py | 6 +- python/copilot/generated/session_events.py | 6389 ++++++++++------- python/copilot/session.py | 198 +- python/e2e/test_permissions.py | 53 +- python/e2e/test_session.py | 15 +- python/e2e/test_session_fs.py | 9 +- .../e2e/test_ui_elicitation_multi_client.py | 30 +- python/e2e/testharness/helper.py | 37 +- python/samples/chat.py | 21 +- python/test_commands_and_elicitation.py | 27 +- python/test_event_forward_compatibility.py | 50 +- scripts/codegen/python.ts | 1092 ++- 15 files changed, 5305 insertions(+), 3028 deletions(-) create mode 100644 nodejs/test/python-codegen.test.ts diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index 845a20af5..0e2d11020 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -54,6 +54,7 @@ await client.stop(); ```python from copilot import CopilotClient +from copilot.generated.session_events import AssistantMessageData from copilot.session import PermissionHandler client = CopilotClient({ @@ -63,7 +64,10 @@ await client.start() session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-4.1") response = await session.send_and_wait("Hello!") -print(response.data.content) +if response: + match response.data: + case AssistantMessageData() as data: + print(data.content) await client.stop() ``` @@ -99,8 +103,10 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) + if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } } ``` @@ -117,8 +123,10 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -if d, ok := response.Data.(*copilot.AssistantMessageData); ok { - fmt.Println(d.Content) +if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` diff --git a/nodejs/test/python-codegen.test.ts b/nodejs/test/python-codegen.test.ts new file mode 100644 index 000000000..4032ce2cc --- /dev/null +++ b/nodejs/test/python-codegen.test.ts @@ -0,0 +1,329 @@ +import type { JSONSchema7 } from "json-schema"; +import { describe, expect, it } from "vitest"; + +import { generatePythonSessionEventsCode } from "../../scripts/codegen/python.ts"; + +describe("python session event codegen", () => { + it("maps special schema formats to the expected Python types", () => { + const schema: JSONSchema7 = { + definitions: { + SessionEvent: { + anyOf: [ + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "session.synthetic" }, + data: { + type: "object", + required: [ + "at", + "identifier", + "duration", + "integerDuration", + "uri", + "pattern", + "payload", + "encoded", + "count", + ], + properties: { + at: { type: "string", format: "date-time" }, + identifier: { type: "string", format: "uuid" }, + duration: { type: "number", format: "duration" }, + integerDuration: { type: "integer", format: "duration" }, + optionalDuration: { + type: ["number", "null"], + format: "duration", + }, + action: { + type: "string", + enum: ["store", "vote"], + default: "store", + }, + summary: { type: "string", default: "" }, + uri: { type: "string", format: "uri" }, + pattern: { type: "string", format: "regex" }, + payload: { type: "string", format: "byte" }, + encoded: { type: "string", contentEncoding: "base64" }, + count: { type: "integer" }, + }, + }, + }, + }, + ], + }, + }, + }; + + const code = generatePythonSessionEventsCode(schema); + + expect(code).toContain("from datetime import datetime, timedelta"); + expect(code).toContain("at: datetime"); + expect(code).toContain("identifier: UUID"); + expect(code).toContain("duration: timedelta"); + expect(code).toContain("integer_duration: timedelta"); + expect(code).toContain("optional_duration: timedelta | None = None"); + expect(code).toContain('duration = from_timedelta(obj.get("duration"))'); + expect(code).toContain('result["duration"] = to_timedelta(self.duration)'); + expect(code).toContain( + 'result["integerDuration"] = to_timedelta_int(self.integer_duration)' + ); + expect(code).toContain("def to_timedelta_int(x: timedelta) -> int:"); + expect(code).toContain( + 'action = from_union([from_none, lambda x: parse_enum(SessionSyntheticDataAction, x)], obj.get("action", "store"))' + ); + expect(code).toContain( + 'summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary", ""))' + ); + expect(code).toContain("uri: str"); + expect(code).toContain("pattern: str"); + expect(code).toContain("payload: str"); + expect(code).toContain("encoded: str"); + expect(code).toContain("count: int"); + }); + + it("preserves key shortened nested type names", () => { + const schema: JSONSchema7 = { + definitions: { + SessionEvent: { + anyOf: [ + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "permission.requested" }, + data: { + type: "object", + required: ["requestId", "permissionRequest"], + properties: { + requestId: { type: "string" }, + permissionRequest: { + anyOf: [ + { + type: "object", + required: [ + "kind", + "fullCommandText", + "intention", + "commands", + "possiblePaths", + "possibleUrls", + "hasWriteFileRedirection", + "canOfferSessionApproval", + ], + properties: { + kind: { const: "shell", type: "string" }, + fullCommandText: { type: "string" }, + intention: { type: "string" }, + commands: { + type: "array", + items: { + type: "object", + required: [ + "identifier", + "readOnly", + ], + properties: { + identifier: { type: "string" }, + readOnly: { type: "boolean" }, + }, + }, + }, + possiblePaths: { + type: "array", + items: { type: "string" }, + }, + possibleUrls: { + type: "array", + items: { + type: "object", + required: ["url"], + properties: { + url: { type: "string" }, + }, + }, + }, + hasWriteFileRedirection: { + type: "boolean", + }, + canOfferSessionApproval: { + type: "boolean", + }, + }, + }, + { + type: "object", + required: ["kind", "fact"], + properties: { + kind: { const: "memory", type: "string" }, + fact: { type: "string" }, + action: { + type: "string", + enum: ["store", "vote"], + default: "store", + }, + direction: { + type: "string", + enum: ["upvote", "downvote"], + }, + }, + }, + ], + }, + }, + }, + }, + }, + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "elicitation.requested" }, + data: { + type: "object", + properties: { + requestedSchema: { + type: "object", + required: ["type", "properties"], + properties: { + type: { const: "object", type: "string" }, + properties: { + type: "object", + additionalProperties: {}, + }, + }, + }, + mode: { + type: "string", + enum: ["form", "url"], + }, + }, + }, + }, + }, + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "capabilities.changed" }, + data: { + type: "object", + properties: { + ui: { + type: "object", + properties: { + elicitation: { type: "boolean" }, + }, + }, + }, + }, + }, + }, + ], + }, + }, + }; + + const code = generatePythonSessionEventsCode(schema); + + expect(code).toContain("class PermissionRequest:"); + expect(code).toContain("class PermissionRequestShellCommand:"); + expect(code).toContain("class PermissionRequestShellPossibleURL:"); + expect(code).toContain("class PermissionRequestMemoryAction(Enum):"); + expect(code).toContain("class PermissionRequestMemoryDirection(Enum):"); + expect(code).toContain("class ElicitationRequestedSchema:"); + expect(code).toContain("class ElicitationRequestedMode(Enum):"); + expect(code).toContain("class CapabilitiesChangedUI:"); + expect(code).not.toContain("class PermissionRequestedDataPermissionRequest:"); + expect(code).not.toContain("class ElicitationRequestedDataRequestedSchema:"); + expect(code).not.toContain("class CapabilitiesChangedDataUi:"); + }); + + it("keeps distinct enum types even when they share the same values", () => { + const schema: JSONSchema7 = { + definitions: { + SessionEvent: { + anyOf: [ + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "assistant.message" }, + data: { + type: "object", + properties: { + toolRequests: { + type: "array", + items: { + type: "object", + required: ["toolCallId", "name", "type"], + properties: { + toolCallId: { type: "string" }, + name: { type: "string" }, + type: { + type: "string", + enum: ["function", "custom"], + }, + }, + }, + }, + }, + }, + }, + }, + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "session.import_legacy" }, + data: { + type: "object", + properties: { + legacySession: { + type: "object", + properties: { + chatMessages: { + type: "array", + items: { + type: "object", + properties: { + toolCalls: { + type: "array", + items: { + type: "object", + properties: { + type: { + type: "string", + enum: [ + "function", + "custom", + ], + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ], + }, + }, + }; + + const code = generatePythonSessionEventsCode(schema); + + expect(code).toContain("class AssistantMessageToolRequestType(Enum):"); + expect(code).toContain("type: AssistantMessageToolRequestType"); + expect(code).toContain("parse_enum(AssistantMessageToolRequestType,"); + expect(code).toContain( + "class SessionImportLegacyDataLegacySessionChatMessagesItemToolCallsItemType(Enum):" + ); + }); +}); diff --git a/python/README.md b/python/README.md index a023c6102..b65b14736 100644 --- a/python/README.md +++ b/python/README.md @@ -25,8 +25,9 @@ python chat.py ```python import asyncio + from copilot import CopilotClient -from copilot.session import PermissionHandler +from copilot.generated.session_events import AssistantMessageData, SessionIdleData async def main(): # Client automatically starts on enter and cleans up on exit @@ -37,10 +38,11 @@ async def main(): done = asyncio.Event() def on_event(event): - if event.type.value == "assistant.message": - print(event.data.content) - elif event.type.value == "session.idle": - done.set() + match event.data: + case AssistantMessageData() as data: + print(data.content) + case SessionIdleData(): + done.set() session.on(on_event) @@ -57,7 +59,9 @@ If you need more control over the lifecycle, you can call `start()`, `stop()`, a ```python import asyncio + from copilot import CopilotClient +from copilot.generated.session_events import AssistantMessageData, SessionIdleData from copilot.session import PermissionHandler async def main(): @@ -73,10 +77,11 @@ async def main(): done = asyncio.Event() def on_event(event): - if event.type.value == "assistant.message": - print(event.data.content) - elif event.type.value == "session.idle": - done.set() + match event.data: + case AssistantMessageData() as data: + print(data.content) + case SessionIdleData(): + done.set() session.on(on_event) await session.send("What is 2+2?") @@ -333,7 +338,15 @@ Enable streaming to receive assistant response chunks as they're generated: ```python import asyncio + from copilot import CopilotClient +from copilot.generated.session_events import ( + AssistantMessageData, + AssistantMessageDeltaData, + AssistantReasoningData, + AssistantReasoningDeltaData, + SessionIdleData, +) from copilot.session import PermissionHandler async def main(): @@ -347,24 +360,24 @@ async def main(): done = asyncio.Event() def on_event(event): - match event.type.value: - case "assistant.message_delta": + match event.data: + case AssistantMessageDeltaData() as data: # Streaming message chunk - print incrementally - delta = event.data.delta_content or "" + delta = data.delta_content or "" print(delta, end="", flush=True) - case "assistant.reasoning_delta": + case AssistantReasoningDeltaData() as data: # Streaming reasoning chunk (if model supports reasoning) - delta = event.data.delta_content or "" + delta = data.delta_content or "" print(delta, end="", flush=True) - case "assistant.message": + case AssistantMessageData() as data: # Final message - complete content print("\n--- Final message ---") - print(event.data.content) - case "assistant.reasoning": + print(data.content) + case AssistantReasoningData() as data: # Final reasoning content (if model supports reasoning) print("--- Reasoning ---") - print(event.data.content) - case "session.idle": + print(data.content) + case SessionIdleData(): # Session finished processing done.set() @@ -547,7 +560,9 @@ Provide your own function to inspect each request and apply custom logic (sync o from copilot.session import PermissionRequestResult from copilot.generated.session_events import PermissionRequest -def on_permission_request(request: PermissionRequest, invocation: dict) -> PermissionRequestResult: +def on_permission_request( + request: PermissionRequest, invocation: dict +) -> PermissionRequestResult: # request.kind — what type of operation is being requested: # "shell" — executing a shell command # "write" — writing or editing a file @@ -577,7 +592,9 @@ session = await client.create_session( Async handlers are also supported: ```python -async def on_permission_request(request: PermissionRequest, invocation: dict) -> PermissionRequestResult: +async def on_permission_request( + request: PermissionRequest, invocation: dict +) -> PermissionRequestResult: # Simulate an async approval check (e.g., prompting a user over a network) await asyncio.sleep(0) return PermissionRequestResult(kind="approved") diff --git a/python/copilot/client.py b/python/copilot/client.py index c47acdf14..f59816d6e 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -37,7 +37,11 @@ ServerRpc, register_client_session_api_handlers, ) -from .generated.session_events import PermissionRequest, SessionEvent, session_event_from_dict +from .generated.session_events import ( + PermissionRequest, + SessionEvent, + session_event_from_dict, +) from .session import ( CommandDefinition, CopilotSession, diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 2c1dbffb6..400883850 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -3,13 +3,16 @@ Generated from: session-events.schema.json """ -from enum import Enum +from __future__ import annotations + +from collections.abc import Callable from dataclasses import dataclass -from typing import Any, TypeVar, Callable, cast from datetime import datetime +from enum import Enum +from typing import Any, TypeVar, cast from uuid import UUID -import dateutil.parser +import dateutil.parser T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) @@ -20,9 +23,24 @@ def from_str(x: Any) -> str: return x -def from_list(f: Callable[[Any], T], x: Any) -> list[T]: - assert isinstance(x, list) - return [f(y) for y in x] +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def to_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def to_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) def from_bool(x: Any) -> bool: @@ -35,7 +53,7 @@ def from_none(x: Any) -> Any: return x -def from_union(fs, x): +def from_union(fs: list[Callable[[Any], T]], x: Any) -> T: for f in fs: try: return f(x) @@ -44,14 +62,35 @@ def from_union(fs, x): assert False -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) +def from_list(f: Callable[[Any], T], x: Any) -> list[T]: + assert isinstance(x, list) + return [f(item) for item in x] -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) - return x +def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: + assert isinstance(x, dict) + return {key: f(value) for key, value in x.items()} + + +def from_datetime(x: Any) -> datetime: + return dateutil.parser.parse(from_str(x)) + + +def to_datetime(x: datetime) -> str: + return x.isoformat() + + +def from_uuid(x: Any) -> UUID: + return UUID(from_str(x)) + + +def to_uuid(x: UUID) -> str: + return str(x) + + +def parse_enum(c: type[EnumT], x: Any) -> EnumT: + assert isinstance(x, str) + return c(x) def to_class(c: type[T], x: Any) -> dict: @@ -59,792 +98,867 @@ def to_class(c: type[T], x: Any) -> dict: return cast(Any, x).to_dict() -def to_enum(c: type[EnumT], x: Any) -> EnumT: +def to_enum(c: type[EnumT], x: Any) -> str: assert isinstance(x, c) - return x.value + return cast(str, x.value) -def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: - assert isinstance(x, dict) - return { k: f(v) for (k, v) in x.items() } +class SessionEventType(Enum): + SESSION_START = "session.start" + SESSION_RESUME = "session.resume" + SESSION_REMOTE_STEERABLE_CHANGED = "session.remote_steerable_changed" + SESSION_ERROR = "session.error" + SESSION_IDLE = "session.idle" + SESSION_TITLE_CHANGED = "session.title_changed" + SESSION_INFO = "session.info" + SESSION_WARNING = "session.warning" + SESSION_MODEL_CHANGE = "session.model_change" + SESSION_MODE_CHANGED = "session.mode_changed" + SESSION_PLAN_CHANGED = "session.plan_changed" + SESSION_WORKSPACE_FILE_CHANGED = "session.workspace_file_changed" + SESSION_HANDOFF = "session.handoff" + SESSION_TRUNCATION = "session.truncation" + SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" + SESSION_SHUTDOWN = "session.shutdown" + SESSION_CONTEXT_CHANGED = "session.context_changed" + SESSION_USAGE_INFO = "session.usage_info" + SESSION_COMPACTION_START = "session.compaction_start" + SESSION_COMPACTION_COMPLETE = "session.compaction_complete" + SESSION_TASK_COMPLETE = "session.task_complete" + USER_MESSAGE = "user.message" + PENDING_MESSAGES_MODIFIED = "pending_messages.modified" + ASSISTANT_TURN_START = "assistant.turn_start" + ASSISTANT_INTENT = "assistant.intent" + ASSISTANT_REASONING = "assistant.reasoning" + ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" + ASSISTANT_STREAMING_DELTA = "assistant.streaming_delta" + ASSISTANT_MESSAGE = "assistant.message" + ASSISTANT_MESSAGE_DELTA = "assistant.message_delta" + ASSISTANT_TURN_END = "assistant.turn_end" + ASSISTANT_USAGE = "assistant.usage" + ABORT = "abort" + TOOL_USER_REQUESTED = "tool.user_requested" + TOOL_EXECUTION_START = "tool.execution_start" + TOOL_EXECUTION_PARTIAL_RESULT = "tool.execution_partial_result" + TOOL_EXECUTION_PROGRESS = "tool.execution_progress" + TOOL_EXECUTION_COMPLETE = "tool.execution_complete" + SKILL_INVOKED = "skill.invoked" + SUBAGENT_STARTED = "subagent.started" + SUBAGENT_COMPLETED = "subagent.completed" + SUBAGENT_FAILED = "subagent.failed" + SUBAGENT_SELECTED = "subagent.selected" + SUBAGENT_DESELECTED = "subagent.deselected" + HOOK_START = "hook.start" + HOOK_END = "hook.end" + SYSTEM_MESSAGE = "system.message" + SYSTEM_NOTIFICATION = "system.notification" + PERMISSION_REQUESTED = "permission.requested" + PERMISSION_COMPLETED = "permission.completed" + USER_INPUT_REQUESTED = "user_input.requested" + USER_INPUT_COMPLETED = "user_input.completed" + ELICITATION_REQUESTED = "elicitation.requested" + ELICITATION_COMPLETED = "elicitation.completed" + SAMPLING_REQUESTED = "sampling.requested" + SAMPLING_COMPLETED = "sampling.completed" + MCP_OAUTH_REQUIRED = "mcp.oauth_required" + MCP_OAUTH_COMPLETED = "mcp.oauth_completed" + EXTERNAL_TOOL_REQUESTED = "external_tool.requested" + EXTERNAL_TOOL_COMPLETED = "external_tool.completed" + COMMAND_QUEUED = "command.queued" + COMMAND_EXECUTE = "command.execute" + COMMAND_COMPLETED = "command.completed" + COMMANDS_CHANGED = "commands.changed" + CAPABILITIES_CHANGED = "capabilities.changed" + EXIT_PLAN_MODE_REQUESTED = "exit_plan_mode.requested" + EXIT_PLAN_MODE_COMPLETED = "exit_plan_mode.completed" + SESSION_TOOLS_UPDATED = "session.tools_updated" + SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" + SESSION_SKILLS_LOADED = "session.skills_loaded" + SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" + SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" + SESSION_MCP_SERVER_STATUS_CHANGED = "session.mcp_server_status_changed" + SESSION_EXTENSIONS_LOADED = "session.extensions_loaded" + UNKNOWN = "unknown" + + @classmethod + def _missing_(cls, value: object) -> "SessionEventType": + return cls.UNKNOWN -def from_datetime(x: Any) -> datetime: - return dateutil.parser.parse(x) +@dataclass +class RawSessionEventData: + raw: Any + @staticmethod + def from_dict(obj: Any) -> "RawSessionEventData": + return RawSessionEventData(obj) -def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x + def to_dict(self) -> Any: + return self.raw -class ElicitationCompletedAction(Enum): - """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" - (dismissed) - """ - ACCEPT = "accept" - CANCEL = "cancel" - DECLINE = "decline" +def _compat_to_python_key(name: str) -> str: + normalized = name.replace(".", "_") + result: list[str] = [] + for index, char in enumerate(normalized): + if char.isupper() and index > 0 and (not normalized[index - 1].isupper() or (index + 1 < len(normalized) and normalized[index + 1].islower())): + result.append("_") + result.append(char.lower()) + return "".join(result) -class UserMessageAgentMode(Enum): - """The agent mode that was active when this message was sent""" +def _compat_to_json_key(name: str) -> str: + parts = name.split("_") + if not parts: + return name + return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:]) - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" - SHELL = "shell" +def _compat_to_json_value(value: Any) -> Any: + if hasattr(value, "to_dict"): + return cast(Any, value).to_dict() + if isinstance(value, Enum): + return value.value + if isinstance(value, datetime): + return value.isoformat() + if isinstance(value, UUID): + return str(value) + if isinstance(value, list): + return [_compat_to_json_value(item) for item in value] + if isinstance(value, dict): + return {key: _compat_to_json_value(item) for key, item in value.items()} + return value -@dataclass -class CustomAgentsUpdatedAgent: - description: str - """Description of what the agent does""" - display_name: str - """Human-readable display name""" +def _compat_from_json_value(value: Any) -> Any: + return value - id: str - """Unique identifier for the agent""" - name: str - """Internal name of the agent""" +class Data: + """Backward-compatible shim for manually constructed event payloads.""" - source: str - """Source location: user, project, inherited, remote, or plugin""" + def __init__(self, **kwargs: Any): + self._values = {key: _compat_from_json_value(value) for key, value in kwargs.items()} + for key, value in self._values.items(): + setattr(self, key, value) - tools: list[str] - """List of tool names available to this agent""" + @staticmethod + def from_dict(obj: Any) -> "Data": + assert isinstance(obj, dict) + return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()}) - user_invocable: bool - """Whether the agent can be selected by the user""" + def to_dict(self) -> dict: + return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None} - model: str | None = None - """Model override for this agent, if set""" + +@dataclass +class SessionStartDataContext: + "Working directory and git context at session start" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionStartDataContextHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'CustomAgentsUpdatedAgent': + def from_dict(obj: Any) -> "SessionStartDataContext": assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - tools = from_list(from_str, obj.get("tools")) - user_invocable = from_bool(obj.get("userInvocable")) - model = from_union([from_str, from_none], obj.get("model")) - return CustomAgentsUpdatedAgent(description, display_name, id, name, source, tools, user_invocable, model) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionStartDataContext( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["tools"] = from_list(from_str, self.tools) - result["userInvocable"] = from_bool(self.user_invocable) - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result @dataclass -class UserMessageAttachmentFileLineRange: - """Optional line range to scope the attachment to a specific section of the file""" - - end: float - """End line number (1-based, inclusive)""" - - start: float - """Start line number (1-based)""" +class SessionStartData: + "Session initialization metadata including context and configuration" + session_id: str + version: float + producer: str + copilot_version: str + start_time: datetime + selected_model: str | None = None + reasoning_effort: str | None = None + context: SessionStartDataContext | None = None + already_in_use: bool | None = None + remote_steerable: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'UserMessageAttachmentFileLineRange': + def from_dict(obj: Any) -> "SessionStartData": assert isinstance(obj, dict) - end = from_float(obj.get("end")) - start = from_float(obj.get("start")) - return UserMessageAttachmentFileLineRange(end, start) + session_id = from_str(obj.get("sessionId")) + version = from_float(obj.get("version")) + producer = from_str(obj.get("producer")) + copilot_version = from_str(obj.get("copilotVersion")) + start_time = from_datetime(obj.get("startTime")) + selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + context = from_union([from_none, lambda x: SessionStartDataContext.from_dict(x)], obj.get("context")) + already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + return SessionStartData( + session_id=session_id, + version=version, + producer=producer, + copilot_version=copilot_version, + start_time=start_time, + selected_model=selected_model, + reasoning_effort=reasoning_effort, + context=context, + already_in_use=already_in_use, + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["end"] = to_float(self.end) - result["start"] = to_float(self.start) + result["sessionId"] = from_str(self.session_id) + result["version"] = to_float(self.version) + result["producer"] = from_str(self.producer) + result["copilotVersion"] = from_str(self.copilot_version) + result["startTime"] = to_datetime(self.start_time) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(SessionStartDataContext, x)], self.context) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) return result -class UserMessageAttachmentGithubReferenceType(Enum): - """Type of GitHub reference""" - - DISCUSSION = "discussion" - ISSUE = "issue" - PR = "pr" - - @dataclass -class UserMessageAttachmentSelectionDetailsEnd: - """End position of the selection""" - - character: float - """End character offset within the line (0-based)""" - - line: float - """End line number (0-based)""" +class SessionResumeDataContext: + "Updated working directory and git context at resume time" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionResumeDataContextHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetailsEnd': + def from_dict(obj: Any) -> "SessionResumeDataContext": assert isinstance(obj, dict) - character = from_float(obj.get("character")) - line = from_float(obj.get("line")) - return UserMessageAttachmentSelectionDetailsEnd(character, line) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionResumeDataContextHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionResumeDataContext( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["character"] = to_float(self.character) - result["line"] = to_float(self.line) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionResumeDataContextHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result @dataclass -class UserMessageAttachmentSelectionDetailsStart: - """Start position of the selection""" - - character: float - """Start character offset within the line (0-based)""" - - line: float - """Start line number (0-based)""" +class SessionResumeData: + "Session resume metadata including current context and event count" + resume_time: datetime + event_count: float + selected_model: str | None = None + reasoning_effort: str | None = None + context: SessionResumeDataContext | None = None + already_in_use: bool | None = None + remote_steerable: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetailsStart': + def from_dict(obj: Any) -> "SessionResumeData": assert isinstance(obj, dict) - character = from_float(obj.get("character")) - line = from_float(obj.get("line")) - return UserMessageAttachmentSelectionDetailsStart(character, line) + resume_time = from_datetime(obj.get("resumeTime")) + event_count = from_float(obj.get("eventCount")) + selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + context = from_union([from_none, lambda x: SessionResumeDataContext.from_dict(x)], obj.get("context")) + already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + return SessionResumeData( + resume_time=resume_time, + event_count=event_count, + selected_model=selected_model, + reasoning_effort=reasoning_effort, + context=context, + already_in_use=already_in_use, + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["character"] = to_float(self.character) - result["line"] = to_float(self.line) + result["resumeTime"] = to_datetime(self.resume_time) + result["eventCount"] = to_float(self.event_count) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(SessionResumeDataContext, x)], self.context) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) return result @dataclass -class UserMessageAttachmentSelectionDetails: - """Position range of the selection within the file""" - - end: UserMessageAttachmentSelectionDetailsEnd - """End position of the selection""" - - start: UserMessageAttachmentSelectionDetailsStart - """Start position of the selection""" +class SessionRemoteSteerableChangedData: + "Notifies Mission Control that the session's remote steering capability has changed" + remote_steerable: bool @staticmethod - def from_dict(obj: Any) -> 'UserMessageAttachmentSelectionDetails': + def from_dict(obj: Any) -> "SessionRemoteSteerableChangedData": assert isinstance(obj, dict) - end = UserMessageAttachmentSelectionDetailsEnd.from_dict(obj.get("end")) - start = UserMessageAttachmentSelectionDetailsStart.from_dict(obj.get("start")) - return UserMessageAttachmentSelectionDetails(end, start) + remote_steerable = from_bool(obj.get("remoteSteerable")) + return SessionRemoteSteerableChangedData( + remote_steerable=remote_steerable, + ) def to_dict(self) -> dict: result: dict = {} - result["end"] = to_class(UserMessageAttachmentSelectionDetailsEnd, self.end) - result["start"] = to_class(UserMessageAttachmentSelectionDetailsStart, self.start) + result["remoteSteerable"] = from_bool(self.remote_steerable) return result -class UserMessageAttachmentType(Enum): - BLOB = "blob" - DIRECTORY = "directory" - FILE = "file" - GITHUB_REFERENCE = "github_reference" - SELECTION = "selection" - - @dataclass -class UserMessageAttachment: - """A user message attachment — a file, directory, code selection, blob, or GitHub reference - - File attachment - - Directory attachment - - Code selection attachment from an editor - - GitHub issue, pull request, or discussion reference - - Blob attachment with inline base64-encoded data - """ - type: UserMessageAttachmentType - """Attachment type discriminator""" - - display_name: str | None = None - """User-facing display name for the attachment - - User-facing display name for the selection - """ - line_range: UserMessageAttachmentFileLineRange | None = None - """Optional line range to scope the attachment to a specific section of the file""" - - path: str | None = None - """Absolute file path - - Absolute directory path - """ - file_path: str | None = None - """Absolute path to the file containing the selection""" - - selection: UserMessageAttachmentSelectionDetails | None = None - """Position range of the selection within the file""" - - text: str | None = None - """The selected text content""" - - number: float | None = None - """Issue, pull request, or discussion number""" - - reference_type: UserMessageAttachmentGithubReferenceType | None = None - """Type of GitHub reference""" - - state: str | None = None - """Current state of the referenced item (e.g., open, closed, merged)""" - - title: str | None = None - """Title of the referenced item""" - +class SessionErrorData: + "Error details for timeline display including message and optional diagnostic information" + error_type: str + message: str + stack: str | None = None + status_code: int | None = None + provider_call_id: str | None = None url: str | None = None - """URL to the referenced item on GitHub""" - - data: str | None = None - """Base64-encoded content""" - - mime_type: str | None = None - """MIME type of the inline data""" @staticmethod - def from_dict(obj: Any) -> 'UserMessageAttachment': + def from_dict(obj: Any) -> "SessionErrorData": assert isinstance(obj, dict) - type = UserMessageAttachmentType(obj.get("type")) - display_name = from_union([from_str, from_none], obj.get("displayName")) - line_range = from_union([UserMessageAttachmentFileLineRange.from_dict, from_none], obj.get("lineRange")) - path = from_union([from_str, from_none], obj.get("path")) - file_path = from_union([from_str, from_none], obj.get("filePath")) - selection = from_union([UserMessageAttachmentSelectionDetails.from_dict, from_none], obj.get("selection")) - text = from_union([from_str, from_none], obj.get("text")) - number = from_union([from_float, from_none], obj.get("number")) - reference_type = from_union([UserMessageAttachmentGithubReferenceType, from_none], obj.get("referenceType")) - state = from_union([from_str, from_none], obj.get("state")) - title = from_union([from_str, from_none], obj.get("title")) - url = from_union([from_str, from_none], obj.get("url")) - data = from_union([from_str, from_none], obj.get("data")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - return UserMessageAttachment(type, display_name, line_range, path, file_path, selection, text, number, reference_type, state, title, url, data, mime_type) + error_type = from_str(obj.get("errorType")) + message = from_str(obj.get("message")) + stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + status_code = from_union([from_none, lambda x: from_int(x)], obj.get("statusCode")) + provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionErrorData( + error_type=error_type, + message=message, + stack=stack, + status_code=status_code, + provider_call_id=provider_call_id, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(UserMessageAttachmentType, self.type) - if self.display_name is not None: - result["displayName"] = from_union([from_str, from_none], self.display_name) - if self.line_range is not None: - result["lineRange"] = from_union([lambda x: to_class(UserMessageAttachmentFileLineRange, x), from_none], self.line_range) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.file_path is not None: - result["filePath"] = from_union([from_str, from_none], self.file_path) - if self.selection is not None: - result["selection"] = from_union([lambda x: to_class(UserMessageAttachmentSelectionDetails, x), from_none], self.selection) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.number is not None: - result["number"] = from_union([to_float, from_none], self.number) - if self.reference_type is not None: - result["referenceType"] = from_union([lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x), from_none], self.reference_type) - if self.state is not None: - result["state"] = from_union([from_str, from_none], self.state) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["errorType"] = from_str(self.error_type) + result["message"] = from_str(self.message) + if self.stack is not None: + result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + if self.status_code is not None: + result["statusCode"] = from_union([from_none, lambda x: to_int(x)], self.status_code) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.data is not None: - result["data"] = from_union([from_str, from_none], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result @dataclass -class ShutdownCodeChanges: - """Aggregate code change metrics for the session""" - - files_modified: list[str] - """List of file paths that were modified during the session""" - - lines_added: float - """Total number of lines added during the session""" - - lines_removed: float - """Total number of lines removed during the session""" +class SessionIdleData: + "Payload indicating the session is idle with no background agents in flight" + aborted: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'ShutdownCodeChanges': + def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) - files_modified = from_list(from_str, obj.get("filesModified")) - lines_added = from_float(obj.get("linesAdded")) - lines_removed = from_float(obj.get("linesRemoved")) - return ShutdownCodeChanges(files_modified, lines_added, lines_removed) + aborted = from_union([from_none, lambda x: from_bool(x)], obj.get("aborted")) + return SessionIdleData( + aborted=aborted, + ) def to_dict(self) -> dict: result: dict = {} - result["filesModified"] = from_list(from_str, self.files_modified) - result["linesAdded"] = to_float(self.lines_added) - result["linesRemoved"] = to_float(self.lines_removed) + if self.aborted is not None: + result["aborted"] = from_union([from_none, lambda x: from_bool(x)], self.aborted) return result @dataclass -class CommandsChangedCommand: - name: str - description: str | None = None +class SessionTitleChangedData: + "Session title change payload containing the new display title" + title: str @staticmethod - def from_dict(obj: Any) -> 'CommandsChangedCommand': + def from_dict(obj: Any) -> "SessionTitleChangedData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - description = from_union([from_str, from_none], obj.get("description")) - return CommandsChangedCommand(name, description) + title = from_str(obj.get("title")) + return SessionTitleChangedData( + title=title, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) + result["title"] = from_str(self.title) return result @dataclass -class CompactionCompleteCompactionTokensUsed: - """Token usage breakdown for the compaction LLM call""" - - cached_input: float - """Cached input tokens reused in the compaction LLM call""" - - input: float - """Input tokens consumed by the compaction LLM call""" - - output: float - """Output tokens produced by the compaction LLM call""" +class SessionInfoData: + "Informational message for timeline display with categorization" + info_type: str + message: str + url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'CompactionCompleteCompactionTokensUsed': + def from_dict(obj: Any) -> "SessionInfoData": assert isinstance(obj, dict) - cached_input = from_float(obj.get("cachedInput")) - input = from_float(obj.get("input")) - output = from_float(obj.get("output")) - return CompactionCompleteCompactionTokensUsed(cached_input, input, output) + info_type = from_str(obj.get("infoType")) + message = from_str(obj.get("message")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionInfoData( + info_type=info_type, + message=message, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["cachedInput"] = to_float(self.cached_input) - result["input"] = to_float(self.input) - result["output"] = to_float(self.output) + result["infoType"] = from_str(self.info_type) + result["message"] = from_str(self.message) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result -class ContextChangedHostType(Enum): - """Hosting platform type of the repository (github or ado)""" - - ADO = "ado" - GITHUB = "github" - - @dataclass -class Context: - """Working directory and git context at session start - - Updated working directory and git context at resume time - """ - cwd: str - """Current working directory path""" - - base_commit: str | None = None - """Base commit of current git branch at session start time""" - - branch: str | None = None - """Current git branch name""" - - git_root: str | None = None - """Root directory of the git repository, resolved via git rev-parse""" - - head_commit: str | None = None - """Head commit of current git branch at session start time""" - - host_type: ContextChangedHostType | None = None - """Hosting platform type of the repository (github or ado)""" - - repository: str | None = None - """Repository identifier derived from the git remote URL ("owner/name" for GitHub, - "org/project/repo" for Azure DevOps) - """ +class SessionWarningData: + "Warning message for timeline display with categorization" + warning_type: str + message: str + url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'Context': + def from_dict(obj: Any) -> "SessionWarningData": assert isinstance(obj, dict) - cwd = from_str(obj.get("cwd")) - base_commit = from_union([from_str, from_none], obj.get("baseCommit")) - branch = from_union([from_str, from_none], obj.get("branch")) - git_root = from_union([from_str, from_none], obj.get("gitRoot")) - head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([ContextChangedHostType, from_none], obj.get("hostType")) - repository = from_union([from_str, from_none], obj.get("repository")) - return Context(cwd, base_commit, branch, git_root, head_commit, host_type, repository) + warning_type = from_str(obj.get("warningType")) + message = from_str(obj.get("message")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return SessionWarningData( + warning_type=warning_type, + message=message, + url=url, + ) def to_dict(self) -> dict: result: dict = {} - result["cwd"] = from_str(self.cwd) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_str, from_none], self.base_commit) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.git_root is not None: - result["gitRoot"] = from_union([from_str, from_none], self.git_root) - if self.head_commit is not None: - result["headCommit"] = from_union([from_str, from_none], self.head_commit) - if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(ContextChangedHostType, x), from_none], self.host_type) - if self.repository is not None: - result["repository"] = from_union([from_str, from_none], self.repository) + result["warningType"] = from_str(self.warning_type) + result["message"] = from_str(self.message) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) return result @dataclass -class AssistantUsageCopilotUsageTokenDetail: - """Token usage detail for a single billing category""" - - batch_size: float - """Number of tokens in this billing batch""" - - cost_per_batch: float - """Cost per batch of tokens""" - - token_count: float - """Total token count for this entry""" - - token_type: str - """Token category (e.g., "input", "output")""" +class SessionModelChangeData: + "Model change details including previous and new model identifiers" + new_model: str + previous_model: str | None = None + previous_reasoning_effort: str | None = None + reasoning_effort: str | None = None @staticmethod - def from_dict(obj: Any) -> 'AssistantUsageCopilotUsageTokenDetail': + def from_dict(obj: Any) -> "SessionModelChangeData": assert isinstance(obj, dict) - batch_size = from_float(obj.get("batchSize")) - cost_per_batch = from_float(obj.get("costPerBatch")) - token_count = from_float(obj.get("tokenCount")) - token_type = from_str(obj.get("tokenType")) - return AssistantUsageCopilotUsageTokenDetail(batch_size, cost_per_batch, token_count, token_type) + new_model = from_str(obj.get("newModel")) + previous_model = from_union([from_none, lambda x: from_str(x)], obj.get("previousModel")) + previous_reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("previousReasoningEffort")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + return SessionModelChangeData( + new_model=new_model, + previous_model=previous_model, + previous_reasoning_effort=previous_reasoning_effort, + reasoning_effort=reasoning_effort, + ) def to_dict(self) -> dict: result: dict = {} - result["batchSize"] = to_float(self.batch_size) - result["costPerBatch"] = to_float(self.cost_per_batch) - result["tokenCount"] = to_float(self.token_count) - result["tokenType"] = from_str(self.token_type) + result["newModel"] = from_str(self.new_model) + if self.previous_model is not None: + result["previousModel"] = from_union([from_none, lambda x: from_str(x)], self.previous_model) + if self.previous_reasoning_effort is not None: + result["previousReasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.previous_reasoning_effort) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) return result @dataclass -class AssistantUsageCopilotUsage: - """Per-request cost and usage data from the CAPI copilot_usage response field""" - - token_details: list[AssistantUsageCopilotUsageTokenDetail] - """Itemized token usage breakdown""" - - total_nano_aiu: float - """Total cost in nano-AIU (AI Units) for this request""" +class SessionModeChangedData: + "Agent mode change details including previous and new modes" + previous_mode: str + new_mode: str @staticmethod - def from_dict(obj: Any) -> 'AssistantUsageCopilotUsage': + def from_dict(obj: Any) -> "SessionModeChangedData": assert isinstance(obj, dict) - token_details = from_list(AssistantUsageCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) - total_nano_aiu = from_float(obj.get("totalNanoAiu")) - return AssistantUsageCopilotUsage(token_details, total_nano_aiu) + previous_mode = from_str(obj.get("previousMode")) + new_mode = from_str(obj.get("newMode")) + return SessionModeChangedData( + previous_mode=previous_mode, + new_mode=new_mode, + ) def to_dict(self) -> dict: result: dict = {} - result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageCopilotUsageTokenDetail, x), self.token_details) - result["totalNanoAiu"] = to_float(self.total_nano_aiu) + result["previousMode"] = from_str(self.previous_mode) + result["newMode"] = from_str(self.new_mode) return result @dataclass -class Error: - """Error details when the tool execution failed - - Error details when the hook failed - """ - message: str - """Human-readable error message""" - - code: str | None = None - """Machine-readable error code""" - - stack: str | None = None - """Error stack trace, when available""" +class SessionPlanChangedData: + "Plan file operation details indicating what changed" + operation: SessionPlanChangedDataOperation @staticmethod - def from_dict(obj: Any) -> 'Error': + def from_dict(obj: Any) -> "SessionPlanChangedData": assert isinstance(obj, dict) - message = from_str(obj.get("message")) - code = from_union([from_str, from_none], obj.get("code")) - stack = from_union([from_str, from_none], obj.get("stack")) - return Error(message, code, stack) + operation = parse_enum(SessionPlanChangedDataOperation, obj.get("operation")) + return SessionPlanChangedData( + operation=operation, + ) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.code is not None: - result["code"] = from_union([from_str, from_none], self.code) - if self.stack is not None: - result["stack"] = from_union([from_str, from_none], self.stack) + result["operation"] = to_enum(SessionPlanChangedDataOperation, self.operation) return result -class ExtensionsLoadedExtensionSource(Enum): - """Discovery source""" - - PROJECT = "project" - USER = "user" - +@dataclass +class SessionWorkspaceFileChangedData: + "Workspace file change details including path and operation type" + path: str + operation: SessionWorkspaceFileChangedDataOperation -class ExtensionsLoadedExtensionStatus(Enum): - """Current status: running, disabled, failed, or starting""" + @staticmethod + def from_dict(obj: Any) -> "SessionWorkspaceFileChangedData": + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + operation = parse_enum(SessionWorkspaceFileChangedDataOperation, obj.get("operation")) + return SessionWorkspaceFileChangedData( + path=path, + operation=operation, + ) - DISABLED = "disabled" - FAILED = "failed" - RUNNING = "running" - STARTING = "starting" + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["operation"] = to_enum(SessionWorkspaceFileChangedDataOperation, self.operation) + return result @dataclass -class ExtensionsLoadedExtension: - id: str - """Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper')""" - +class HandoffRepository: + "Repository context for the handed-off session" + owner: str name: str - """Extension name (directory name)""" - - source: ExtensionsLoadedExtensionSource - """Discovery source""" - - status: ExtensionsLoadedExtensionStatus - """Current status: running, disabled, failed, or starting""" + branch: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ExtensionsLoadedExtension': + def from_dict(obj: Any) -> "HandoffRepository": assert isinstance(obj, dict) - id = from_str(obj.get("id")) + owner = from_str(obj.get("owner")) name = from_str(obj.get("name")) - source = ExtensionsLoadedExtensionSource(obj.get("source")) - status = ExtensionsLoadedExtensionStatus(obj.get("status")) - return ExtensionsLoadedExtension(id, name, source, status) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + return HandoffRepository( + owner=owner, + name=name, + branch=branch, + ) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["owner"] = from_str(self.owner) result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionsLoadedExtensionSource, self.source) - result["status"] = to_enum(ExtensionsLoadedExtensionStatus, self.status) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) return result -class SystemNotificationAgentCompletedStatus(Enum): - """Whether the agent completed successfully or failed""" - - COMPLETED = "completed" - FAILED = "failed" +@dataclass +class SessionHandoffData: + "Session handoff metadata including source, context, and repository information" + handoff_time: datetime + source_type: HandoffSourceType + repository: HandoffRepository | None = None + context: str | None = None + summary: str | None = None + remote_session_id: str | None = None + host: str | None = None + @staticmethod + def from_dict(obj: Any) -> "SessionHandoffData": + assert isinstance(obj, dict) + handoff_time = from_datetime(obj.get("handoffTime")) + source_type = parse_enum(HandoffSourceType, obj.get("sourceType")) + repository = from_union([from_none, lambda x: HandoffRepository.from_dict(x)], obj.get("repository")) + context = from_union([from_none, lambda x: from_str(x)], obj.get("context")) + summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary")) + remote_session_id = from_union([from_none, lambda x: from_str(x)], obj.get("remoteSessionId")) + host = from_union([from_none, lambda x: from_str(x)], obj.get("host")) + return SessionHandoffData( + handoff_time=handoff_time, + source_type=source_type, + repository=repository, + context=context, + summary=summary, + remote_session_id=remote_session_id, + host=host, + ) -class SystemNotificationType(Enum): - AGENT_COMPLETED = "agent_completed" - AGENT_IDLE = "agent_idle" - SHELL_COMPLETED = "shell_completed" - SHELL_DETACHED_COMPLETED = "shell_detached_completed" + def to_dict(self) -> dict: + result: dict = {} + result["handoffTime"] = to_datetime(self.handoff_time) + result["sourceType"] = to_enum(HandoffSourceType, self.source_type) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: to_class(HandoffRepository, x)], self.repository) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: from_str(x)], self.context) + if self.summary is not None: + result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + if self.remote_session_id is not None: + result["remoteSessionId"] = from_union([from_none, lambda x: from_str(x)], self.remote_session_id) + if self.host is not None: + result["host"] = from_union([from_none, lambda x: from_str(x)], self.host) + return result @dataclass -class SystemNotification: - """Structured metadata identifying what triggered this notification""" - - type: SystemNotificationType - agent_id: str | None = None - """Unique identifier of the background agent""" - - agent_type: str | None = None - """Type of the agent (e.g., explore, task, general-purpose)""" +class SessionTruncationData: + "Conversation truncation statistics including token counts and removed content metrics" + token_limit: float + pre_truncation_tokens_in_messages: float + pre_truncation_messages_length: float + post_truncation_tokens_in_messages: float + post_truncation_messages_length: float + tokens_removed_during_truncation: float + messages_removed_during_truncation: float + performed_by: str - description: str | None = None - """Human-readable description of the agent task - - Human-readable description of the command - """ - prompt: str | None = None - """The full prompt given to the background agent""" + @staticmethod + def from_dict(obj: Any) -> "SessionTruncationData": + assert isinstance(obj, dict) + token_limit = from_float(obj.get("tokenLimit")) + pre_truncation_tokens_in_messages = from_float(obj.get("preTruncationTokensInMessages")) + pre_truncation_messages_length = from_float(obj.get("preTruncationMessagesLength")) + post_truncation_tokens_in_messages = from_float(obj.get("postTruncationTokensInMessages")) + post_truncation_messages_length = from_float(obj.get("postTruncationMessagesLength")) + tokens_removed_during_truncation = from_float(obj.get("tokensRemovedDuringTruncation")) + messages_removed_during_truncation = from_float(obj.get("messagesRemovedDuringTruncation")) + performed_by = from_str(obj.get("performedBy")) + return SessionTruncationData( + token_limit=token_limit, + pre_truncation_tokens_in_messages=pre_truncation_tokens_in_messages, + pre_truncation_messages_length=pre_truncation_messages_length, + post_truncation_tokens_in_messages=post_truncation_tokens_in_messages, + post_truncation_messages_length=post_truncation_messages_length, + tokens_removed_during_truncation=tokens_removed_during_truncation, + messages_removed_during_truncation=messages_removed_during_truncation, + performed_by=performed_by, + ) - status: SystemNotificationAgentCompletedStatus | None = None - """Whether the agent completed successfully or failed""" + def to_dict(self) -> dict: + result: dict = {} + result["tokenLimit"] = to_float(self.token_limit) + result["preTruncationTokensInMessages"] = to_float(self.pre_truncation_tokens_in_messages) + result["preTruncationMessagesLength"] = to_float(self.pre_truncation_messages_length) + result["postTruncationTokensInMessages"] = to_float(self.post_truncation_tokens_in_messages) + result["postTruncationMessagesLength"] = to_float(self.post_truncation_messages_length) + result["tokensRemovedDuringTruncation"] = to_float(self.tokens_removed_during_truncation) + result["messagesRemovedDuringTruncation"] = to_float(self.messages_removed_during_truncation) + result["performedBy"] = from_str(self.performed_by) + return result - exit_code: float | None = None - """Exit code of the shell command, if available""" - shell_id: str | None = None - """Unique identifier of the shell session - - Unique identifier of the detached shell session - """ +@dataclass +class SessionSnapshotRewindData: + "Session rewind details including target event and count of removed events" + up_to_event_id: str + events_removed: float @staticmethod - def from_dict(obj: Any) -> 'SystemNotification': + def from_dict(obj: Any) -> "SessionSnapshotRewindData": assert isinstance(obj, dict) - type = SystemNotificationType(obj.get("type")) - agent_id = from_union([from_str, from_none], obj.get("agentId")) - agent_type = from_union([from_str, from_none], obj.get("agentType")) - description = from_union([from_str, from_none], obj.get("description")) - prompt = from_union([from_str, from_none], obj.get("prompt")) - status = from_union([SystemNotificationAgentCompletedStatus, from_none], obj.get("status")) - exit_code = from_union([from_float, from_none], obj.get("exitCode")) - shell_id = from_union([from_str, from_none], obj.get("shellId")) - return SystemNotification(type, agent_id, agent_type, description, prompt, status, exit_code, shell_id) + up_to_event_id = from_str(obj.get("upToEventId")) + events_removed = from_float(obj.get("eventsRemoved")) + return SessionSnapshotRewindData( + up_to_event_id=up_to_event_id, + events_removed=events_removed, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(SystemNotificationType, self.type) - if self.agent_id is not None: - result["agentId"] = from_union([from_str, from_none], self.agent_id) - if self.agent_type is not None: - result["agentType"] = from_union([from_str, from_none], self.agent_type) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) - if self.status is not None: - result["status"] = from_union([lambda x: to_enum(SystemNotificationAgentCompletedStatus, x), from_none], self.status) - if self.exit_code is not None: - result["exitCode"] = from_union([to_float, from_none], self.exit_code) - if self.shell_id is not None: - result["shellId"] = from_union([from_str, from_none], self.shell_id) + result["upToEventId"] = from_str(self.up_to_event_id) + result["eventsRemoved"] = to_float(self.events_removed) return result @dataclass -class SystemMessageMetadata: - """Metadata about the prompt template and its construction""" - - prompt_version: str | None = None - """Version identifier of the prompt template used""" - - variables: dict[str, Any] | None = None - """Template variables used when constructing the prompt""" +class ShutdownCodeChanges: + "Aggregate code change metrics for the session" + lines_added: float + lines_removed: float + files_modified: list[str] @staticmethod - def from_dict(obj: Any) -> 'SystemMessageMetadata': + def from_dict(obj: Any) -> "ShutdownCodeChanges": assert isinstance(obj, dict) - prompt_version = from_union([from_str, from_none], obj.get("promptVersion")) - variables = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("variables")) - return SystemMessageMetadata(prompt_version, variables) + lines_added = from_float(obj.get("linesAdded")) + lines_removed = from_float(obj.get("linesRemoved")) + files_modified = from_list(lambda x: from_str(x), obj.get("filesModified")) + return ShutdownCodeChanges( + lines_added=lines_added, + lines_removed=lines_removed, + files_modified=files_modified, + ) def to_dict(self) -> dict: result: dict = {} - if self.prompt_version is not None: - result["promptVersion"] = from_union([from_str, from_none], self.prompt_version) - if self.variables is not None: - result["variables"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.variables) + result["linesAdded"] = to_float(self.lines_added) + result["linesRemoved"] = to_float(self.lines_removed) + result["filesModified"] = from_list(lambda x: from_str(x), self.files_modified) return result -class ElicitationRequestedMode(Enum): - """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to - "form" when absent. - """ - FORM = "form" - URL = "url" - - @dataclass class ShutdownModelMetricRequests: - """Request count and cost metrics""" - - cost: float - """Cumulative cost multiplier for requests to this model""" - + "Request count and cost metrics" count: float - """Total number of API requests made to this model""" + cost: float @staticmethod - def from_dict(obj: Any) -> 'ShutdownModelMetricRequests': + def from_dict(obj: Any) -> "ShutdownModelMetricRequests": assert isinstance(obj, dict) - cost = from_float(obj.get("cost")) count = from_float(obj.get("count")) - return ShutdownModelMetricRequests(cost, count) + cost = from_float(obj.get("cost")) + return ShutdownModelMetricRequests( + count=count, + cost=cost, + ) def to_dict(self) -> dict: result: dict = {} - result["cost"] = to_float(self.cost) result["count"] = to_float(self.count) + result["cost"] = to_float(self.cost) return result @dataclass class ShutdownModelMetricUsage: - """Token usage breakdown""" - - cache_read_tokens: float - """Total tokens read from prompt cache across all requests""" - - cache_write_tokens: float - """Total tokens written to prompt cache across all requests""" - + "Token usage breakdown" input_tokens: float - """Total input tokens consumed across all requests to this model""" - output_tokens: float - """Total output tokens produced across all requests to this model""" - + cache_read_tokens: float + cache_write_tokens: float reasoning_tokens: float | None = None - """Total reasoning tokens produced across all requests to this model""" @staticmethod - def from_dict(obj: Any) -> 'ShutdownModelMetricUsage': + def from_dict(obj: Any) -> "ShutdownModelMetricUsage": assert isinstance(obj, dict) - cache_read_tokens = from_float(obj.get("cacheReadTokens")) - cache_write_tokens = from_float(obj.get("cacheWriteTokens")) input_tokens = from_float(obj.get("inputTokens")) output_tokens = from_float(obj.get("outputTokens")) - reasoning_tokens = from_union([from_float, from_none], obj.get("reasoningTokens")) - return ShutdownModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) + cache_read_tokens = from_float(obj.get("cacheReadTokens")) + cache_write_tokens = from_float(obj.get("cacheWriteTokens")) + reasoning_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("reasoningTokens")) + return ShutdownModelMetricUsage( + input_tokens=input_tokens, + output_tokens=output_tokens, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + reasoning_tokens=reasoning_tokens, + ) def to_dict(self) -> dict: result: dict = {} - result["cacheReadTokens"] = to_float(self.cache_read_tokens) - result["cacheWriteTokens"] = to_float(self.cache_write_tokens) result["inputTokens"] = to_float(self.input_tokens) result["outputTokens"] = to_float(self.output_tokens) + result["cacheReadTokens"] = to_float(self.cache_read_tokens) + result["cacheWriteTokens"] = to_float(self.cache_write_tokens) if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([to_float, from_none], self.reasoning_tokens) + result["reasoningTokens"] = from_union([from_none, lambda x: to_float(x)], self.reasoning_tokens) return result @dataclass class ShutdownModelMetric: requests: ShutdownModelMetricRequests - """Request count and cost metrics""" - usage: ShutdownModelMetricUsage - """Token usage breakdown""" @staticmethod - def from_dict(obj: Any) -> 'ShutdownModelMetric': + def from_dict(obj: Any) -> "ShutdownModelMetric": assert isinstance(obj, dict) requests = ShutdownModelMetricRequests.from_dict(obj.get("requests")) usage = ShutdownModelMetricUsage.from_dict(obj.get("usage")) - return ShutdownModelMetric(requests, usage) + return ShutdownModelMetric( + requests=requests, + usage=usage, + ) def to_dict(self) -> dict: result: dict = {} @@ -853,2572 +967,3286 @@ def to_dict(self) -> dict: return result -class ChangedOperation(Enum): - """The type of operation performed on the plan file - - Whether the file was newly created or updated - """ - CREATE = "create" - DELETE = "delete" - UPDATE = "update" - +@dataclass +class SessionShutdownData: + "Session termination metrics including usage statistics, code changes, and shutdown reason" + shutdown_type: ShutdownType + total_premium_requests: float + total_api_duration_ms: float + session_start_time: float + code_changes: ShutdownCodeChanges + model_metrics: dict[str, ShutdownModelMetric] + error_reason: str | None = None + current_model: str | None = None + current_tokens: float | None = None + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None -class PermissionRequestMemoryAction(Enum): - """Whether this is a store or vote memory operation""" + @staticmethod + def from_dict(obj: Any) -> "SessionShutdownData": + assert isinstance(obj, dict) + shutdown_type = parse_enum(ShutdownType, obj.get("shutdownType")) + total_premium_requests = from_float(obj.get("totalPremiumRequests")) + total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + session_start_time = from_float(obj.get("sessionStartTime")) + code_changes = ShutdownCodeChanges.from_dict(obj.get("codeChanges")) + model_metrics = from_dict(lambda x: ShutdownModelMetric.from_dict(x), obj.get("modelMetrics")) + error_reason = from_union([from_none, lambda x: from_str(x)], obj.get("errorReason")) + current_model = from_union([from_none, lambda x: from_str(x)], obj.get("currentModel")) + current_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("currentTokens")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionShutdownData( + shutdown_type=shutdown_type, + total_premium_requests=total_premium_requests, + total_api_duration_ms=total_api_duration_ms, + session_start_time=session_start_time, + code_changes=code_changes, + model_metrics=model_metrics, + error_reason=error_reason, + current_model=current_model, + current_tokens=current_tokens, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) - STORE = "store" - VOTE = "vote" + def to_dict(self) -> dict: + result: dict = {} + result["shutdownType"] = to_enum(ShutdownType, self.shutdown_type) + result["totalPremiumRequests"] = to_float(self.total_premium_requests) + result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["sessionStartTime"] = to_float(self.session_start_time) + result["codeChanges"] = to_class(ShutdownCodeChanges, self.code_changes) + result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) + if self.error_reason is not None: + result["errorReason"] = from_union([from_none, lambda x: from_str(x)], self.error_reason) + if self.current_model is not None: + result["currentModel"] = from_union([from_none, lambda x: from_str(x)], self.current_model) + if self.current_tokens is not None: + result["currentTokens"] = from_union([from_none, lambda x: to_float(x)], self.current_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + return result @dataclass -class PermissionRequestShellCommand: - identifier: str - """Command identifier (e.g., executable name)""" - - read_only: bool - """Whether this command is read-only (no side effects)""" +class SessionContextChangedData: + "Updated working directory and git context after the change" + cwd: str + git_root: str | None = None + repository: str | None = None + host_type: SessionContextChangedDataHostType | None = None + branch: str | None = None + head_commit: str | None = None + base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestShellCommand': + def from_dict(obj: Any) -> "SessionContextChangedData": assert isinstance(obj, dict) - identifier = from_str(obj.get("identifier")) - read_only = from_bool(obj.get("readOnly")) - return PermissionRequestShellCommand(identifier, read_only) + cwd = from_str(obj.get("cwd")) + git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) + repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + host_type = from_union([from_none, lambda x: parse_enum(SessionContextChangedDataHostType, x)], obj.get("hostType")) + branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) + base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + return SessionContextChangedData( + cwd=cwd, + git_root=git_root, + repository=repository, + host_type=host_type, + branch=branch, + head_commit=head_commit, + base_commit=base_commit, + ) def to_dict(self) -> dict: result: dict = {} - result["identifier"] = from_str(self.identifier) - result["readOnly"] = from_bool(self.read_only) + result["cwd"] = from_str(self.cwd) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(SessionContextChangedDataHostType, x)], self.host_type) + if self.branch is not None: + result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) return result -class PermissionRequestMemoryDirection(Enum): - """Vote direction (vote only)""" - - DOWNVOTE = "downvote" - UPVOTE = "upvote" +@dataclass +class SessionUsageInfoData: + "Current context window usage statistics including token and message counts" + token_limit: float + current_tokens: float + messages_length: float + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None + is_initial: bool | None = None + @staticmethod + def from_dict(obj: Any) -> "SessionUsageInfoData": + assert isinstance(obj, dict) + token_limit = from_float(obj.get("tokenLimit")) + current_tokens = from_float(obj.get("currentTokens")) + messages_length = from_float(obj.get("messagesLength")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + is_initial = from_union([from_none, lambda x: from_bool(x)], obj.get("isInitial")) + return SessionUsageInfoData( + token_limit=token_limit, + current_tokens=current_tokens, + messages_length=messages_length, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + is_initial=is_initial, + ) -class Kind(Enum): - CUSTOM_TOOL = "custom-tool" - HOOK = "hook" - MCP = "mcp" - MEMORY = "memory" - READ = "read" - SHELL = "shell" - URL = "url" - WRITE = "write" + def to_dict(self) -> dict: + result: dict = {} + result["tokenLimit"] = to_float(self.token_limit) + result["currentTokens"] = to_float(self.current_tokens) + result["messagesLength"] = to_float(self.messages_length) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + if self.is_initial is not None: + result["isInitial"] = from_union([from_none, lambda x: from_bool(x)], self.is_initial) + return result @dataclass -class PermissionRequestShellPossibleURL: - url: str - """URL that may be accessed by the command""" +class SessionCompactionStartData: + "Context window breakdown at the start of LLM-powered conversation compaction" + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestShellPossibleURL': + def from_dict(obj: Any) -> "SessionCompactionStartData": assert isinstance(obj, dict) - url = from_str(obj.get("url")) - return PermissionRequestShellPossibleURL(url) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionCompactionStartData( + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) def to_dict(self) -> dict: result: dict = {} - result["url"] = from_str(self.url) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) return result @dataclass -class PermissionRequest: - """Details of the permission being requested - - Shell command permission request - - File write permission request - - File or directory read permission request - - MCP tool invocation permission request - - URL access permission request - - Memory operation permission request - - Custom tool invocation permission request - - Hook confirmation permission request - """ - kind: Kind - """Permission kind discriminator""" +class CompactionCompleteCompactionTokensUsed: + "Token usage breakdown for the compaction LLM call" + input: float + output: float + cached_input: float - can_offer_session_approval: bool | None = None - """Whether the UI can offer session-wide approval for this command pattern""" + @staticmethod + def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": + assert isinstance(obj, dict) + input = from_float(obj.get("input")) + output = from_float(obj.get("output")) + cached_input = from_float(obj.get("cachedInput")) + return CompactionCompleteCompactionTokensUsed( + input=input, + output=output, + cached_input=cached_input, + ) - commands: list[PermissionRequestShellCommand] | None = None - """Parsed command identifiers found in the command text""" + def to_dict(self) -> dict: + result: dict = {} + result["input"] = to_float(self.input) + result["output"] = to_float(self.output) + result["cachedInput"] = to_float(self.cached_input) + return result - full_command_text: str | None = None - """The complete shell command text to be executed""" - has_write_file_redirection: bool | None = None - """Whether the command includes a file write redirection (e.g., > or >>)""" +@dataclass +class SessionCompactionCompleteData: + "Conversation compaction results including success status, metrics, and optional error details" + success: bool + error: str | None = None + pre_compaction_tokens: float | None = None + post_compaction_tokens: float | None = None + pre_compaction_messages_length: float | None = None + messages_removed: float | None = None + tokens_removed: float | None = None + summary_content: str | None = None + checkpoint_number: float | None = None + checkpoint_path: str | None = None + compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None + request_id: str | None = None + system_tokens: float | None = None + conversation_tokens: float | None = None + tool_definitions_tokens: float | None = None - intention: str | None = None - """Human-readable description of what the command intends to do - - Human-readable description of the intended file change - - Human-readable description of why the file is being read - - Human-readable description of why the URL is being accessed - """ - possible_paths: list[str] | None = None - """File paths that may be read or written by the command""" + @staticmethod + def from_dict(obj: Any) -> "SessionCompactionCompleteData": + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) + pre_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionTokens")) + post_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("postCompactionTokens")) + pre_compaction_messages_length = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionMessagesLength")) + messages_removed = from_union([from_none, lambda x: from_float(x)], obj.get("messagesRemoved")) + tokens_removed = from_union([from_none, lambda x: from_float(x)], obj.get("tokensRemoved")) + summary_content = from_union([from_none, lambda x: from_str(x)], obj.get("summaryContent")) + checkpoint_number = from_union([from_none, lambda x: from_float(x)], obj.get("checkpointNumber")) + checkpoint_path = from_union([from_none, lambda x: from_str(x)], obj.get("checkpointPath")) + compaction_tokens_used = from_union([from_none, lambda x: CompactionCompleteCompactionTokensUsed.from_dict(x)], obj.get("compactionTokensUsed")) + request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) + system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + return SessionCompactionCompleteData( + success=success, + error=error, + pre_compaction_tokens=pre_compaction_tokens, + post_compaction_tokens=post_compaction_tokens, + pre_compaction_messages_length=pre_compaction_messages_length, + messages_removed=messages_removed, + tokens_removed=tokens_removed, + summary_content=summary_content, + checkpoint_number=checkpoint_number, + checkpoint_path=checkpoint_path, + compaction_tokens_used=compaction_tokens_used, + request_id=request_id, + system_tokens=system_tokens, + conversation_tokens=conversation_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) - possible_urls: list[PermissionRequestShellPossibleURL] | None = None - """URLs that may be accessed by the command""" + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + if self.pre_compaction_tokens is not None: + result["preCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_tokens) + if self.post_compaction_tokens is not None: + result["postCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.post_compaction_tokens) + if self.pre_compaction_messages_length is not None: + result["preCompactionMessagesLength"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_messages_length) + if self.messages_removed is not None: + result["messagesRemoved"] = from_union([from_none, lambda x: to_float(x)], self.messages_removed) + if self.tokens_removed is not None: + result["tokensRemoved"] = from_union([from_none, lambda x: to_float(x)], self.tokens_removed) + if self.summary_content is not None: + result["summaryContent"] = from_union([from_none, lambda x: from_str(x)], self.summary_content) + if self.checkpoint_number is not None: + result["checkpointNumber"] = from_union([from_none, lambda x: to_float(x)], self.checkpoint_number) + if self.checkpoint_path is not None: + result["checkpointPath"] = from_union([from_none, lambda x: from_str(x)], self.checkpoint_path) + if self.compaction_tokens_used is not None: + result["compactionTokensUsed"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsed, x)], self.compaction_tokens_used) + if self.request_id is not None: + result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + return result - tool_call_id: str | None = None - """Tool call ID that triggered this permission request""" - warning: str | None = None - """Optional warning message about risks of running this command""" +@dataclass +class SessionTaskCompleteData: + "Task completion notification with summary from the agent" + summary: str | None = None + success: bool | None = None - diff: str | None = None - """Unified diff showing the proposed changes""" + @staticmethod + def from_dict(obj: Any) -> "SessionTaskCompleteData": + assert isinstance(obj, dict) + summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary", "")) + success = from_union([from_none, lambda x: from_bool(x)], obj.get("success")) + return SessionTaskCompleteData( + summary=summary, + success=success, + ) - file_name: str | None = None - """Path of the file being written to""" + def to_dict(self) -> dict: + result: dict = {} + if self.summary is not None: + result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + if self.success is not None: + result["success"] = from_union([from_none, lambda x: from_bool(x)], self.success) + return result - new_file_contents: str | None = None - """Complete new file contents for newly created files""" - path: str | None = None - """Path of the file or directory being read""" +@dataclass +class UserMessageAttachmentFileLineRange: + "Optional line range to scope the attachment to a specific section of the file" + start: float + end: float - args: Any = None - """Arguments to pass to the MCP tool - - Arguments to pass to the custom tool - """ - read_only: bool | None = None - """Whether this MCP tool is read-only (no side effects)""" + @staticmethod + def from_dict(obj: Any) -> "UserMessageAttachmentFileLineRange": + assert isinstance(obj, dict) + start = from_float(obj.get("start")) + end = from_float(obj.get("end")) + return UserMessageAttachmentFileLineRange( + start=start, + end=end, + ) - server_name: str | None = None - """Name of the MCP server providing the tool""" + def to_dict(self) -> dict: + result: dict = {} + result["start"] = to_float(self.start) + result["end"] = to_float(self.end) + return result - tool_name: str | None = None - """Internal name of the MCP tool - - Name of the custom tool - - Name of the tool the hook is gating - """ - tool_title: str | None = None - """Human-readable title of the MCP tool""" - url: str | None = None - """URL to be fetched""" +@dataclass +class UserMessageAttachmentSelectionDetailsStart: + "Start position of the selection" + line: float + character: float - action: PermissionRequestMemoryAction | None = None - """Whether this is a store or vote memory operation""" + @staticmethod + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsStart": + assert isinstance(obj, dict) + line = from_float(obj.get("line")) + character = from_float(obj.get("character")) + return UserMessageAttachmentSelectionDetailsStart( + line=line, + character=character, + ) - citations: str | None = None - """Source references for the stored fact (store only)""" + def to_dict(self) -> dict: + result: dict = {} + result["line"] = to_float(self.line) + result["character"] = to_float(self.character) + return result - direction: PermissionRequestMemoryDirection | None = None - """Vote direction (vote only)""" - fact: str | None = None - """The fact being stored or voted on""" +@dataclass +class UserMessageAttachmentSelectionDetailsEnd: + "End position of the selection" + line: float + character: float - reason: str | None = None - """Reason for the vote (vote only)""" + @staticmethod + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsEnd": + assert isinstance(obj, dict) + line = from_float(obj.get("line")) + character = from_float(obj.get("character")) + return UserMessageAttachmentSelectionDetailsEnd( + line=line, + character=character, + ) - subject: str | None = None - """Topic or subject of the memory (store only)""" + def to_dict(self) -> dict: + result: dict = {} + result["line"] = to_float(self.line) + result["character"] = to_float(self.character) + return result - tool_description: str | None = None - """Description of what the custom tool does""" - hook_message: str | None = None - """Optional message from the hook explaining why confirmation is needed""" +@dataclass +class UserMessageAttachmentSelectionDetails: + "Position range of the selection within the file" + start: UserMessageAttachmentSelectionDetailsStart + end: UserMessageAttachmentSelectionDetailsEnd - tool_args: Any = None - """Arguments of the tool call being gated""" - - @staticmethod - def from_dict(obj: Any) -> 'PermissionRequest': - assert isinstance(obj, dict) - kind = Kind(obj.get("kind")) - can_offer_session_approval = from_union([from_bool, from_none], obj.get("canOfferSessionApproval")) - commands = from_union([lambda x: from_list(PermissionRequestShellCommand.from_dict, x), from_none], obj.get("commands")) - full_command_text = from_union([from_str, from_none], obj.get("fullCommandText")) - has_write_file_redirection = from_union([from_bool, from_none], obj.get("hasWriteFileRedirection")) - intention = from_union([from_str, from_none], obj.get("intention")) - possible_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("possiblePaths")) - possible_urls = from_union([lambda x: from_list(PermissionRequestShellPossibleURL.from_dict, x), from_none], obj.get("possibleUrls")) - tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) - warning = from_union([from_str, from_none], obj.get("warning")) - diff = from_union([from_str, from_none], obj.get("diff")) - file_name = from_union([from_str, from_none], obj.get("fileName")) - new_file_contents = from_union([from_str, from_none], obj.get("newFileContents")) - path = from_union([from_str, from_none], obj.get("path")) - args = obj.get("args") - read_only = from_union([from_bool, from_none], obj.get("readOnly")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - tool_name = from_union([from_str, from_none], obj.get("toolName")) - tool_title = from_union([from_str, from_none], obj.get("toolTitle")) - url = from_union([from_str, from_none], obj.get("url")) - action = from_union([PermissionRequestMemoryAction, from_none], obj.get("action")) - citations = from_union([from_str, from_none], obj.get("citations")) - direction = from_union([PermissionRequestMemoryDirection, from_none], obj.get("direction")) - fact = from_union([from_str, from_none], obj.get("fact")) - reason = from_union([from_str, from_none], obj.get("reason")) - subject = from_union([from_str, from_none], obj.get("subject")) - tool_description = from_union([from_str, from_none], obj.get("toolDescription")) - hook_message = from_union([from_str, from_none], obj.get("hookMessage")) - tool_args = obj.get("toolArgs") - return PermissionRequest(kind, can_offer_session_approval, commands, full_command_text, has_write_file_redirection, intention, possible_paths, possible_urls, tool_call_id, warning, diff, file_name, new_file_contents, path, args, read_only, server_name, tool_name, tool_title, url, action, citations, direction, fact, reason, subject, tool_description, hook_message, tool_args) + @staticmethod + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetails": + assert isinstance(obj, dict) + start = UserMessageAttachmentSelectionDetailsStart.from_dict(obj.get("start")) + end = UserMessageAttachmentSelectionDetailsEnd.from_dict(obj.get("end")) + return UserMessageAttachmentSelectionDetails( + start=start, + end=end, + ) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(Kind, self.kind) - if self.can_offer_session_approval is not None: - result["canOfferSessionApproval"] = from_union([from_bool, from_none], self.can_offer_session_approval) - if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x), from_none], self.commands) - if self.full_command_text is not None: - result["fullCommandText"] = from_union([from_str, from_none], self.full_command_text) - if self.has_write_file_redirection is not None: - result["hasWriteFileRedirection"] = from_union([from_bool, from_none], self.has_write_file_redirection) - if self.intention is not None: - result["intention"] = from_union([from_str, from_none], self.intention) - if self.possible_paths is not None: - result["possiblePaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.possible_paths) - if self.possible_urls is not None: - result["possibleUrls"] = from_union([lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x), from_none], self.possible_urls) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) - if self.warning is not None: - result["warning"] = from_union([from_str, from_none], self.warning) - if self.diff is not None: - result["diff"] = from_union([from_str, from_none], self.diff) - if self.file_name is not None: - result["fileName"] = from_union([from_str, from_none], self.file_name) - if self.new_file_contents is not None: - result["newFileContents"] = from_union([from_str, from_none], self.new_file_contents) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.args is not None: - result["args"] = self.args - if self.read_only is not None: - result["readOnly"] = from_union([from_bool, from_none], self.read_only) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.tool_name is not None: - result["toolName"] = from_union([from_str, from_none], self.tool_name) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_str, from_none], self.tool_title) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.action is not None: - result["action"] = from_union([lambda x: to_enum(PermissionRequestMemoryAction, x), from_none], self.action) - if self.citations is not None: - result["citations"] = from_union([from_str, from_none], self.citations) - if self.direction is not None: - result["direction"] = from_union([lambda x: to_enum(PermissionRequestMemoryDirection, x), from_none], self.direction) - if self.fact is not None: - result["fact"] = from_union([from_str, from_none], self.fact) - if self.reason is not None: - result["reason"] = from_union([from_str, from_none], self.reason) - if self.subject is not None: - result["subject"] = from_union([from_str, from_none], self.subject) - if self.tool_description is not None: - result["toolDescription"] = from_union([from_str, from_none], self.tool_description) - if self.hook_message is not None: - result["hookMessage"] = from_union([from_str, from_none], self.hook_message) - if self.tool_args is not None: - result["toolArgs"] = self.tool_args + result["start"] = to_class(UserMessageAttachmentSelectionDetailsStart, self.start) + result["end"] = to_class(UserMessageAttachmentSelectionDetailsEnd, self.end) return result @dataclass -class AssistantUsageQuotaSnapshot: - entitlement_requests: float - """Total requests allowed by the entitlement""" - - is_unlimited_entitlement: bool - """Whether the user has an unlimited usage entitlement""" - - overage: float - """Number of requests over the entitlement limit""" - - overage_allowed_with_exhausted_quota: bool - """Whether overage is allowed when quota is exhausted""" +class UserMessageAttachment: + "A user message attachment — a file, directory, code selection, blob, or GitHub reference" + type: UserMessageAttachmentType + path: str | None = None + display_name: str | None = None + line_range: UserMessageAttachmentFileLineRange | None = None + file_path: str | None = None + text: str | None = None + selection: UserMessageAttachmentSelectionDetails | None = None + number: float | None = None + title: str | None = None + reference_type: UserMessageAttachmentGithubReferenceType | None = None + state: str | None = None + url: str | None = None + data: str | None = None + mime_type: str | None = None - remaining_percentage: float - """Percentage of quota remaining (0.0 to 1.0)""" + @staticmethod + def from_dict(obj: Any) -> "UserMessageAttachment": + assert isinstance(obj, dict) + type = parse_enum(UserMessageAttachmentType, obj.get("type")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + display_name = from_union([from_none, lambda x: from_str(x)], obj.get("displayName")) + line_range = from_union([from_none, lambda x: UserMessageAttachmentFileLineRange.from_dict(x)], obj.get("lineRange")) + file_path = from_union([from_none, lambda x: from_str(x)], obj.get("filePath")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + selection = from_union([from_none, lambda x: UserMessageAttachmentSelectionDetails.from_dict(x)], obj.get("selection")) + number = from_union([from_none, lambda x: from_float(x)], obj.get("number")) + title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + reference_type = from_union([from_none, lambda x: parse_enum(UserMessageAttachmentGithubReferenceType, x)], obj.get("referenceType")) + state = from_union([from_none, lambda x: from_str(x)], obj.get("state")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + return UserMessageAttachment( + type=type, + path=path, + display_name=display_name, + line_range=line_range, + file_path=file_path, + text=text, + selection=selection, + number=number, + title=title, + reference_type=reference_type, + state=state, + url=url, + data=data, + mime_type=mime_type, + ) - usage_allowed_with_exhausted_quota: bool - """Whether usage is still permitted after quota exhaustion""" + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(UserMessageAttachmentType, self.type) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + if self.display_name is not None: + result["displayName"] = from_union([from_none, lambda x: from_str(x)], self.display_name) + if self.line_range is not None: + result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentFileLineRange, x)], self.line_range) + if self.file_path is not None: + result["filePath"] = from_union([from_none, lambda x: from_str(x)], self.file_path) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.selection is not None: + result["selection"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentSelectionDetails, x)], self.selection) + if self.number is not None: + result["number"] = from_union([from_none, lambda x: to_float(x)], self.number) + if self.title is not None: + result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + if self.reference_type is not None: + result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x)], self.reference_type) + if self.state is not None: + result["state"] = from_union([from_none, lambda x: from_str(x)], self.state) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + if self.data is not None: + result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + return result - used_requests: float - """Number of requests already consumed""" - reset_date: datetime | None = None - """Date when the quota resets""" +@dataclass +class UserMessageData: + content: str + transformed_content: str | None = None + attachments: list[UserMessageAttachment] | None = None + source: str | None = None + agent_mode: UserMessageAgentMode | None = None + interaction_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'AssistantUsageQuotaSnapshot': + def from_dict(obj: Any) -> "UserMessageData": assert isinstance(obj, dict) - entitlement_requests = from_float(obj.get("entitlementRequests")) - is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) - overage = from_float(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) - used_requests = from_float(obj.get("usedRequests")) - reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return AssistantUsageQuotaSnapshot(entitlement_requests, is_unlimited_entitlement, overage, overage_allowed_with_exhausted_quota, remaining_percentage, usage_allowed_with_exhausted_quota, used_requests, reset_date) + content = from_str(obj.get("content")) + transformed_content = from_union([from_none, lambda x: from_str(x)], obj.get("transformedContent")) + attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments")) + source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) + agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + return UserMessageData( + content=content, + transformed_content=transformed_content, + attachments=attachments, + source=source, + agent_mode=agent_mode, + interaction_id=interaction_id, + ) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = to_float(self.entitlement_requests) - result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) - result["overage"] = to_float(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) - result["usedRequests"] = to_float(self.used_requests) - if self.reset_date is not None: - result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + result["content"] = from_str(self.content) + if self.transformed_content is not None: + result["transformedContent"] = from_union([from_none, lambda x: from_str(x)], self.transformed_content) + if self.attachments is not None: + result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments) + if self.source is not None: + result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + if self.agent_mode is not None: + result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) return result @dataclass -class HandoffRepository: - """Repository context for the handed-off session""" +class PendingMessagesModifiedData: + "Empty payload; the event signals that the pending message queue has changed" + @staticmethod + def from_dict(obj: Any) -> "PendingMessagesModifiedData": + assert isinstance(obj, dict) + return PendingMessagesModifiedData() - name: str - """Repository name""" + def to_dict(self) -> dict: + return {} - owner: str - """Repository owner (user or organization)""" - branch: str | None = None - """Git branch name, if applicable""" +@dataclass +class AssistantTurnStartData: + "Turn initialization metadata including identifier and interaction tracking" + turn_id: str + interaction_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'HandoffRepository': + def from_dict(obj: Any) -> "AssistantTurnStartData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - owner = from_str(obj.get("owner")) - branch = from_union([from_str, from_none], obj.get("branch")) - return HandoffRepository(name, owner, branch) + turn_id = from_str(obj.get("turnId")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + return AssistantTurnStartData( + turn_id=turn_id, + interaction_id=interaction_id, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["owner"] = from_str(self.owner) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) + result["turnId"] = from_str(self.turn_id) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) return result -class RequestedSchemaType(Enum): - OBJECT = "object" - - @dataclass -class ElicitationRequestedSchema: - """JSON Schema describing the form fields to present to the user (form mode only)""" - - properties: dict[str, Any] - """Form field definitions, keyed by field name""" - - type: RequestedSchemaType - """Schema type indicator (always 'object')""" - - required: list[str] | None = None - """List of required field names""" +class AssistantIntentData: + "Agent intent description for current activity or plan" + intent: str @staticmethod - def from_dict(obj: Any) -> 'ElicitationRequestedSchema': + def from_dict(obj: Any) -> "AssistantIntentData": assert isinstance(obj, dict) - properties = from_dict(lambda x: x, obj.get("properties")) - type = RequestedSchemaType(obj.get("type")) - required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) - return ElicitationRequestedSchema(properties, type, required) + intent = from_str(obj.get("intent")) + return AssistantIntentData( + intent=intent, + ) def to_dict(self) -> dict: result: dict = {} - result["properties"] = from_dict(lambda x: x, self.properties) - result["type"] = to_enum(RequestedSchemaType, self.type) - if self.required is not None: - result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) + result["intent"] = from_str(self.intent) return result -class ToolExecutionCompleteContentResourceLinkIconTheme(Enum): - """Theme variant this icon is intended for""" - - DARK = "dark" - LIGHT = "light" - - @dataclass -class ToolExecutionCompleteContentResourceLinkIcon: - """Icon image for a resource""" +class AssistantReasoningData: + "Assistant reasoning content for timeline display with complete thinking text" + reasoning_id: str + content: str - src: str - """URL or path to the icon image""" + @staticmethod + def from_dict(obj: Any) -> "AssistantReasoningData": + assert isinstance(obj, dict) + reasoning_id = from_str(obj.get("reasoningId")) + content = from_str(obj.get("content")) + return AssistantReasoningData( + reasoning_id=reasoning_id, + content=content, + ) - mime_type: str | None = None - """MIME type of the icon image""" + def to_dict(self) -> dict: + result: dict = {} + result["reasoningId"] = from_str(self.reasoning_id) + result["content"] = from_str(self.content) + return result - sizes: list[str] | None = None - """Available icon sizes (e.g., ['16x16', '32x32'])""" - theme: ToolExecutionCompleteContentResourceLinkIconTheme | None = None - """Theme variant this icon is intended for""" +@dataclass +class AssistantReasoningDeltaData: + "Streaming reasoning delta for incremental extended thinking updates" + reasoning_id: str + delta_content: str @staticmethod - def from_dict(obj: Any) -> 'ToolExecutionCompleteContentResourceLinkIcon': + def from_dict(obj: Any) -> "AssistantReasoningDeltaData": assert isinstance(obj, dict) - src = from_str(obj.get("src")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) - theme = from_union([ToolExecutionCompleteContentResourceLinkIconTheme, from_none], obj.get("theme")) - return ToolExecutionCompleteContentResourceLinkIcon(src, mime_type, sizes, theme) + reasoning_id = from_str(obj.get("reasoningId")) + delta_content = from_str(obj.get("deltaContent")) + return AssistantReasoningDeltaData( + reasoning_id=reasoning_id, + delta_content=delta_content, + ) def to_dict(self) -> dict: result: dict = {} - result["src"] = from_str(self.src) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.sizes is not None: - result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) - if self.theme is not None: - result["theme"] = from_union([lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x), from_none], self.theme) + result["reasoningId"] = from_str(self.reasoning_id) + result["deltaContent"] = from_str(self.delta_content) return result @dataclass -class ToolExecutionCompleteContentResourceDetails: - """The embedded resource contents, either text or base64-encoded binary""" - - uri: str - """URI identifying the resource""" - - mime_type: str | None = None - """MIME type of the text content - - MIME type of the blob content - """ - text: str | None = None - """Text content of the resource""" - - blob: str | None = None - """Base64-encoded binary content of the resource""" +class AssistantStreamingDeltaData: + "Streaming response progress with cumulative byte count" + total_response_size_bytes: float @staticmethod - def from_dict(obj: Any) -> 'ToolExecutionCompleteContentResourceDetails': + def from_dict(obj: Any) -> "AssistantStreamingDeltaData": assert isinstance(obj, dict) - uri = from_str(obj.get("uri")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - text = from_union([from_str, from_none], obj.get("text")) - blob = from_union([from_str, from_none], obj.get("blob")) - return ToolExecutionCompleteContentResourceDetails(uri, mime_type, text, blob) + total_response_size_bytes = from_float(obj.get("totalResponseSizeBytes")) + return AssistantStreamingDeltaData( + total_response_size_bytes=total_response_size_bytes, + ) def to_dict(self) -> dict: result: dict = {} - result["uri"] = from_str(self.uri) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.blob is not None: - result["blob"] = from_union([from_str, from_none], self.blob) + result["totalResponseSizeBytes"] = to_float(self.total_response_size_bytes) return result -class ToolExecutionCompleteContentType(Enum): - AUDIO = "audio" - IMAGE = "image" - RESOURCE = "resource" - RESOURCE_LINK = "resource_link" - TERMINAL = "terminal" - TEXT = "text" - - @dataclass -class ToolExecutionCompleteContent: - """A content block within a tool result, which may be text, terminal output, image, audio, - or a resource - - Plain text content block - - Terminal/shell output content block with optional exit code and working directory - - Image content block with base64-encoded data - - Audio content block with base64-encoded data - - Resource link content block referencing an external resource - - Embedded resource content block with inline text or binary data - """ - type: ToolExecutionCompleteContentType - """Content block type discriminator""" - - text: str | None = None - """The text content - - Terminal/shell output text - """ - cwd: str | None = None - """Working directory where the command was executed""" +class AssistantMessageToolRequest: + "A tool invocation request from the assistant" + tool_call_id: str + name: str + arguments: Any = None + type: AssistantMessageToolRequestType | None = None + tool_title: str | None = None + mcp_server_name: str | None = None + intention_summary: str | None = None - exit_code: float | None = None - """Process exit code, if the command has completed""" + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageToolRequest": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + name = from_str(obj.get("name")) + arguments = obj.get("arguments") + type = from_union([from_none, lambda x: parse_enum(AssistantMessageToolRequestType, x)], obj.get("type")) + tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) + mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) + intention_summary = from_union([from_none, lambda x: from_str(x)], obj.get("intentionSummary")) + return AssistantMessageToolRequest( + tool_call_id=tool_call_id, + name=name, + arguments=arguments, + type=type, + tool_title=tool_title, + mcp_server_name=mcp_server_name, + intention_summary=intention_summary, + ) - data: str | None = None - """Base64-encoded image data - - Base64-encoded audio data - """ - mime_type: str | None = None - """MIME type of the image (e.g., image/png, image/jpeg) - - MIME type of the audio (e.g., audio/wav, audio/mpeg) - - MIME type of the resource content - """ - description: str | None = None - """Human-readable description of the resource""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["name"] = from_str(self.name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.type is not None: + result["type"] = from_union([from_none, lambda x: to_enum(AssistantMessageToolRequestType, x)], self.type) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + if self.intention_summary is not None: + result["intentionSummary"] = from_union([from_none, lambda x: from_str(x)], self.intention_summary) + return result - icons: list[ToolExecutionCompleteContentResourceLinkIcon] | None = None - """Icons associated with this resource""" - name: str | None = None - """Resource name identifier""" +@dataclass +class AssistantMessageData: + "Assistant response containing text content, optional tool requests, and interaction metadata" + message_id: str + content: str + tool_requests: list[AssistantMessageToolRequest] | None = None + reasoning_opaque: str | None = None + reasoning_text: str | None = None + encrypted_content: str | None = None + phase: str | None = None + output_tokens: float | None = None + interaction_id: str | None = None + request_id: str | None = None + parent_tool_call_id: str | None = None - size: float | None = None - """Size of the resource in bytes""" + @staticmethod + def from_dict(obj: Any) -> "AssistantMessageData": + assert isinstance(obj, dict) + message_id = from_str(obj.get("messageId")) + content = from_str(obj.get("content")) + tool_requests = from_union([from_none, lambda x: from_list(lambda x: AssistantMessageToolRequest.from_dict(x), x)], obj.get("toolRequests")) + reasoning_opaque = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningOpaque")) + reasoning_text = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningText")) + encrypted_content = from_union([from_none, lambda x: from_str(x)], obj.get("encryptedContent")) + phase = from_union([from_none, lambda x: from_str(x)], obj.get("phase")) + output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return AssistantMessageData( + message_id=message_id, + content=content, + tool_requests=tool_requests, + reasoning_opaque=reasoning_opaque, + reasoning_text=reasoning_text, + encrypted_content=encrypted_content, + phase=phase, + output_tokens=output_tokens, + interaction_id=interaction_id, + request_id=request_id, + parent_tool_call_id=parent_tool_call_id, + ) - title: str | None = None - """Human-readable display title for the resource""" + def to_dict(self) -> dict: + result: dict = {} + result["messageId"] = from_str(self.message_id) + result["content"] = from_str(self.content) + if self.tool_requests is not None: + result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x)], self.tool_requests) + if self.reasoning_opaque is not None: + result["reasoningOpaque"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_opaque) + if self.reasoning_text is not None: + result["reasoningText"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_text) + if self.encrypted_content is not None: + result["encryptedContent"] = from_union([from_none, lambda x: from_str(x)], self.encrypted_content) + if self.phase is not None: + result["phase"] = from_union([from_none, lambda x: from_str(x)], self.phase) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + if self.request_id is not None: + result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + return result - uri: str | None = None - """URI identifying the resource""" - resource: ToolExecutionCompleteContentResourceDetails | None = None - """The embedded resource contents, either text or base64-encoded binary""" +@dataclass +class AssistantMessageDeltaData: + "Streaming assistant message delta for incremental response updates" + message_id: str + delta_content: str + parent_tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ToolExecutionCompleteContent': + def from_dict(obj: Any) -> "AssistantMessageDeltaData": assert isinstance(obj, dict) - type = ToolExecutionCompleteContentType(obj.get("type")) - text = from_union([from_str, from_none], obj.get("text")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - exit_code = from_union([from_float, from_none], obj.get("exitCode")) - data = from_union([from_str, from_none], obj.get("data")) - mime_type = from_union([from_str, from_none], obj.get("mimeType")) - description = from_union([from_str, from_none], obj.get("description")) - icons = from_union([lambda x: from_list(ToolExecutionCompleteContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons")) - name = from_union([from_str, from_none], obj.get("name")) - size = from_union([from_float, from_none], obj.get("size")) - title = from_union([from_str, from_none], obj.get("title")) - uri = from_union([from_str, from_none], obj.get("uri")) - resource = from_union([ToolExecutionCompleteContentResourceDetails.from_dict, from_none], obj.get("resource")) - return ToolExecutionCompleteContent(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource) + message_id = from_str(obj.get("messageId")) + delta_content = from_str(obj.get("deltaContent")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return AssistantMessageDeltaData( + message_id=message_id, + delta_content=delta_content, + parent_tool_call_id=parent_tool_call_id, + ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(ToolExecutionCompleteContentType, self.type) - if self.text is not None: - result["text"] = from_union([from_str, from_none], self.text) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.exit_code is not None: - result["exitCode"] = from_union([to_float, from_none], self.exit_code) - if self.data is not None: - result["data"] = from_union([from_str, from_none], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_str, from_none], self.mime_type) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.icons is not None: - result["icons"] = from_union([lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContentResourceLinkIcon, x), x), from_none], self.icons) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.size is not None: - result["size"] = from_union([to_float, from_none], self.size) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.uri is not None: - result["uri"] = from_union([from_str, from_none], self.uri) - if self.resource is not None: - result["resource"] = from_union([lambda x: to_class(ToolExecutionCompleteContentResourceDetails, x), from_none], self.resource) + result["messageId"] = from_str(self.message_id) + result["deltaContent"] = from_str(self.delta_content) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) return result -class PermissionCompletedKind(Enum): - """The outcome of the permission request""" - - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - - @dataclass -class Result: - """Tool execution result on success - - The result of the permission request - """ - content: str | None = None - """Concise tool result text sent to the LLM for chat completion, potentially truncated for - token efficiency - """ - contents: list[ToolExecutionCompleteContent] | None = None - """Structured content blocks (text, images, audio, resources) returned by the tool in their - native format - """ - detailed_content: str | None = None - """Full detailed tool result for UI/timeline display, preserving complete content such as - diffs. Falls back to content when absent. - """ - kind: PermissionCompletedKind | None = None - """The outcome of the permission request""" +class AssistantTurnEndData: + "Turn completion metadata including the turn identifier" + turn_id: str @staticmethod - def from_dict(obj: Any) -> 'Result': + def from_dict(obj: Any) -> "AssistantTurnEndData": assert isinstance(obj, dict) - content = from_union([from_str, from_none], obj.get("content")) - contents = from_union([lambda x: from_list(ToolExecutionCompleteContent.from_dict, x), from_none], obj.get("contents")) - detailed_content = from_union([from_str, from_none], obj.get("detailedContent")) - kind = from_union([PermissionCompletedKind, from_none], obj.get("kind")) - return Result(content, contents, detailed_content, kind) + turn_id = from_str(obj.get("turnId")) + return AssistantTurnEndData( + turn_id=turn_id, + ) def to_dict(self) -> dict: result: dict = {} - if self.content is not None: - result["content"] = from_union([from_str, from_none], self.content) - if self.contents is not None: - result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContent, x), x), from_none], self.contents) - if self.detailed_content is not None: - result["detailedContent"] = from_union([from_str, from_none], self.detailed_content) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_enum(PermissionCompletedKind, x), from_none], self.kind) + result["turnId"] = from_str(self.turn_id) return result -class SystemMessageRole(Enum): - """Message role: "system" for system prompts, "developer" for developer-injected instructions""" - - DEVELOPER = "developer" - SYSTEM = "system" +@dataclass +class AssistantUsageQuotaSnapshot: + is_unlimited_entitlement: bool + entitlement_requests: float + used_requests: float + usage_allowed_with_exhausted_quota: bool + overage: float + overage_allowed_with_exhausted_quota: bool + remaining_percentage: float + reset_date: datetime | None = None + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageQuotaSnapshot": + assert isinstance(obj, dict) + is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) + entitlement_requests = from_float(obj.get("entitlementRequests")) + used_requests = from_float(obj.get("usedRequests")) + usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) + overage = from_float(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + reset_date = from_union([from_none, lambda x: from_datetime(x)], obj.get("resetDate")) + return AssistantUsageQuotaSnapshot( + is_unlimited_entitlement=is_unlimited_entitlement, + entitlement_requests=entitlement_requests, + used_requests=used_requests, + usage_allowed_with_exhausted_quota=usage_allowed_with_exhausted_quota, + overage=overage, + overage_allowed_with_exhausted_quota=overage_allowed_with_exhausted_quota, + remaining_percentage=remaining_percentage, + reset_date=reset_date, + ) -class MCPServerStatus(Enum): - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - - New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - """ - CONNECTED = "connected" - DISABLED = "disabled" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - NOT_CONFIGURED = "not_configured" - PENDING = "pending" + def to_dict(self) -> dict: + result: dict = {} + result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) + result["entitlementRequests"] = to_float(self.entitlement_requests) + result["usedRequests"] = to_float(self.used_requests) + result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) + result["overage"] = to_float(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + if self.reset_date is not None: + result["resetDate"] = from_union([from_none, lambda x: to_datetime(x)], self.reset_date) + return result @dataclass -class MCPServersLoadedServer: - name: str - """Server name (config key)""" - - status: MCPServerStatus - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - error: str | None = None - """Error message if the server failed to connect""" - - source: str | None = None - """Configuration source: user, workspace, plugin, or builtin""" +class AssistantUsageCopilotUsageTokenDetail: + "Token usage detail for a single billing category" + batch_size: float + cost_per_batch: float + token_count: float + token_type: str @staticmethod - def from_dict(obj: Any) -> 'MCPServersLoadedServer': + def from_dict(obj: Any) -> "AssistantUsageCopilotUsageTokenDetail": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - status = MCPServerStatus(obj.get("status")) - error = from_union([from_str, from_none], obj.get("error")) - source = from_union([from_str, from_none], obj.get("source")) - return MCPServersLoadedServer(name, status, error, source) + batch_size = from_float(obj.get("batchSize")) + cost_per_batch = from_float(obj.get("costPerBatch")) + token_count = from_float(obj.get("tokenCount")) + token_type = from_str(obj.get("tokenType")) + return AssistantUsageCopilotUsageTokenDetail( + batch_size=batch_size, + cost_per_batch=cost_per_batch, + token_count=token_count, + token_type=token_type, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["status"] = to_enum(MCPServerStatus, self.status) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) + result["batchSize"] = to_float(self.batch_size) + result["costPerBatch"] = to_float(self.cost_per_batch) + result["tokenCount"] = to_float(self.token_count) + result["tokenType"] = from_str(self.token_type) return result -class ShutdownType(Enum): - """Whether the session ended normally ("routine") or due to a crash/fatal error ("error")""" - - ERROR = "error" - ROUTINE = "routine" - - @dataclass -class SkillsLoadedSkill: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled""" +class AssistantUsageCopilotUsage: + "Per-request cost and usage data from the CAPI copilot_usage response field" + token_details: list[AssistantUsageCopilotUsageTokenDetail] + total_nano_aiu: float - name: str - """Unique identifier for the skill""" + @staticmethod + def from_dict(obj: Any) -> "AssistantUsageCopilotUsage": + assert isinstance(obj, dict) + token_details = from_list(lambda x: AssistantUsageCopilotUsageTokenDetail.from_dict(x), obj.get("tokenDetails")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + return AssistantUsageCopilotUsage( + token_details=token_details, + total_nano_aiu=total_nano_aiu, + ) - source: str - """Source location type of the skill (e.g., project, personal, plugin)""" + def to_dict(self) -> dict: + result: dict = {} + result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageCopilotUsageTokenDetail, x), self.token_details) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + return result - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" - path: str | None = None - """Absolute path to the skill file, if available""" +@dataclass +class AssistantUsageData: + "LLM API call usage metrics including tokens, costs, quotas, and billing information" + model: str + input_tokens: float | None = None + output_tokens: float | None = None + cache_read_tokens: float | None = None + cache_write_tokens: float | None = None + reasoning_tokens: float | None = None + cost: float | None = None + duration: float | None = None + ttft_ms: float | None = None + inter_token_latency_ms: float | None = None + initiator: str | None = None + api_call_id: str | None = None + provider_call_id: str | None = None + parent_tool_call_id: str | None = None + quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None + copilot_usage: AssistantUsageCopilotUsage | None = None + reasoning_effort: str | None = None @staticmethod - def from_dict(obj: Any) -> 'SkillsLoadedSkill': + def from_dict(obj: Any) -> "AssistantUsageData": assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - return SkillsLoadedSkill(description, enabled, name, source, user_invocable, path) + model = from_str(obj.get("model")) + input_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("inputTokens")) + output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) + cache_read_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheReadTokens")) + cache_write_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheWriteTokens")) + reasoning_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("reasoningTokens")) + cost = from_union([from_none, lambda x: from_float(x)], obj.get("cost")) + duration = from_union([from_none, lambda x: from_float(x)], obj.get("duration")) + ttft_ms = from_union([from_none, lambda x: from_float(x)], obj.get("ttftMs")) + inter_token_latency_ms = from_union([from_none, lambda x: from_float(x)], obj.get("interTokenLatencyMs")) + initiator = from_union([from_none, lambda x: from_str(x)], obj.get("initiator")) + api_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("apiCallId")) + provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + quota_snapshots = from_union([from_none, lambda x: from_dict(lambda x: AssistantUsageQuotaSnapshot.from_dict(x), x)], obj.get("quotaSnapshots")) + copilot_usage = from_union([from_none, lambda x: AssistantUsageCopilotUsage.from_dict(x)], obj.get("copilotUsage")) + reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + return AssistantUsageData( + model=model, + input_tokens=input_tokens, + output_tokens=output_tokens, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + reasoning_tokens=reasoning_tokens, + cost=cost, + duration=duration, + ttft_ms=ttft_ms, + inter_token_latency_ms=inter_token_latency_ms, + initiator=initiator, + api_call_id=api_call_id, + provider_call_id=provider_call_id, + parent_tool_call_id=parent_tool_call_id, + quota_snapshots=quota_snapshots, + copilot_usage=copilot_usage, + reasoning_effort=reasoning_effort, + ) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) + result["model"] = from_str(self.model) + if self.input_tokens is not None: + result["inputTokens"] = from_union([from_none, lambda x: to_float(x)], self.input_tokens) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + if self.cache_read_tokens is not None: + result["cacheReadTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_read_tokens) + if self.cache_write_tokens is not None: + result["cacheWriteTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_write_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_none, lambda x: to_float(x)], self.reasoning_tokens) + if self.cost is not None: + result["cost"] = from_union([from_none, lambda x: to_float(x)], self.cost) + if self.duration is not None: + result["duration"] = from_union([from_none, lambda x: to_float(x)], self.duration) + if self.ttft_ms is not None: + result["ttftMs"] = from_union([from_none, lambda x: to_float(x)], self.ttft_ms) + if self.inter_token_latency_ms is not None: + result["interTokenLatencyMs"] = from_union([from_none, lambda x: to_float(x)], self.inter_token_latency_ms) + if self.initiator is not None: + result["initiator"] = from_union([from_none, lambda x: from_str(x)], self.initiator) + if self.api_call_id is not None: + result["apiCallId"] = from_union([from_none, lambda x: from_str(x)], self.api_call_id) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + if self.quota_snapshots is not None: + result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x)], self.quota_snapshots) + if self.copilot_usage is not None: + result["copilotUsage"] = from_union([from_none, lambda x: to_class(AssistantUsageCopilotUsage, x)], self.copilot_usage) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) return result -class HandoffSourceType(Enum): - """Origin type of the session being handed off""" - - LOCAL = "local" - REMOTE = "remote" - - @dataclass -class MCPOauthRequiredStaticClientConfig: - """Static OAuth client configuration, if the server specifies one""" - - client_id: str - """OAuth client ID for the server""" - - public_client: bool | None = None - """Whether this is a public OAuth client""" +class AbortData: + "Turn abort information including the reason for termination" + reason: str @staticmethod - def from_dict(obj: Any) -> 'MCPOauthRequiredStaticClientConfig': + def from_dict(obj: Any) -> "AbortData": assert isinstance(obj, dict) - client_id = from_str(obj.get("clientId")) - public_client = from_union([from_bool, from_none], obj.get("publicClient")) - return MCPOauthRequiredStaticClientConfig(client_id, public_client) + reason = from_str(obj.get("reason")) + return AbortData( + reason=reason, + ) def to_dict(self) -> dict: result: dict = {} - result["clientId"] = from_str(self.client_id) - if self.public_client is not None: - result["publicClient"] = from_union([from_bool, from_none], self.public_client) + result["reason"] = from_str(self.reason) return result -class AssistantMessageToolRequestType(Enum): - """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool - calls. Defaults to "function" when absent. - """ - CUSTOM = "custom" - FUNCTION = "function" - - @dataclass -class AssistantMessageToolRequest: - """A tool invocation request from the assistant""" - - name: str - """Name of the tool being invoked""" - +class ToolUserRequestedData: + "User-initiated tool invocation request with tool name and arguments" tool_call_id: str - """Unique identifier for this tool call""" - + tool_name: str arguments: Any = None - """Arguments to pass to the tool, format depends on the tool""" - intention_summary: str | None = None - """Resolved intention summary describing what this specific call does""" + @staticmethod + def from_dict(obj: Any) -> "ToolUserRequestedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + return ToolUserRequestedData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + ) - mcp_server_name: str | None = None - """Name of the MCP server hosting this tool, when the tool is an MCP tool""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + return result - tool_title: str | None = None - """Human-readable display title for the tool""" - type: AssistantMessageToolRequestType | None = None - """Tool call type: "function" for standard tool calls, "custom" for grammar-based tool - calls. Defaults to "function" when absent. - """ +@dataclass +class ToolExecutionStartData: + "Tool execution startup details including MCP server information when applicable" + tool_call_id: str + tool_name: str + arguments: Any = None + mcp_server_name: str | None = None + mcp_tool_name: str | None = None + parent_tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> 'AssistantMessageToolRequest': + def from_dict(obj: Any) -> "ToolExecutionStartData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") - intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) - mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) - tool_title = from_union([from_str, from_none], obj.get("toolTitle")) - type = from_union([AssistantMessageToolRequestType, from_none], obj.get("type")) - return AssistantMessageToolRequest(name, tool_call_id, arguments, intention_summary, mcp_server_name, tool_title, type) + mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) + mcp_tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpToolName")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return ToolExecutionStartData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + mcp_server_name=mcp_server_name, + mcp_tool_name=mcp_tool_name, + parent_tool_call_id=parent_tool_call_id, + ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) if self.arguments is not None: result["arguments"] = self.arguments - if self.intention_summary is not None: - result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_str, from_none], self.tool_title) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(AssistantMessageToolRequestType, x), from_none], self.type) + result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + if self.mcp_tool_name is not None: + result["mcpToolName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_tool_name) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) return result @dataclass -class CapabilitiesChangedUI: - """UI capability changes""" - - elicitation: bool | None = None - """Whether elicitation is now supported""" +class ToolExecutionPartialResultData: + "Streaming tool execution output for incremental result display" + tool_call_id: str + partial_output: str @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesChangedUI': + def from_dict(obj: Any) -> "ToolExecutionPartialResultData": assert isinstance(obj, dict) - elicitation = from_union([from_bool, from_none], obj.get("elicitation")) - return CapabilitiesChangedUI(elicitation) + tool_call_id = from_str(obj.get("toolCallId")) + partial_output = from_str(obj.get("partialOutput")) + return ToolExecutionPartialResultData( + tool_call_id=tool_call_id, + partial_output=partial_output, + ) def to_dict(self) -> dict: result: dict = {} - if self.elicitation is not None: - result["elicitation"] = from_union([from_bool, from_none], self.elicitation) + result["toolCallId"] = from_str(self.tool_call_id) + result["partialOutput"] = from_str(self.partial_output) return result @dataclass -class Data: - """Session initialization metadata including context and configuration - - Session resume metadata including current context and event count - - Notifies Mission Control that the session's remote steering capability has changed - - Error details for timeline display including message and optional diagnostic information - - Payload indicating the session is idle with no background agents in flight - - Session title change payload containing the new display title - - Informational message for timeline display with categorization - - Warning message for timeline display with categorization - - Model change details including previous and new model identifiers - - Agent mode change details including previous and new modes - - Plan file operation details indicating what changed - - Workspace file change details including path and operation type - - Session handoff metadata including source, context, and repository information - - Conversation truncation statistics including token counts and removed content metrics - - Session rewind details including target event and count of removed events - - Session termination metrics including usage statistics, code changes, and shutdown - reason - - Updated working directory and git context after the change - - Current context window usage statistics including token and message counts - - Context window breakdown at the start of LLM-powered conversation compaction - - Conversation compaction results including success status, metrics, and optional error - details - - Task completion notification with summary from the agent - - Empty payload; the event signals that the pending message queue has changed - - Turn initialization metadata including identifier and interaction tracking - - Agent intent description for current activity or plan - - Assistant reasoning content for timeline display with complete thinking text - - Streaming reasoning delta for incremental extended thinking updates - - Streaming response progress with cumulative byte count - - Assistant response containing text content, optional tool requests, and interaction - metadata - - Streaming assistant message delta for incremental response updates - - Turn completion metadata including the turn identifier - - LLM API call usage metrics including tokens, costs, quotas, and billing information - - Turn abort information including the reason for termination - - User-initiated tool invocation request with tool name and arguments - - Tool execution startup details including MCP server information when applicable - - Streaming tool execution output for incremental result display - - Tool execution progress notification with status message - - Tool execution completion results including success status, detailed output, and error - information - - Skill invocation details including content, allowed tools, and plugin metadata - - Sub-agent startup details including parent tool call and agent information - - Sub-agent completion details for successful execution - - Sub-agent failure details including error message and agent information - - Custom agent selection details including name and available tools - - Empty payload; the event signals that the custom agent was deselected, returning to the - default agent - - Hook invocation start details including type and input data - - Hook invocation completion details including output, success status, and error - information - - System or developer message content with role and optional template metadata - - System-generated notification for runtime events like background task completion - - Permission request notification requiring client approval with request details - - Permission request completion notification signaling UI dismissal - - User input request notification with question and optional predefined choices - - User input request completion with the user's response - - Elicitation request; may be form-based (structured input) or URL-based (browser - redirect) - - Elicitation request completion with the user's response - - Sampling request from an MCP server; contains the server name and a requestId for - correlation - - Sampling request completion notification signaling UI dismissal - - OAuth authentication request for an MCP server - - MCP OAuth request completion notification - - External tool invocation request for client-side tool execution - - External tool completion notification signaling UI dismissal - - Queued slash command dispatch request for client execution - - Registered command dispatch request routed to the owning client - - Queued command completion notification signaling UI dismissal - - SDK command registration change notification - - Session capability change notification - - Plan approval request with plan content and available user actions - - Plan mode exit completion with the user's approval decision and optional feedback - """ - already_in_use: bool | None = None - """Whether the session was already in use by another client at start time - - Whether the session was already in use by another client at resume time - """ - context: Context | str | None = None - """Working directory and git context at session start - - Updated working directory and git context at resume time - - Additional context information for the handoff - """ - copilot_version: str | None = None - """Version string of the Copilot application""" - - producer: str | None = None - """Identifier of the software producing the events (e.g., "copilot-agent")""" - - reasoning_effort: str | None = None - """Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", - "xhigh") - - Reasoning effort level after the model change, if applicable - """ - remote_steerable: bool | None = None - """Whether this session supports remote steering via Mission Control - - Whether this session now supports remote steering via Mission Control - """ - selected_model: str | None = None - """Model selected at session creation time, if any - - Model currently selected at resume time - """ - session_id: str | None = None - """Unique identifier for the session - - Session ID that this external tool request belongs to - """ - start_time: datetime | None = None - """ISO 8601 timestamp when the session was created""" - - version: float | None = None - """Schema version number for the session event format""" - - event_count: float | None = None - """Total number of persisted events in the session at the time of resume""" - - resume_time: datetime | None = None - """ISO 8601 timestamp when the session was resumed""" - - error_type: str | None = None - """Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", - "context_limit", "query") - """ - message: str | None = None - """Human-readable error message - - Human-readable informational message for display in the timeline - - Human-readable warning message for display in the timeline - - Message describing what information is needed from the user - """ - provider_call_id: str | None = None - """GitHub request tracing ID (x-github-request-id header) for correlating with server-side - logs - - GitHub request tracing ID (x-github-request-id header) for server-side log correlation - """ - stack: str | None = None - """Error stack trace, when available""" - - status_code: int | None = None - """HTTP status code from the upstream request, if applicable""" +class ToolExecutionProgressData: + "Tool execution progress notification with status message" + tool_call_id: str + progress_message: str - url: str | None = None - """Optional URL associated with this error that the user can open in a browser - - Optional URL associated with this message that the user can open in a browser - - Optional URL associated with this warning that the user can open in a browser - - URL to open in the user's browser (url mode only) - """ - aborted: bool | None = None - """True when the preceding agentic loop was cancelled via abort signal""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionProgressData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + progress_message = from_str(obj.get("progressMessage")) + return ToolExecutionProgressData( + tool_call_id=tool_call_id, + progress_message=progress_message, + ) - title: str | None = None - """The new display title for the session""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["progressMessage"] = from_str(self.progress_message) + return result - info_type: str | None = None - """Category of informational message (e.g., "notification", "timing", "context_window", - "mcp", "snapshot", "configuration", "authentication", "model") - """ - warning_type: str | None = None - """Category of warning (e.g., "subscription", "policy", "mcp")""" - new_model: str | None = None - """Newly selected model identifier""" +@dataclass +class ToolExecutionCompleteDataResultContentsItemIconsItem: + "Icon image for a resource" + src: str + mime_type: str | None = None + sizes: list[str] | None = None + theme: ToolExecutionCompleteDataResultContentsItemIconsItemTheme | None = None - previous_model: str | None = None - """Model that was previously selected, if any""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItemIconsItem": + assert isinstance(obj, dict) + src = from_str(obj.get("src")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + sizes = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("sizes")) + theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], obj.get("theme")) + return ToolExecutionCompleteDataResultContentsItemIconsItem( + src=src, + mime_type=mime_type, + sizes=sizes, + theme=theme, + ) - previous_reasoning_effort: str | None = None - """Reasoning effort level before the model change, if applicable""" + def to_dict(self) -> dict: + result: dict = {} + result["src"] = from_str(self.src) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.sizes) + if self.theme is not None: + result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], self.theme) + return result - new_mode: str | None = None - """Agent mode after the change (e.g., "interactive", "plan", "autopilot")""" - previous_mode: str | None = None - """Agent mode before the change (e.g., "interactive", "plan", "autopilot")""" +@dataclass +class ToolExecutionCompleteDataResultContentsItem: + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource" + type: ToolExecutionCompleteDataResultContentsItemType + text: str | None = None + exit_code: float | None = None + cwd: str | None = None + data: str | None = None + mime_type: str | None = None + icons: list[ToolExecutionCompleteDataResultContentsItemIconsItem] | None = None + name: str | None = None + title: str | None = None + uri: str | None = None + description: str | None = None + size: float | None = None + resource: Any = None - operation: ChangedOperation | None = None - """The type of operation performed on the plan file - - Whether the file was newly created or updated - """ - path: str | None = None - """Relative path within the session workspace files directory - - File path to the SKILL.md definition - """ - handoff_time: datetime | None = None - """ISO 8601 timestamp when the handoff occurred""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": + assert isinstance(obj, dict) + type = parse_enum(ToolExecutionCompleteDataResultContentsItemType, obj.get("type")) + text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) + exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + cwd = from_union([from_none, lambda x: from_str(x)], obj.get("cwd")) + data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) + mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + icons = from_union([from_none, lambda x: from_list(lambda x: ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict(x), x)], obj.get("icons")) + name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) + title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + uri = from_union([from_none, lambda x: from_str(x)], obj.get("uri")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + size = from_union([from_none, lambda x: from_float(x)], obj.get("size")) + resource = obj.get("resource") + return ToolExecutionCompleteDataResultContentsItem( + type=type, + text=text, + exit_code=exit_code, + cwd=cwd, + data=data, + mime_type=mime_type, + icons=icons, + name=name, + title=title, + uri=uri, + description=description, + size=size, + resource=resource, + ) - host: str | None = None - """GitHub host URL for the source session (e.g., https://github.com or - https://tenant.ghe.com) - """ - remote_session_id: str | None = None - """Session ID of the remote session being handed off""" + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(ToolExecutionCompleteDataResultContentsItemType, self.type) + if self.text is not None: + result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + if self.cwd is not None: + result["cwd"] = from_union([from_none, lambda x: from_str(x)], self.cwd) + if self.data is not None: + result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + if self.icons is not None: + result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItemIconsItem, x), x)], self.icons) + if self.name is not None: + result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + if self.title is not None: + result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + if self.uri is not None: + result["uri"] = from_union([from_none, lambda x: from_str(x)], self.uri) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + if self.size is not None: + result["size"] = from_union([from_none, lambda x: to_float(x)], self.size) + if self.resource is not None: + result["resource"] = self.resource + return result - repository: HandoffRepository | str | None = None - """Repository context for the handed-off session - - Repository identifier derived from the git remote URL ("owner/name" for GitHub, - "org/project/repo" for Azure DevOps) - """ - source_type: HandoffSourceType | None = None - """Origin type of the session being handed off""" - summary: str | None = None - """Summary of the work done in the source session - - Summary of the completed task, provided by the agent - - Summary of the plan that was created - """ - messages_removed_during_truncation: float | None = None - """Number of messages removed by truncation""" +@dataclass +class ToolExecutionCompleteDataResult: + "Tool execution result on success" + content: str + detailed_content: str | None = None + contents: list[ToolExecutionCompleteDataResultContentsItem] | None = None - performed_by: str | None = None - """Identifier of the component that performed truncation (e.g., "BasicTruncator")""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataResult": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + detailed_content = from_union([from_none, lambda x: from_str(x)], obj.get("detailedContent")) + contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItem.from_dict, x)], obj.get("contents")) + return ToolExecutionCompleteDataResult( + content=content, + detailed_content=detailed_content, + contents=contents, + ) - post_truncation_messages_length: float | None = None - """Number of conversation messages after truncation""" - - post_truncation_tokens_in_messages: float | None = None - """Total tokens in conversation messages after truncation""" - - pre_truncation_messages_length: float | None = None - """Number of conversation messages before truncation""" - - pre_truncation_tokens_in_messages: float | None = None - """Total tokens in conversation messages before truncation""" + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + if self.detailed_content is not None: + result["detailedContent"] = from_union([from_none, lambda x: from_str(x)], self.detailed_content) + if self.contents is not None: + result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItem, x), x)], self.contents) + return result - token_limit: float | None = None - """Maximum token count for the model's context window""" - tokens_removed_during_truncation: float | None = None - """Number of tokens removed by truncation""" +@dataclass +class ToolExecutionCompleteDataError: + "Error details when the tool execution failed" + message: str + code: str | None = None - events_removed: float | None = None - """Number of events that were removed by the rewind""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteDataError": + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + code = from_union([from_none, lambda x: from_str(x)], obj.get("code")) + return ToolExecutionCompleteDataError( + message=message, + code=code, + ) - up_to_event_id: str | None = None - """Event ID that was rewound to; this event and all after it were removed""" + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.code is not None: + result["code"] = from_union([from_none, lambda x: from_str(x)], self.code) + return result - code_changes: ShutdownCodeChanges | None = None - """Aggregate code change metrics for the session""" - conversation_tokens: float | None = None - """Non-system message token count at shutdown - - Token count from non-system messages (user, assistant, tool) - - Token count from non-system messages (user, assistant, tool) at compaction start - - Token count from non-system messages (user, assistant, tool) after compaction - """ - current_model: str | None = None - """Model that was selected at the time of shutdown""" +@dataclass +class ToolExecutionCompleteData: + "Tool execution completion results including success status, detailed output, and error information" + tool_call_id: str + success: bool + model: str | None = None + interaction_id: str | None = None + is_user_requested: bool | None = None + result: ToolExecutionCompleteDataResult | None = None + error: ToolExecutionCompleteDataError | None = None + tool_telemetry: dict[str, Any] | None = None + parent_tool_call_id: str | None = None - current_tokens: float | None = None - """Total tokens in context window at shutdown - - Current number of tokens in the context window - """ - error_reason: str | None = None - """Error description when shutdownType is "error\"""" + @staticmethod + def from_dict(obj: Any) -> "ToolExecutionCompleteData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + success = from_bool(obj.get("success")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + is_user_requested = from_union([from_none, lambda x: from_bool(x)], obj.get("isUserRequested")) + result = from_union([from_none, lambda x: ToolExecutionCompleteDataResult.from_dict(x)], obj.get("result")) + error = from_union([from_none, lambda x: ToolExecutionCompleteDataError.from_dict(x)], obj.get("error")) + tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) + parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + return ToolExecutionCompleteData( + tool_call_id=tool_call_id, + success=success, + model=model, + interaction_id=interaction_id, + is_user_requested=is_user_requested, + result=result, + error=error, + tool_telemetry=tool_telemetry, + parent_tool_call_id=parent_tool_call_id, + ) - model_metrics: dict[str, ShutdownModelMetric] | None = None - """Per-model usage breakdown, keyed by model identifier""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["success"] = from_bool(self.success) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + if self.is_user_requested is not None: + result["isUserRequested"] = from_union([from_none, lambda x: from_bool(x)], self.is_user_requested) + if self.result is not None: + result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataResult, x)], self.result) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataError, x)], self.error) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + return result - session_start_time: float | None = None - """Unix timestamp (milliseconds) when the session started""" - shutdown_type: ShutdownType | None = None - """Whether the session ended normally ("routine") or due to a crash/fatal error ("error")""" +@dataclass +class SkillInvokedData: + "Skill invocation details including content, allowed tools, and plugin metadata" + name: str + path: str + content: str + allowed_tools: list[str] | None = None + plugin_name: str | None = None + plugin_version: str | None = None + description: str | None = None - system_tokens: float | None = None - """System message token count at shutdown - - Token count from system message(s) - - Token count from system message(s) at compaction start - - Token count from system message(s) after compaction - """ - tool_definitions_tokens: float | None = None - """Tool definitions token count at shutdown - - Token count from tool definitions - - Token count from tool definitions at compaction start - - Token count from tool definitions after compaction - """ - total_api_duration_ms: float | None = None - """Cumulative time spent in API calls during the session, in milliseconds""" - - total_premium_requests: float | None = None - """Total number of premium API requests used during the session""" + @staticmethod + def from_dict(obj: Any) -> "SkillInvokedData": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + path = from_str(obj.get("path")) + content = from_str(obj.get("content")) + allowed_tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("allowedTools")) + plugin_name = from_union([from_none, lambda x: from_str(x)], obj.get("pluginName")) + plugin_version = from_union([from_none, lambda x: from_str(x)], obj.get("pluginVersion")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + return SkillInvokedData( + name=name, + path=path, + content=content, + allowed_tools=allowed_tools, + plugin_name=plugin_name, + plugin_version=plugin_version, + description=description, + ) - base_commit: str | None = None - """Base commit of current git branch at session start time""" + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["content"] = from_str(self.content) + if self.allowed_tools is not None: + result["allowedTools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.allowed_tools) + if self.plugin_name is not None: + result["pluginName"] = from_union([from_none, lambda x: from_str(x)], self.plugin_name) + if self.plugin_version is not None: + result["pluginVersion"] = from_union([from_none, lambda x: from_str(x)], self.plugin_version) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + return result - branch: str | None = None - """Current git branch name""" - cwd: str | None = None - """Current working directory path""" +@dataclass +class SubagentStartedData: + "Sub-agent startup details including parent tool call and agent information" + tool_call_id: str + agent_name: str + agent_display_name: str + agent_description: str - git_root: str | None = None - """Root directory of the git repository, resolved via git rev-parse""" + @staticmethod + def from_dict(obj: Any) -> "SubagentStartedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_description = from_str(obj.get("agentDescription")) + return SubagentStartedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + agent_description=agent_description, + ) - head_commit: str | None = None - """Head commit of current git branch at session start time""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentDescription"] = from_str(self.agent_description) + return result - host_type: ContextChangedHostType | None = None - """Hosting platform type of the repository (github or ado)""" - is_initial: bool | None = None - """Whether this is the first usage_info event emitted in this session""" +@dataclass +class SubagentCompletedData: + "Sub-agent completion details for successful execution" + tool_call_id: str + agent_name: str + agent_display_name: str + model: str | None = None + total_tool_calls: float | None = None + total_tokens: float | None = None + duration_ms: float | None = None - messages_length: float | None = None - """Current number of messages in the conversation""" + @staticmethod + def from_dict(obj: Any) -> "SubagentCompletedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) + duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + return SubagentCompletedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + model=model, + total_tool_calls=total_tool_calls, + total_tokens=total_tokens, + duration_ms=duration_ms, + ) - checkpoint_number: float | None = None - """Checkpoint snapshot number created for recovery""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + return result - checkpoint_path: str | None = None - """File path where the checkpoint was stored""" - compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None - """Token usage breakdown for the compaction LLM call""" - - error: Error | str | None = None - """Error message if compaction failed - - Error details when the tool execution failed - - Error message describing why the sub-agent failed - - Error details when the hook failed - """ - messages_removed: float | None = None - """Number of messages removed during compaction""" +@dataclass +class SubagentFailedData: + "Sub-agent failure details including error message and agent information" + tool_call_id: str + agent_name: str + agent_display_name: str + error: str + model: str | None = None + total_tool_calls: float | None = None + total_tokens: float | None = None + duration_ms: float | None = None - post_compaction_tokens: float | None = None - """Total tokens in conversation after compaction""" + @staticmethod + def from_dict(obj: Any) -> "SubagentFailedData": + assert isinstance(obj, dict) + tool_call_id = from_str(obj.get("toolCallId")) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + error = from_str(obj.get("error")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) + duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + return SubagentFailedData( + tool_call_id=tool_call_id, + agent_name=agent_name, + agent_display_name=agent_display_name, + error=error, + model=model, + total_tool_calls=total_tool_calls, + total_tokens=total_tokens, + duration_ms=duration_ms, + ) - pre_compaction_messages_length: float | None = None - """Number of messages before compaction""" + def to_dict(self) -> dict: + result: dict = {} + result["toolCallId"] = from_str(self.tool_call_id) + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["error"] = from_str(self.error) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + return result - pre_compaction_tokens: float | None = None - """Total tokens in conversation before compaction""" - request_id: str | None = None - """GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - - GitHub request tracing ID (x-github-request-id header) for correlating with server-side - logs - - Unique identifier for this permission request; used to respond via - session.respondToPermission() - - Request ID of the resolved permission request; clients should dismiss any UI for this - request - - Unique identifier for this input request; used to respond via - session.respondToUserInput() - - Request ID of the resolved user input request; clients should dismiss any UI for this - request - - Unique identifier for this elicitation request; used to respond via - session.respondToElicitation() - - Request ID of the resolved elicitation request; clients should dismiss any UI for this - request - - Unique identifier for this sampling request; used to respond via - session.respondToSampling() - - Request ID of the resolved sampling request; clients should dismiss any UI for this - request - - Unique identifier for this OAuth request; used to respond via - session.respondToMcpOAuth() - - Request ID of the resolved OAuth request - - Unique identifier for this request; used to respond via session.respondToExternalTool() - - Request ID of the resolved external tool request; clients should dismiss any UI for this - request - - Unique identifier for this request; used to respond via session.respondToQueuedCommand() - - Unique identifier; used to respond via session.commands.handlePendingCommand() - - Request ID of the resolved command request; clients should dismiss any UI for this - request - - Unique identifier for this request; used to respond via session.respondToExitPlanMode() - - Request ID of the resolved exit plan mode request; clients should dismiss any UI for this - request - """ - success: bool | None = None - """Whether compaction completed successfully - - Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - - Whether the tool execution completed successfully - - Whether the hook completed successfully - """ - summary_content: str | None = None - """LLM-generated summary of the compacted conversation history""" +@dataclass +class SubagentSelectedData: + "Custom agent selection details including name and available tools" + agent_name: str + agent_display_name: str + tools: list[str] | None - tokens_removed: float | None = None - """Number of tokens removed during compaction""" + @staticmethod + def from_dict(obj: Any) -> "SubagentSelectedData": + assert isinstance(obj, dict) + agent_name = from_str(obj.get("agentName")) + agent_display_name = from_str(obj.get("agentDisplayName")) + tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("tools")) + return SubagentSelectedData( + agent_name=agent_name, + agent_display_name=agent_display_name, + tools=tools, + ) - agent_mode: UserMessageAgentMode | None = None - """The agent mode that was active when this message was sent""" + def to_dict(self) -> dict: + result: dict = {} + result["agentName"] = from_str(self.agent_name) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["tools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.tools) + return result - attachments: list[UserMessageAttachment] | None = None - """Files, selections, or GitHub references attached to the message""" - - content: str | dict[str, float | bool | list[str] | str] | None = None - """The user's message text as displayed in the timeline - - The complete extended thinking text from the model - - The assistant's text response content - - Full content of the skill file, injected into the conversation for the model - - The system or developer prompt text - - The notification text, typically wrapped in XML tags - - The submitted form data when action is 'accept'; keys match the requested schema fields - """ - interaction_id: str | None = None - """CAPI interaction ID for correlating this user message with its turn - - CAPI interaction ID for correlating this turn with upstream telemetry - - CAPI interaction ID for correlating this message with upstream telemetry - - CAPI interaction ID for correlating this tool execution with upstream telemetry - """ - source: str | None = None - """Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected - messages that should be hidden from the user) - """ - transformed_content: str | None = None - """Transformed version of the message sent to the model, with XML wrapping, timestamps, and - other augmentations for prompt caching - """ - turn_id: str | None = None - """Identifier for this turn within the agentic loop, typically a stringified turn number - - Identifier of the turn that has ended, matching the corresponding assistant.turn_start - event - """ - intent: str | None = None - """Short description of what the agent is currently doing or planning to do""" - - reasoning_id: str | None = None - """Unique identifier for this reasoning block - - Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning - event - """ - delta_content: str | None = None - """Incremental text chunk to append to the reasoning content - - Incremental text chunk to append to the message content - """ - total_response_size_bytes: float | None = None - """Cumulative total bytes received from the streaming response so far""" - encrypted_content: str | None = None - """Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume.""" +@dataclass +class SubagentDeselectedData: + "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" + @staticmethod + def from_dict(obj: Any) -> "SubagentDeselectedData": + assert isinstance(obj, dict) + return SubagentDeselectedData() - message_id: str | None = None - """Unique identifier for this assistant message - - Message ID this delta belongs to, matching the corresponding assistant.message event - """ - output_tokens: float | None = None - """Actual output token count from the API response (completion_tokens), used for accurate - token accounting - - Number of output tokens produced - """ - parent_tool_call_id: str | None = None - """Tool call ID of the parent tool invocation when this event originates from a sub-agent - - Parent tool call ID when this usage originates from a sub-agent - """ - phase: str | None = None - """Generation phase for phased-output models (e.g., thinking vs. response phases)""" + def to_dict(self) -> dict: + return {} - reasoning_opaque: str | None = None - """Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped - on resume. - """ - reasoning_text: str | None = None - """Readable reasoning text from the model's extended thinking""" - tool_requests: list[AssistantMessageToolRequest] | None = None - """Tool invocations requested by the assistant in this message""" +@dataclass +class HookStartData: + "Hook invocation start details including type and input data" + hook_invocation_id: str + hook_type: str + input: Any = None - api_call_id: str | None = None - """Completion ID from the model provider (e.g., chatcmpl-abc123)""" + @staticmethod + def from_dict(obj: Any) -> "HookStartData": + assert isinstance(obj, dict) + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) + input = obj.get("input") + return HookStartData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + input=input, + ) - cache_read_tokens: float | None = None - """Number of tokens read from prompt cache""" + def to_dict(self) -> dict: + result: dict = {} + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) + if self.input is not None: + result["input"] = self.input + return result - cache_write_tokens: float | None = None - """Number of tokens written to prompt cache""" - copilot_usage: AssistantUsageCopilotUsage | None = None - """Per-request cost and usage data from the CAPI copilot_usage response field""" +@dataclass +class HookEndDataError: + "Error details when the hook failed" + message: str + stack: str | None = None - cost: float | None = None - """Model multiplier cost for billing purposes""" + @staticmethod + def from_dict(obj: Any) -> "HookEndDataError": + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + return HookEndDataError( + message=message, + stack=stack, + ) - duration: float | None = None - """Duration of the API call in milliseconds""" + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + if self.stack is not None: + result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + return result - initiator: str | None = None - """What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for - user-initiated calls - """ - input_tokens: float | None = None - """Number of input tokens consumed""" - inter_token_latency_ms: float | None = None - """Average inter-token latency in milliseconds. Only available for streaming requests""" +@dataclass +class HookEndData: + "Hook invocation completion details including output, success status, and error information" + hook_invocation_id: str + hook_type: str + success: bool + output: Any = None + error: HookEndDataError | None = None - model: str | None = None - """Model identifier used for this API call - - Model identifier that generated this tool call - - Model used by the sub-agent - - Model used by the sub-agent (if any model calls succeeded before failure) - """ - quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None - """Per-quota resource usage snapshots, keyed by quota identifier""" + @staticmethod + def from_dict(obj: Any) -> "HookEndData": + assert isinstance(obj, dict) + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) + success = from_bool(obj.get("success")) + output = obj.get("output") + error = from_union([from_none, lambda x: HookEndDataError.from_dict(x)], obj.get("error")) + return HookEndData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + success=success, + output=output, + error=error, + ) - reasoning_tokens: float | None = None - """Number of output tokens used for reasoning (e.g., chain-of-thought)""" + def to_dict(self) -> dict: + result: dict = {} + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) + result["success"] = from_bool(self.success) + if self.output is not None: + result["output"] = self.output + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(HookEndDataError, x)], self.error) + return result - ttft_ms: float | None = None - """Time to first token in milliseconds. Only available for streaming requests""" - reason: str | None = None - """Reason the current turn was aborted (e.g., "user initiated")""" +@dataclass +class SystemMessageDataMetadata: + "Metadata about the prompt template and its construction" + prompt_version: str | None = None + variables: dict[str, Any] | None = None - arguments: Any = None - """Arguments for the tool invocation - - Arguments passed to the tool - - Arguments to pass to the external tool - """ - tool_call_id: str | None = None - """Unique identifier for this tool call - - Tool call ID this partial result belongs to - - Tool call ID this progress notification belongs to - - Unique identifier for the completed tool call - - Tool call ID of the parent tool invocation that spawned this sub-agent - - The LLM-assigned tool call ID that triggered this request; used by remote UIs to - correlate responses - - Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id - for remote UIs - - Tool call ID assigned to this external tool invocation - """ - tool_name: str | None = None - """Name of the tool the user wants to invoke - - Name of the tool being executed - - Name of the external tool to invoke - """ - mcp_server_name: str | None = None - """Name of the MCP server hosting this tool, when the tool is an MCP tool""" + @staticmethod + def from_dict(obj: Any) -> "SystemMessageDataMetadata": + assert isinstance(obj, dict) + prompt_version = from_union([from_none, lambda x: from_str(x)], obj.get("promptVersion")) + variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) + return SystemMessageDataMetadata( + prompt_version=prompt_version, + variables=variables, + ) - mcp_tool_name: str | None = None - """Original tool name on the MCP server, when the tool is an MCP tool""" + def to_dict(self) -> dict: + result: dict = {} + if self.prompt_version is not None: + result["promptVersion"] = from_union([from_none, lambda x: from_str(x)], self.prompt_version) + if self.variables is not None: + result["variables"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.variables) + return result - partial_output: str | None = None - """Incremental output chunk from the running tool""" - progress_message: str | None = None - """Human-readable progress status message (e.g., from an MCP server)""" +@dataclass +class SystemMessageData: + "System or developer message content with role and optional template metadata" + content: str + role: SystemMessageDataRole + name: str | None = None + metadata: SystemMessageDataMetadata | None = None - is_user_requested: bool | None = None - """Whether this tool call was explicitly requested by the user rather than the assistant""" + @staticmethod + def from_dict(obj: Any) -> "SystemMessageData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + role = parse_enum(SystemMessageDataRole, obj.get("role")) + name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) + metadata = from_union([from_none, lambda x: SystemMessageDataMetadata.from_dict(x)], obj.get("metadata")) + return SystemMessageData( + content=content, + role=role, + name=name, + metadata=metadata, + ) - result: Result | None = None - """Tool execution result on success - - The result of the permission request - """ - tool_telemetry: dict[str, Any] | None = None - """Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts)""" + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["role"] = to_enum(SystemMessageDataRole, self.role) + if self.name is not None: + result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + if self.metadata is not None: + result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageDataMetadata, x)], self.metadata) + return result - allowed_tools: list[str] | None = None - """Tool names that should be auto-approved when this skill is active""" +@dataclass +class SystemNotificationDataKind: + "Structured metadata identifying what triggered this notification" + type: SystemNotificationDataKindType + agent_id: str | None = None + agent_type: str | None = None + status: SystemNotificationDataKindStatus | None = None description: str | None = None - """Description of the skill from its SKILL.md frontmatter""" + prompt: str | None = None + shell_id: str | None = None + exit_code: float | None = None - name: str | None = None - """Name of the invoked skill - - Optional name identifier for the message source - """ - plugin_name: str | None = None - """Name of the plugin this skill originated from, when applicable""" + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationDataKind": + assert isinstance(obj, dict) + type = parse_enum(SystemNotificationDataKindType, obj.get("type")) + agent_id = from_union([from_none, lambda x: from_str(x)], obj.get("agentId")) + agent_type = from_union([from_none, lambda x: from_str(x)], obj.get("agentType")) + status = from_union([from_none, lambda x: parse_enum(SystemNotificationDataKindStatus, x)], obj.get("status")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + prompt = from_union([from_none, lambda x: from_str(x)], obj.get("prompt")) + shell_id = from_union([from_none, lambda x: from_str(x)], obj.get("shellId")) + exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + return SystemNotificationDataKind( + type=type, + agent_id=agent_id, + agent_type=agent_type, + status=status, + description=description, + prompt=prompt, + shell_id=shell_id, + exit_code=exit_code, + ) - plugin_version: str | None = None - """Version of the plugin this skill originated from, when applicable""" - - agent_description: str | None = None - """Description of what the sub-agent does""" - - agent_display_name: str | None = None - """Human-readable display name of the sub-agent - - Human-readable display name of the selected custom agent - """ - agent_name: str | None = None - """Internal name of the sub-agent - - Internal name of the selected custom agent - """ - duration_ms: float | None = None - """Wall-clock duration of the sub-agent execution in milliseconds""" + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(SystemNotificationDataKindType, self.type) + if self.agent_id is not None: + result["agentId"] = from_union([from_none, lambda x: from_str(x)], self.agent_id) + if self.agent_type is not None: + result["agentType"] = from_union([from_none, lambda x: from_str(x)], self.agent_type) + if self.status is not None: + result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationDataKindStatus, x)], self.status) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + if self.prompt is not None: + result["prompt"] = from_union([from_none, lambda x: from_str(x)], self.prompt) + if self.shell_id is not None: + result["shellId"] = from_union([from_none, lambda x: from_str(x)], self.shell_id) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + return result - total_tokens: float | None = None - """Total tokens (input + output) consumed by the sub-agent - - Total tokens (input + output) consumed before the sub-agent failed - """ - total_tool_calls: float | None = None - """Total number of tool calls made by the sub-agent - - Total number of tool calls made before the sub-agent failed - """ - tools: list[str] | None = None - """List of tool names available to this agent, or null for all tools""" - - hook_invocation_id: str | None = None - """Unique identifier for this hook invocation - - Identifier matching the corresponding hook.start event - """ - hook_type: str | None = None - """Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - - Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - """ - input: Any = None - """Input data passed to the hook""" - output: Any = None - """Output data produced by the hook""" +@dataclass +class SystemNotificationData: + "System-generated notification for runtime events like background task completion" + content: str + kind: SystemNotificationDataKind - metadata: SystemMessageMetadata | None = None - """Metadata about the prompt template and its construction""" + @staticmethod + def from_dict(obj: Any) -> "SystemNotificationData": + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + kind = SystemNotificationDataKind.from_dict(obj.get("kind")) + return SystemNotificationData( + content=content, + kind=kind, + ) - role: SystemMessageRole | None = None - """Message role: "system" for system prompts, "developer" for developer-injected instructions""" + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["kind"] = to_class(SystemNotificationDataKind, self.kind) + return result - kind: SystemNotification | None = None - """Structured metadata identifying what triggered this notification""" - permission_request: PermissionRequest | None = None - """Details of the permission being requested""" +@dataclass +class PermissionRequestShellCommand: + identifier: str + read_only: bool + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestShellCommand": + assert isinstance(obj, dict) + identifier = from_str(obj.get("identifier")) + read_only = from_bool(obj.get("readOnly")) + return PermissionRequestShellCommand( + identifier=identifier, + read_only=read_only, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["identifier"] = from_str(self.identifier) + result["readOnly"] = from_bool(self.read_only) + return result + + +@dataclass +class PermissionRequestShellPossibleURL: + url: str + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestShellPossibleURL": + assert isinstance(obj, dict) + url = from_str(obj.get("url")) + return PermissionRequestShellPossibleURL( + url=url, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["url"] = from_str(self.url) + return result + + +@dataclass +class PermissionRequest: + "Details of the permission being requested" + kind: PermissionRequestedDataPermissionRequestKind + tool_call_id: str | None = None + full_command_text: str | None = None + intention: str | None = None + commands: list[PermissionRequestShellCommand] | None = None + possible_paths: list[str] | None = None + possible_urls: list[PermissionRequestShellPossibleURL] | None = None + has_write_file_redirection: bool | None = None + can_offer_session_approval: bool | None = None + warning: str | None = None + file_name: str | None = None + diff: str | None = None + new_file_contents: str | None = None + path: str | None = None + server_name: str | None = None + tool_name: str | None = None + tool_title: str | None = None + args: Any = None + read_only: bool | None = None + url: str | None = None + action: PermissionRequestMemoryAction | None = None + subject: str | None = None + fact: str | None = None + citations: str | None = None + direction: PermissionRequestMemoryDirection | None = None + reason: str | None = None + tool_description: str | None = None + tool_args: Any = None + hook_message: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequest": + assert isinstance(obj, dict) + kind = parse_enum(PermissionRequestedDataPermissionRequestKind, obj.get("kind")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + full_command_text = from_union([from_none, lambda x: from_str(x)], obj.get("fullCommandText")) + intention = from_union([from_none, lambda x: from_str(x)], obj.get("intention")) + commands = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestShellCommand.from_dict(x), x)], obj.get("commands")) + possible_paths = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("possiblePaths")) + possible_urls = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestShellPossibleURL.from_dict(x), x)], obj.get("possibleUrls")) + has_write_file_redirection = from_union([from_none, lambda x: from_bool(x)], obj.get("hasWriteFileRedirection")) + can_offer_session_approval = from_union([from_none, lambda x: from_bool(x)], obj.get("canOfferSessionApproval")) + warning = from_union([from_none, lambda x: from_str(x)], obj.get("warning")) + file_name = from_union([from_none, lambda x: from_str(x)], obj.get("fileName")) + diff = from_union([from_none, lambda x: from_str(x)], obj.get("diff")) + new_file_contents = from_union([from_none, lambda x: from_str(x)], obj.get("newFileContents")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + server_name = from_union([from_none, lambda x: from_str(x)], obj.get("serverName")) + tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("toolName")) + tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) + args = obj.get("args") + read_only = from_union([from_none, lambda x: from_bool(x)], obj.get("readOnly")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store")) + subject = from_union([from_none, lambda x: from_str(x)], obj.get("subject")) + fact = from_union([from_none, lambda x: from_str(x)], obj.get("fact")) + citations = from_union([from_none, lambda x: from_str(x)], obj.get("citations")) + direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) + reason = from_union([from_none, lambda x: from_str(x)], obj.get("reason")) + tool_description = from_union([from_none, lambda x: from_str(x)], obj.get("toolDescription")) + tool_args = obj.get("toolArgs") + hook_message = from_union([from_none, lambda x: from_str(x)], obj.get("hookMessage")) + return PermissionRequest( + kind=kind, + tool_call_id=tool_call_id, + full_command_text=full_command_text, + intention=intention, + commands=commands, + possible_paths=possible_paths, + possible_urls=possible_urls, + has_write_file_redirection=has_write_file_redirection, + can_offer_session_approval=can_offer_session_approval, + warning=warning, + file_name=file_name, + diff=diff, + new_file_contents=new_file_contents, + path=path, + server_name=server_name, + tool_name=tool_name, + tool_title=tool_title, + args=args, + read_only=read_only, + url=url, + action=action, + subject=subject, + fact=fact, + citations=citations, + direction=direction, + reason=reason, + tool_description=tool_description, + tool_args=tool_args, + hook_message=hook_message, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionRequestedDataPermissionRequestKind, self.kind) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + if self.full_command_text is not None: + result["fullCommandText"] = from_union([from_none, lambda x: from_str(x)], self.full_command_text) + if self.intention is not None: + result["intention"] = from_union([from_none, lambda x: from_str(x)], self.intention) + if self.commands is not None: + result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x)], self.commands) + if self.possible_paths is not None: + result["possiblePaths"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.possible_paths) + if self.possible_urls is not None: + result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x)], self.possible_urls) + if self.has_write_file_redirection is not None: + result["hasWriteFileRedirection"] = from_union([from_none, lambda x: from_bool(x)], self.has_write_file_redirection) + if self.can_offer_session_approval is not None: + result["canOfferSessionApproval"] = from_union([from_none, lambda x: from_bool(x)], self.can_offer_session_approval) + if self.warning is not None: + result["warning"] = from_union([from_none, lambda x: from_str(x)], self.warning) + if self.file_name is not None: + result["fileName"] = from_union([from_none, lambda x: from_str(x)], self.file_name) + if self.diff is not None: + result["diff"] = from_union([from_none, lambda x: from_str(x)], self.diff) + if self.new_file_contents is not None: + result["newFileContents"] = from_union([from_none, lambda x: from_str(x)], self.new_file_contents) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + if self.server_name is not None: + result["serverName"] = from_union([from_none, lambda x: from_str(x)], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, lambda x: from_str(x)], self.tool_name) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + if self.args is not None: + result["args"] = self.args + if self.read_only is not None: + result["readOnly"] = from_union([from_none, lambda x: from_bool(x)], self.read_only) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) + if self.subject is not None: + result["subject"] = from_union([from_none, lambda x: from_str(x)], self.subject) + if self.fact is not None: + result["fact"] = from_union([from_none, lambda x: from_str(x)], self.fact) + if self.citations is not None: + result["citations"] = from_union([from_none, lambda x: from_str(x)], self.citations) + if self.direction is not None: + result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) + if self.reason is not None: + result["reason"] = from_union([from_none, lambda x: from_str(x)], self.reason) + if self.tool_description is not None: + result["toolDescription"] = from_union([from_none, lambda x: from_str(x)], self.tool_description) + if self.tool_args is not None: + result["toolArgs"] = self.tool_args + if self.hook_message is not None: + result["hookMessage"] = from_union([from_none, lambda x: from_str(x)], self.hook_message) + return result + + +@dataclass +class PermissionRequestedData: + "Permission request notification requiring client approval with request details" + request_id: str + permission_request: PermissionRequest + resolved_by_hook: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + permission_request = PermissionRequest.from_dict(obj.get("permissionRequest")) + resolved_by_hook = from_union([from_none, lambda x: from_bool(x)], obj.get("resolvedByHook")) + return PermissionRequestedData( + request_id=request_id, + permission_request=permission_request, + resolved_by_hook=resolved_by_hook, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["permissionRequest"] = to_class(PermissionRequest, self.permission_request) + if self.resolved_by_hook is not None: + result["resolvedByHook"] = from_union([from_none, lambda x: from_bool(x)], self.resolved_by_hook) + return result + + +@dataclass +class PermissionCompletedDataResult: + "The result of the permission request" + kind: PermissionCompletedKind + + @staticmethod + def from_dict(obj: Any) -> "PermissionCompletedDataResult": + assert isinstance(obj, dict) + kind = parse_enum(PermissionCompletedKind, obj.get("kind")) + return PermissionCompletedDataResult( + kind=kind, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionCompletedKind, self.kind) + return result + + +@dataclass +class PermissionCompletedData: + "Permission request completion notification signaling UI dismissal" + request_id: str + result: PermissionCompletedDataResult + + @staticmethod + def from_dict(obj: Any) -> "PermissionCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = PermissionCompletedDataResult.from_dict(obj.get("result")) + return PermissionCompletedData( + request_id=request_id, + result=result, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionCompletedDataResult, self.result) + return result - resolved_by_hook: bool | None = None - """When true, this permission was already resolved by a permissionRequest hook and requires - no client action - """ - allow_freeform: bool | None = None - """Whether the user can provide a free-form text response in addition to predefined choices""" +@dataclass +class UserInputRequestedData: + "User input request notification with question and optional predefined choices" + request_id: str + question: str choices: list[str] | None = None - """Predefined choices for the user to select from, if applicable""" + allow_freeform: bool | None = None + tool_call_id: str | None = None - question: str | None = None - """The question or prompt to present to the user""" + @staticmethod + def from_dict(obj: Any) -> "UserInputRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + question = from_str(obj.get("question")) + choices = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("choices")) + allow_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("allowFreeform")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + return UserInputRequestedData( + request_id=request_id, + question=question, + choices=choices, + allow_freeform=allow_freeform, + tool_call_id=tool_call_id, + ) - answer: str | None = None - """The user's answer to the input request""" + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["question"] = from_str(self.question) + if self.choices is not None: + result["choices"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.choices) + if self.allow_freeform is not None: + result["allowFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.allow_freeform) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + return result + +@dataclass +class UserInputCompletedData: + "User input request completion with the user's response" + request_id: str + answer: str | None = None was_freeform: bool | None = None - """Whether the answer was typed as free-form text rather than selected from choices""" - elicitation_source: str | None = None - """The source that initiated the request (MCP server name, or absent for agent-initiated)""" + @staticmethod + def from_dict(obj: Any) -> "UserInputCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + answer = from_union([from_none, lambda x: from_str(x)], obj.get("answer")) + was_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("wasFreeform")) + return UserInputCompletedData( + request_id=request_id, + answer=answer, + was_freeform=was_freeform, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.answer is not None: + result["answer"] = from_union([from_none, lambda x: from_str(x)], self.answer) + if self.was_freeform is not None: + result["wasFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.was_freeform) + return result + + +@dataclass +class ElicitationRequestedSchema: + "JSON Schema describing the form fields to present to the user (form mode only)" + type: str + properties: dict[str, Any] + required: list[str] | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationRequestedSchema": + assert isinstance(obj, dict) + type = from_str(obj.get("type")) + properties = from_dict(lambda x: x, obj.get("properties")) + required = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("required")) + return ElicitationRequestedSchema( + type=type, + properties=properties, + required=required, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = from_str(self.type) + result["properties"] = from_dict(lambda x: x, self.properties) + if self.required is not None: + result["required"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.required) + return result + +@dataclass +class ElicitationRequestedData: + "Elicitation request; may be form-based (structured input) or URL-based (browser redirect)" + request_id: str + message: str + tool_call_id: str | None = None + elicitation_source: str | None = None mode: ElicitationRequestedMode | None = None - """Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to - "form" when absent. - """ requested_schema: ElicitationRequestedSchema | None = None - """JSON Schema describing the form fields to present to the user (form mode only)""" + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + message = from_str(obj.get("message")) + tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + elicitation_source = from_union([from_none, lambda x: from_str(x)], obj.get("elicitationSource")) + mode = from_union([from_none, lambda x: parse_enum(ElicitationRequestedMode, x)], obj.get("mode")) + requested_schema = from_union([from_none, lambda x: ElicitationRequestedSchema.from_dict(x)], obj.get("requestedSchema")) + url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + return ElicitationRequestedData( + request_id=request_id, + message=message, + tool_call_id=tool_call_id, + elicitation_source=elicitation_source, + mode=mode, + requested_schema=requested_schema, + url=url, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["message"] = from_str(self.message) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + if self.elicitation_source is not None: + result["elicitationSource"] = from_union([from_none, lambda x: from_str(x)], self.elicitation_source) + if self.mode is not None: + result["mode"] = from_union([from_none, lambda x: to_enum(ElicitationRequestedMode, x)], self.mode) + if self.requested_schema is not None: + result["requestedSchema"] = from_union([from_none, lambda x: to_class(ElicitationRequestedSchema, x)], self.requested_schema) + if self.url is not None: + result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + return result + +@dataclass +class ElicitationCompletedData: + "Elicitation request completion with the user's response" + request_id: str action: ElicitationCompletedAction | None = None - """The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" - (dismissed) - """ - mcp_request_id: float | str | None = None - """The JSON-RPC request ID from the MCP protocol""" + content: dict[str, Any] | None = None + + @staticmethod + def from_dict(obj: Any) -> "ElicitationCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + action = from_union([from_none, lambda x: parse_enum(ElicitationCompletedAction, x)], obj.get("action")) + content = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("content")) + return ElicitationCompletedData( + request_id=request_id, + action=action, + content=content, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(ElicitationCompletedAction, x)], self.action) + if self.content is not None: + result["content"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.content) + return result + + +@dataclass +class SamplingRequestedData: + "Sampling request from an MCP server; contains the server name and a requestId for correlation" + request_id: str + server_name: str + mcp_request_id: Any + + @staticmethod + def from_dict(obj: Any) -> "SamplingRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + mcp_request_id = obj.get("mcpRequestId") + return SamplingRequestedData( + request_id=request_id, + server_name=server_name, + mcp_request_id=mcp_request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) + result["mcpRequestId"] = self.mcp_request_id + return result + + +@dataclass +class SamplingCompletedData: + "Sampling request completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "SamplingCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return SamplingCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class MCPOauthRequiredStaticClientConfig: + "Static OAuth client configuration, if the server specifies one" + client_id: str + public_client: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "MCPOauthRequiredStaticClientConfig": + assert isinstance(obj, dict) + client_id = from_str(obj.get("clientId")) + public_client = from_union([from_none, lambda x: from_bool(x)], obj.get("publicClient")) + return MCPOauthRequiredStaticClientConfig( + client_id=client_id, + public_client=public_client, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["clientId"] = from_str(self.client_id) + if self.public_client is not None: + result["publicClient"] = from_union([from_none, lambda x: from_bool(x)], self.public_client) + return result + + +@dataclass +class McpOauthRequiredData: + "OAuth authentication request for an MCP server" + request_id: str + server_name: str + server_url: str + static_client_config: MCPOauthRequiredStaticClientConfig | None = None + + @staticmethod + def from_dict(obj: Any) -> "McpOauthRequiredData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + server_url = from_str(obj.get("serverUrl")) + static_client_config = from_union([from_none, lambda x: MCPOauthRequiredStaticClientConfig.from_dict(x)], obj.get("staticClientConfig")) + return McpOauthRequiredData( + request_id=request_id, + server_name=server_name, + server_url=server_url, + static_client_config=static_client_config, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) + result["serverUrl"] = from_str(self.server_url) + if self.static_client_config is not None: + result["staticClientConfig"] = from_union([from_none, lambda x: to_class(MCPOauthRequiredStaticClientConfig, x)], self.static_client_config) + return result + + +@dataclass +class McpOauthCompletedData: + "MCP OAuth request completion notification" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "McpOauthCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return McpOauthCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class ExternalToolRequestedData: + "External tool invocation request for client-side tool execution" + request_id: str + session_id: str + tool_call_id: str + tool_name: str + arguments: Any = None + traceparent: str | None = None + tracestate: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ExternalToolRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + session_id = from_str(obj.get("sessionId")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + traceparent = from_union([from_none, lambda x: from_str(x)], obj.get("traceparent")) + tracestate = from_union([from_none, lambda x: from_str(x)], obj.get("tracestate")) + return ExternalToolRequestedData( + request_id=request_id, + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + traceparent=traceparent, + tracestate=tracestate, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["sessionId"] = from_str(self.session_id) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.traceparent is not None: + result["traceparent"] = from_union([from_none, lambda x: from_str(x)], self.traceparent) + if self.tracestate is not None: + result["tracestate"] = from_union([from_none, lambda x: from_str(x)], self.tracestate) + return result + + +@dataclass +class ExternalToolCompletedData: + "External tool completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "ExternalToolCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return ExternalToolCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class CommandQueuedData: + "Queued slash command dispatch request for client execution" + request_id: str + command: str + + @staticmethod + def from_dict(obj: Any) -> "CommandQueuedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + command = from_str(obj.get("command")) + return CommandQueuedData( + request_id=request_id, + command=command, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["command"] = from_str(self.command) + return result + + +@dataclass +class CommandExecuteData: + "Registered command dispatch request routed to the owning client" + request_id: str + command: str + command_name: str + args: str + + @staticmethod + def from_dict(obj: Any) -> "CommandExecuteData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + command = from_str(obj.get("command")) + command_name = from_str(obj.get("commandName")) + args = from_str(obj.get("args")) + return CommandExecuteData( + request_id=request_id, + command=command, + command_name=command_name, + args=args, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["command"] = from_str(self.command) + result["commandName"] = from_str(self.command_name) + result["args"] = from_str(self.args) + return result + + +@dataclass +class CommandCompletedData: + "Queued command completion notification signaling UI dismissal" + request_id: str + + @staticmethod + def from_dict(obj: Any) -> "CommandCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + return CommandCompletedData( + request_id=request_id, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + return result + + +@dataclass +class CommandsChangedCommand: + name: str + description: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "CommandsChangedCommand": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + return CommandsChangedCommand( + name=name, + description=description, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + if self.description is not None: + result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + return result + + +@dataclass +class CommandsChangedData: + "SDK command registration change notification" + commands: list[CommandsChangedCommand] + + @staticmethod + def from_dict(obj: Any) -> "CommandsChangedData": + assert isinstance(obj, dict) + commands = from_list(lambda x: CommandsChangedCommand.from_dict(x), obj.get("commands")) + return CommandsChangedData( + commands=commands, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["commands"] = from_list(lambda x: to_class(CommandsChangedCommand, x), self.commands) + return result + + +@dataclass +class CapabilitiesChangedUI: + "UI capability changes" + elicitation: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> "CapabilitiesChangedUI": + assert isinstance(obj, dict) + elicitation = from_union([from_none, lambda x: from_bool(x)], obj.get("elicitation")) + return CapabilitiesChangedUI( + elicitation=elicitation, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.elicitation is not None: + result["elicitation"] = from_union([from_none, lambda x: from_bool(x)], self.elicitation) + return result + + +@dataclass +class CapabilitiesChangedData: + "Session capability change notification" + ui: CapabilitiesChangedUI | None = None + + @staticmethod + def from_dict(obj: Any) -> "CapabilitiesChangedData": + assert isinstance(obj, dict) + ui = from_union([from_none, lambda x: CapabilitiesChangedUI.from_dict(x)], obj.get("ui")) + return CapabilitiesChangedData( + ui=ui, + ) + + def to_dict(self) -> dict: + result: dict = {} + if self.ui is not None: + result["ui"] = from_union([from_none, lambda x: to_class(CapabilitiesChangedUI, x)], self.ui) + return result + + +@dataclass +class ExitPlanModeRequestedData: + "Plan approval request with plan content and available user actions" + request_id: str + summary: str + plan_content: str + actions: list[str] + recommended_action: str + + @staticmethod + def from_dict(obj: Any) -> "ExitPlanModeRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + summary = from_str(obj.get("summary")) + plan_content = from_str(obj.get("planContent")) + actions = from_list(lambda x: from_str(x), obj.get("actions")) + recommended_action = from_str(obj.get("recommendedAction")) + return ExitPlanModeRequestedData( + request_id=request_id, + summary=summary, + plan_content=plan_content, + actions=actions, + recommended_action=recommended_action, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["summary"] = from_str(self.summary) + result["planContent"] = from_str(self.plan_content) + result["actions"] = from_list(lambda x: from_str(x), self.actions) + result["recommendedAction"] = from_str(self.recommended_action) + return result + + +@dataclass +class ExitPlanModeCompletedData: + "Plan mode exit completion with the user's approval decision and optional feedback" + request_id: str + approved: bool | None = None + selected_action: str | None = None + auto_approve_edits: bool | None = None + feedback: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "ExitPlanModeCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + approved = from_union([from_none, lambda x: from_bool(x)], obj.get("approved")) + selected_action = from_union([from_none, lambda x: from_str(x)], obj.get("selectedAction")) + auto_approve_edits = from_union([from_none, lambda x: from_bool(x)], obj.get("autoApproveEdits")) + feedback = from_union([from_none, lambda x: from_str(x)], obj.get("feedback")) + return ExitPlanModeCompletedData( + request_id=request_id, + approved=approved, + selected_action=selected_action, + auto_approve_edits=auto_approve_edits, + feedback=feedback, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.approved is not None: + result["approved"] = from_union([from_none, lambda x: from_bool(x)], self.approved) + if self.selected_action is not None: + result["selectedAction"] = from_union([from_none, lambda x: from_str(x)], self.selected_action) + if self.auto_approve_edits is not None: + result["autoApproveEdits"] = from_union([from_none, lambda x: from_bool(x)], self.auto_approve_edits) + if self.feedback is not None: + result["feedback"] = from_union([from_none, lambda x: from_str(x)], self.feedback) + return result + + +@dataclass +class SessionToolsUpdatedData: + model: str + + @staticmethod + def from_dict(obj: Any) -> "SessionToolsUpdatedData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + return SessionToolsUpdatedData( + model=model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + return result + + +@dataclass +class SessionBackgroundTasksChangedData: + @staticmethod + def from_dict(obj: Any) -> "SessionBackgroundTasksChangedData": + assert isinstance(obj, dict) + return SessionBackgroundTasksChangedData() + + def to_dict(self) -> dict: + return {} + + +@dataclass +class SkillsLoadedSkill: + name: str + description: str + source: str + user_invocable: bool + enabled: bool + path: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "SkillsLoadedSkill": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + description = from_str(obj.get("description")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + enabled = from_bool(obj.get("enabled")) + path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + return SkillsLoadedSkill( + name=name, + description=description, + source=source, + user_invocable=user_invocable, + enabled=enabled, + path=path, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["description"] = from_str(self.description) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + result["enabled"] = from_bool(self.enabled) + if self.path is not None: + result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + return result + + +@dataclass +class SessionSkillsLoadedData: + skills: list[SkillsLoadedSkill] + + @staticmethod + def from_dict(obj: Any) -> "SessionSkillsLoadedData": + assert isinstance(obj, dict) + skills = from_list(lambda x: SkillsLoadedSkill.from_dict(x), obj.get("skills")) + return SessionSkillsLoadedData( + skills=skills, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(SkillsLoadedSkill, x), self.skills) + return result + + +@dataclass +class CustomAgentsUpdatedAgent: + id: str + name: str + display_name: str + description: str + source: str + tools: list[str] + user_invocable: bool + model: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + display_name = from_str(obj.get("displayName")) + description = from_str(obj.get("description")) + source = from_str(obj.get("source")) + tools = from_list(lambda x: from_str(x), obj.get("tools")) + user_invocable = from_bool(obj.get("userInvocable")) + model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + return CustomAgentsUpdatedAgent( + id=id, + name=name, + display_name=display_name, + description=description, + source=source, + tools=tools, + user_invocable=user_invocable, + model=model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["displayName"] = from_str(self.display_name) + result["description"] = from_str(self.description) + result["source"] = from_str(self.source) + result["tools"] = from_list(lambda x: from_str(x), self.tools) + result["userInvocable"] = from_bool(self.user_invocable) + if self.model is not None: + result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + return result + + +@dataclass +class SessionCustomAgentsUpdatedData: + agents: list[CustomAgentsUpdatedAgent] + warnings: list[str] + errors: list[str] + + @staticmethod + def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": + assert isinstance(obj, dict) + agents = from_list(lambda x: CustomAgentsUpdatedAgent.from_dict(x), obj.get("agents")) + warnings = from_list(lambda x: from_str(x), obj.get("warnings")) + errors = from_list(lambda x: from_str(x), obj.get("errors")) + return SessionCustomAgentsUpdatedData( + agents=agents, + warnings=warnings, + errors=errors, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), self.agents) + result["warnings"] = from_list(lambda x: from_str(x), self.warnings) + result["errors"] = from_list(lambda x: from_str(x), self.errors) + return result + + +@dataclass +class MCPServersLoadedServer: + name: str + status: MCPServerStatus + source: str | None = None + error: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "MCPServersLoadedServer": + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + status = parse_enum(MCPServerStatus, obj.get("status")) + source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) + error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) + return MCPServersLoadedServer( + name=name, + status=status, + source=source, + error=error, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["status"] = to_enum(MCPServerStatus, self.status) + if self.source is not None: + result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + return result + + +@dataclass +class SessionMcpServersLoadedData: + servers: list[MCPServersLoadedServer] + + @staticmethod + def from_dict(obj: Any) -> "SessionMcpServersLoadedData": + assert isinstance(obj, dict) + servers = from_list(lambda x: MCPServersLoadedServer.from_dict(x), obj.get("servers")) + return SessionMcpServersLoadedData( + servers=servers, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = from_list(lambda x: to_class(MCPServersLoadedServer, x), self.servers) + return result + + +@dataclass +class SessionMcpServerStatusChangedData: + server_name: str + status: SessionMcpServerStatusChangedDataStatus + + @staticmethod + def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": + assert isinstance(obj, dict) + server_name = from_str(obj.get("serverName")) + status = parse_enum(SessionMcpServerStatusChangedDataStatus, obj.get("status")) + return SessionMcpServerStatusChangedData( + server_name=server_name, + status=status, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + result["status"] = to_enum(SessionMcpServerStatusChangedDataStatus, self.status) + return result + + +@dataclass +class ExtensionsLoadedExtension: + id: str + name: str + source: ExtensionsLoadedExtensionSource + status: ExtensionsLoadedExtensionStatus + + @staticmethod + def from_dict(obj: Any) -> "ExtensionsLoadedExtension": + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = parse_enum(ExtensionsLoadedExtensionSource, obj.get("source")) + status = parse_enum(ExtensionsLoadedExtensionStatus, obj.get("status")) + return ExtensionsLoadedExtension( + id=id, + name=name, + source=source, + status=status, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionsLoadedExtensionSource, self.source) + result["status"] = to_enum(ExtensionsLoadedExtensionStatus, self.status) + return result + + +@dataclass +class SessionExtensionsLoadedData: + extensions: list[ExtensionsLoadedExtension] + + @staticmethod + def from_dict(obj: Any) -> "SessionExtensionsLoadedData": + assert isinstance(obj, dict) + extensions = from_list(lambda x: ExtensionsLoadedExtension.from_dict(x), obj.get("extensions")) + return SessionExtensionsLoadedData( + extensions=extensions, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["extensions"] = from_list(lambda x: to_class(ExtensionsLoadedExtension, x), self.extensions) + return result + + +class SessionStartDataContextHostType(Enum): + "Hosting platform type of the repository (github or ado)" + GITHUB = "github" + ADO = "ado" + + +class SessionResumeDataContextHostType(Enum): + "Hosting platform type of the repository (github or ado)" + GITHUB = "github" + ADO = "ado" + + +class SessionPlanChangedDataOperation(Enum): + "The type of operation performed on the plan file" + CREATE = "create" + UPDATE = "update" + DELETE = "delete" + + +class SessionWorkspaceFileChangedDataOperation(Enum): + "Whether the file was newly created or updated" + CREATE = "create" + UPDATE = "update" + + +class HandoffSourceType(Enum): + "Origin type of the session being handed off" + REMOTE = "remote" + LOCAL = "local" + + +class ShutdownType(Enum): + "Whether the session ended normally (\"routine\") or due to a crash/fatal error (\"error\")" + ROUTINE = "routine" + ERROR = "error" + + +class SessionContextChangedDataHostType(Enum): + "Hosting platform type of the repository (github or ado)" + GITHUB = "github" + ADO = "ado" + + +class UserMessageAttachmentType(Enum): + "A user message attachment — a file, directory, code selection, blob, or GitHub reference discriminator" + FILE = "file" + DIRECTORY = "directory" + SELECTION = "selection" + GITHUB_REFERENCE = "github_reference" + BLOB = "blob" + + +class UserMessageAttachmentGithubReferenceType(Enum): + "Type of GitHub reference" + ISSUE = "issue" + PR = "pr" + DISCUSSION = "discussion" + + +class UserMessageAgentMode(Enum): + "The agent mode that was active when this message was sent" + INTERACTIVE = "interactive" + PLAN = "plan" + AUTOPILOT = "autopilot" + SHELL = "shell" + + +class AssistantMessageToolRequestType(Enum): + "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." + FUNCTION = "function" + CUSTOM = "custom" + + +class ToolExecutionCompleteDataResultContentsItemType(Enum): + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator" + TEXT = "text" + TERMINAL = "terminal" + IMAGE = "image" + AUDIO = "audio" + RESOURCE_LINK = "resource_link" + RESOURCE = "resource" + + +class ToolExecutionCompleteDataResultContentsItemIconsItemTheme(Enum): + "Theme variant this icon is intended for" + LIGHT = "light" + DARK = "dark" + + +class SystemMessageDataRole(Enum): + "Message role: \"system\" for system prompts, \"developer\" for developer-injected instructions" + SYSTEM = "system" + DEVELOPER = "developer" + + +class SystemNotificationDataKindType(Enum): + "Structured metadata identifying what triggered this notification discriminator" + AGENT_COMPLETED = "agent_completed" + AGENT_IDLE = "agent_idle" + SHELL_COMPLETED = "shell_completed" + SHELL_DETACHED_COMPLETED = "shell_detached_completed" + + +class SystemNotificationDataKindStatus(Enum): + "Whether the agent completed successfully or failed" + COMPLETED = "completed" + FAILED = "failed" + + +class PermissionRequestedDataPermissionRequestKind(Enum): + "Details of the permission being requested discriminator" + SHELL = "shell" + WRITE = "write" + READ = "read" + MCP = "mcp" + URL = "url" + MEMORY = "memory" + CUSTOM_TOOL = "custom-tool" + HOOK = "hook" - server_name: str | None = None - """Name of the MCP server that initiated the sampling request - - Display name of the MCP server that requires OAuth - - Name of the MCP server whose status changed - """ - server_url: str | None = None - """URL of the MCP server that requires OAuth""" - static_client_config: MCPOauthRequiredStaticClientConfig | None = None - """Static OAuth client configuration, if the server specifies one""" +class PermissionRequestMemoryAction(Enum): + "Whether this is a store or vote memory operation" + STORE = "store" + VOTE = "vote" - traceparent: str | None = None - """W3C Trace Context traceparent header for the execute_tool span""" - tracestate: str | None = None - """W3C Trace Context tracestate header for the execute_tool span""" +class PermissionRequestMemoryDirection(Enum): + "Vote direction (vote only)" + UPVOTE = "upvote" + DOWNVOTE = "downvote" - command: str | None = None - """The slash command text to be executed (e.g., /help, /clear) - - The full command text (e.g., /deploy production) - """ - args: str | None = None - """Raw argument string after the command name""" - command_name: str | None = None - """Command name without leading /""" +class PermissionCompletedKind(Enum): + "The outcome of the permission request" + APPROVED = "approved" + DENIED_BY_RULES = "denied-by-rules" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - commands: list[CommandsChangedCommand] | None = None - """Current list of registered SDK commands""" - ui: CapabilitiesChangedUI | None = None - """UI capability changes""" +class ElicitationRequestedMode(Enum): + "Elicitation mode; \"form\" for structured input, \"url\" for browser-based. Defaults to \"form\" when absent." + FORM = "form" + URL = "url" - actions: list[str] | None = None - """Available actions the user can take (e.g., approve, edit, reject)""" - plan_content: str | None = None - """Full content of the plan file""" +class ElicitationCompletedAction(Enum): + "The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)" + ACCEPT = "accept" + DECLINE = "decline" + CANCEL = "cancel" - recommended_action: str | None = None - """The recommended action for the user to take""" - approved: bool | None = None - """Whether the plan was approved by the user""" +class MCPServerStatus(Enum): + "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + CONNECTED = "connected" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + PENDING = "pending" + DISABLED = "disabled" + NOT_CONFIGURED = "not_configured" - auto_approve_edits: bool | None = None - """Whether edits should be auto-approved without confirmation""" - feedback: str | None = None - """Free-form feedback from the user if they requested changes to the plan""" +class SessionMcpServerStatusChangedDataStatus(Enum): + "New connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + CONNECTED = "connected" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + PENDING = "pending" + DISABLED = "disabled" + NOT_CONFIGURED = "not_configured" - selected_action: str | None = None - """Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only')""" - - skills: list[SkillsLoadedSkill] | None = None - """Array of resolved skill metadata""" - - agents: list[CustomAgentsUpdatedAgent] | None = None - """Array of loaded custom agent metadata""" - - errors: list[str] | None = None - """Fatal errors from agent loading""" - - warnings: list[str] | None = None - """Non-fatal warnings from agent loading""" - - servers: list[MCPServersLoadedServer] | None = None - """Array of MCP server status summaries""" - - status: MCPServerStatus | None = None - """New connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - extensions: list[ExtensionsLoadedExtension] | None = None - """Array of discovered extensions and their status""" - - @staticmethod - def from_dict(obj: Any) -> 'Data': - assert isinstance(obj, dict) - already_in_use = from_union([from_bool, from_none], obj.get("alreadyInUse")) - context = from_union([Context.from_dict, from_str, from_none], obj.get("context")) - copilot_version = from_union([from_str, from_none], obj.get("copilotVersion")) - producer = from_union([from_str, from_none], obj.get("producer")) - reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) - remote_steerable = from_union([from_bool, from_none], obj.get("remoteSteerable")) - selected_model = from_union([from_str, from_none], obj.get("selectedModel")) - session_id = from_union([from_str, from_none], obj.get("sessionId")) - start_time = from_union([from_datetime, from_none], obj.get("startTime")) - version = from_union([from_float, from_none], obj.get("version")) - event_count = from_union([from_float, from_none], obj.get("eventCount")) - resume_time = from_union([from_datetime, from_none], obj.get("resumeTime")) - error_type = from_union([from_str, from_none], obj.get("errorType")) - message = from_union([from_str, from_none], obj.get("message")) - provider_call_id = from_union([from_str, from_none], obj.get("providerCallId")) - stack = from_union([from_str, from_none], obj.get("stack")) - status_code = from_union([from_int, from_none], obj.get("statusCode")) - url = from_union([from_str, from_none], obj.get("url")) - aborted = from_union([from_bool, from_none], obj.get("aborted")) - title = from_union([from_str, from_none], obj.get("title")) - info_type = from_union([from_str, from_none], obj.get("infoType")) - warning_type = from_union([from_str, from_none], obj.get("warningType")) - new_model = from_union([from_str, from_none], obj.get("newModel")) - previous_model = from_union([from_str, from_none], obj.get("previousModel")) - previous_reasoning_effort = from_union([from_str, from_none], obj.get("previousReasoningEffort")) - new_mode = from_union([from_str, from_none], obj.get("newMode")) - previous_mode = from_union([from_str, from_none], obj.get("previousMode")) - operation = from_union([ChangedOperation, from_none], obj.get("operation")) - path = from_union([from_str, from_none], obj.get("path")) - handoff_time = from_union([from_datetime, from_none], obj.get("handoffTime")) - host = from_union([from_str, from_none], obj.get("host")) - remote_session_id = from_union([from_str, from_none], obj.get("remoteSessionId")) - repository = from_union([HandoffRepository.from_dict, from_str, from_none], obj.get("repository")) - source_type = from_union([HandoffSourceType, from_none], obj.get("sourceType")) - summary = from_union([from_str, from_none], obj.get("summary")) - messages_removed_during_truncation = from_union([from_float, from_none], obj.get("messagesRemovedDuringTruncation")) - performed_by = from_union([from_str, from_none], obj.get("performedBy")) - post_truncation_messages_length = from_union([from_float, from_none], obj.get("postTruncationMessagesLength")) - post_truncation_tokens_in_messages = from_union([from_float, from_none], obj.get("postTruncationTokensInMessages")) - pre_truncation_messages_length = from_union([from_float, from_none], obj.get("preTruncationMessagesLength")) - pre_truncation_tokens_in_messages = from_union([from_float, from_none], obj.get("preTruncationTokensInMessages")) - token_limit = from_union([from_float, from_none], obj.get("tokenLimit")) - tokens_removed_during_truncation = from_union([from_float, from_none], obj.get("tokensRemovedDuringTruncation")) - events_removed = from_union([from_float, from_none], obj.get("eventsRemoved")) - up_to_event_id = from_union([from_str, from_none], obj.get("upToEventId")) - code_changes = from_union([ShutdownCodeChanges.from_dict, from_none], obj.get("codeChanges")) - conversation_tokens = from_union([from_float, from_none], obj.get("conversationTokens")) - current_model = from_union([from_str, from_none], obj.get("currentModel")) - current_tokens = from_union([from_float, from_none], obj.get("currentTokens")) - error_reason = from_union([from_str, from_none], obj.get("errorReason")) - model_metrics = from_union([lambda x: from_dict(ShutdownModelMetric.from_dict, x), from_none], obj.get("modelMetrics")) - session_start_time = from_union([from_float, from_none], obj.get("sessionStartTime")) - shutdown_type = from_union([ShutdownType, from_none], obj.get("shutdownType")) - system_tokens = from_union([from_float, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_float, from_none], obj.get("toolDefinitionsTokens")) - total_api_duration_ms = from_union([from_float, from_none], obj.get("totalApiDurationMs")) - total_premium_requests = from_union([from_float, from_none], obj.get("totalPremiumRequests")) - base_commit = from_union([from_str, from_none], obj.get("baseCommit")) - branch = from_union([from_str, from_none], obj.get("branch")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - git_root = from_union([from_str, from_none], obj.get("gitRoot")) - head_commit = from_union([from_str, from_none], obj.get("headCommit")) - host_type = from_union([ContextChangedHostType, from_none], obj.get("hostType")) - is_initial = from_union([from_bool, from_none], obj.get("isInitial")) - messages_length = from_union([from_float, from_none], obj.get("messagesLength")) - checkpoint_number = from_union([from_float, from_none], obj.get("checkpointNumber")) - checkpoint_path = from_union([from_str, from_none], obj.get("checkpointPath")) - compaction_tokens_used = from_union([CompactionCompleteCompactionTokensUsed.from_dict, from_none], obj.get("compactionTokensUsed")) - error = from_union([Error.from_dict, from_str, from_none], obj.get("error")) - messages_removed = from_union([from_float, from_none], obj.get("messagesRemoved")) - post_compaction_tokens = from_union([from_float, from_none], obj.get("postCompactionTokens")) - pre_compaction_messages_length = from_union([from_float, from_none], obj.get("preCompactionMessagesLength")) - pre_compaction_tokens = from_union([from_float, from_none], obj.get("preCompactionTokens")) - request_id = from_union([from_str, from_none], obj.get("requestId")) - success = from_union([from_bool, from_none], obj.get("success")) - summary_content = from_union([from_str, from_none], obj.get("summaryContent")) - tokens_removed = from_union([from_float, from_none], obj.get("tokensRemoved")) - agent_mode = from_union([UserMessageAgentMode, from_none], obj.get("agentMode")) - attachments = from_union([lambda x: from_list(UserMessageAttachment.from_dict, x), from_none], obj.get("attachments")) - content = from_union([from_str, lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - interaction_id = from_union([from_str, from_none], obj.get("interactionId")) - source = from_union([from_str, from_none], obj.get("source")) - transformed_content = from_union([from_str, from_none], obj.get("transformedContent")) - turn_id = from_union([from_str, from_none], obj.get("turnId")) - intent = from_union([from_str, from_none], obj.get("intent")) - reasoning_id = from_union([from_str, from_none], obj.get("reasoningId")) - delta_content = from_union([from_str, from_none], obj.get("deltaContent")) - total_response_size_bytes = from_union([from_float, from_none], obj.get("totalResponseSizeBytes")) - encrypted_content = from_union([from_str, from_none], obj.get("encryptedContent")) - message_id = from_union([from_str, from_none], obj.get("messageId")) - output_tokens = from_union([from_float, from_none], obj.get("outputTokens")) - parent_tool_call_id = from_union([from_str, from_none], obj.get("parentToolCallId")) - phase = from_union([from_str, from_none], obj.get("phase")) - reasoning_opaque = from_union([from_str, from_none], obj.get("reasoningOpaque")) - reasoning_text = from_union([from_str, from_none], obj.get("reasoningText")) - tool_requests = from_union([lambda x: from_list(AssistantMessageToolRequest.from_dict, x), from_none], obj.get("toolRequests")) - api_call_id = from_union([from_str, from_none], obj.get("apiCallId")) - cache_read_tokens = from_union([from_float, from_none], obj.get("cacheReadTokens")) - cache_write_tokens = from_union([from_float, from_none], obj.get("cacheWriteTokens")) - copilot_usage = from_union([AssistantUsageCopilotUsage.from_dict, from_none], obj.get("copilotUsage")) - cost = from_union([from_float, from_none], obj.get("cost")) - duration = from_union([from_float, from_none], obj.get("duration")) - initiator = from_union([from_str, from_none], obj.get("initiator")) - input_tokens = from_union([from_float, from_none], obj.get("inputTokens")) - inter_token_latency_ms = from_union([from_float, from_none], obj.get("interTokenLatencyMs")) - model = from_union([from_str, from_none], obj.get("model")) - quota_snapshots = from_union([lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x), from_none], obj.get("quotaSnapshots")) - reasoning_tokens = from_union([from_float, from_none], obj.get("reasoningTokens")) - ttft_ms = from_union([from_float, from_none], obj.get("ttftMs")) - reason = from_union([from_str, from_none], obj.get("reason")) - arguments = obj.get("arguments") - tool_call_id = from_union([from_str, from_none], obj.get("toolCallId")) - tool_name = from_union([from_str, from_none], obj.get("toolName")) - mcp_server_name = from_union([from_str, from_none], obj.get("mcpServerName")) - mcp_tool_name = from_union([from_str, from_none], obj.get("mcpToolName")) - partial_output = from_union([from_str, from_none], obj.get("partialOutput")) - progress_message = from_union([from_str, from_none], obj.get("progressMessage")) - is_user_requested = from_union([from_bool, from_none], obj.get("isUserRequested")) - result = from_union([Result.from_dict, from_none], obj.get("result")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - allowed_tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("allowedTools")) - description = from_union([from_str, from_none], obj.get("description")) - name = from_union([from_str, from_none], obj.get("name")) - plugin_name = from_union([from_str, from_none], obj.get("pluginName")) - plugin_version = from_union([from_str, from_none], obj.get("pluginVersion")) - agent_description = from_union([from_str, from_none], obj.get("agentDescription")) - agent_display_name = from_union([from_str, from_none], obj.get("agentDisplayName")) - agent_name = from_union([from_str, from_none], obj.get("agentName")) - duration_ms = from_union([from_float, from_none], obj.get("durationMs")) - total_tokens = from_union([from_float, from_none], obj.get("totalTokens")) - total_tool_calls = from_union([from_float, from_none], obj.get("totalToolCalls")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - hook_invocation_id = from_union([from_str, from_none], obj.get("hookInvocationId")) - hook_type = from_union([from_str, from_none], obj.get("hookType")) - input = obj.get("input") - output = obj.get("output") - metadata = from_union([SystemMessageMetadata.from_dict, from_none], obj.get("metadata")) - role = from_union([SystemMessageRole, from_none], obj.get("role")) - kind = from_union([SystemNotification.from_dict, from_none], obj.get("kind")) - permission_request = from_union([PermissionRequest.from_dict, from_none], obj.get("permissionRequest")) - resolved_by_hook = from_union([from_bool, from_none], obj.get("resolvedByHook")) - allow_freeform = from_union([from_bool, from_none], obj.get("allowFreeform")) - choices = from_union([lambda x: from_list(from_str, x), from_none], obj.get("choices")) - question = from_union([from_str, from_none], obj.get("question")) - answer = from_union([from_str, from_none], obj.get("answer")) - was_freeform = from_union([from_bool, from_none], obj.get("wasFreeform")) - elicitation_source = from_union([from_str, from_none], obj.get("elicitationSource")) - mode = from_union([ElicitationRequestedMode, from_none], obj.get("mode")) - requested_schema = from_union([ElicitationRequestedSchema.from_dict, from_none], obj.get("requestedSchema")) - action = from_union([ElicitationCompletedAction, from_none], obj.get("action")) - mcp_request_id = from_union([from_float, from_str, from_none], obj.get("mcpRequestId")) - server_name = from_union([from_str, from_none], obj.get("serverName")) - server_url = from_union([from_str, from_none], obj.get("serverUrl")) - static_client_config = from_union([MCPOauthRequiredStaticClientConfig.from_dict, from_none], obj.get("staticClientConfig")) - traceparent = from_union([from_str, from_none], obj.get("traceparent")) - tracestate = from_union([from_str, from_none], obj.get("tracestate")) - command = from_union([from_str, from_none], obj.get("command")) - args = from_union([from_str, from_none], obj.get("args")) - command_name = from_union([from_str, from_none], obj.get("commandName")) - commands = from_union([lambda x: from_list(CommandsChangedCommand.from_dict, x), from_none], obj.get("commands")) - ui = from_union([CapabilitiesChangedUI.from_dict, from_none], obj.get("ui")) - actions = from_union([lambda x: from_list(from_str, x), from_none], obj.get("actions")) - plan_content = from_union([from_str, from_none], obj.get("planContent")) - recommended_action = from_union([from_str, from_none], obj.get("recommendedAction")) - approved = from_union([from_bool, from_none], obj.get("approved")) - auto_approve_edits = from_union([from_bool, from_none], obj.get("autoApproveEdits")) - feedback = from_union([from_str, from_none], obj.get("feedback")) - selected_action = from_union([from_str, from_none], obj.get("selectedAction")) - skills = from_union([lambda x: from_list(SkillsLoadedSkill.from_dict, x), from_none], obj.get("skills")) - agents = from_union([lambda x: from_list(CustomAgentsUpdatedAgent.from_dict, x), from_none], obj.get("agents")) - errors = from_union([lambda x: from_list(from_str, x), from_none], obj.get("errors")) - warnings = from_union([lambda x: from_list(from_str, x), from_none], obj.get("warnings")) - servers = from_union([lambda x: from_list(MCPServersLoadedServer.from_dict, x), from_none], obj.get("servers")) - status = from_union([MCPServerStatus, from_none], obj.get("status")) - extensions = from_union([lambda x: from_list(ExtensionsLoadedExtension.from_dict, x), from_none], obj.get("extensions")) - return Data(already_in_use, context, copilot_version, producer, reasoning_effort, remote_steerable, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, provider_call_id, stack, status_code, url, aborted, title, info_type, warning_type, new_model, previous_model, previous_reasoning_effort, new_mode, previous_mode, operation, path, handoff_time, host, remote_session_id, repository, source_type, summary, messages_removed_during_truncation, performed_by, post_truncation_messages_length, post_truncation_tokens_in_messages, pre_truncation_messages_length, pre_truncation_tokens_in_messages, token_limit, tokens_removed_during_truncation, events_removed, up_to_event_id, code_changes, conversation_tokens, current_model, current_tokens, error_reason, model_metrics, session_start_time, shutdown_type, system_tokens, tool_definitions_tokens, total_api_duration_ms, total_premium_requests, base_commit, branch, cwd, git_root, head_commit, host_type, is_initial, messages_length, checkpoint_number, checkpoint_path, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, request_id, success, summary_content, tokens_removed, agent_mode, attachments, content, interaction_id, source, transformed_content, turn_id, intent, reasoning_id, delta_content, total_response_size_bytes, encrypted_content, message_id, output_tokens, parent_tool_call_id, phase, reasoning_opaque, reasoning_text, tool_requests, api_call_id, cache_read_tokens, cache_write_tokens, copilot_usage, cost, duration, initiator, input_tokens, inter_token_latency_ms, model, quota_snapshots, reasoning_tokens, ttft_ms, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, allowed_tools, description, name, plugin_name, plugin_version, agent_description, agent_display_name, agent_name, duration_ms, total_tokens, total_tool_calls, tools, hook_invocation_id, hook_type, input, output, metadata, role, kind, permission_request, resolved_by_hook, allow_freeform, choices, question, answer, was_freeform, elicitation_source, mode, requested_schema, action, mcp_request_id, server_name, server_url, static_client_config, traceparent, tracestate, command, args, command_name, commands, ui, actions, plan_content, recommended_action, approved, auto_approve_edits, feedback, selected_action, skills, agents, errors, warnings, servers, status, extensions) - def to_dict(self) -> dict: - result: dict = {} - if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_bool, from_none], self.already_in_use) - if self.context is not None: - result["context"] = from_union([lambda x: to_class(Context, x), from_str, from_none], self.context) - if self.copilot_version is not None: - result["copilotVersion"] = from_union([from_str, from_none], self.copilot_version) - if self.producer is not None: - result["producer"] = from_union([from_str, from_none], self.producer) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) - if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_bool, from_none], self.remote_steerable) - if self.selected_model is not None: - result["selectedModel"] = from_union([from_str, from_none], self.selected_model) - if self.session_id is not None: - result["sessionId"] = from_union([from_str, from_none], self.session_id) - if self.start_time is not None: - result["startTime"] = from_union([lambda x: x.isoformat(), from_none], self.start_time) - if self.version is not None: - result["version"] = from_union([to_float, from_none], self.version) - if self.event_count is not None: - result["eventCount"] = from_union([to_float, from_none], self.event_count) - if self.resume_time is not None: - result["resumeTime"] = from_union([lambda x: x.isoformat(), from_none], self.resume_time) - if self.error_type is not None: - result["errorType"] = from_union([from_str, from_none], self.error_type) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_str, from_none], self.provider_call_id) - if self.stack is not None: - result["stack"] = from_union([from_str, from_none], self.stack) - if self.status_code is not None: - result["statusCode"] = from_union([from_int, from_none], self.status_code) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - if self.aborted is not None: - result["aborted"] = from_union([from_bool, from_none], self.aborted) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.info_type is not None: - result["infoType"] = from_union([from_str, from_none], self.info_type) - if self.warning_type is not None: - result["warningType"] = from_union([from_str, from_none], self.warning_type) - if self.new_model is not None: - result["newModel"] = from_union([from_str, from_none], self.new_model) - if self.previous_model is not None: - result["previousModel"] = from_union([from_str, from_none], self.previous_model) - if self.previous_reasoning_effort is not None: - result["previousReasoningEffort"] = from_union([from_str, from_none], self.previous_reasoning_effort) - if self.new_mode is not None: - result["newMode"] = from_union([from_str, from_none], self.new_mode) - if self.previous_mode is not None: - result["previousMode"] = from_union([from_str, from_none], self.previous_mode) - if self.operation is not None: - result["operation"] = from_union([lambda x: to_enum(ChangedOperation, x), from_none], self.operation) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.handoff_time is not None: - result["handoffTime"] = from_union([lambda x: x.isoformat(), from_none], self.handoff_time) - if self.host is not None: - result["host"] = from_union([from_str, from_none], self.host) - if self.remote_session_id is not None: - result["remoteSessionId"] = from_union([from_str, from_none], self.remote_session_id) - if self.repository is not None: - result["repository"] = from_union([lambda x: to_class(HandoffRepository, x), from_str, from_none], self.repository) - if self.source_type is not None: - result["sourceType"] = from_union([lambda x: to_enum(HandoffSourceType, x), from_none], self.source_type) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) - if self.messages_removed_during_truncation is not None: - result["messagesRemovedDuringTruncation"] = from_union([to_float, from_none], self.messages_removed_during_truncation) - if self.performed_by is not None: - result["performedBy"] = from_union([from_str, from_none], self.performed_by) - if self.post_truncation_messages_length is not None: - result["postTruncationMessagesLength"] = from_union([to_float, from_none], self.post_truncation_messages_length) - if self.post_truncation_tokens_in_messages is not None: - result["postTruncationTokensInMessages"] = from_union([to_float, from_none], self.post_truncation_tokens_in_messages) - if self.pre_truncation_messages_length is not None: - result["preTruncationMessagesLength"] = from_union([to_float, from_none], self.pre_truncation_messages_length) - if self.pre_truncation_tokens_in_messages is not None: - result["preTruncationTokensInMessages"] = from_union([to_float, from_none], self.pre_truncation_tokens_in_messages) - if self.token_limit is not None: - result["tokenLimit"] = from_union([to_float, from_none], self.token_limit) - if self.tokens_removed_during_truncation is not None: - result["tokensRemovedDuringTruncation"] = from_union([to_float, from_none], self.tokens_removed_during_truncation) - if self.events_removed is not None: - result["eventsRemoved"] = from_union([to_float, from_none], self.events_removed) - if self.up_to_event_id is not None: - result["upToEventId"] = from_union([from_str, from_none], self.up_to_event_id) - if self.code_changes is not None: - result["codeChanges"] = from_union([lambda x: to_class(ShutdownCodeChanges, x), from_none], self.code_changes) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([to_float, from_none], self.conversation_tokens) - if self.current_model is not None: - result["currentModel"] = from_union([from_str, from_none], self.current_model) - if self.current_tokens is not None: - result["currentTokens"] = from_union([to_float, from_none], self.current_tokens) - if self.error_reason is not None: - result["errorReason"] = from_union([from_str, from_none], self.error_reason) - if self.model_metrics is not None: - result["modelMetrics"] = from_union([lambda x: from_dict(lambda x: to_class(ShutdownModelMetric, x), x), from_none], self.model_metrics) - if self.session_start_time is not None: - result["sessionStartTime"] = from_union([to_float, from_none], self.session_start_time) - if self.shutdown_type is not None: - result["shutdownType"] = from_union([lambda x: to_enum(ShutdownType, x), from_none], self.shutdown_type) - if self.system_tokens is not None: - result["systemTokens"] = from_union([to_float, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([to_float, from_none], self.tool_definitions_tokens) - if self.total_api_duration_ms is not None: - result["totalApiDurationMs"] = from_union([to_float, from_none], self.total_api_duration_ms) - if self.total_premium_requests is not None: - result["totalPremiumRequests"] = from_union([to_float, from_none], self.total_premium_requests) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_str, from_none], self.base_commit) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.git_root is not None: - result["gitRoot"] = from_union([from_str, from_none], self.git_root) - if self.head_commit is not None: - result["headCommit"] = from_union([from_str, from_none], self.head_commit) - if self.host_type is not None: - result["hostType"] = from_union([lambda x: to_enum(ContextChangedHostType, x), from_none], self.host_type) - if self.is_initial is not None: - result["isInitial"] = from_union([from_bool, from_none], self.is_initial) - if self.messages_length is not None: - result["messagesLength"] = from_union([to_float, from_none], self.messages_length) - if self.checkpoint_number is not None: - result["checkpointNumber"] = from_union([to_float, from_none], self.checkpoint_number) - if self.checkpoint_path is not None: - result["checkpointPath"] = from_union([from_str, from_none], self.checkpoint_path) - if self.compaction_tokens_used is not None: - result["compactionTokensUsed"] = from_union([lambda x: to_class(CompactionCompleteCompactionTokensUsed, x), from_none], self.compaction_tokens_used) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(Error, x), from_str, from_none], self.error) - if self.messages_removed is not None: - result["messagesRemoved"] = from_union([to_float, from_none], self.messages_removed) - if self.post_compaction_tokens is not None: - result["postCompactionTokens"] = from_union([to_float, from_none], self.post_compaction_tokens) - if self.pre_compaction_messages_length is not None: - result["preCompactionMessagesLength"] = from_union([to_float, from_none], self.pre_compaction_messages_length) - if self.pre_compaction_tokens is not None: - result["preCompactionTokens"] = from_union([to_float, from_none], self.pre_compaction_tokens) - if self.request_id is not None: - result["requestId"] = from_union([from_str, from_none], self.request_id) - if self.success is not None: - result["success"] = from_union([from_bool, from_none], self.success) - if self.summary_content is not None: - result["summaryContent"] = from_union([from_str, from_none], self.summary_content) - if self.tokens_removed is not None: - result["tokensRemoved"] = from_union([to_float, from_none], self.tokens_removed) - if self.agent_mode is not None: - result["agentMode"] = from_union([lambda x: to_enum(UserMessageAgentMode, x), from_none], self.agent_mode) - if self.attachments is not None: - result["attachments"] = from_union([lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x), from_none], self.attachments) - if self.content is not None: - result["content"] = from_union([from_str, lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_str, from_none], self.interaction_id) - if self.source is not None: - result["source"] = from_union([from_str, from_none], self.source) - if self.transformed_content is not None: - result["transformedContent"] = from_union([from_str, from_none], self.transformed_content) - if self.turn_id is not None: - result["turnId"] = from_union([from_str, from_none], self.turn_id) - if self.intent is not None: - result["intent"] = from_union([from_str, from_none], self.intent) - if self.reasoning_id is not None: - result["reasoningId"] = from_union([from_str, from_none], self.reasoning_id) - if self.delta_content is not None: - result["deltaContent"] = from_union([from_str, from_none], self.delta_content) - if self.total_response_size_bytes is not None: - result["totalResponseSizeBytes"] = from_union([to_float, from_none], self.total_response_size_bytes) - if self.encrypted_content is not None: - result["encryptedContent"] = from_union([from_str, from_none], self.encrypted_content) - if self.message_id is not None: - result["messageId"] = from_union([from_str, from_none], self.message_id) - if self.output_tokens is not None: - result["outputTokens"] = from_union([to_float, from_none], self.output_tokens) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_str, from_none], self.parent_tool_call_id) - if self.phase is not None: - result["phase"] = from_union([from_str, from_none], self.phase) - if self.reasoning_opaque is not None: - result["reasoningOpaque"] = from_union([from_str, from_none], self.reasoning_opaque) - if self.reasoning_text is not None: - result["reasoningText"] = from_union([from_str, from_none], self.reasoning_text) - if self.tool_requests is not None: - result["toolRequests"] = from_union([lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x), from_none], self.tool_requests) - if self.api_call_id is not None: - result["apiCallId"] = from_union([from_str, from_none], self.api_call_id) - if self.cache_read_tokens is not None: - result["cacheReadTokens"] = from_union([to_float, from_none], self.cache_read_tokens) - if self.cache_write_tokens is not None: - result["cacheWriteTokens"] = from_union([to_float, from_none], self.cache_write_tokens) - if self.copilot_usage is not None: - result["copilotUsage"] = from_union([lambda x: to_class(AssistantUsageCopilotUsage, x), from_none], self.copilot_usage) - if self.cost is not None: - result["cost"] = from_union([to_float, from_none], self.cost) - if self.duration is not None: - result["duration"] = from_union([to_float, from_none], self.duration) - if self.initiator is not None: - result["initiator"] = from_union([from_str, from_none], self.initiator) - if self.input_tokens is not None: - result["inputTokens"] = from_union([to_float, from_none], self.input_tokens) - if self.inter_token_latency_ms is not None: - result["interTokenLatencyMs"] = from_union([to_float, from_none], self.inter_token_latency_ms) - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) - if self.quota_snapshots is not None: - result["quotaSnapshots"] = from_union([lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x), from_none], self.quota_snapshots) - if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([to_float, from_none], self.reasoning_tokens) - if self.ttft_ms is not None: - result["ttftMs"] = from_union([to_float, from_none], self.ttft_ms) - if self.reason is not None: - result["reason"] = from_union([from_str, from_none], self.reason) - if self.arguments is not None: - result["arguments"] = self.arguments - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_str, from_none], self.tool_call_id) - if self.tool_name is not None: - result["toolName"] = from_union([from_str, from_none], self.tool_name) - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_str, from_none], self.mcp_server_name) - if self.mcp_tool_name is not None: - result["mcpToolName"] = from_union([from_str, from_none], self.mcp_tool_name) - if self.partial_output is not None: - result["partialOutput"] = from_union([from_str, from_none], self.partial_output) - if self.progress_message is not None: - result["progressMessage"] = from_union([from_str, from_none], self.progress_message) - if self.is_user_requested is not None: - result["isUserRequested"] = from_union([from_bool, from_none], self.is_user_requested) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(Result, x), from_none], self.result) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) - if self.allowed_tools is not None: - result["allowedTools"] = from_union([lambda x: from_list(from_str, x), from_none], self.allowed_tools) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.plugin_name is not None: - result["pluginName"] = from_union([from_str, from_none], self.plugin_name) - if self.plugin_version is not None: - result["pluginVersion"] = from_union([from_str, from_none], self.plugin_version) - if self.agent_description is not None: - result["agentDescription"] = from_union([from_str, from_none], self.agent_description) - if self.agent_display_name is not None: - result["agentDisplayName"] = from_union([from_str, from_none], self.agent_display_name) - if self.agent_name is not None: - result["agentName"] = from_union([from_str, from_none], self.agent_name) - if self.duration_ms is not None: - result["durationMs"] = from_union([to_float, from_none], self.duration_ms) - if self.total_tokens is not None: - result["totalTokens"] = from_union([to_float, from_none], self.total_tokens) - if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([to_float, from_none], self.total_tool_calls) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.hook_invocation_id is not None: - result["hookInvocationId"] = from_union([from_str, from_none], self.hook_invocation_id) - if self.hook_type is not None: - result["hookType"] = from_union([from_str, from_none], self.hook_type) - if self.input is not None: - result["input"] = self.input - if self.output is not None: - result["output"] = self.output - if self.metadata is not None: - result["metadata"] = from_union([lambda x: to_class(SystemMessageMetadata, x), from_none], self.metadata) - if self.role is not None: - result["role"] = from_union([lambda x: to_enum(SystemMessageRole, x), from_none], self.role) - if self.kind is not None: - result["kind"] = from_union([lambda x: to_class(SystemNotification, x), from_none], self.kind) - if self.permission_request is not None: - result["permissionRequest"] = from_union([lambda x: to_class(PermissionRequest, x), from_none], self.permission_request) - if self.resolved_by_hook is not None: - result["resolvedByHook"] = from_union([from_bool, from_none], self.resolved_by_hook) - if self.allow_freeform is not None: - result["allowFreeform"] = from_union([from_bool, from_none], self.allow_freeform) - if self.choices is not None: - result["choices"] = from_union([lambda x: from_list(from_str, x), from_none], self.choices) - if self.question is not None: - result["question"] = from_union([from_str, from_none], self.question) - if self.answer is not None: - result["answer"] = from_union([from_str, from_none], self.answer) - if self.was_freeform is not None: - result["wasFreeform"] = from_union([from_bool, from_none], self.was_freeform) - if self.elicitation_source is not None: - result["elicitationSource"] = from_union([from_str, from_none], self.elicitation_source) - if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(ElicitationRequestedMode, x), from_none], self.mode) - if self.requested_schema is not None: - result["requestedSchema"] = from_union([lambda x: to_class(ElicitationRequestedSchema, x), from_none], self.requested_schema) - if self.action is not None: - result["action"] = from_union([lambda x: to_enum(ElicitationCompletedAction, x), from_none], self.action) - if self.mcp_request_id is not None: - result["mcpRequestId"] = from_union([to_float, from_str, from_none], self.mcp_request_id) - if self.server_name is not None: - result["serverName"] = from_union([from_str, from_none], self.server_name) - if self.server_url is not None: - result["serverUrl"] = from_union([from_str, from_none], self.server_url) - if self.static_client_config is not None: - result["staticClientConfig"] = from_union([lambda x: to_class(MCPOauthRequiredStaticClientConfig, x), from_none], self.static_client_config) - if self.traceparent is not None: - result["traceparent"] = from_union([from_str, from_none], self.traceparent) - if self.tracestate is not None: - result["tracestate"] = from_union([from_str, from_none], self.tracestate) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.args is not None: - result["args"] = from_union([from_str, from_none], self.args) - if self.command_name is not None: - result["commandName"] = from_union([from_str, from_none], self.command_name) - if self.commands is not None: - result["commands"] = from_union([lambda x: from_list(lambda x: to_class(CommandsChangedCommand, x), x), from_none], self.commands) - if self.ui is not None: - result["ui"] = from_union([lambda x: to_class(CapabilitiesChangedUI, x), from_none], self.ui) - if self.actions is not None: - result["actions"] = from_union([lambda x: from_list(from_str, x), from_none], self.actions) - if self.plan_content is not None: - result["planContent"] = from_union([from_str, from_none], self.plan_content) - if self.recommended_action is not None: - result["recommendedAction"] = from_union([from_str, from_none], self.recommended_action) - if self.approved is not None: - result["approved"] = from_union([from_bool, from_none], self.approved) - if self.auto_approve_edits is not None: - result["autoApproveEdits"] = from_union([from_bool, from_none], self.auto_approve_edits) - if self.feedback is not None: - result["feedback"] = from_union([from_str, from_none], self.feedback) - if self.selected_action is not None: - result["selectedAction"] = from_union([from_str, from_none], self.selected_action) - if self.skills is not None: - result["skills"] = from_union([lambda x: from_list(lambda x: to_class(SkillsLoadedSkill, x), x), from_none], self.skills) - if self.agents is not None: - result["agents"] = from_union([lambda x: from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), x), from_none], self.agents) - if self.errors is not None: - result["errors"] = from_union([lambda x: from_list(from_str, x), from_none], self.errors) - if self.warnings is not None: - result["warnings"] = from_union([lambda x: from_list(from_str, x), from_none], self.warnings) - if self.servers is not None: - result["servers"] = from_union([lambda x: from_list(lambda x: to_class(MCPServersLoadedServer, x), x), from_none], self.servers) - if self.status is not None: - result["status"] = from_union([lambda x: to_enum(MCPServerStatus, x), from_none], self.status) - if self.extensions is not None: - result["extensions"] = from_union([lambda x: from_list(lambda x: to_class(ExtensionsLoadedExtension, x), x), from_none], self.extensions) - return result +class ExtensionsLoadedExtensionSource(Enum): + "Discovery source" + PROJECT = "project" + USER = "user" -class SessionEventType(Enum): - ABORT = "abort" - ASSISTANT_INTENT = "assistant.intent" - ASSISTANT_MESSAGE = "assistant.message" - ASSISTANT_MESSAGE_DELTA = "assistant.message_delta" - ASSISTANT_REASONING = "assistant.reasoning" - ASSISTANT_REASONING_DELTA = "assistant.reasoning_delta" - ASSISTANT_STREAMING_DELTA = "assistant.streaming_delta" - ASSISTANT_TURN_END = "assistant.turn_end" - ASSISTANT_TURN_START = "assistant.turn_start" - ASSISTANT_USAGE = "assistant.usage" - CAPABILITIES_CHANGED = "capabilities.changed" - COMMANDS_CHANGED = "commands.changed" - COMMAND_COMPLETED = "command.completed" - COMMAND_EXECUTE = "command.execute" - COMMAND_QUEUED = "command.queued" - ELICITATION_COMPLETED = "elicitation.completed" - ELICITATION_REQUESTED = "elicitation.requested" - EXIT_PLAN_MODE_COMPLETED = "exit_plan_mode.completed" - EXIT_PLAN_MODE_REQUESTED = "exit_plan_mode.requested" - EXTERNAL_TOOL_COMPLETED = "external_tool.completed" - EXTERNAL_TOOL_REQUESTED = "external_tool.requested" - HOOK_END = "hook.end" - HOOK_START = "hook.start" - MCP_OAUTH_COMPLETED = "mcp.oauth_completed" - MCP_OAUTH_REQUIRED = "mcp.oauth_required" - PENDING_MESSAGES_MODIFIED = "pending_messages.modified" - PERMISSION_COMPLETED = "permission.completed" - PERMISSION_REQUESTED = "permission.requested" - SAMPLING_COMPLETED = "sampling.completed" - SAMPLING_REQUESTED = "sampling.requested" - SESSION_BACKGROUND_TASKS_CHANGED = "session.background_tasks_changed" - SESSION_COMPACTION_COMPLETE = "session.compaction_complete" - SESSION_COMPACTION_START = "session.compaction_start" - SESSION_CONTEXT_CHANGED = "session.context_changed" - SESSION_CUSTOM_AGENTS_UPDATED = "session.custom_agents_updated" - SESSION_ERROR = "session.error" - SESSION_EXTENSIONS_LOADED = "session.extensions_loaded" - SESSION_HANDOFF = "session.handoff" - SESSION_IDLE = "session.idle" - SESSION_INFO = "session.info" - SESSION_MCP_SERVERS_LOADED = "session.mcp_servers_loaded" - SESSION_MCP_SERVER_STATUS_CHANGED = "session.mcp_server_status_changed" - SESSION_MODEL_CHANGE = "session.model_change" - SESSION_MODE_CHANGED = "session.mode_changed" - SESSION_PLAN_CHANGED = "session.plan_changed" - SESSION_REMOTE_STEERABLE_CHANGED = "session.remote_steerable_changed" - SESSION_RESUME = "session.resume" - SESSION_SHUTDOWN = "session.shutdown" - SESSION_SKILLS_LOADED = "session.skills_loaded" - SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" - SESSION_START = "session.start" - SESSION_TASK_COMPLETE = "session.task_complete" - SESSION_TITLE_CHANGED = "session.title_changed" - SESSION_TOOLS_UPDATED = "session.tools_updated" - SESSION_TRUNCATION = "session.truncation" - SESSION_USAGE_INFO = "session.usage_info" - SESSION_WARNING = "session.warning" - SESSION_WORKSPACE_FILE_CHANGED = "session.workspace_file_changed" - SKILL_INVOKED = "skill.invoked" - SUBAGENT_COMPLETED = "subagent.completed" - SUBAGENT_DESELECTED = "subagent.deselected" - SUBAGENT_FAILED = "subagent.failed" - SUBAGENT_SELECTED = "subagent.selected" - SUBAGENT_STARTED = "subagent.started" - SYSTEM_MESSAGE = "system.message" - SYSTEM_NOTIFICATION = "system.notification" - TOOL_EXECUTION_COMPLETE = "tool.execution_complete" - TOOL_EXECUTION_PARTIAL_RESULT = "tool.execution_partial_result" - TOOL_EXECUTION_PROGRESS = "tool.execution_progress" - TOOL_EXECUTION_START = "tool.execution_start" - TOOL_USER_REQUESTED = "tool.user_requested" - USER_INPUT_COMPLETED = "user_input.completed" - USER_INPUT_REQUESTED = "user_input.requested" - USER_MESSAGE = "user.message" - # UNKNOWN is used for forward compatibility - UNKNOWN = "unknown" +class ExtensionsLoadedExtensionStatus(Enum): + "Current status: running, disabled, failed, or starting" + RUNNING = "running" + DISABLED = "disabled" + FAILED = "failed" + STARTING = "starting" - @classmethod - def _missing_(cls, value: object) -> "SessionEventType": - """Handle unknown event types gracefully for forward compatibility.""" - return cls.UNKNOWN +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data @dataclass class SessionEvent: - data: Data - """Session initialization metadata including context and configuration - - Session resume metadata including current context and event count - - Notifies Mission Control that the session's remote steering capability has changed - - Error details for timeline display including message and optional diagnostic information - - Payload indicating the session is idle with no background agents in flight - - Session title change payload containing the new display title - - Informational message for timeline display with categorization - - Warning message for timeline display with categorization - - Model change details including previous and new model identifiers - - Agent mode change details including previous and new modes - - Plan file operation details indicating what changed - - Workspace file change details including path and operation type - - Session handoff metadata including source, context, and repository information - - Conversation truncation statistics including token counts and removed content metrics - - Session rewind details including target event and count of removed events - - Session termination metrics including usage statistics, code changes, and shutdown - reason - - Updated working directory and git context after the change - - Current context window usage statistics including token and message counts - - Context window breakdown at the start of LLM-powered conversation compaction - - Conversation compaction results including success status, metrics, and optional error - details - - Task completion notification with summary from the agent - - Empty payload; the event signals that the pending message queue has changed - - Turn initialization metadata including identifier and interaction tracking - - Agent intent description for current activity or plan - - Assistant reasoning content for timeline display with complete thinking text - - Streaming reasoning delta for incremental extended thinking updates - - Streaming response progress with cumulative byte count - - Assistant response containing text content, optional tool requests, and interaction - metadata - - Streaming assistant message delta for incremental response updates - - Turn completion metadata including the turn identifier - - LLM API call usage metrics including tokens, costs, quotas, and billing information - - Turn abort information including the reason for termination - - User-initiated tool invocation request with tool name and arguments - - Tool execution startup details including MCP server information when applicable - - Streaming tool execution output for incremental result display - - Tool execution progress notification with status message - - Tool execution completion results including success status, detailed output, and error - information - - Skill invocation details including content, allowed tools, and plugin metadata - - Sub-agent startup details including parent tool call and agent information - - Sub-agent completion details for successful execution - - Sub-agent failure details including error message and agent information - - Custom agent selection details including name and available tools - - Empty payload; the event signals that the custom agent was deselected, returning to the - default agent - - Hook invocation start details including type and input data - - Hook invocation completion details including output, success status, and error - information - - System or developer message content with role and optional template metadata - - System-generated notification for runtime events like background task completion - - Permission request notification requiring client approval with request details - - Permission request completion notification signaling UI dismissal - - User input request notification with question and optional predefined choices - - User input request completion with the user's response - - Elicitation request; may be form-based (structured input) or URL-based (browser - redirect) - - Elicitation request completion with the user's response - - Sampling request from an MCP server; contains the server name and a requestId for - correlation - - Sampling request completion notification signaling UI dismissal - - OAuth authentication request for an MCP server - - MCP OAuth request completion notification - - External tool invocation request for client-side tool execution - - External tool completion notification signaling UI dismissal - - Queued slash command dispatch request for client execution - - Registered command dispatch request routed to the owning client - - Queued command completion notification signaling UI dismissal - - SDK command registration change notification - - Session capability change notification - - Plan approval request with plan content and available user actions - - Plan mode exit completion with the user's approval decision and optional feedback - """ + data: SessionEventData id: UUID - """Unique event identifier (UUID v4), generated when the event is emitted""" - timestamp: datetime - """ISO 8601 timestamp when the event was created""" - type: SessionEventType ephemeral: bool | None = None - """When true, the event is transient and not persisted to the session event log on disk""" - parent_id: UUID | None = None - """ID of the chronologically preceding event in the session, forming a linked chain. Null - for the first event. - """ + raw_type: str | None = None @staticmethod - def from_dict(obj: Any) -> 'SessionEvent': + def from_dict(obj: Any) -> "SessionEvent": assert isinstance(obj, dict) - data = Data.from_dict(obj.get("data")) - id = UUID(obj.get("id")) + raw_type = from_str(obj.get("type")) + event_type = SessionEventType(raw_type) + event_id = from_uuid(obj.get("id")) timestamp = from_datetime(obj.get("timestamp")) - type = SessionEventType(obj.get("type")) ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - parent_id = from_union([from_none, lambda x: UUID(x)], obj.get("parentId")) - return SessionEvent(data, id, timestamp, type, ephemeral, parent_id) + parent_id = from_union([from_none, from_uuid], obj.get("parentId")) + data_obj = obj.get("data") + match event_type: + case SessionEventType.SESSION_START: data = SessionStartData.from_dict(data_obj) + case SessionEventType.SESSION_RESUME: data = SessionResumeData.from_dict(data_obj) + case SessionEventType.SESSION_REMOTE_STEERABLE_CHANGED: data = SessionRemoteSteerableChangedData.from_dict(data_obj) + case SessionEventType.SESSION_ERROR: data = SessionErrorData.from_dict(data_obj) + case SessionEventType.SESSION_IDLE: data = SessionIdleData.from_dict(data_obj) + case SessionEventType.SESSION_TITLE_CHANGED: data = SessionTitleChangedData.from_dict(data_obj) + case SessionEventType.SESSION_INFO: data = SessionInfoData.from_dict(data_obj) + case SessionEventType.SESSION_WARNING: data = SessionWarningData.from_dict(data_obj) + case SessionEventType.SESSION_MODEL_CHANGE: data = SessionModelChangeData.from_dict(data_obj) + case SessionEventType.SESSION_MODE_CHANGED: data = SessionModeChangedData.from_dict(data_obj) + case SessionEventType.SESSION_PLAN_CHANGED: data = SessionPlanChangedData.from_dict(data_obj) + case SessionEventType.SESSION_WORKSPACE_FILE_CHANGED: data = SessionWorkspaceFileChangedData.from_dict(data_obj) + case SessionEventType.SESSION_HANDOFF: data = SessionHandoffData.from_dict(data_obj) + case SessionEventType.SESSION_TRUNCATION: data = SessionTruncationData.from_dict(data_obj) + case SessionEventType.SESSION_SNAPSHOT_REWIND: data = SessionSnapshotRewindData.from_dict(data_obj) + case SessionEventType.SESSION_SHUTDOWN: data = SessionShutdownData.from_dict(data_obj) + case SessionEventType.SESSION_CONTEXT_CHANGED: data = SessionContextChangedData.from_dict(data_obj) + case SessionEventType.SESSION_USAGE_INFO: data = SessionUsageInfoData.from_dict(data_obj) + case SessionEventType.SESSION_COMPACTION_START: data = SessionCompactionStartData.from_dict(data_obj) + case SessionEventType.SESSION_COMPACTION_COMPLETE: data = SessionCompactionCompleteData.from_dict(data_obj) + case SessionEventType.SESSION_TASK_COMPLETE: data = SessionTaskCompleteData.from_dict(data_obj) + case SessionEventType.USER_MESSAGE: data = UserMessageData.from_dict(data_obj) + case SessionEventType.PENDING_MESSAGES_MODIFIED: data = PendingMessagesModifiedData.from_dict(data_obj) + case SessionEventType.ASSISTANT_TURN_START: data = AssistantTurnStartData.from_dict(data_obj) + case SessionEventType.ASSISTANT_INTENT: data = AssistantIntentData.from_dict(data_obj) + case SessionEventType.ASSISTANT_REASONING: data = AssistantReasoningData.from_dict(data_obj) + case SessionEventType.ASSISTANT_REASONING_DELTA: data = AssistantReasoningDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_STREAMING_DELTA: data = AssistantStreamingDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_MESSAGE: data = AssistantMessageData.from_dict(data_obj) + case SessionEventType.ASSISTANT_MESSAGE_DELTA: data = AssistantMessageDeltaData.from_dict(data_obj) + case SessionEventType.ASSISTANT_TURN_END: data = AssistantTurnEndData.from_dict(data_obj) + case SessionEventType.ASSISTANT_USAGE: data = AssistantUsageData.from_dict(data_obj) + case SessionEventType.ABORT: data = AbortData.from_dict(data_obj) + case SessionEventType.TOOL_USER_REQUESTED: data = ToolUserRequestedData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_START: data = ToolExecutionStartData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_PARTIAL_RESULT: data = ToolExecutionPartialResultData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_PROGRESS: data = ToolExecutionProgressData.from_dict(data_obj) + case SessionEventType.TOOL_EXECUTION_COMPLETE: data = ToolExecutionCompleteData.from_dict(data_obj) + case SessionEventType.SKILL_INVOKED: data = SkillInvokedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_STARTED: data = SubagentStartedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_COMPLETED: data = SubagentCompletedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_FAILED: data = SubagentFailedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_SELECTED: data = SubagentSelectedData.from_dict(data_obj) + case SessionEventType.SUBAGENT_DESELECTED: data = SubagentDeselectedData.from_dict(data_obj) + case SessionEventType.HOOK_START: data = HookStartData.from_dict(data_obj) + case SessionEventType.HOOK_END: data = HookEndData.from_dict(data_obj) + case SessionEventType.SYSTEM_MESSAGE: data = SystemMessageData.from_dict(data_obj) + case SessionEventType.SYSTEM_NOTIFICATION: data = SystemNotificationData.from_dict(data_obj) + case SessionEventType.PERMISSION_REQUESTED: data = PermissionRequestedData.from_dict(data_obj) + case SessionEventType.PERMISSION_COMPLETED: data = PermissionCompletedData.from_dict(data_obj) + case SessionEventType.USER_INPUT_REQUESTED: data = UserInputRequestedData.from_dict(data_obj) + case SessionEventType.USER_INPUT_COMPLETED: data = UserInputCompletedData.from_dict(data_obj) + case SessionEventType.ELICITATION_REQUESTED: data = ElicitationRequestedData.from_dict(data_obj) + case SessionEventType.ELICITATION_COMPLETED: data = ElicitationCompletedData.from_dict(data_obj) + case SessionEventType.SAMPLING_REQUESTED: data = SamplingRequestedData.from_dict(data_obj) + case SessionEventType.SAMPLING_COMPLETED: data = SamplingCompletedData.from_dict(data_obj) + case SessionEventType.MCP_OAUTH_REQUIRED: data = McpOauthRequiredData.from_dict(data_obj) + case SessionEventType.MCP_OAUTH_COMPLETED: data = McpOauthCompletedData.from_dict(data_obj) + case SessionEventType.EXTERNAL_TOOL_REQUESTED: data = ExternalToolRequestedData.from_dict(data_obj) + case SessionEventType.EXTERNAL_TOOL_COMPLETED: data = ExternalToolCompletedData.from_dict(data_obj) + case SessionEventType.COMMAND_QUEUED: data = CommandQueuedData.from_dict(data_obj) + case SessionEventType.COMMAND_EXECUTE: data = CommandExecuteData.from_dict(data_obj) + case SessionEventType.COMMAND_COMPLETED: data = CommandCompletedData.from_dict(data_obj) + case SessionEventType.COMMANDS_CHANGED: data = CommandsChangedData.from_dict(data_obj) + case SessionEventType.CAPABILITIES_CHANGED: data = CapabilitiesChangedData.from_dict(data_obj) + case SessionEventType.EXIT_PLAN_MODE_REQUESTED: data = ExitPlanModeRequestedData.from_dict(data_obj) + case SessionEventType.EXIT_PLAN_MODE_COMPLETED: data = ExitPlanModeCompletedData.from_dict(data_obj) + case SessionEventType.SESSION_TOOLS_UPDATED: data = SessionToolsUpdatedData.from_dict(data_obj) + case SessionEventType.SESSION_BACKGROUND_TASKS_CHANGED: data = SessionBackgroundTasksChangedData.from_dict(data_obj) + case SessionEventType.SESSION_SKILLS_LOADED: data = SessionSkillsLoadedData.from_dict(data_obj) + case SessionEventType.SESSION_CUSTOM_AGENTS_UPDATED: data = SessionCustomAgentsUpdatedData.from_dict(data_obj) + case SessionEventType.SESSION_MCP_SERVERS_LOADED: data = SessionMcpServersLoadedData.from_dict(data_obj) + case SessionEventType.SESSION_MCP_SERVER_STATUS_CHANGED: data = SessionMcpServerStatusChangedData.from_dict(data_obj) + case SessionEventType.SESSION_EXTENSIONS_LOADED: data = SessionExtensionsLoadedData.from_dict(data_obj) + case _: data = RawSessionEventData.from_dict(data_obj) + return SessionEvent( + data=data, + id=event_id, + timestamp=timestamp, + type=event_type, + ephemeral=ephemeral, + parent_id=parent_id, + raw_type=raw_type if event_type == SessionEventType.UNKNOWN else None, + ) def to_dict(self) -> dict: result: dict = {} - result["data"] = to_class(Data, self.data) - result["id"] = str(self.id) - result["timestamp"] = self.timestamp.isoformat() - result["type"] = to_enum(SessionEventType, self.type) + result["data"] = self.data.to_dict() + result["id"] = to_uuid(self.id) + result["timestamp"] = to_datetime(self.timestamp) + result["type"] = self.raw_type if self.type == SessionEventType.UNKNOWN and self.raw_type is not None else to_enum(SessionEventType, self.type) if self.ephemeral is not None: - result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) - result["parentId"] = from_union([from_none, lambda x: str(x)], self.parent_id) + result["ephemeral"] = from_bool(self.ephemeral) + result["parentId"] = from_union([from_none, to_uuid], self.parent_id) return result @@ -3427,4 +4255,5 @@ def session_event_from_dict(s: Any) -> SessionEvent: def session_event_to_dict(x: SessionEvent) -> Any: - return to_class(SessionEvent, x) + return x.to_dict() + diff --git a/python/copilot/session.py b/python/copilot/session.py index 5edbe924b..443cfc969 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -45,9 +45,16 @@ ) from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride from .generated.session_events import ( + AssistantMessageData, + CapabilitiesChangedData, + CommandExecuteData, + ElicitationRequestedData, + ExternalToolRequestedData, PermissionRequest, + PermissionRequestedData, + SessionErrorData, SessionEvent, - SessionEventType, + SessionIdleData, session_event_from_dict, ) from .tools import Tool, ToolHandler, ToolInvocation, ToolResult @@ -1127,9 +1134,12 @@ async def send_and_wait( Exception: If the session has been disconnected or the connection fails. Example: + >>> from copilot.generated.session_events import AssistantMessageData >>> response = await session.send_and_wait("What is 2+2?") >>> if response: - ... print(response.data.content) + ... match response.data: + ... case AssistantMessageData() as data: + ... print(data.content) """ idle_event = asyncio.Event() error_event: Exception | None = None @@ -1137,15 +1147,14 @@ async def send_and_wait( def handler(event: SessionEventTypeAlias) -> None: nonlocal last_assistant_message, error_event - if event.type == SessionEventType.ASSISTANT_MESSAGE: - last_assistant_message = event - elif event.type == SessionEventType.SESSION_IDLE: - idle_event.set() - elif event.type == SessionEventType.SESSION_ERROR: - error_event = Exception( - f"Session error: {getattr(event.data, 'message', str(event.data))}" - ) - idle_event.set() + match event.data: + case AssistantMessageData(): + last_assistant_message = event + case SessionIdleData(): + idle_event.set() + case SessionErrorData() as data: + error_event = Exception(f"Session error: {data.message or str(data)}") + idle_event.set() unsubscribe = self.on(handler) try: @@ -1175,11 +1184,13 @@ def on(self, handler: Callable[[SessionEvent], None]) -> Callable[[], None]: A function that, when called, unsubscribes the handler. Example: + >>> from copilot.generated.session_events import AssistantMessageData, SessionErrorData >>> def handle_event(event): - ... if event.type == "assistant.message": - ... print(f"Assistant: {event.data.content}") - ... elif event.type == "session.error": - ... print(f"Error: {event.data.message}") + ... match event.data: + ... case AssistantMessageData() as data: + ... print(f"Assistant: {data.content}") + ... case SessionErrorData() as data: + ... print(f"Error: {data.message}") >>> unsubscribe = session.on(handle_event) >>> # Later, to stop receiving events: >>> unsubscribe() @@ -1225,88 +1236,91 @@ def _handle_broadcast_event(self, event: SessionEvent) -> None: Implements the protocol v3 broadcast model where tool calls and permission requests are broadcast as session events to all clients. """ - if event.type == SessionEventType.EXTERNAL_TOOL_REQUESTED: - request_id = event.data.request_id - tool_name = event.data.tool_name - if not request_id or not tool_name: - return - - handler = self._get_tool_handler(tool_name) - if not handler: - return # This client doesn't handle this tool; another client will. - - tool_call_id = event.data.tool_call_id or "" - arguments = event.data.arguments - tp = getattr(event.data, "traceparent", None) - ts = getattr(event.data, "tracestate", None) - asyncio.ensure_future( - self._execute_tool_and_respond( - request_id, tool_name, tool_call_id, arguments, handler, tp, ts + match event.data: + case ExternalToolRequestedData() as data: + request_id = data.request_id + tool_name = data.tool_name + if not request_id or not tool_name: + return + + handler = self._get_tool_handler(tool_name) + if not handler: + return # This client doesn't handle this tool; another client will. + + tool_call_id = data.tool_call_id or "" + arguments = data.arguments + tp = getattr(data, "traceparent", None) + ts = getattr(data, "tracestate", None) + asyncio.ensure_future( + self._execute_tool_and_respond( + request_id, tool_name, tool_call_id, arguments, handler, tp, ts + ) ) - ) - elif event.type == SessionEventType.PERMISSION_REQUESTED: - request_id = event.data.request_id - permission_request = event.data.permission_request - if not request_id or not permission_request: - return + case PermissionRequestedData() as data: + request_id = data.request_id + permission_request = data.permission_request + if not request_id or not permission_request: + return - resolved_by_hook = getattr(event.data, "resolved_by_hook", None) - if resolved_by_hook: - return # Already resolved by a permissionRequest hook; no client action needed. + resolved_by_hook = getattr(data, "resolved_by_hook", None) + if resolved_by_hook: + return # Already resolved by a permissionRequest hook; no client action needed. - with self._permission_handler_lock: - perm_handler = self._permission_handler - if not perm_handler: - return # This client doesn't handle permissions; another client will. + with self._permission_handler_lock: + perm_handler = self._permission_handler + if not perm_handler: + return # This client doesn't handle permissions; another client will. - asyncio.ensure_future( - self._execute_permission_and_respond(request_id, permission_request, perm_handler) - ) + asyncio.ensure_future( + self._execute_permission_and_respond( + request_id, permission_request, perm_handler + ) + ) - elif event.type == SessionEventType.COMMAND_EXECUTE: - request_id = event.data.request_id - command_name = event.data.command_name - command = event.data.command - args = event.data.args - if not request_id or not command_name: - return - asyncio.ensure_future( - self._execute_command_and_respond( - request_id, command_name, command or "", args or "" + case CommandExecuteData() as data: + request_id = data.request_id + command_name = data.command_name + command = data.command + args = data.args + if not request_id or not command_name: + return + asyncio.ensure_future( + self._execute_command_and_respond( + request_id, command_name, command or "", args or "" + ) ) - ) - elif event.type == SessionEventType.ELICITATION_REQUESTED: - with self._elicitation_handler_lock: - handler = self._elicitation_handler - if not handler: - return - request_id = event.data.request_id - if not request_id: - return - context: ElicitationContext = { - "session_id": self.session_id, - "message": event.data.message or "", - } - if event.data.requested_schema is not None: - context["requestedSchema"] = event.data.requested_schema.to_dict() - if event.data.mode is not None: - context["mode"] = event.data.mode.value - if event.data.elicitation_source is not None: - context["elicitationSource"] = event.data.elicitation_source - if event.data.url is not None: - context["url"] = event.data.url - asyncio.ensure_future(self._handle_elicitation_request(context, request_id)) - - elif event.type == SessionEventType.CAPABILITIES_CHANGED: - cap: SessionCapabilities = {} - if event.data.ui is not None: - ui_cap: SessionUiCapabilities = {} - if event.data.ui.elicitation is not None: - ui_cap["elicitation"] = event.data.ui.elicitation - cap["ui"] = ui_cap - self._capabilities = {**self._capabilities, **cap} + case ElicitationRequestedData() as data: + with self._elicitation_handler_lock: + handler = self._elicitation_handler + if not handler: + return + request_id = data.request_id + if not request_id: + return + context: ElicitationContext = { + "session_id": self.session_id, + "message": data.message or "", + } + if data.requested_schema is not None: + context["requestedSchema"] = data.requested_schema.to_dict() + if data.mode is not None: + context["mode"] = data.mode.value + if data.elicitation_source is not None: + context["elicitationSource"] = data.elicitation_source + if data.url is not None: + context["url"] = data.url + asyncio.ensure_future(self._handle_elicitation_request(context, request_id)) + + case CapabilitiesChangedData() as data: + cap: SessionCapabilities = {} + if data.ui is not None: + ui_cap: SessionUiCapabilities = {} + if data.ui.elicitation is not None: + ui_cap["elicitation"] = data.ui.elicitation + cap["ui"] = ui_cap + self._capabilities = {**self._capabilities, **cap} async def _execute_tool_and_respond( self, @@ -1795,10 +1809,12 @@ async def get_messages(self) -> list[SessionEvent]: Exception: If the session has been disconnected or the connection fails. Example: + >>> from copilot.generated.session_events import AssistantMessageData >>> events = await session.get_messages() >>> for event in events: - ... if event.type == "assistant.message": - ... print(f"Assistant: {event.data.content}") + ... match event.data: + ... case AssistantMessageData() as data: + ... print(f"Assistant: {data.content}") """ response = await self._client.request("session.getMessages", {"sessionId": self.session_id}) # Convert dict events to SessionEvent objects diff --git a/python/e2e/test_permissions.py b/python/e2e/test_permissions.py index 692c600e0..86beb3a5c 100644 --- a/python/e2e/test_permissions.py +++ b/python/e2e/test_permissions.py @@ -6,7 +6,12 @@ import pytest -from copilot.session import PermissionHandler, PermissionRequest, PermissionRequestResult +from copilot.generated.session_events import ( + PermissionRequest, + SessionIdleData, + ToolExecutionCompleteData, +) +from copilot.session import PermissionHandler, PermissionRequestResult from .testharness import E2ETestContext from .testharness.helper import read_file, write_file @@ -76,17 +81,18 @@ def deny_all(request, invocation): done_event = asyncio.Event() def on_event(event): - if event.type.value == "tool.execution_complete" and event.data.success is False: - error = event.data.error - msg = ( - error - if isinstance(error, str) - else (getattr(error, "message", None) if error is not None else None) - ) - if msg and "Permission denied" in msg: - denied_events.append(event) - elif event.type.value == "session.idle": - done_event.set() + match event.data: + case ToolExecutionCompleteData(success=False) as data: + error = data.error + msg = ( + error + if isinstance(error, str) + else (getattr(error, "message", None) if error is not None else None) + ) + if msg and "Permission denied" in msg: + denied_events.append(event) + case SessionIdleData(): + done_event.set() session.on(on_event) @@ -116,17 +122,18 @@ def deny_all(request, invocation): done_event = asyncio.Event() def on_event(event): - if event.type.value == "tool.execution_complete" and event.data.success is False: - error = event.data.error - msg = ( - error - if isinstance(error, str) - else (getattr(error, "message", None) if error is not None else None) - ) - if msg and "Permission denied" in msg: - denied_events.append(event) - elif event.type.value == "session.idle": - done_event.set() + match event.data: + case ToolExecutionCompleteData(success=False) as data: + error = data.error + msg = ( + error + if isinstance(error, str) + else (getattr(error, "message", None) if error is not None else None) + ) + if msg and "Permission denied" in msg: + denied_events.append(event) + case SessionIdleData(): + done_event.set() session2.on(on_event) diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index 1a249b516..621062e4e 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -7,6 +7,7 @@ from copilot import CopilotClient from copilot.client import SubprocessConfig +from copilot.generated.session_events import SessionModelChangeData from copilot.session import PermissionHandler from copilot.tools import Tool, ToolResult @@ -600,16 +601,20 @@ async def test_should_set_model_with_reasoning_effort(self, ctx: E2ETestContext) model_change_event = asyncio.get_event_loop().create_future() def on_event(event): - if not model_change_event.done() and event.type.value == "session.model_change": - model_change_event.set_result(event) + if model_change_event.done(): + return + + match event.data: + case SessionModelChangeData() as data: + model_change_event.set_result(data) session.on(on_event) await session.set_model("gpt-4.1", reasoning_effort="high") - event = await asyncio.wait_for(model_change_event, timeout=30) - assert event.data.new_model == "gpt-4.1" - assert event.data.reasoning_effort == "high" + data = await asyncio.wait_for(model_change_event, timeout=30) + assert data.new_model == "gpt-4.1" + assert data.reasoning_effort == "high" async def test_should_accept_blob_attachments(self, ctx: E2ETestContext): # Write the image to disk so the model can view it diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py index d9bfabb55..bc228707b 100644 --- a/python/e2e/test_session_fs.py +++ b/python/e2e/test_session_fs.py @@ -20,7 +20,7 @@ SessionFSReadFileResult, SessionFSStatResult, ) -from copilot.generated.session_events import SessionEvent +from copilot.generated.session_events import SessionCompactionCompleteData, SessionEvent from copilot.session import PermissionHandler from .testharness import E2ETestContext @@ -192,9 +192,10 @@ async def test_should_succeed_with_compaction_while_using_sessionfs( def on_event(event: SessionEvent): nonlocal compaction_success - if event.type.value == "session.compaction_complete": - compaction_success = event.data.success - compaction_event.set() + match event.data: + case SessionCompactionCompleteData() as data: + compaction_success = data.success + compaction_event.set() session.on(on_event) diff --git a/python/e2e/test_ui_elicitation_multi_client.py b/python/e2e/test_ui_elicitation_multi_client.py index 45280f6b2..4c63fb6b2 100644 --- a/python/e2e/test_ui_elicitation_multi_client.py +++ b/python/e2e/test_ui_elicitation_multi_client.py @@ -17,6 +17,7 @@ from copilot import CopilotClient from copilot.client import ExternalServerConfig, SubprocessConfig +from copilot.generated.session_events import CapabilitiesChangedData from copilot.session import ( ElicitationContext, ElicitationResult, @@ -194,11 +195,12 @@ async def test_capabilities_changed_when_second_client_joins_with_elicitation( cap_event_data: dict = {} def on_event(event): - if event.type.value == "capabilities.changed": - ui = getattr(event.data, "ui", None) - if ui: - cap_event_data["elicitation"] = getattr(ui, "elicitation", None) - cap_changed.set() + match event.data: + case CapabilitiesChangedData() as data: + ui = data.ui + if ui: + cap_event_data["elicitation"] = ui.elicitation + cap_changed.set() unsubscribe = session1.on(on_event) @@ -239,10 +241,11 @@ async def test_capabilities_changed_when_elicitation_provider_disconnects( cap_enabled = asyncio.Event() def on_enabled(event): - if event.type.value == "capabilities.changed": - ui = getattr(event.data, "ui", None) - if ui and getattr(ui, "elicitation", None) is True: - cap_enabled.set() + match event.data: + case CapabilitiesChangedData() as data: + ui = data.ui + if ui and ui.elicitation is True: + cap_enabled.set() unsub_enabled = session1.on(on_enabled) @@ -269,10 +272,11 @@ async def handler( cap_disabled = asyncio.Event() def on_disabled(event): - if event.type.value == "capabilities.changed": - ui = getattr(event.data, "ui", None) - if ui and getattr(ui, "elicitation", None) is False: - cap_disabled.set() + match event.data: + case CapabilitiesChangedData() as data: + ui = data.ui + if ui and ui.elicitation is False: + cap_disabled.set() unsub_disabled = session1.on(on_disabled) diff --git a/python/e2e/testharness/helper.py b/python/e2e/testharness/helper.py index e0e3d267c..c603a8ec5 100644 --- a/python/e2e/testharness/helper.py +++ b/python/e2e/testharness/helper.py @@ -6,6 +6,11 @@ import os from copilot import CopilotSession +from copilot.generated.session_events import ( + AssistantMessageData, + SessionErrorData, + SessionIdleData, +) async def get_final_assistant_message( @@ -34,14 +39,15 @@ def on_event(event): if result_future.done(): return - if event.type.value == "assistant.message": - final_assistant_message = event - elif event.type.value == "session.idle": - if final_assistant_message is not None: - result_future.set_result(final_assistant_message) - elif event.type.value == "session.error": - msg = event.data.message if event.data.message else "session error" - result_future.set_exception(RuntimeError(msg)) + match event.data: + case AssistantMessageData(): + final_assistant_message = event + case SessionIdleData(): + if final_assistant_message is not None: + result_future.set_result(final_assistant_message) + case SessionErrorData() as data: + msg = data.message if data.message else "session error" + result_future.set_exception(RuntimeError(msg)) # Subscribe to future events unsubscribe = session.on(on_event) @@ -75,9 +81,10 @@ async def _get_existing_final_response(session: CopilotSession, already_idle: bo # Check for errors for msg in current_turn_messages: - if msg.type.value == "session.error": - err_msg = msg.data.message if msg.data.message else "session error" - raise RuntimeError(err_msg) + match msg.data: + case SessionErrorData() as data: + err_msg = data.message if data.message else "session error" + raise RuntimeError(err_msg) # Find session.idle and get last assistant message before it if already_idle: @@ -156,9 +163,11 @@ def on_event(event): if event.type.value == event_type: result_future.set_result(event) - elif event.type.value == "session.error": - msg = event.data.message if event.data.message else "session error" - result_future.set_exception(RuntimeError(msg)) + else: + match event.data: + case SessionErrorData() as data: + msg = data.message if data.message else "session error" + result_future.set_exception(RuntimeError(msg)) unsubscribe = session.on(on_event) diff --git a/python/samples/chat.py b/python/samples/chat.py index 890191b19..2e48c7ed5 100644 --- a/python/samples/chat.py +++ b/python/samples/chat.py @@ -1,6 +1,11 @@ import asyncio from copilot import CopilotClient +from copilot.generated.session_events import ( + AssistantMessageData, + AssistantReasoningData, + ToolExecutionStartData, +) from copilot.session import PermissionHandler BLUE = "\033[34m" @@ -14,10 +19,11 @@ async def main(): def on_event(event): output = None - if event.type.value == "assistant.reasoning": - output = f"[reasoning: {event.data.content}]" - elif event.type.value == "tool.execution_start": - output = f"[tool: {event.data.tool_name}]" + match event.data: + case AssistantReasoningData() as data: + output = f"[reasoning: {data.content}]" + case ToolExecutionStartData() as data: + output = f"[tool: {data.tool_name}]" if output: print(f"{BLUE}{output}{RESET}") @@ -32,7 +38,12 @@ def on_event(event): print() reply = await session.send_and_wait(user_input) - print(f"\nAssistant: {reply.data.content if reply else None}\n") + assistant_output = None + if reply: + match reply.data: + case AssistantMessageData() as data: + assistant_output = data.content + print(f"\nAssistant: {assistant_output}\n") if __name__ == "__main__": diff --git a/python/test_commands_and_elicitation.py b/python/test_commands_and_elicitation.py index 6b8518e26..40f95724c 100644 --- a/python/test_commands_and_elicitation.py +++ b/python/test_commands_and_elicitation.py @@ -136,13 +136,13 @@ async def mock_request(method, params): # Simulate a command.execute broadcast event from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-1", command="/deploy production", command_name="deploy", @@ -203,13 +203,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-2", command="/fail", command_name="fail", @@ -257,13 +257,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + CommandExecuteData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=CommandExecuteData( request_id="req-3", command="/unknown", command_name="unknown", @@ -519,13 +519,13 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + ElicitationRequestedData, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=ElicitationRequestedData( request_id="req-elicit-1", message="Pick a color", ), @@ -578,19 +578,18 @@ async def mock_request(method, params): client._client.request = mock_request from copilot.generated.session_events import ( - Data, + ElicitationRequestedData, ElicitationRequestedSchema, - RequestedSchemaType, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data( + data=ElicitationRequestedData( request_id="req-schema-1", message="Fill in your details", requested_schema=ElicitationRequestedSchema( - type=RequestedSchemaType.OBJECT, + type="object", properties={ "name": {"type": "string"}, "age": {"type": "number"}, @@ -638,14 +637,14 @@ async def test_capabilities_changed_event_updates_session(self): session._set_capabilities({}) from copilot.generated.session_events import ( + CapabilitiesChangedData, CapabilitiesChangedUI, - Data, SessionEvent, SessionEventType, ) event = SessionEvent( - data=Data(ui=CapabilitiesChangedUI(elicitation=True)), + data=CapabilitiesChangedData(ui=CapabilitiesChangedUI(elicitation=True)), id="evt-cap-1", timestamp="2025-01-01T00:00:00Z", type=SessionEventType.CAPABILITIES_CHANGED, diff --git a/python/test_event_forward_compatibility.py b/python/test_event_forward_compatibility.py index 017cff2e8..733a9b24b 100644 --- a/python/test_event_forward_compatibility.py +++ b/python/test_event_forward_compatibility.py @@ -12,7 +12,19 @@ import pytest -from copilot.generated.session_events import SessionEventType, session_event_from_dict +from copilot.generated.session_events import ( + Data, + ElicitationCompletedAction, + ElicitationRequestedMode, + ElicitationRequestedSchema, + PermissionRequest, + PermissionRequestMemoryAction, + SessionEventType, + SessionTaskCompleteData, + UserMessageAgentMode, + UserMessageAttachmentGithubReferenceType, + session_event_from_dict, +) class TestEventForwardCompatibility: @@ -62,3 +74,39 @@ def test_malformed_timestamp_raises_error(self): # This should raise an error and NOT be silently suppressed with pytest.raises((ValueError, TypeError)): session_event_from_dict(malformed_event) + + def test_explicit_generated_symbols_remain_available(self): + """Explicit generated helper symbols should remain importable.""" + assert ElicitationCompletedAction.ACCEPT.value == "accept" + assert UserMessageAgentMode.INTERACTIVE.value == "interactive" + assert ElicitationRequestedMode.FORM.value == "form" + assert UserMessageAttachmentGithubReferenceType.PR.value == "pr" + + schema = ElicitationRequestedSchema( + properties={"answer": {"type": "string"}}, type="object" + ) + assert schema.to_dict()["type"] == "object" + + def test_data_shim_preserves_raw_mapping_values(self): + """Compatibility Data should keep arbitrary nested mappings as plain dicts.""" + parsed = Data.from_dict( + { + "arguments": {"toolCallId": "call-1"}, + "input": {"step_name": "build"}, + } + ) + assert parsed.arguments == {"toolCallId": "call-1"} + assert isinstance(parsed.arguments, dict) + assert parsed.input == {"step_name": "build"} + assert isinstance(parsed.input, dict) + + constructed = Data(arguments={"tool_call_id": "call-1"}) + assert constructed.to_dict() == {"arguments": {"tool_call_id": "call-1"}} + + def test_schema_defaults_are_applied_for_missing_optional_fields(self): + """Generated event models should honor primitive schema defaults during parsing.""" + request = PermissionRequest.from_dict({"kind": "memory", "fact": "remember this"}) + assert request.action == PermissionRequestMemoryAction.STORE + + task_complete = SessionTaskCompleteData.from_dict({"success": True}) + assert task_complete.summary == "" diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index f22a83ff9..c1a80aa06 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -7,8 +7,9 @@ */ import fs from "fs/promises"; +import path from "path"; import type { JSONSchema7 } from "json-schema"; -import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from "quicktype-core"; +import { fileURLToPath } from "url"; import { cloneSchemaForCodegen, getApiSchemaPath, @@ -18,9 +19,9 @@ import { isObjectSchema, isVoidSchema, isRpcMethod, + isNodeFullyExperimental, postProcessSchema, writeGeneratedFile, - isNodeFullyExperimental, type ApiSchema, type RpcMethod, } from "./utils.js"; @@ -78,6 +79,14 @@ function splitTopLevelCommas(s: string): string[] { return parts; } +function pyDocstringLiteral(text: string): string { + const normalized = text + .split(/\r?\n/) + .map((line) => line.replace(/\s+$/g, "")) + .join("\n"); + return JSON.stringify(normalized); +} + function modernizePython(code: string): string { // Replace Optional[X] with X | None (handles arbitrarily nested brackets) code = replaceBalancedBrackets(code, "Optional", (inner) => `${inner} | None`); @@ -210,66 +219,1042 @@ function pythonParamsTypeName(method: RpcMethod): string { } // ── Session Events ────────────────────────────────────────────────────────── +// ── Session Events (custom codegen — dedicated per-event payload types) ───── -async function generateSessionEvents(schemaPath?: string): Promise { - console.log("Python: generating session-events..."); +interface PyEventVariant { + typeName: string; + dataClassName: string; + dataSchema: JSONSchema7; + dataDescription?: string; +} - const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7); - const resolvedSchema = (schema.definitions?.SessionEvent as JSONSchema7) || schema; - const processed = postProcessSchema(resolvedSchema); +interface PyResolvedType { + annotation: string; + fromExpr: (expr: string) => string; + toExpr: (expr: string) => string; +} - // Hoist titled inline schemas (enums etc.) to definitions so quicktype - // uses the schema-defined names instead of its own structural heuristics. - const { rootDefinitions: hoistedRoots, sharedDefinitions } = hoistTitledSchemas({ SessionEvent: processed }); - const hoisted = hoistedRoots.SessionEvent; - if (Object.keys(sharedDefinitions).length > 0) { - hoisted.definitions = { ...hoisted.definitions, ...sharedDefinitions }; +interface PyCodegenCtx { + classes: string[]; + enums: string[]; + enumsByName: Map; + generatedNames: Set; + usesTimedelta: boolean; + usesIntegerTimedelta: boolean; +} + +function toEnumMemberName(value: string): string { + const cleaned = value + .replace(/([a-z])([A-Z])/g, "$1_$2") + .replace(/[^A-Za-z0-9]+/g, "_") + .replace(/^_+|_+$/g, "") + .toUpperCase(); + if (!cleaned) { + return "VALUE"; } + return /^[0-9]/.test(cleaned) ? `VALUE_${cleaned}` : cleaned; +} - const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - await schemaInput.addSource({ name: "SessionEvent", schema: JSON.stringify(hoisted) }); +function wrapParser(resolved: PyResolvedType, arg = "x"): string { + return `lambda ${arg}: ${resolved.fromExpr(arg)}`; +} - const inputData = new InputData(); - inputData.addInput(schemaInput); +function wrapSerializer(resolved: PyResolvedType, arg = "x"): string { + return `lambda ${arg}: ${resolved.toExpr(arg)}`; +} - const result = await quicktype({ - inputData, - lang: "python", - rendererOptions: { "python-version": "3.7" }, +const PY_SESSION_EVENT_TYPE_RENAMES: Record = { + AssistantMessageDataToolRequestsItem: "AssistantMessageToolRequest", + AssistantMessageDataToolRequestsItemType: "AssistantMessageToolRequestType", + AssistantUsageDataCopilotUsage: "AssistantUsageCopilotUsage", + AssistantUsageDataCopilotUsageTokenDetailsItem: "AssistantUsageCopilotUsageTokenDetail", + AssistantUsageDataQuotaSnapshotsValue: "AssistantUsageQuotaSnapshot", + CapabilitiesChangedDataUi: "CapabilitiesChangedUI", + CommandsChangedDataCommandsItem: "CommandsChangedCommand", + ElicitationCompletedDataAction: "ElicitationCompletedAction", + ElicitationRequestedDataMode: "ElicitationRequestedMode", + ElicitationRequestedDataRequestedSchema: "ElicitationRequestedSchema", + McpOauthRequiredDataStaticClientConfig: "MCPOauthRequiredStaticClientConfig", + PermissionCompletedDataResultKind: "PermissionCompletedKind", + PermissionRequestedDataPermissionRequest: "PermissionRequest", + PermissionRequestedDataPermissionRequestAction: "PermissionRequestMemoryAction", + PermissionRequestedDataPermissionRequestCommandsItem: "PermissionRequestShellCommand", + PermissionRequestedDataPermissionRequestDirection: "PermissionRequestMemoryDirection", + PermissionRequestedDataPermissionRequestPossibleUrlsItem: "PermissionRequestShellPossibleURL", + SessionCompactionCompleteDataCompactionTokensUsed: "CompactionCompleteCompactionTokensUsed", + SessionCustomAgentsUpdatedDataAgentsItem: "CustomAgentsUpdatedAgent", + SessionExtensionsLoadedDataExtensionsItem: "ExtensionsLoadedExtension", + SessionExtensionsLoadedDataExtensionsItemSource: "ExtensionsLoadedExtensionSource", + SessionExtensionsLoadedDataExtensionsItemStatus: "ExtensionsLoadedExtensionStatus", + SessionHandoffDataRepository: "HandoffRepository", + SessionHandoffDataSourceType: "HandoffSourceType", + SessionMcpServersLoadedDataServersItem: "MCPServersLoadedServer", + SessionMcpServersLoadedDataServersItemStatus: "MCPServerStatus", + SessionShutdownDataCodeChanges: "ShutdownCodeChanges", + SessionShutdownDataModelMetricsValue: "ShutdownModelMetric", + SessionShutdownDataModelMetricsValueRequests: "ShutdownModelMetricRequests", + SessionShutdownDataModelMetricsValueUsage: "ShutdownModelMetricUsage", + SessionShutdownDataShutdownType: "ShutdownType", + SessionSkillsLoadedDataSkillsItem: "SkillsLoadedSkill", + UserMessageDataAgentMode: "UserMessageAgentMode", + UserMessageDataAttachmentsItem: "UserMessageAttachment", + UserMessageDataAttachmentsItemLineRange: "UserMessageAttachmentFileLineRange", + UserMessageDataAttachmentsItemReferenceType: "UserMessageAttachmentGithubReferenceType", + UserMessageDataAttachmentsItemSelection: "UserMessageAttachmentSelectionDetails", + UserMessageDataAttachmentsItemSelectionEnd: "UserMessageAttachmentSelectionDetailsEnd", + UserMessageDataAttachmentsItemSelectionStart: "UserMessageAttachmentSelectionDetailsStart", + UserMessageDataAttachmentsItemType: "UserMessageAttachmentType", +}; + +function postProcessPythonSessionEventCode(code: string): string { + for (const [from, to] of Object.entries(PY_SESSION_EVENT_TYPE_RENAMES).sort( + ([left], [right]) => right.length - left.length + )) { + code = code.replace(new RegExp(`\\b${from}\\b`, "g"), to); + } + return code; +} + +function pyPrimitiveResolvedType(annotation: string, fromFn: string, toFn = fromFn): PyResolvedType { + return { + annotation, + fromExpr: (expr) => `${fromFn}(${expr})`, + toExpr: (expr) => `${toFn}(${expr})`, + }; +} + +function pyOptionalResolvedType(inner: PyResolvedType): PyResolvedType { + return { + annotation: `${inner.annotation} | None`, + fromExpr: (expr) => `from_union([from_none, ${wrapParser(inner)}], ${expr})`, + toExpr: (expr) => `from_union([from_none, ${wrapSerializer(inner)}], ${expr})`, + }; +} + +function pyAnyResolvedType(): PyResolvedType { + return { + annotation: "Any", + fromExpr: (expr) => expr, + toExpr: (expr) => expr, + }; +} + +function pyDurationResolvedType(ctx: PyCodegenCtx, isInteger: boolean): PyResolvedType { + ctx.usesTimedelta = true; + if (isInteger) { + ctx.usesIntegerTimedelta = true; + } + return { + annotation: "timedelta", + fromExpr: (expr) => `from_timedelta(${expr})`, + toExpr: (expr) => (isInteger ? `to_timedelta_int(${expr})` : `to_timedelta(${expr})`), + }; +} + +function isPyBase64StringSchema(schema: JSONSchema7): boolean { + return schema.format === "byte" || (schema as Record).contentEncoding === "base64"; +} + +function toPythonLiteral(value: unknown): string | undefined { + if (typeof value === "string") { + return JSON.stringify(value); + } + if (typeof value === "number") { + return Number.isFinite(value) ? String(value) : undefined; + } + if (typeof value === "boolean") { + return value ? "True" : "False"; + } + if (value === null) { + return "None"; + } + return undefined; +} + +function extractPyEventVariants(schema: JSONSchema7): PyEventVariant[] { + const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + if (!sessionEvent?.anyOf) { + throw new Error("Schema must have SessionEvent definition with anyOf"); + } + + return (sessionEvent.anyOf as JSONSchema7[]) + .map((variant) => { + if (typeof variant !== "object" || !variant.properties) { + throw new Error("Invalid event variant"); + } + + const typeSchema = variant.properties.type as JSONSchema7; + const typeName = typeSchema?.const as string; + if (!typeName) { + throw new Error("Event variant must define type.const"); + } + + const dataSchema = (variant.properties.data as JSONSchema7) || {}; + return { + typeName, + dataClassName: `${toPascalCase(typeName)}Data`, + dataSchema, + dataDescription: dataSchema.description, + }; + }); +} + +function findPyDiscriminator( + variants: JSONSchema7[] +): { property: string; mapping: Map } | null { + if (variants.length === 0) { + return null; + } + + const firstVariant = variants[0]; + if (!firstVariant.properties) { + return null; + } + + for (const [propName, propSchema] of Object.entries(firstVariant.properties)) { + if (typeof propSchema !== "object") { + continue; + } + if ((propSchema as JSONSchema7).const === undefined) { + continue; + } + + const mapping = new Map(); + let valid = true; + for (const variant of variants) { + if (!variant.properties) { + valid = false; + break; + } + + const variantProp = variant.properties[propName]; + if (typeof variantProp !== "object" || (variantProp as JSONSchema7).const === undefined) { + valid = false; + break; + } + + mapping.set(String((variantProp as JSONSchema7).const), variant); + } + + if (valid && mapping.size === variants.length) { + return { property: propName, mapping }; + } + } + + return null; +} + +function getOrCreatePyEnum( + enumName: string, + values: string[], + ctx: PyCodegenCtx, + description?: string +): string { + const existing = ctx.enumsByName.get(enumName); + if (existing) { + return existing; + } + + const lines: string[] = []; + if (description) { + lines.push(`class ${enumName}(Enum):`); + lines.push(` ${pyDocstringLiteral(description)}`); + } else { + lines.push(`class ${enumName}(Enum):`); + } + for (const value of values) { + lines.push(` ${toEnumMemberName(value)} = ${JSON.stringify(value)}`); + } + ctx.enumsByName.set(enumName, enumName); + ctx.enums.push(lines.join("\n")); + return enumName; +} + +function resolvePyPropertyType( + propSchema: JSONSchema7, + parentTypeName: string, + jsonPropName: string, + isRequired: boolean, + ctx: PyCodegenCtx +): PyResolvedType { + const nestedName = parentTypeName + toPascalCase(jsonPropName); + + if (propSchema.allOf && propSchema.allOf.length === 1 && typeof propSchema.allOf[0] === "object") { + return resolvePyPropertyType( + propSchema.allOf[0] as JSONSchema7, + parentTypeName, + jsonPropName, + isRequired, + ctx + ); + } + + if (propSchema.anyOf) { + const variants = (propSchema.anyOf as JSONSchema7[]).filter((item) => typeof item === "object"); + const nonNull = variants.filter((item) => item.type !== "null"); + const hasNull = variants.length !== nonNull.length; + + if (nonNull.length === 1) { + const inner = resolvePyPropertyType(nonNull[0], parentTypeName, jsonPropName, true, ctx); + return hasNull || !isRequired ? pyOptionalResolvedType(inner) : inner; + } + + if (nonNull.length > 1) { + const discriminator = findPyDiscriminator(nonNull); + if (discriminator) { + emitPyFlatDiscriminatedUnion( + nestedName, + discriminator.property, + discriminator.mapping, + ctx, + propSchema.description + ); + const resolved: PyResolvedType = { + annotation: nestedName, + fromExpr: (expr) => `${nestedName}.from_dict(${expr})`, + toExpr: (expr) => `to_class(${nestedName}, ${expr})`, + }; + return hasNull || !isRequired ? pyOptionalResolvedType(resolved) : resolved; + } + + return pyAnyResolvedType(); + } + } + + if (propSchema.enum && Array.isArray(propSchema.enum) && propSchema.enum.every((value) => typeof value === "string")) { + const enumType = getOrCreatePyEnum( + nestedName, + propSchema.enum as string[], + ctx, + propSchema.description + ); + const resolved: PyResolvedType = { + annotation: enumType, + fromExpr: (expr) => `parse_enum(${enumType}, ${expr})`, + toExpr: (expr) => `to_enum(${enumType}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (propSchema.const !== undefined) { + if (typeof propSchema.const === "string") { + const resolved = pyPrimitiveResolvedType("str", "from_str"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (typeof propSchema.const === "boolean") { + const resolved = pyPrimitiveResolvedType("bool", "from_bool"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (typeof propSchema.const === "number") { + const resolved = Number.isInteger(propSchema.const) + ? pyPrimitiveResolvedType("int", "from_int", "to_int") + : pyPrimitiveResolvedType("float", "from_float", "to_float"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + } + + const type = propSchema.type; + const format = propSchema.format; + + if (Array.isArray(type)) { + const nonNullTypes = type.filter((value) => value !== "null"); + if (nonNullTypes.length === 1) { + const inner = resolvePyPropertyType( + { ...propSchema, type: nonNullTypes[0] as JSONSchema7["type"] }, + parentTypeName, + jsonPropName, + true, + ctx + ); + return pyOptionalResolvedType(inner); + } + } + + if (type === "string") { + if (format === "date-time") { + const resolved = pyPrimitiveResolvedType("datetime", "from_datetime", "to_datetime"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (format === "uuid") { + const resolved = pyPrimitiveResolvedType("UUID", "from_uuid", "to_uuid"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + if (format === "uri" || format === "regex" || isPyBase64StringSchema(propSchema)) { + const resolved = pyPrimitiveResolvedType("str", "from_str"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + const resolved = pyPrimitiveResolvedType("str", "from_str"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "integer") { + if (format === "duration") { + const resolved = pyDurationResolvedType(ctx, true); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + const resolved = pyPrimitiveResolvedType("int", "from_int", "to_int"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "number") { + if (format === "duration") { + const resolved = pyDurationResolvedType(ctx, false); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + const resolved = pyPrimitiveResolvedType("float", "from_float", "to_float"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "boolean") { + const resolved = pyPrimitiveResolvedType("bool", "from_bool"); + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "array") { + const items = propSchema.items as JSONSchema7 | undefined; + if (!items) { + const resolved: PyResolvedType = { + annotation: "list[Any]", + fromExpr: (expr) => `from_list(lambda x: x, ${expr})`, + toExpr: (expr) => `from_list(lambda x: x, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (items.allOf && items.allOf.length === 1 && typeof items.allOf[0] === "object") { + return resolvePyPropertyType( + { ...propSchema, items: items.allOf[0] as JSONSchema7 }, + parentTypeName, + jsonPropName, + isRequired, + ctx + ); + } + + if (items.anyOf) { + const itemVariants = (items.anyOf as JSONSchema7[]) + .filter((variant) => typeof variant === "object") + .filter((variant) => variant.type !== "null"); + const discriminator = findPyDiscriminator(itemVariants); + if (discriminator) { + const itemTypeName = nestedName + "Item"; + emitPyFlatDiscriminatedUnion( + itemTypeName, + discriminator.property, + discriminator.mapping, + ctx, + items.description + ); + const resolved: PyResolvedType = { + annotation: `list[${itemTypeName}]`, + fromExpr: (expr) => `from_list(${itemTypeName}.from_dict, ${expr})`, + toExpr: (expr) => `from_list(lambda x: to_class(${itemTypeName}, x), ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + } + + const itemType = resolvePyPropertyType(items, parentTypeName, jsonPropName + "Item", true, ctx); + const resolved: PyResolvedType = { + annotation: `list[${itemType.annotation}]`, + fromExpr: (expr) => `from_list(${wrapParser(itemType)}, ${expr})`, + toExpr: (expr) => `from_list(${wrapSerializer(itemType)}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (type === "object" || (propSchema.properties && !type)) { + if (propSchema.properties) { + emitPyClass(nestedName, propSchema, ctx, propSchema.description); + const resolved: PyResolvedType = { + annotation: nestedName, + fromExpr: (expr) => `${nestedName}.from_dict(${expr})`, + toExpr: (expr) => `to_class(${nestedName}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + if (propSchema.additionalProperties) { + if ( + typeof propSchema.additionalProperties === "object" && + Object.keys(propSchema.additionalProperties as Record).length > 0 + ) { + const valueType = resolvePyPropertyType( + propSchema.additionalProperties as JSONSchema7, + parentTypeName, + jsonPropName + "Value", + true, + ctx + ); + const resolved: PyResolvedType = { + annotation: `dict[str, ${valueType.annotation}]`, + fromExpr: (expr) => `from_dict(${wrapParser(valueType)}, ${expr})`, + toExpr: (expr) => `from_dict(${wrapSerializer(valueType)}, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + const resolved: PyResolvedType = { + annotation: "dict[str, Any]", + fromExpr: (expr) => `from_dict(lambda x: x, ${expr})`, + toExpr: (expr) => `from_dict(lambda x: x, ${expr})`, + }; + return isRequired ? resolved : pyOptionalResolvedType(resolved); + } + + return pyAnyResolvedType(); + } + + return pyAnyResolvedType(); +} + +function emitPyClass( + typeName: string, + schema: JSONSchema7, + ctx: PyCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) { + return; + } + ctx.generatedNames.add(typeName); + + const required = new Set(schema.required || []); + const fieldEntries = Object.entries(schema.properties || {}).filter( + ([, value]) => typeof value === "object" + ) as Array<[string, JSONSchema7]>; + const orderedFieldEntries = [ + ...fieldEntries.filter(([name]) => required.has(name)), + ...fieldEntries.filter(([name]) => !required.has(name)), + ]; + + const fieldInfos = orderedFieldEntries.map(([propName, propSchema]) => { + const isRequired = required.has(propName); + const resolved = resolvePyPropertyType(propSchema, typeName, propName, isRequired, ctx); + return { + jsonName: propName, + fieldName: toSnakeCase(propName), + isRequired, + resolved, + defaultLiteral: isRequired ? undefined : toPythonLiteral(propSchema.default), + }; }); - let code = result.lines.join("\n"); + const lines: string[] = []; + lines.push(`@dataclass`); + lines.push(`class ${typeName}:`); + if (description || schema.description) { + lines.push(` ${pyDocstringLiteral(description || schema.description || "")}`); + } - // Fix dataclass field ordering (Any fields need defaults) - code = code.replace(/: Any$/gm, ": Any = None"); - // Fix bare except: to use Exception (required by ruff/pylint) - code = code.replace(/except:/g, "except Exception:"); - // Modernize to Python 3.11+ syntax - code = modernizePython(code); + if (fieldInfos.length === 0) { + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + lines.push(` return ${typeName}()`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` return {}`); + ctx.classes.push(lines.join("\n")); + return; + } - // Add UNKNOWN enum value for forward compatibility - code = code.replace( - /^(class SessionEventType\(Enum\):.*?)(^\s*\n@dataclass)/ms, - `$1 # UNKNOWN is used for forward compatibility - UNKNOWN = "unknown" + for (const field of fieldInfos) { + const suffix = field.isRequired ? "" : " = None"; + lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); + } - @classmethod - def _missing_(cls, value: object) -> "SessionEventType": - """Handle unknown event types gracefully for forward compatibility.""" - return cls.UNKNOWN + lines.push(``); + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + for (const field of fieldInfos) { + const sourceExpr = field.defaultLiteral + ? `obj.get(${JSON.stringify(field.jsonName)}, ${field.defaultLiteral})` + : `obj.get(${JSON.stringify(field.jsonName)})`; + lines.push( + ` ${field.fieldName} = ${field.resolved.fromExpr(sourceExpr)}` + ); + } + lines.push(` return ${typeName}(`); + for (const field of fieldInfos) { + lines.push(` ${field.fieldName}=${field.fieldName},`); + } + lines.push(` )`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` result: dict = {}`); + for (const field of fieldInfos) { + const valueExpr = field.resolved.toExpr(`self.${field.fieldName}`); + if (field.isRequired) { + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } else { + lines.push(` if self.${field.fieldName} is not None:`); + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } + } + lines.push(` return result`); + + ctx.classes.push(lines.join("\n")); +} + +function emitPyFlatDiscriminatedUnion( + typeName: string, + discriminatorProp: string, + mapping: Map, + ctx: PyCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) { + return; + } + ctx.generatedNames.add(typeName); + + const allProps = new Map(); + for (const [, variant] of mapping) { + const required = new Set(variant.required || []); + for (const [propName, propSchema] of Object.entries(variant.properties || {})) { + if (typeof propSchema !== "object") { + continue; + } + if (!allProps.has(propName)) { + allProps.set(propName, { + schema: propSchema as JSONSchema7, + requiredInAll: required.has(propName), + }); + } else if (!required.has(propName)) { + allProps.get(propName)!.requiredInAll = false; + } + } + } -$2` + const variantCount = mapping.size; + for (const [propName, info] of allProps) { + let presentCount = 0; + for (const [, variant] of mapping) { + if (variant.properties && propName in variant.properties) { + presentCount++; + } + } + if (presentCount < variantCount) { + info.requiredInAll = false; + } + } + + const discriminatorEnumName = getOrCreatePyEnum( + typeName + toPascalCase(discriminatorProp), + [...mapping.keys()], + ctx, + description ? `${description} discriminator` : `${typeName} discriminator` ); - const banner = `""" -AUTO-GENERATED FILE - DO NOT EDIT -Generated from: session-events.schema.json -""" + const fieldEntries: Array<[string, JSONSchema7, boolean]> = [ + [ + discriminatorProp, + { + type: "string", + enum: [...mapping.keys()], + }, + true, + ], + ...[...allProps.entries()] + .filter(([propName]) => propName !== discriminatorProp) + .map(([propName, info]) => [propName, info.schema, info.requiredInAll] as [string, JSONSchema7, boolean]), + ]; + + const orderedFieldEntries = [ + ...fieldEntries.filter(([, , requiredInAll]) => requiredInAll), + ...fieldEntries.filter(([, , requiredInAll]) => !requiredInAll), + ]; + + const fieldInfos = orderedFieldEntries.map(([propName, propSchema, requiredInAll]) => { + let resolved: PyResolvedType; + if (propName === discriminatorProp) { + resolved = { + annotation: discriminatorEnumName, + fromExpr: (expr) => `parse_enum(${discriminatorEnumName}, ${expr})`, + toExpr: (expr) => `to_enum(${discriminatorEnumName}, ${expr})`, + }; + } else { + resolved = resolvePyPropertyType(propSchema, typeName, propName, requiredInAll, ctx); + } + + return { + jsonName: propName, + fieldName: toSnakeCase(propName), + isRequired: requiredInAll, + resolved, + defaultLiteral: requiredInAll ? undefined : toPythonLiteral(propSchema.default), + }; + }); -`; + const lines: string[] = []; + lines.push(`@dataclass`); + lines.push(`class ${typeName}:`); + if (description) { + lines.push(` ${pyDocstringLiteral(description)}`); + } + for (const field of fieldInfos) { + const suffix = field.isRequired ? "" : " = None"; + lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); + } + lines.push(``); + lines.push(` @staticmethod`); + lines.push(` def from_dict(obj: Any) -> "${typeName}":`); + lines.push(` assert isinstance(obj, dict)`); + for (const field of fieldInfos) { + const sourceExpr = field.defaultLiteral + ? `obj.get(${JSON.stringify(field.jsonName)}, ${field.defaultLiteral})` + : `obj.get(${JSON.stringify(field.jsonName)})`; + lines.push( + ` ${field.fieldName} = ${field.resolved.fromExpr(sourceExpr)}` + ); + } + lines.push(` return ${typeName}(`); + for (const field of fieldInfos) { + lines.push(` ${field.fieldName}=${field.fieldName},`); + } + lines.push(` )`); + lines.push(``); + lines.push(` def to_dict(self) -> dict:`); + lines.push(` result: dict = {}`); + for (const field of fieldInfos) { + const valueExpr = field.resolved.toExpr(`self.${field.fieldName}`); + if (field.isRequired) { + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } else { + lines.push(` if self.${field.fieldName} is not None:`); + lines.push(` result[${JSON.stringify(field.jsonName)}] = ${valueExpr}`); + } + } + lines.push(` return result`); + + ctx.classes.push(lines.join("\n")); +} - const outPath = await writeGeneratedFile("python/copilot/generated/session_events.py", banner + code); +export function generatePythonSessionEventsCode(schema: JSONSchema7): string { + const variants = extractPyEventVariants(schema); + const ctx: PyCodegenCtx = { + classes: [], + enums: [], + enumsByName: new Map(), + generatedNames: new Set(), + usesTimedelta: false, + usesIntegerTimedelta: false, + }; + + for (const variant of variants) { + emitPyClass(variant.dataClassName, variant.dataSchema, ctx, variant.dataDescription); + } + + const eventTypeLines: string[] = []; + eventTypeLines.push(`class SessionEventType(Enum):`); + for (const variant of variants) { + eventTypeLines.push(` ${toEnumMemberName(variant.typeName)} = ${JSON.stringify(variant.typeName)}`); + } + eventTypeLines.push(` UNKNOWN = "unknown"`); + eventTypeLines.push(``); + eventTypeLines.push(` @classmethod`); + eventTypeLines.push(` def _missing_(cls, value: object) -> "SessionEventType":`); + eventTypeLines.push(` return cls.UNKNOWN`); + + const out: string[] = []; + out.push(`"""`); + out.push(`AUTO-GENERATED FILE - DO NOT EDIT`); + out.push(`Generated from: session-events.schema.json`); + out.push(`"""`); + out.push(``); + out.push(`from __future__ import annotations`); + out.push(``); + out.push(`from collections.abc import Callable`); + out.push(`from dataclasses import dataclass`); + out.push(ctx.usesTimedelta ? `from datetime import datetime, timedelta` : `from datetime import datetime`); + out.push(`from enum import Enum`); + out.push(`from typing import Any, TypeVar, cast`); + out.push(`from uuid import UUID`); + out.push(``); + out.push(`import dateutil.parser`); + out.push(``); + out.push(`T = TypeVar("T")`); + out.push(`EnumT = TypeVar("EnumT", bound=Enum)`); + out.push(``); + out.push(``); + out.push(`def from_str(x: Any) -> str:`); + out.push(` assert isinstance(x, str)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_int(x: Any) -> int:`); + out.push(` assert isinstance(x, int) and not isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def to_int(x: Any) -> int:`); + out.push(` assert isinstance(x, int) and not isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_float(x: Any) -> float:`); + out.push(` assert isinstance(x, (float, int)) and not isinstance(x, bool)`); + out.push(` return float(x)`); + out.push(``); + out.push(``); + out.push(`def to_float(x: Any) -> float:`); + out.push(` assert isinstance(x, (float, int)) and not isinstance(x, bool)`); + out.push(` return float(x)`); + out.push(``); + out.push(``); + if (ctx.usesTimedelta) { + out.push(`def from_timedelta(x: Any) -> timedelta:`); + out.push(` assert isinstance(x, (float, int)) and not isinstance(x, bool)`); + out.push(` return timedelta(milliseconds=float(x))`); + out.push(``); + out.push(``); + if (ctx.usesIntegerTimedelta) { + out.push(`def to_timedelta_int(x: timedelta) -> int:`); + out.push(` assert isinstance(x, timedelta)`); + out.push(` milliseconds = x.total_seconds() * 1000.0`); + out.push(` assert milliseconds.is_integer()`); + out.push(` return int(milliseconds)`); + out.push(``); + out.push(``); + } + out.push(`def to_timedelta(x: timedelta) -> float:`); + out.push(` assert isinstance(x, timedelta)`); + out.push(` return x.total_seconds() * 1000.0`); + out.push(``); + out.push(``); + } + out.push(`def from_bool(x: Any) -> bool:`); + out.push(` assert isinstance(x, bool)`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_none(x: Any) -> Any:`); + out.push(` assert x is None`); + out.push(` return x`); + out.push(``); + out.push(``); + out.push(`def from_union(fs: list[Callable[[Any], T]], x: Any) -> T:`); + out.push(` for f in fs:`); + out.push(` try:`); + out.push(` return f(x)`); + out.push(` except Exception:`); + out.push(` pass`); + out.push(` assert False`); + out.push(``); + out.push(``); + out.push(`def from_list(f: Callable[[Any], T], x: Any) -> list[T]:`); + out.push(` assert isinstance(x, list)`); + out.push(` return [f(item) for item in x]`); + out.push(``); + out.push(``); + out.push(`def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]:`); + out.push(` assert isinstance(x, dict)`); + out.push(` return {key: f(value) for key, value in x.items()}`); + out.push(``); + out.push(``); + out.push(`def from_datetime(x: Any) -> datetime:`); + out.push(` return dateutil.parser.parse(from_str(x))`); + out.push(``); + out.push(``); + out.push(`def to_datetime(x: datetime) -> str:`); + out.push(` return x.isoformat()`); + out.push(``); + out.push(``); + out.push(`def from_uuid(x: Any) -> UUID:`); + out.push(` return UUID(from_str(x))`); + out.push(``); + out.push(``); + out.push(`def to_uuid(x: UUID) -> str:`); + out.push(` return str(x)`); + out.push(``); + out.push(``); + out.push(`def parse_enum(c: type[EnumT], x: Any) -> EnumT:`); + out.push(` assert isinstance(x, str)`); + out.push(` return c(x)`); + out.push(``); + out.push(``); + out.push(`def to_class(c: type[T], x: Any) -> dict:`); + out.push(` assert isinstance(x, c)`); + out.push(` return cast(Any, x).to_dict()`); + out.push(``); + out.push(``); + out.push(`def to_enum(c: type[EnumT], x: Any) -> str:`); + out.push(` assert isinstance(x, c)`); + out.push(` return cast(str, x.value)`); + out.push(``); + out.push(``); + out.push(eventTypeLines.join("\n")); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class RawSessionEventData:`); + out.push(` raw: Any`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "RawSessionEventData":`); + out.push(` return RawSessionEventData(obj)`); + out.push(``); + out.push(` def to_dict(self) -> Any:`); + out.push(` return self.raw`); + out.push(``); + out.push(``); + out.push(`def _compat_to_python_key(name: str) -> str:`); + out.push(` normalized = name.replace(".", "_")`); + out.push(` result: list[str] = []`); + out.push(` for index, char in enumerate(normalized):`); + out.push( + ` if char.isupper() and index > 0 and (not normalized[index - 1].isupper() or (index + 1 < len(normalized) and normalized[index + 1].islower())):` + ); + out.push(` result.append("_")`); + out.push(` result.append(char.lower())`); + out.push(` return "".join(result)`); + out.push(``); + out.push(``); + out.push(`def _compat_to_json_key(name: str) -> str:`); + out.push(` parts = name.split("_")`); + out.push(` if not parts:`); + out.push(` return name`); + out.push(` return parts[0] + "".join(part[:1].upper() + part[1:] for part in parts[1:])`); + out.push(``); + out.push(``); + out.push(`def _compat_to_json_value(value: Any) -> Any:`); + out.push(` if hasattr(value, "to_dict"):`); + out.push(` return cast(Any, value).to_dict()`); + out.push(` if isinstance(value, Enum):`); + out.push(` return value.value`); + out.push(` if isinstance(value, datetime):`); + out.push(` return value.isoformat()`); + if (ctx.usesTimedelta) { + out.push(` if isinstance(value, timedelta):`); + out.push(` return value.total_seconds() * 1000.0`); + } + out.push(` if isinstance(value, UUID):`); + out.push(` return str(value)`); + out.push(` if isinstance(value, list):`); + out.push(` return [_compat_to_json_value(item) for item in value]`); + out.push(` if isinstance(value, dict):`); + out.push(` return {key: _compat_to_json_value(item) for key, item in value.items()}`); + out.push(` return value`); + out.push(``); + out.push(``); + out.push(`def _compat_from_json_value(value: Any) -> Any:`); + out.push(` return value`); + out.push(``); + out.push(``); + out.push(`class Data:`); + out.push(` """Backward-compatible shim for manually constructed event payloads."""`); + out.push(``); + out.push(` def __init__(self, **kwargs: Any):`); + out.push(` self._values = {key: _compat_from_json_value(value) for key, value in kwargs.items()}`); + out.push(` for key, value in self._values.items():`); + out.push(` setattr(self, key, value)`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "Data":`); + out.push(` assert isinstance(obj, dict)`); + out.push( + ` return Data(**{_compat_to_python_key(key): _compat_from_json_value(value) for key, value in obj.items()})` + ); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push( + ` return {_compat_to_json_key(key): _compat_to_json_value(value) for key, value in self._values.items() if value is not None}` + ); + out.push(``); + out.push(``); + for (const classDef of ctx.classes) { + out.push(classDef); + out.push(``); + out.push(``); + } + for (const enumDef of ctx.enums) { + out.push(enumDef); + out.push(``); + out.push(``); + } + + const sessionEventDataTypes = [ + ...variants.map((variant) => variant.dataClassName), + "RawSessionEventData", + "Data", + ]; + out.push(`SessionEventData = ${sessionEventDataTypes.join(" | ")}`); + out.push(``); + out.push(``); + out.push(`@dataclass`); + out.push(`class SessionEvent:`); + out.push(` data: SessionEventData`); + out.push(` id: UUID`); + out.push(` timestamp: datetime`); + out.push(` type: SessionEventType`); + out.push(` ephemeral: bool | None = None`); + out.push(` parent_id: UUID | None = None`); + out.push(` raw_type: str | None = None`); + out.push(``); + out.push(` @staticmethod`); + out.push(` def from_dict(obj: Any) -> "SessionEvent":`); + out.push(` assert isinstance(obj, dict)`); + out.push(` raw_type = from_str(obj.get("type"))`); + out.push(` event_type = SessionEventType(raw_type)`); + out.push(` event_id = from_uuid(obj.get("id"))`); + out.push(` timestamp = from_datetime(obj.get("timestamp"))`); + out.push(` ephemeral = from_union([from_bool, from_none], obj.get("ephemeral"))`); + out.push(` parent_id = from_union([from_none, from_uuid], obj.get("parentId"))`); + out.push(` data_obj = obj.get("data")`); + out.push(` match event_type:`); + for (const variant of variants) { + out.push( + ` case SessionEventType.${toEnumMemberName(variant.typeName)}: data = ${variant.dataClassName}.from_dict(data_obj)` + ); + } + out.push(` case _: data = RawSessionEventData.from_dict(data_obj)`); + out.push(` return SessionEvent(`); + out.push(` data=data,`); + out.push(` id=event_id,`); + out.push(` timestamp=timestamp,`); + out.push(` type=event_type,`); + out.push(` ephemeral=ephemeral,`); + out.push(` parent_id=parent_id,`); + out.push(` raw_type=raw_type if event_type == SessionEventType.UNKNOWN else None,`); + out.push(` )`); + out.push(``); + out.push(` def to_dict(self) -> dict:`); + out.push(` result: dict = {}`); + out.push(` result["data"] = self.data.to_dict()`); + out.push(` result["id"] = to_uuid(self.id)`); + out.push(` result["timestamp"] = to_datetime(self.timestamp)`); + out.push( + ` result["type"] = self.raw_type if self.type == SessionEventType.UNKNOWN and self.raw_type is not None else to_enum(SessionEventType, self.type)` + ); + out.push(` if self.ephemeral is not None:`); + out.push(` result["ephemeral"] = from_bool(self.ephemeral)`); + out.push(` result["parentId"] = from_union([from_none, to_uuid], self.parent_id)`); + out.push(` return result`); + out.push(``); + out.push(``); + out.push(`def session_event_from_dict(s: Any) -> SessionEvent:`); + out.push(` return SessionEvent.from_dict(s)`); + out.push(``); + out.push(``); + out.push(`def session_event_to_dict(x: SessionEvent) -> Any:`); + out.push(` return x.to_dict()`); + out.push(``); + out.push(``); + + return postProcessPythonSessionEventCode(out.join("\n")); +} + +async function generateSessionEvents(schemaPath?: string): Promise { + console.log("Python: generating session-events..."); + + const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); + const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const processed = postProcessSchema(schema); + const code = generatePythonSessionEventsCode(processed); + + const outPath = await writeGeneratedFile("python/copilot/generated/session_events.py", code); console.log(` ✓ ${outPath}`); } @@ -277,6 +1262,7 @@ Generated from: session-events.schema.json async function generateRpc(schemaPath?: string): Promise { console.log("Python: generating RPC types..."); + const { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } = await import("quicktype-core"); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); @@ -695,9 +1681,13 @@ async function generate(sessionSchemaPath?: string, apiSchemaPath?: string): Pro } } -const sessionArg = process.argv[2] || undefined; -const apiArg = process.argv[3] || undefined; -generate(sessionArg, apiArg).catch((err) => { - console.error("Python generation failed:", err); - process.exit(1); -}); +const __filename = fileURLToPath(import.meta.url); + +if (process.argv[1] && path.resolve(process.argv[1]) === __filename) { + const sessionArg = process.argv[2] || undefined; + const apiArg = process.argv[3] || undefined; + generate(sessionArg, apiArg).catch((err) => { + console.error("Python generation failed:", err); + process.exit(1); + }); +} From 972b66399dee72d69843d7055ba238f4c2389c57 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 14 Apr 2026 16:28:27 -0400 Subject: [PATCH 15/34] Add $ref support to all four language code generators (#1062) * Add $ref support to all four language code generators Enable JSON Schema $ref for type deduplication across all SDK code generators (TypeScript, Python, Go, C#). Changes: - utils.ts: Add resolveRef(), refTypeName(), collectDefinitions() helpers; normalize $defs to definitions in postProcessSchema - typescript.ts: Build combined schema with shared definitions and compile once via unreachableDefinitions, instead of per-method compilation - python.ts/go.ts: Include all definitions alongside SessionEvent for quicktype resolution; include shared API defs in RPC combined schema - csharp.ts: Add handling to resolveSessionPropertyType and resolveRpcType; generate classes for referenced types on demand Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align rebased ref generator changes Keep the rebased $ref generator follow-up aligned with the latest C# typing changes and clean up the Python/TypeScript generator adjustments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Handle mixed schema definitions in codegen Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refresh $ref codegen for latest schema Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix ref codegen review issues Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/generated/rpc.ts | 385 +++++++--------------------------- scripts/codegen/csharp.ts | 277 +++++++++++++++--------- scripts/codegen/go.ts | 224 +++++++++++++++----- scripts/codegen/python.ts | 195 +++++++++++++---- scripts/codegen/typescript.ts | 283 ++++++++++++++++++++++--- scripts/codegen/utils.ts | 328 ++++++++++++++++++++++++++++- 6 files changed, 1162 insertions(+), 530 deletions(-) diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index d8d4cceca..8214dec4e 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,6 +5,84 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; +/** + * The agent mode. Valid values: "interactive", "plan", "autopilot". + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionMode". + */ +export type SessionMode = "interactive" | "plan" | "autopilot"; +/** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + */ +export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; +export type UIElicitationFieldValue = string | number | boolean | string[]; +export type PermissionDecision = + | { + /** + * The permission request was approved + */ + kind: "approved"; + } + | { + /** + * Denied because approval rules explicitly blocked it + */ + kind: "denied-by-rules"; + /** + * Rules that denied the request + */ + rules: unknown[]; + } + | { + /** + * Denied because no approval rule matched and user confirmation was unavailable + */ + kind: "denied-no-approval-rule-and-could-not-request-from-user"; + } + | { + /** + * Denied by the user during an interactive prompt + */ + kind: "denied-interactively-by-user"; + /** + * Optional feedback from the user explaining the denial + */ + feedback?: string; + } + | { + /** + * Denied by the organization's content exclusion policy + */ + kind: "denied-by-content-exclusion-policy"; + /** + * File path that triggered the exclusion + */ + path: string; + /** + * Human-readable explanation of why the path was excluded + */ + message: string; + } + | { + /** + * Denied by a permission request hook registered by an extension or plugin + */ + kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ + message?: string; + /** + * Whether to interrupt the current agent turn + */ + interrupt?: boolean; + }; +/** + * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + */ +export type SessionLogLevel = "info" | "warning" | "error"; + export interface PingResult { /** * Echoed message (or default greeting) @@ -457,13 +535,6 @@ export interface CurrentModel { modelId?: string; } -export interface SessionModelGetCurrentRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface ModelSwitchToResult { /** * Currently active model identifier after the switch @@ -472,10 +543,6 @@ export interface ModelSwitchToResult { } export interface ModelSwitchToRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Model identifier to switch to */ @@ -524,23 +591,7 @@ export interface ModelCapabilitiesOverride { }; } -/** - * The agent mode. Valid values: "interactive", "plan", "autopilot". - */ -export type SessionMode = "interactive" | "plan" | "autopilot"; - -export interface SessionModeGetRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface ModeSetRequest { - /** - * Target session identifier - */ - sessionId: string; mode: SessionMode; } @@ -559,31 +610,13 @@ export interface PlanReadResult { path: string | null; } -export interface SessionPlanReadRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface PlanUpdateRequest { - /** - * Target session identifier - */ - sessionId: string; /** * The new content for the plan file */ content: string; } -export interface SessionPlanDeleteRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface WorkspaceListFilesResult { /** * Relative file paths in the workspace files directory @@ -591,13 +624,6 @@ export interface WorkspaceListFilesResult { files: string[]; } -export interface SessionWorkspaceListFilesRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface WorkspaceReadFileResult { /** * File content as a UTF-8 string @@ -606,10 +632,6 @@ export interface WorkspaceReadFileResult { } export interface WorkspaceReadFileRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Relative path within the workspace files directory */ @@ -617,10 +639,6 @@ export interface WorkspaceReadFileRequest { } export interface WorkspaceCreateFileRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Relative path within the workspace files directory */ @@ -641,10 +659,6 @@ export interface FleetStartResult { /** @experimental */ export interface FleetStartRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Optional user prompt to combine with fleet instructions */ @@ -672,14 +686,6 @@ export interface AgentList { }[]; } -/** @experimental */ -export interface SessionAgentListRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface AgentGetCurrentResult { /** @@ -701,14 +707,6 @@ export interface AgentGetCurrentResult { } | null; } -/** @experimental */ -export interface SessionAgentGetCurrentRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface AgentSelectResult { /** @@ -732,24 +730,12 @@ export interface AgentSelectResult { /** @experimental */ export interface AgentSelectRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Name of the custom agent to select */ name: string; } -/** @experimental */ -export interface SessionAgentDeselectRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface AgentReloadResult { /** @@ -771,14 +757,6 @@ export interface AgentReloadResult { }[]; } -/** @experimental */ -export interface SessionAgentReloadRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface SkillList { /** @@ -812,20 +790,8 @@ export interface SkillList { }[]; } -/** @experimental */ -export interface SessionSkillsListRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface SkillsEnableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Name of the skill to enable */ @@ -834,24 +800,12 @@ export interface SkillsEnableRequest { /** @experimental */ export interface SkillsDisableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Name of the skill to disable */ name: string; } -/** @experimental */ -export interface SessionSkillsReloadRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface McpServerList { /** @@ -877,20 +831,8 @@ export interface McpServerList { }[]; } -/** @experimental */ -export interface SessionMcpListRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface McpEnableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Name of the MCP server to enable */ @@ -899,24 +841,12 @@ export interface McpEnableRequest { /** @experimental */ export interface McpDisableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Name of the MCP server to disable */ serverName: string; } -/** @experimental */ -export interface SessionMcpReloadRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface PluginList { /** @@ -942,14 +872,6 @@ export interface PluginList { }[]; } -/** @experimental */ -export interface SessionPluginsListRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface ExtensionList { /** @@ -979,20 +901,8 @@ export interface ExtensionList { }[]; } -/** @experimental */ -export interface SessionExtensionsListRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface ExtensionsEnableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Source-qualified extension ID to enable */ @@ -1001,24 +911,12 @@ export interface ExtensionsEnableRequest { /** @experimental */ export interface ExtensionsDisableRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Source-qualified extension ID to disable */ id: string; } -/** @experimental */ -export interface SessionExtensionsReloadRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface HandleToolCallResult { /** * Whether the tool call result was handled successfully @@ -1027,10 +925,6 @@ export interface HandleToolCallResult { } export interface ToolsHandlePendingToolCallRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Request ID of the pending tool call */ @@ -1073,10 +967,6 @@ export interface CommandsHandlePendingCommandResult { } export interface CommandsHandlePendingCommandRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Request ID from the command invocation event */ @@ -1086,14 +976,11 @@ export interface CommandsHandlePendingCommandRequest { */ error?: string; } - -/** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ -export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; -export type UIElicitationFieldValue = string | number | boolean | string[]; /** * The elicitation response (accept with form values, decline, or cancel) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponse". */ export interface UIElicitationResponse { action: UIElicitationResponseAction; @@ -1107,10 +994,6 @@ export interface UIElicitationResponseContent { } export interface UIElicitationRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Message describing what information is needed from the user */ @@ -1206,10 +1089,6 @@ export interface UIElicitationResult { } export interface UIHandlePendingElicitationRequest { - /** - * Target session identifier - */ - sessionId: string; /** * The unique request ID from the elicitation.requested event */ @@ -1224,73 +1103,7 @@ export interface PermissionRequestResult { success: boolean; } -export type PermissionDecision = - | { - /** - * The permission request was approved - */ - kind: "approved"; - } -| { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; - export interface PermissionDecisionRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Request ID of the pending permission request */ @@ -1305,15 +1118,7 @@ export interface LogResult { eventId: string; } -/** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - */ -export type SessionLogLevel = "info" | "warning" | "error"; export interface LogRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Human-readable message */ @@ -1337,10 +1142,6 @@ export interface ShellExecResult { } export interface ShellExecRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Shell command to execute */ @@ -1363,10 +1164,6 @@ export interface ShellKillResult { } export interface ShellKillRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Process identifier returned by shell.exec */ @@ -1422,14 +1219,6 @@ export interface HistoryCompactResult { }; } -/** @experimental */ -export interface SessionHistoryCompactRequest { - /** - * Target session identifier - */ - sessionId: string; -} - /** @experimental */ export interface HistoryTruncateResult { /** @@ -1440,10 +1229,6 @@ export interface HistoryTruncateResult { /** @experimental */ export interface HistoryTruncateRequest { - /** - * Target session identifier - */ - sessionId: string; /** * Event ID to truncate to. This event and all events after it are removed from the session. */ @@ -1544,14 +1329,6 @@ export interface UsageGetMetricsResult { lastCallOutputTokens: number; } -/** @experimental */ -export interface SessionUsageGetMetricsRequest { - /** - * Target session identifier - */ - sessionId: string; -} - export interface SessionFsReadFileResult { /** * File content as UTF-8 string diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 96da352e8..9e63b68ea 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -16,13 +16,20 @@ import { getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, + writeGeneratedFile, + collectDefinitionCollections, + postProcessSchema, + resolveRef, + resolveObjectSchema, + resolveSchema, + refTypeName, + isRpcMethod, isNodeFullyExperimental, isObjectSchema, isVoidSchema, - isRpcMethod, REPO_ROOT, - writeGeneratedFile, type ApiSchema, + type DefinitionCollections, type RpcMethod, } from "./utils.js"; @@ -301,6 +308,9 @@ interface EventVariant { let generatedEnums = new Map(); +/** Schema definitions available during session event generation (for $ref resolution). */ +let sessionDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; + function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string): string { const enumName = explicitName ?? `${parentClassName}${propName}`; const existing = generatedEnums.get(enumName); @@ -320,17 +330,27 @@ function getOrCreateEnum(parentClassName: string, propName: string, values: stri } function extractEventVariants(schema: JSONSchema7): EventVariant[] { - const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + const definitionCollections = collectDefinitionCollections(schema as Record); + const sessionEvent = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections); if (!sessionEvent?.anyOf) throw new Error("Schema must have SessionEvent definition with anyOf"); return sessionEvent.anyOf .map((variant) => { - if (typeof variant !== "object" || !variant.properties) throw new Error("Invalid variant"); - const typeSchema = variant.properties.type as JSONSchema7; + const resolvedVariant = + resolveObjectSchema(variant as JSONSchema7, definitionCollections) ?? + resolveSchema(variant as JSONSchema7, definitionCollections) ?? + (variant as JSONSchema7); + if (typeof resolvedVariant !== "object" || !resolvedVariant.properties) throw new Error("Invalid variant"); + const typeSchema = resolvedVariant.properties.type as JSONSchema7; const typeName = typeSchema?.const as string; if (!typeName) throw new Error("Variant must have type.const"); const baseName = typeToClassName(typeName); - const dataSchema = variant.properties.data as JSONSchema7; + const dataSchema = + resolveObjectSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + resolveSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + (resolvedVariant.properties.data as JSONSchema7); return { typeName, className: `${baseName}Event`, @@ -505,6 +525,28 @@ function resolveSessionPropertyType( nestedClasses: Map, enumOutput: string[] ): string { + // Handle $ref by resolving against schema definitions + if (propSchema.$ref) { + const className = typeToClassName(refTypeName(propSchema.$ref, sessionDefinitions)); + const refSchema = resolveRef(propSchema.$ref, sessionDefinitions); + if (!refSchema) { + return isRequired ? className : `${className}?`; + } + + if (refSchema.enum && Array.isArray(refSchema.enum)) { + const enumName = getOrCreateEnum(className, "", refSchema.enum as string[], enumOutput, refSchema.description); + return isRequired ? enumName : `${enumName}?`; + } + + if (refSchema.type === "object" && refSchema.properties) { + if (!nestedClasses.has(className)) { + nestedClasses.set(className, generateNestedClass(className, refSchema, knownTypes, nestedClasses, enumOutput)); + } + return isRequired ? className : `${className}?`; + } + + return resolveSessionPropertyType(refSchema, parentClassName, propName, isRequired, knownTypes, nestedClasses, enumOutput); + } if (propSchema.anyOf) { const hasNull = propSchema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null"); @@ -536,28 +578,15 @@ function resolveSessionPropertyType( } if (propSchema.type === "array" && propSchema.items) { const items = propSchema.items as JSONSchema7; - // Array of discriminated union (anyOf with shared discriminator) - if (items.anyOf && Array.isArray(items.anyOf)) { - const variants = items.anyOf.filter((v): v is JSONSchema7 => typeof v === "object"); - const discriminatorInfo = findDiscriminator(variants); - if (discriminatorInfo) { - const baseClassName = (items.title as string) ?? `${parentClassName}${propName}Item`; - const renamedBase = applyTypeRename(baseClassName); - const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, knownTypes, nestedClasses, enumOutput, items.description); - nestedClasses.set(renamedBase, polymorphicCode); - return isRequired ? `${renamedBase}[]` : `${renamedBase}[]?`; - } - } - if (items.type === "object" && items.properties) { - const itemClassName = (items.title as string) ?? `${parentClassName}${propName}Item`; - nestedClasses.set(itemClassName, generateNestedClass(itemClassName, items, knownTypes, nestedClasses, enumOutput)); - return isRequired ? `${itemClassName}[]` : `${itemClassName}[]?`; - } - if (items.enum && Array.isArray(items.enum)) { - const enumName = getOrCreateEnum(parentClassName, `${propName}Item`, items.enum as string[], enumOutput, items.description, items.title as string | undefined); - return isRequired ? `${enumName}[]` : `${enumName}[]?`; - } - const itemType = schemaTypeToCSharp(items, true, knownTypes); + const itemType = resolveSessionPropertyType( + items, + parentClassName, + `${propName}Item`, + true, + knownTypes, + nestedClasses, + enumOutput + ); return isRequired ? `${itemType}[]` : `${itemType}[]?`; } return schemaTypeToCSharp(propSchema, isRequired, knownTypes); @@ -596,14 +625,24 @@ function generateDataClass(variant: EventVariant, knownTypes: Map); const variants = extractEventVariants(schema); const knownTypes = new Map(); const nestedClasses = new Map(); const enumOutput: string[] = []; // Extract descriptions for base class properties from the first variant - const firstVariant = (schema.definitions?.SessionEvent as JSONSchema7)?.anyOf?.[0]; - const baseProps = typeof firstVariant === "object" && firstVariant?.properties ? firstVariant.properties : {}; + const sessionEventDefinition = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, sessionDefinitions) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, sessionDefinitions); + const firstVariant = + typeof sessionEventDefinition === "object" ? (sessionEventDefinition.anyOf?.[0] as JSONSchema7 | undefined) : undefined; + const resolvedFirstVariant = + resolveObjectSchema(firstVariant, sessionDefinitions) ?? + resolveSchema(firstVariant, sessionDefinitions) ?? + firstVariant; + const baseProps = + typeof resolvedFirstVariant === "object" && resolvedFirstVariant?.properties ? resolvedFirstVariant.properties : {}; const baseDesc = (name: string) => { const prop = baseProps[name]; return typeof prop === "object" ? (prop as JSONSchema7).description : undefined; @@ -692,7 +731,8 @@ export async function generateSessionEvents(schemaPath?: string): Promise console.log("C#: generating session-events..."); const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7); - const code = generateSessionEventsCode(schema); + const processed = postProcessSchema(schema); + const code = generateSessionEventsCode(processed); const outPath = await writeGeneratedFile("dotnet/src/Generated/SessionEvents.cs", code); console.log(` ✓ ${outPath}`); await formatCSharpFile(outPath); @@ -708,6 +748,9 @@ let experimentalRpcTypes = new Set(); let rpcKnownTypes = new Map(); let rpcEnumOutput: string[] = []; +/** Schema definitions available during RPC generation (for $ref resolution). */ +let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; + function singularPascal(s: string): string { const p = toPascalCase(s); if (p.endsWith("ies")) return `${p.slice(0, -3)}y`; @@ -716,12 +759,25 @@ function singularPascal(s: string): string { return p; } +function getMethodResultSchema(method: RpcMethod): JSONSchema7 | undefined { + return resolveSchema(method.result, rpcDefinitions) ?? method.result ?? undefined; +} + function resultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.result, `${typeToClassName(method.rpcMethod)}Result`); + return getRpcSchemaTypeName(getMethodResultSchema(method), `${typeToClassName(method.rpcMethod)}Result`); } function paramsTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.params, `${typeToClassName(method.rpcMethod)}Request`); + return getRpcSchemaTypeName(resolveMethodParamsSchema(method), `${typeToClassName(method.rpcMethod)}Request`); +} + +function resolveMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { + return ( + resolveObjectSchema(method.params, rpcDefinitions) ?? + resolveSchema(method.params, rpcDefinitions) ?? + method.params ?? + undefined + ); } function stableStringify(value: unknown): string { @@ -736,6 +792,27 @@ function stableStringify(value: unknown): string { } function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassName: string, propName: string, classes: string[]): string { + // Handle $ref by resolving against schema definitions and generating the referenced class + if (schema.$ref) { + const typeName = typeToClassName(refTypeName(schema.$ref, rpcDefinitions)); + const refSchema = resolveRef(schema.$ref, rpcDefinitions); + if (!refSchema) { + return isRequired ? typeName : `${typeName}?`; + } + + if (refSchema.enum && Array.isArray(refSchema.enum)) { + const enumName = getOrCreateEnum(typeName, "", refSchema.enum as string[], rpcEnumOutput, refSchema.description); + return isRequired ? enumName : `${enumName}?`; + } + + if (refSchema.type === "object" && refSchema.properties) { + const cls = emitRpcClass(typeName, refSchema, "public", classes); + if (cls) classes.push(cls); + return isRequired ? typeName : `${typeName}?`; + } + + return resolveRpcType(refSchema, isRequired, parentClassName, propName, classes); + } // Handle anyOf: [T, null] → T? (nullable typed property) if (schema.anyOf) { const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); @@ -764,39 +841,32 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam if (schema.type === "array" && schema.items) { const items = schema.items as JSONSchema7; if (items.type === "object" && items.properties) { - const itemClass = (items.title as string) ?? singularPascal(propName); + const itemClass = (items.title as string) ?? `${parentClassName}${singularPascal(propName)}`; classes.push(emitRpcClass(itemClass, items, "public", classes)); return isRequired ? `IList<${itemClass}>` : `IList<${itemClass}>?`; } - if (items.enum && Array.isArray(items.enum)) { - const itemEnum = getOrCreateEnum( - parentClassName, - `${propName}Item`, - items.enum as string[], - rpcEnumOutput, - items.description, - items.title as string | undefined, - ); - return isRequired ? `IList<${itemEnum}>` : `IList<${itemEnum}>?`; - } - const itemType = schemaTypeToCSharp(items, true, rpcKnownTypes); + const itemType = resolveRpcType(items, true, parentClassName, `${propName}Item`, classes); return isRequired ? `IList<${itemType}>` : `IList<${itemType}>?`; } if (schema.type === "object" && schema.additionalProperties && typeof schema.additionalProperties === "object") { const vs = schema.additionalProperties as JSONSchema7; - if (vs.type === "object" && vs.properties) { - const valClass = (vs.title as string) ?? `${parentClassName}${propName}Value`; - classes.push(emitRpcClass(valClass, vs, "public", classes)); - return isRequired ? `IDictionary` : `IDictionary?`; - } - const valueType = schemaTypeToCSharp(vs, true, rpcKnownTypes); + const valueType = resolveRpcType(vs, true, parentClassName, `${propName}Value`, classes); return isRequired ? `IDictionary` : `IDictionary?`; } return schemaTypeToCSharp(schema, isRequired, rpcKnownTypes); } -function emitRpcClass(className: string, schema: JSONSchema7, visibility: "public" | "internal", extraClasses: string[]): string { - const schemaKey = stableStringify(schema); +function emitRpcClass( + className: string, + schema: JSONSchema7, + visibility: "public" | "internal", + extraClasses: string[] +): string { + const effectiveSchema = + resolveObjectSchema(schema, rpcDefinitions) ?? + resolveSchema(schema, rpcDefinitions) ?? + schema; + const schemaKey = stableStringify(effectiveSchema); const existingSchema = emittedRpcClassSchemas.get(className); if (existingSchema) { if (existingSchema !== schemaKey) { @@ -809,15 +879,15 @@ function emitRpcClass(className: string, schema: JSONSchema7, visibility: "publi emittedRpcClassSchemas.set(className, schemaKey); - const requiredSet = new Set(schema.required || []); + const requiredSet = new Set(effectiveSchema.required || []); const lines: string[] = []; - lines.push(...xmlDocComment(schema.description || `RPC data type for ${className.replace(/(Request|Result|Params)$/, "")} operations.`, "")); + lines.push(...xmlDocComment(schema.description || effectiveSchema.description || `RPC data type for ${className.replace(/(Request|Result|Params)$/, "")} operations.`, "")); if (experimentalRpcTypes.has(className)) { lines.push(`[Experimental(Diagnostics.Experimental)]`); } lines.push(`${visibility} sealed class ${className}`, `{`); - const props = Object.entries(schema.properties || {}); + const props = Object.entries(effectiveSchema.properties || {}); for (let i = 0; i < props.length; i++) { const [propName, propSchema] = props[i]; if (typeof propSchema !== "object") continue; @@ -952,19 +1022,21 @@ function emitServerInstanceMethod( groupExperimental: boolean ): void { const methodName = toPascalCase(name); - let resultClassName = !isVoidSchema(method.result) ? resultTypeName(method) : ""; - if (!isVoidSchema(method.result) && method.stability === "experimental") { + const resultSchema = getMethodResultSchema(method); + let resultClassName = !isVoidSchema(resultSchema) ? resultTypeName(method) : ""; + if (!isVoidSchema(resultSchema) && method.stability === "experimental") { experimentalRpcTypes.add(resultClassName); } - if (isObjectSchema(method.result)) { - const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); + if (isObjectSchema(resultSchema)) { + const resultClass = emitRpcClass(resultClassName, resultSchema!, "public", classes); if (resultClass) classes.push(resultClass); - } else if (!isVoidSchema(method.result)) { - resultClassName = emitNonObjectResultType(resultClassName, method.result, classes); + } else if (!isVoidSchema(resultSchema)) { + resultClassName = emitNonObjectResultType(resultClassName, resultSchema!, classes); } - const paramEntries = method.params?.properties ? Object.entries(method.params.properties) : []; - const requiredSet = new Set(method.params?.required || []); + const effectiveParams = resolveMethodParamsSchema(method); + const paramEntries = effectiveParams?.properties ? Object.entries(effectiveParams.properties) : []; + const requiredSet = new Set(effectiveParams?.required || []); let requestClassName: string | null = null; if (paramEntries.length > 0) { @@ -972,7 +1044,7 @@ function emitServerInstanceMethod( if (method.stability === "experimental") { experimentalRpcTypes.add(requestClassName); } - const reqClass = emitRpcClass(requestClassName, method.params!, "internal", classes); + const reqClass = emitRpcClass(requestClassName, effectiveParams!, "internal", classes); if (reqClass) classes.push(reqClass); } @@ -989,32 +1061,26 @@ function emitServerInstanceMethod( if (typeof pSchema !== "object") continue; const isReq = requiredSet.has(pName); const jsonSchema = pSchema as JSONSchema7; - let csType: string; - // If the property has an enum, resolve to the generated enum type by title - if (jsonSchema.enum && Array.isArray(jsonSchema.enum) && requestClassName) { - const enumTitle = (jsonSchema.title as string) ?? `${requestClassName}${toPascalCase(pName)}`; - const match = generatedEnums.get(enumTitle); - csType = match ? (isReq ? match.enumName : `${match.enumName}?`) : schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes); - } else { - csType = schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes); - } + const csType = requestClassName + ? resolveRpcType(jsonSchema, isReq, requestClassName, toPascalCase(pName), classes) + : schemaTypeToCSharp(jsonSchema, isReq, rpcKnownTypes); sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`); bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`); } sigParams.push("CancellationToken cancellationToken = default"); - const taskType = !isVoidSchema(method.result) ? `Task<${resultClassName}>` : "Task"; + const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task"; lines.push(`${indent}public async ${taskType} ${methodName}Async(${sigParams.join(", ")})`); lines.push(`${indent}{`); if (requestClassName && bodyAssignments.length > 0) { lines.push(`${indent} var request = new ${requestClassName} { ${bodyAssignments.join(", ")} };`); - if (!isVoidSchema(method.result)) { + if (!isVoidSchema(resultSchema)) { lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`); } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [request], cancellationToken);`); } } else { - if (!isVoidSchema(method.result)) { + if (!isVoidSchema(resultSchema)) { lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [], cancellationToken);`); } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [], cancellationToken);`); @@ -1052,19 +1118,21 @@ function emitSessionRpcClasses(node: Record, classes: string[]) function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean): void { const methodName = toPascalCase(key); - let resultClassName = !isVoidSchema(method.result) ? resultTypeName(method) : ""; - if (!isVoidSchema(method.result) && method.stability === "experimental") { + const resultSchema = getMethodResultSchema(method); + let resultClassName = !isVoidSchema(resultSchema) ? resultTypeName(method) : ""; + if (!isVoidSchema(resultSchema) && method.stability === "experimental") { experimentalRpcTypes.add(resultClassName); } - if (isObjectSchema(method.result)) { - const resultClass = emitRpcClass(resultClassName, method.result, "public", classes); + if (isObjectSchema(resultSchema)) { + const resultClass = emitRpcClass(resultClassName, resultSchema!, "public", classes); if (resultClass) classes.push(resultClass); - } else if (!isVoidSchema(method.result)) { - resultClassName = emitNonObjectResultType(resultClassName, method.result, classes); + } else if (!isVoidSchema(resultSchema)) { + resultClassName = emitNonObjectResultType(resultClassName, resultSchema!, classes); } - const paramEntries = (method.params?.properties ? Object.entries(method.params.properties) : []).filter(([k]) => k !== "sessionId"); - const requiredSet = new Set(method.params?.required || []); + const effectiveParams = resolveMethodParamsSchema(method); + const paramEntries = (effectiveParams?.properties ? Object.entries(effectiveParams.properties) : []).filter(([k]) => k !== "sessionId"); + const requiredSet = new Set(effectiveParams?.required || []); // Sort so required params come before optional (C# requires defaults at end) paramEntries.sort((a, b) => { @@ -1077,8 +1145,8 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas if (method.stability === "experimental") { experimentalRpcTypes.add(requestClassName); } - if (method.params) { - const reqClass = emitRpcClass(requestClassName, method.params, "internal", classes); + if (effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0) { + const reqClass = emitRpcClass(requestClassName, effectiveParams, "internal", classes); if (reqClass) classes.push(reqClass); } @@ -1098,10 +1166,10 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas } sigParams.push("CancellationToken cancellationToken = default"); - const taskType = !isVoidSchema(method.result) ? `Task<${resultClassName}>` : "Task"; + const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task"; lines.push(`${indent}public async ${taskType} ${methodName}Async(${sigParams.join(", ")})`); lines.push(`${indent}{`, `${indent} var request = new ${requestClassName} { ${bodyAssignments.join(", ")} };`); - if (!isVoidSchema(method.result)) { + if (!isVoidSchema(resultSchema)) { lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`); } else { lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`); @@ -1152,17 +1220,19 @@ function emitClientSessionApiRegistration(clientSchema: Record, for (const { methods } of groups) { for (const method of methods) { - if (!isVoidSchema(method.result)) { - if (isObjectSchema(method.result)) { - const resultClass = emitRpcClass(resultTypeName(method), method.result, "public", classes); + 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), method.result, classes); + emitNonObjectResultType(resultTypeName(method), resultSchema!, classes); } } - if (method.params?.properties && Object.keys(method.params.properties).length > 0) { - const paramsClass = emitRpcClass(paramsTypeName(method), method.params, "public", classes); + const effectiveParams = resolveMethodParamsSchema(method); + if (effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0) { + const paramsClass = emitRpcClass(paramsTypeName(method), effectiveParams, "public", classes); if (paramsClass) classes.push(paramsClass); } } @@ -1178,8 +1248,10 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(`public interface ${interfaceName}`); lines.push(`{`); for (const method of methods) { - const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; - const taskType = !isVoidSchema(method.result) ? `Task<${resultTypeName(method)}>` : "Task"; + const effectiveParams = resolveMethodParamsSchema(method); + const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; + const resultSchema = getMethodResultSchema(method); + const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; lines.push(` /// Handles "${method.rpcMethod}".`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` [Experimental(Diagnostics.Experimental)]`); @@ -1220,9 +1292,11 @@ function emitClientSessionApiRegistration(clientSchema: Record, for (const method of methods) { const handlerProperty = toPascalCase(groupName); const handlerMethod = clientHandlerMethodName(method.rpcMethod); - const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; + const effectiveParams = resolveMethodParamsSchema(method); + const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; + const resultSchema = getMethodResultSchema(method); const paramsClass = paramsTypeName(method); - const taskType = !isVoidSchema(method.result) ? `Task<${resultTypeName(method)}>` : "Task"; + const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; const registrationVar = `register${typeToClassName(method.rpcMethod)}Method`; if (hasParams) { @@ -1230,7 +1304,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(` {`); lines.push(` var handler = getHandlers(request.SessionId).${handlerProperty};`); lines.push(` if (handler is null) throw new InvalidOperationException($"No ${groupName} handler registered for session: {request.SessionId}");`); - if (!isVoidSchema(method.result)) { + if (!isVoidSchema(resultSchema)) { lines.push(` return await handler.${handlerMethod}(request, cancellationToken);`); } else { lines.push(` await handler.${handlerMethod}(request, cancellationToken);`); @@ -1259,6 +1333,7 @@ function generateRpcCode(schema: ApiSchema): string { rpcKnownTypes.clear(); rpcEnumOutput = []; generatedEnums.clear(); // Clear shared enum deduplication map + rpcDefinitions = collectDefinitionCollections(schema as Record); const classes: string[] = []; let serverRpcParts: string[] = []; diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 980fb3b8e..dd87f037b 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -17,12 +17,20 @@ import { getRpcSchemaTypeName, getSessionEventsSchemaPath, hoistTitledSchemas, + hasSchemaPayload, isNodeFullyExperimental, isVoidSchema, isRpcMethod, postProcessSchema, writeGeneratedFile, + collectDefinitionCollections, + resolveObjectSchema, + resolveSchema, + withSharedDefinitions, + refTypeName, + resolveRef, type ApiSchema, + type DefinitionCollections, type RpcMethod, } from "./utils.js"; @@ -173,12 +181,24 @@ function extractFieldNames(qtCode: string): Map> { return result; } -function goResultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.result, toPascalCase(method.rpcMethod) + "Result"); -} +function extractQuicktypeImports(qtCode: string): { code: string; imports: string[] } { + const collectedImports: string[] = []; + let code = qtCode.replace(/^import \(\n([\s\S]*?)^\)\n+/m, (_match, block: string) => { + for (const line of block.split(/\r?\n/)) { + const trimmed = line.trim(); + if (trimmed.length > 0) { + collectedImports.push(trimmed); + } + } + return ""; + }); -function goParamsTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.params, toPascalCase(method.rpcMethod) + "Request"); + code = code.replace(/^import ("[^"]+")\n+/m, (_match, singleImport: string) => { + collectedImports.push(singleImport.trim()); + return ""; + }); + + return { code, imports: collectedImports }; } async function formatGoFile(filePath: string): Promise { @@ -202,6 +222,55 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } +let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; + +function withRootTitle(schema: JSONSchema7, title: string): JSONSchema7 { + return { ...schema, title }; +} + +function goRequestFallbackName(method: RpcMethod): string { + return toPascalCase(method.rpcMethod) + "Request"; +} + +function schemaSourceForNamedDefinition( + schema: JSONSchema7 | null | undefined, + resolvedSchema: JSONSchema7 | undefined +): JSONSchema7 { + if (schema?.$ref && resolvedSchema) { + return resolvedSchema; + } + return schema ?? resolvedSchema ?? { type: "object" }; +} + +function isNamedGoObjectSchema(schema: JSONSchema7 | undefined): schema is JSONSchema7 { + return !!schema && schema.type === "object" && (schema.properties !== undefined || schema.additionalProperties === false); +} + +function getMethodResultSchema(method: RpcMethod): JSONSchema7 | undefined { + return resolveSchema(method.result, rpcDefinitions) ?? method.result ?? undefined; +} + +function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { + return ( + resolveObjectSchema(method.params, rpcDefinitions) ?? + resolveSchema(method.params, rpcDefinitions) ?? + method.params ?? + undefined + ); +} + +function goResultTypeName(method: RpcMethod): string { + return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); +} + +function goParamsTypeName(method: RpcMethod): string { + const fallback = goRequestFallbackName(method); + if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { + return fallback; + } + return getRpcSchemaTypeName(getMethodParamsSchema(method), fallback); +} + // ── Session Events (custom codegen — per-event-type data structs) ─────────── interface GoEventVariant { @@ -216,19 +285,30 @@ interface GoCodegenCtx { enums: string[]; enumsByName: Map; // enumName → enumName (dedup by type name, not values) generatedNames: Set; + definitions?: DefinitionCollections; } function extractGoEventVariants(schema: JSONSchema7): GoEventVariant[] { - const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + const definitionCollections = collectDefinitionCollections(schema as Record); + const sessionEvent = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections); if (!sessionEvent?.anyOf) throw new Error("Schema must have SessionEvent definition with anyOf"); return (sessionEvent.anyOf as JSONSchema7[]) .map((variant) => { - if (typeof variant !== "object" || !variant.properties) throw new Error("Invalid variant"); - const typeSchema = variant.properties.type as JSONSchema7; + const resolvedVariant = + resolveObjectSchema(variant as JSONSchema7, definitionCollections) ?? + resolveSchema(variant as JSONSchema7, definitionCollections) ?? + (variant as JSONSchema7); + if (typeof resolvedVariant !== "object" || !resolvedVariant.properties) throw new Error("Invalid variant"); + const typeSchema = resolvedVariant.properties.type as JSONSchema7; const typeName = typeSchema?.const as string; if (!typeName) throw new Error("Variant must have type.const"); - const dataSchema = (variant.properties.data as JSONSchema7) || {}; + const dataSchema = + resolveObjectSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + resolveSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + ((resolvedVariant.properties.data as JSONSchema7) || {}); return { typeName, dataClassName: `${toPascalCase(typeName)}Data`, @@ -320,6 +400,25 @@ function resolveGoPropertyType( ): string { const nestedName = parentTypeName + toGoFieldName(jsonPropName); + // Handle $ref — resolve the reference and generate the referenced type + if (propSchema.$ref && typeof propSchema.$ref === "string") { + const typeName = toGoFieldName(refTypeName(propSchema.$ref, ctx.definitions)); + const resolved = resolveRef(propSchema.$ref, ctx.definitions); + if (resolved) { + if (resolved.enum) { + const enumType = getOrCreateGoEnum(typeName, resolved.enum as string[], ctx, resolved.description); + return isRequired ? enumType : `*${enumType}`; + } + if (isNamedGoObjectSchema(resolved)) { + emitGoStruct(typeName, resolved, ctx); + return isRequired ? typeName : `*${typeName}`; + } + return resolveGoPropertyType(resolved, parentTypeName, jsonPropName, isRequired, ctx); + } + // Fallback: use the type name directly + return isRequired ? typeName : `*${typeName}`; + } + // Handle anyOf if (propSchema.anyOf) { const nonNull = (propSchema.anyOf as JSONSchema7[]).filter((s) => s.type !== "null"); @@ -580,6 +679,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { enums: [], enumsByName: new Map(), generatedNames: new Set(), + definitions: collectDefinitionCollections(schema as Record), }; // Generate per-event data structs @@ -858,50 +958,73 @@ async function generateRpc(schemaPath?: string): Promise { ...collectRpcMethods(schema.clientSession || {}), ]; - // Build a combined schema for quicktype - prefix types to avoid conflicts - const combinedSchema: JSONSchema7 = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: {}, - }; + // Build a combined schema for quicktype — prefix types to avoid conflicts. + // Include shared definitions from the API schema for $ref resolution. + rpcDefinitions = collectDefinitionCollections(schema as Record); + const combinedSchema = withSharedDefinitions( + { + $schema: "http://json-schema.org/draft-07/schema#", + }, + rpcDefinitions + ); for (const method of allMethods) { - if (isVoidSchema(method.result)) { + const resultSchema = getMethodResultSchema(method); + if (isVoidSchema(resultSchema)) { // Emit an empty struct for void results (forward-compatible with adding fields later) - combinedSchema.definitions![goResultTypeName(method)] = { type: "object", properties: {}, additionalProperties: false }; - } else { - combinedSchema.definitions![goResultTypeName(method)] = method.result; + combinedSchema.definitions![goResultTypeName(method)] = { + title: goResultTypeName(method), + type: "object", + properties: {}, + additionalProperties: false, + }; + } else if (method.result) { + combinedSchema.definitions![goResultTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.result, resultSchema), + goResultTypeName(method) + ); } - if (method.params?.properties && Object.keys(method.params.properties).length > 0) { + const resolvedParams = getMethodParamsSchema(method); + if (method.params && hasSchemaPayload(resolvedParams)) { // For session methods, filter out sessionId from params type - if (method.rpcMethod.startsWith("session.")) { + if (method.rpcMethod.startsWith("session.") && resolvedParams?.properties) { const filtered: JSONSchema7 = { - ...method.params, + ...resolvedParams, properties: Object.fromEntries( - Object.entries(method.params.properties).filter(([k]) => k !== "sessionId") + Object.entries(resolvedParams.properties).filter(([k]) => k !== "sessionId") ), - required: method.params.required?.filter((r) => r !== "sessionId"), + required: resolvedParams.required?.filter((r) => r !== "sessionId"), }; - if (Object.keys(filtered.properties!).length > 0) { - combinedSchema.definitions![goParamsTypeName(method)] = filtered; + if (hasSchemaPayload(filtered)) { + combinedSchema.definitions![goParamsTypeName(method)] = withRootTitle( + filtered, + goParamsTypeName(method) + ); } } else { - combinedSchema.definitions![goParamsTypeName(method)] = method.params; + combinedSchema.definitions![goParamsTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.params, resolvedParams), + goParamsTypeName(method) + ); } } } const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); + const allDefinitions = { ...rootDefinitions, ...sharedDefinitions }; + const allDefinitionCollections: DefinitionCollections = { + definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, + $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, + }; // Generate types via quicktype const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); for (const [name, def] of Object.entries(rootDefinitions)) { - await schemaInput.addSource({ - name, - schema: JSON.stringify({ - ...def, - definitions: sharedDefinitions, - }), - }); + const schemaWithDefs = withSharedDefinitions( + typeof def === "object" ? (def as JSONSchema7) : {}, + allDefinitionCollections + ); + await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); } const inputData = new InputData(); @@ -913,21 +1036,10 @@ async function generateRpc(schemaPath?: string): Promise { rendererOptions: { package: "copilot", "just-types": "true" }, }); - // Post-process quicktype output: fix enum constant names + // Post-process quicktype output: hoist quicktype's imports into the file-level import block let qtCode = qtResult.lines.filter((l) => !l.startsWith("package ")).join("\n"); - // Extract any imports quicktype emitted (e.g., "time") and hoist them - const qtImports: string[] = []; - qtCode = qtCode.replace(/^import\s+"([^"]+)"\s*$/gm, (_match, imp) => { - qtImports.push(`"${imp}"`); - return ""; - }); - qtCode = qtCode.replace(/^import\s+\(([^)]*)\)\s*$/gm, (_match, block) => { - for (const line of block.split("\n")) { - const trimmed = line.trim(); - if (trimmed) qtImports.push(trimmed); - } - return ""; - }); + const quicktypeImports = extractQuicktypeImports(qtCode); + qtCode = quicktypeImports.code; qtCode = postProcessEnumConstants(qtCode); qtCode = collapsePlaceholderGoStructs(qtCode); // Strip trailing whitespace from quicktype output (gofmt requirement) @@ -935,7 +1047,7 @@ async function generateRpc(schemaPath?: string): Promise { // Extract actual type names generated by quicktype (may differ from toPascalCase) const actualTypeNames = new Map(); - const typeRe = /^type\s+(\w+)\s+/gm; + const typeRe = /^type\s+(\w+)\b/gm; let sm; while ((sm = typeRe.exec(qtCode)) !== null) { actualTypeNames.set(sm[1].toLowerCase(), sm[1]); @@ -974,14 +1086,15 @@ async function generateRpc(schemaPath?: string): Promise { lines.push(`package rpc`); lines.push(``); const imports = [`"context"`, `"encoding/json"`]; + for (const imp of quicktypeImports.imports) { + if (!imports.includes(imp)) { + imports.push(imp); + } + } if (schema.clientSession) { imports.push(`"errors"`, `"fmt"`); } imports.push(`"github.com/github/copilot-sdk/go/internal/jsonrpc2"`); - // Add any imports hoisted from quicktype output - for (const qi of qtImports) { - if (!imports.includes(qi)) imports.push(qi); - } lines.push(`import (`); for (const imp of imports) { @@ -1090,10 +1203,11 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc const methodName = toPascalCase(name); const resultType = resolveType(goResultTypeName(method)); - const paramProps = method.params?.properties || {}; - const requiredParams = new Set(method.params?.required || []); + const effectiveParams = getMethodParamsSchema(method); + const paramProps = effectiveParams?.properties || {}; + const requiredParams = new Set(effectiveParams?.required || []); const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); - const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; + const hasParams = isSession ? nonSessionParams.length > 0 : hasSchemaPayload(effectiveParams); const paramsType = hasParams ? resolveType(goParamsTypeName(method)) : ""; // For wrapper-level methods, access fields through a.common; for service type aliases, use a directly diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index c1a80aa06..62b53e1e6 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -22,7 +22,14 @@ import { isNodeFullyExperimental, postProcessSchema, writeGeneratedFile, + collectDefinitionCollections, + hasSchemaPayload, + refTypeName, + resolveObjectSchema, + resolveSchema, + withSharedDefinitions, type ApiSchema, + type DefinitionCollections, type RpcMethod, } from "./utils.js"; @@ -210,12 +217,53 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } +let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; + +function withRootTitle(schema: JSONSchema7, title: string): JSONSchema7 { + return { ...schema, title }; +} + +function pythonRequestFallbackName(method: RpcMethod): string { + return toPascalCase(method.rpcMethod) + "Request"; +} + +function schemaSourceForNamedDefinition( + schema: JSONSchema7 | null | undefined, + resolvedSchema: JSONSchema7 | undefined +): JSONSchema7 { + if (schema?.$ref && resolvedSchema) { + return resolvedSchema; + } + return schema ?? resolvedSchema ?? { type: "object" }; +} + +function isNamedPyObjectSchema(schema: JSONSchema7 | undefined): schema is JSONSchema7 { + return !!schema && schema.type === "object" && (schema.properties !== undefined || schema.additionalProperties === false); +} + +function getMethodResultSchema(method: RpcMethod): JSONSchema7 | undefined { + return resolveSchema(method.result, rpcDefinitions) ?? method.result ?? undefined; +} + +function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { + return ( + resolveObjectSchema(method.params, rpcDefinitions) ?? + resolveSchema(method.params, rpcDefinitions) ?? + method.params ?? + undefined + ); +} + function pythonResultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.result, toPascalCase(method.rpcMethod) + "Result"); + return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); } function pythonParamsTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.params, toPascalCase(method.rpcMethod) + "Request"); + const fallback = pythonRequestFallbackName(method); + if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { + return fallback; + } + return getRpcSchemaTypeName(getMethodParamsSchema(method), fallback); } // ── Session Events ────────────────────────────────────────────────────────── @@ -241,6 +289,7 @@ interface PyCodegenCtx { generatedNames: Set; usesTimedelta: boolean; usesIntegerTimedelta: boolean; + definitions: DefinitionCollections; } function toEnumMemberName(value: string): string { @@ -372,24 +421,34 @@ function toPythonLiteral(value: unknown): string | undefined { } function extractPyEventVariants(schema: JSONSchema7): PyEventVariant[] { - const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + const definitionCollections = collectDefinitionCollections(schema as Record); + const sessionEvent = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections); if (!sessionEvent?.anyOf) { throw new Error("Schema must have SessionEvent definition with anyOf"); } return (sessionEvent.anyOf as JSONSchema7[]) .map((variant) => { - if (typeof variant !== "object" || !variant.properties) { + const resolvedVariant = + resolveObjectSchema(variant as JSONSchema7, definitionCollections) ?? + resolveSchema(variant as JSONSchema7, definitionCollections) ?? + (variant as JSONSchema7); + if (typeof resolvedVariant !== "object" || !resolvedVariant.properties) { throw new Error("Invalid event variant"); } - const typeSchema = variant.properties.type as JSONSchema7; + const typeSchema = resolvedVariant.properties.type as JSONSchema7; const typeName = typeSchema?.const as string; if (!typeName) { throw new Error("Event variant must define type.const"); } - const dataSchema = (variant.properties.data as JSONSchema7) || {}; + const dataSchema = + resolveObjectSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + resolveSchema(resolvedVariant.properties.data as JSONSchema7, definitionCollections) ?? + ((resolvedVariant.properties.data as JSONSchema7) || {}); return { typeName, dataClassName: `${toPascalCase(typeName)}Data`, @@ -479,6 +538,35 @@ function resolvePyPropertyType( ): PyResolvedType { const nestedName = parentTypeName + toPascalCase(jsonPropName); + if (propSchema.$ref && typeof propSchema.$ref === "string") { + const typeName = toPascalCase(refTypeName(propSchema.$ref, ctx.definitions)); + const resolved = resolveSchema(propSchema, ctx.definitions); + if (resolved && resolved !== propSchema) { + if (resolved.enum && Array.isArray(resolved.enum) && resolved.enum.every((value) => typeof value === "string")) { + const enumType = getOrCreatePyEnum(typeName, resolved.enum as string[], ctx, resolved.description); + const enumResolved: PyResolvedType = { + annotation: enumType, + fromExpr: (expr) => `parse_enum(${enumType}, ${expr})`, + toExpr: (expr) => `to_enum(${enumType}, ${expr})`, + }; + return isRequired ? enumResolved : pyOptionalResolvedType(enumResolved); + } + + const resolvedObject = resolveObjectSchema(propSchema, ctx.definitions); + if (isNamedPyObjectSchema(resolvedObject)) { + emitPyClass(typeName, resolvedObject, ctx, resolvedObject.description); + const objectResolved: PyResolvedType = { + annotation: typeName, + fromExpr: (expr) => `${typeName}.from_dict(${expr})`, + toExpr: (expr) => `to_class(${typeName}, ${expr})`, + }; + return isRequired ? objectResolved : pyOptionalResolvedType(objectResolved); + } + + return resolvePyPropertyType(resolved, parentTypeName, jsonPropName, isRequired, ctx); + } + } + if (propSchema.allOf && propSchema.allOf.length === 1 && typeof propSchema.allOf[0] === "object") { return resolvePyPropertyType( propSchema.allOf[0] as JSONSchema7, @@ -490,7 +578,14 @@ function resolvePyPropertyType( } if (propSchema.anyOf) { - const variants = (propSchema.anyOf as JSONSchema7[]).filter((item) => typeof item === "object"); + const variants = (propSchema.anyOf as JSONSchema7[]) + .filter((item) => typeof item === "object") + .map( + (item) => + resolveObjectSchema(item as JSONSchema7, ctx.definitions) ?? + resolveSchema(item as JSONSchema7, ctx.definitions) ?? + (item as JSONSchema7) + ); const nonNull = variants.filter((item) => item.type !== "null"); const hasNull = variants.length !== nonNull.length; @@ -634,6 +729,12 @@ function resolvePyPropertyType( if (items.anyOf) { const itemVariants = (items.anyOf as JSONSchema7[]) .filter((variant) => typeof variant === "object") + .map( + (variant) => + resolveObjectSchema(variant as JSONSchema7, ctx.definitions) ?? + resolveSchema(variant as JSONSchema7, ctx.definitions) ?? + (variant as JSONSchema7) + ) .filter((variant) => variant.type !== "null"); const discriminator = findPyDiscriminator(itemVariants); if (discriminator) { @@ -941,6 +1042,7 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string { generatedNames: new Set(), usesTimedelta: false, usesIntegerTimedelta: false, + definitions: collectDefinitionCollections(schema as Record), }; for (const variant of variants) { @@ -1273,46 +1375,63 @@ async function generateRpc(schemaPath?: string): Promise { ...collectRpcMethods(schema.clientSession || {}), ]; - // Build a combined schema for quicktype - const combinedSchema: JSONSchema7 = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: {}, - }; + // Build a combined schema for quicktype, including shared definitions from the API schema + rpcDefinitions = collectDefinitionCollections(schema as Record); + const combinedSchema = withSharedDefinitions( + { + $schema: "http://json-schema.org/draft-07/schema#", + }, + rpcDefinitions + ); for (const method of allMethods) { - if (!isVoidSchema(method.result)) { - combinedSchema.definitions![pythonResultTypeName(method)] = method.result; + const resultSchema = getMethodResultSchema(method); + if (!isVoidSchema(resultSchema)) { + combinedSchema.definitions![pythonResultTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.result, resultSchema), + pythonResultTypeName(method) + ); } - if (method.params?.properties && Object.keys(method.params.properties).length > 0) { - if (method.rpcMethod.startsWith("session.")) { + const resolvedParams = getMethodParamsSchema(method); + if (method.params && hasSchemaPayload(resolvedParams)) { + if (method.rpcMethod.startsWith("session.") && resolvedParams?.properties) { const filtered: JSONSchema7 = { - ...method.params, + ...resolvedParams, properties: Object.fromEntries( - Object.entries(method.params.properties).filter(([k]) => k !== "sessionId") + Object.entries(resolvedParams.properties).filter(([k]) => k !== "sessionId") ), - required: method.params.required?.filter((r) => r !== "sessionId"), + required: resolvedParams.required?.filter((r) => r !== "sessionId"), }; - if (Object.keys(filtered.properties!).length > 0) { - combinedSchema.definitions![pythonParamsTypeName(method)] = filtered; + if (hasSchemaPayload(filtered)) { + combinedSchema.definitions![pythonParamsTypeName(method)] = withRootTitle( + filtered, + pythonParamsTypeName(method) + ); } } else { - combinedSchema.definitions![pythonParamsTypeName(method)] = method.params; + combinedSchema.definitions![pythonParamsTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.params, resolvedParams), + pythonParamsTypeName(method) + ); } } } const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); + const allDefinitions = { ...rootDefinitions, ...sharedDefinitions }; + const allDefinitionCollections: DefinitionCollections = { + definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, + $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, + }; // Generate types via quicktype const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); for (const [name, def] of Object.entries(rootDefinitions)) { - await schemaInput.addSource({ - name, - schema: JSON.stringify({ - ...def, - definitions: sharedDefinitions, - }), - }); + const schemaWithDefs = withSharedDefinitions( + typeof def === "object" ? (def as JSONSchema7) : {}, + allDefinitionCollections + ); + await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); } const inputData = new InputData(); @@ -1502,13 +1621,15 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false): void { const methodName = toSnakeCase(name); - const hasResult = !isVoidSchema(method.result); + const resultSchema = getMethodResultSchema(method); + const hasResult = !isVoidSchema(resultSchema); const resultType = hasResult ? resolveType(pythonResultTypeName(method)) : "None"; - const resultIsObject = isObjectSchema(method.result); + const resultIsObject = isObjectSchema(resultSchema); - const paramProps = method.params?.properties || {}; + const effectiveParams = getMethodParamsSchema(method); + const paramProps = effectiveParams?.properties || {}; const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); - const hasParams = isSession ? nonSessionParams.length > 0 : Object.keys(paramProps).length > 0; + const hasParams = isSession ? nonSessionParams.length > 0 : hasSchemaPayload(effectiveParams); const paramsType = resolveType(pythonParamsTypeName(method)); // Build signature with typed params + optional timeout @@ -1625,7 +1746,8 @@ function emitClientSessionHandlerMethod( groupExperimental = false ): void { const paramsType = resolveType(pythonParamsTypeName(method)); - const resultType = !isVoidSchema(method.result) ? resolveType(pythonResultTypeName(method)) : "None"; + const resultSchema = getMethodResultSchema(method); + const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : "None"; lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); @@ -1642,7 +1764,8 @@ function emitClientSessionRegistrationMethod( ): void { const handlerVariableName = `handle_${toSnakeCase(groupName)}_${toSnakeCase(methodName)}`; const paramsType = resolveType(pythonParamsTypeName(method)); - const resultType = !isVoidSchema(method.result) ? resolveType(pythonResultTypeName(method)) : null; + const resultSchema = getMethodResultSchema(method); + const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : null; const handlerField = toSnakeCase(groupName); const handlerMethod = toSnakeCase(methodName); @@ -1654,7 +1777,7 @@ function emitClientSessionRegistrationMethod( ); if (resultType) { lines.push(` result = await handler.${handlerMethod}(request)`); - if (isObjectSchema(method.result)) { + if (isObjectSchema(resultSchema)) { lines.push(` return result.to_dict()`); } else { lines.push(` return result.value if hasattr(result, 'value') else result`); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 7dfd5631f..c18108573 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -13,13 +13,20 @@ import { getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, - isNodeFullyExperimental, + normalizeSchemaTitles, + postProcessSchema, + writeGeneratedFile, + collectDefinitionCollections, + hasSchemaPayload, + resolveObjectSchema, + resolveSchema, + withSharedDefinitions, isRpcMethod, + isNodeFullyExperimental, isVoidSchema, - postProcessSchema, stripNonAnnotationTitles, - writeGeneratedFile, type ApiSchema, + type DefinitionCollections, type RpcMethod, } from "./utils.js"; @@ -125,6 +132,111 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } +function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { + const root = structuredClone(schema) as JSONSchema7 & { + definitions?: Record; + $defs?: Record; + }; + const definitions = { ...(root.definitions ?? {}) }; + const draftDefinitionAliases = new Map(); + + for (const [key, value] of Object.entries(root.$defs ?? {})) { + let alias = key; + if (alias in definitions) { + alias = `$defs_${key}`; + while (alias in definitions) { + alias = `$defs_${alias}`; + } + } + draftDefinitionAliases.set(key, alias); + definitions[alias] = value; + } + + root.definitions = definitions; + delete root.$defs; + + const rewrite = (value: unknown): unknown => { + if (Array.isArray(value)) { + return value.map(rewrite); + } + if (!value || typeof value !== "object") { + return value; + } + + const rewritten = Object.fromEntries( + Object.entries(value as Record).map(([key, child]) => [key, rewrite(child)]) + ) as Record; + + if (typeof rewritten.$ref === "string" && rewritten.$ref.startsWith("#/$defs/")) { + const definitionName = rewritten.$ref.slice("#/$defs/".length); + rewritten.$ref = `#/definitions/${draftDefinitionAliases.get(definitionName) ?? definitionName}`; + } + + return rewritten; + }; + + return rewrite(root) as JSONSchema7; +} + +function stableStringify(value: unknown): string { + if (Array.isArray(value)) { + return `[${value.map((item) => stableStringify(item)).join(",")}]`; + } + if (value && typeof value === "object") { + const entries = Object.entries(value as Record).sort(([a], [b]) => a.localeCompare(b)); + return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child)}`).join(",")}}`; + } + return JSON.stringify(value); +} + +function replaceDuplicateTitledSchemasWithRefs( + value: unknown, + definitions: Record, + isRoot = false +): unknown { + if (Array.isArray(value)) { + return value.map((item) => replaceDuplicateTitledSchemasWithRefs(item, definitions)); + } + if (!value || typeof value !== "object") { + return value; + } + + const rewritten = Object.fromEntries( + Object.entries(value as Record).map(([key, child]) => [ + key, + replaceDuplicateTitledSchemasWithRefs(child, definitions), + ]) + ) as Record; + + if (!isRoot && typeof rewritten.title === "string") { + const sharedSchema = definitions[rewritten.title]; + if ( + sharedSchema && + typeof sharedSchema === "object" && + stableStringify(normalizeSchemaTitles(rewritten as JSONSchema7)) === + stableStringify(normalizeSchemaTitles(sharedSchema as JSONSchema7)) + ) { + return { $ref: `#/definitions/${rewritten.title}` }; + } + } + + return rewritten; +} + +function reuseSharedTitledSchemas(schema: JSONSchema7): JSONSchema7 { + const definitions = { ...((schema.definitions ?? {}) as Record) }; + + return { + ...schema, + definitions: Object.fromEntries( + Object.entries(definitions).map(([name, definition]) => [ + name, + replaceDuplicateTitledSchemasWithRefs(definition, definitions, true), + ]) + ), + }; +} + // ── Session Events ────────────────────────────────────────────────────────── async function generateSessionEvents(schemaPath?: string): Promise { @@ -133,8 +245,14 @@ async function generateSessionEvents(schemaPath?: string): Promise { const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; const processed = postProcessSchema(stripNonAnnotationTitles(schema)); - - const ts = await compile(processed, "SessionEvent", { + const definitionCollections = collectDefinitionCollections(processed as Record); + const sessionEvent = + resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? + resolveSchema({ $ref: "#/$defs/SessionEvent" }, definitionCollections) ?? + processed; + const schemaForCompile = withSharedDefinitions(sessionEvent, definitionCollections); + + const ts = await compile(normalizeSchemaForTypeScript(schemaForCompile), "SessionEvent", { bannerComment: `/** * AUTO-GENERATED FILE - DO NOT EDIT * Generated from: session-events.schema.json @@ -149,12 +267,52 @@ async function generateSessionEvents(schemaPath?: string): Promise { // ── RPC Types ─────────────────────────────────────────────────────────────── +let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; + +function withRootTitle(schema: JSONSchema7, title: string): JSONSchema7 { + return { ...schema, title }; +} + +function rpcRequestFallbackName(method: RpcMethod): string { + return method.rpcMethod.split(".").map(toPascalCase).join("") + "Request"; +} + +function schemaSourceForNamedDefinition( + schema: JSONSchema7 | null | undefined, + resolvedSchema: JSONSchema7 | undefined +): JSONSchema7 { + if (schema?.$ref && resolvedSchema) { + return resolvedSchema; + } + return schema ?? resolvedSchema ?? { type: "object" }; +} + +function getMethodResultSchema(method: RpcMethod): JSONSchema7 | undefined { + return resolveSchema(method.result, rpcDefinitions) ?? method.result ?? undefined; +} + +function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { + return ( + resolveObjectSchema(method.params, rpcDefinitions) ?? + resolveSchema(method.params, rpcDefinitions) ?? + method.params ?? + undefined + ); +} + function resultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.result, method.rpcMethod.split(".").map(toPascalCase).join("") + "Result"); + return getRpcSchemaTypeName( + getMethodResultSchema(method), + method.rpcMethod.split(".").map(toPascalCase).join("") + "Result" + ); } function paramsTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(method.params, method.rpcMethod.split(".").map(toPascalCase).join("") + "Request"); + const fallback = rpcRequestFallbackName(method); + if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { + return fallback; + } + return getRpcSchemaTypeName(getMethodParamsSchema(method), fallback); } async function generateRpc(schemaPath?: string): Promise { @@ -176,32 +334,94 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; const clientSessionMethods = collectRpcMethods(schema.clientSession || {}); const seenBlocks = new Map(); + // Build a single combined schema with shared definitions and all method types. + // This ensures $ref-referenced types are generated exactly once. + rpcDefinitions = collectDefinitionCollections(schema as Record); + const combinedSchema = withSharedDefinitions( + { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + }, + rpcDefinitions + ); + + // Track which type names come from experimental methods for JSDoc annotations. + const experimentalTypes = new Set(); + for (const method of [...allMethods, ...clientSessionMethods]) { - if (!isVoidSchema(method.result)) { - const compiled = await compile(stripNonAnnotationTitles(method.result), resultTypeName(method), { - bannerComment: "", - additionalProperties: false, - }); + const resultSchema = getMethodResultSchema(method); + if (!isVoidSchema(resultSchema)) { + combinedSchema.definitions![resultTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.result, resultSchema), + resultTypeName(method) + ); if (method.stability === "experimental") { - lines.push("/** @experimental */"); + experimentalTypes.add(resultTypeName(method)); } - appendUniqueExportBlocks(lines, compiled, seenBlocks); - lines.push(""); } - if (method.params?.properties && Object.keys(method.params.properties).length > 0) { - const paramsCompiled = await compile(stripNonAnnotationTitles(method.params), paramsTypeName(method), { - bannerComment: "", - additionalProperties: false, - }); - if (method.stability === "experimental") { - lines.push("/** @experimental */"); + const resolvedParams = getMethodParamsSchema(method); + if (method.params && hasSchemaPayload(resolvedParams)) { + if (method.rpcMethod.startsWith("session.") && resolvedParams?.properties) { + const filtered: JSONSchema7 = { + ...resolvedParams, + properties: Object.fromEntries( + Object.entries(resolvedParams.properties).filter(([k]) => k !== "sessionId") + ), + required: resolvedParams.required?.filter((r) => r !== "sessionId"), + }; + if (hasSchemaPayload(filtered)) { + combinedSchema.definitions![paramsTypeName(method)] = withRootTitle( + filtered, + paramsTypeName(method) + ); + if (method.stability === "experimental") { + experimentalTypes.add(paramsTypeName(method)); + } + } + } else { + combinedSchema.definitions![paramsTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.params, resolvedParams), + paramsTypeName(method) + ); + if (method.stability === "experimental") { + experimentalTypes.add(paramsTypeName(method)); + } } - appendUniqueExportBlocks(lines, paramsCompiled, seenBlocks); - lines.push(""); } } + const schemaForCompile = reuseSharedTitledSchemas(stripNonAnnotationTitles(combinedSchema)); + + const compiled = await compile(normalizeSchemaForTypeScript(schemaForCompile), "_RpcSchemaRoot", { + bannerComment: "", + additionalProperties: false, + unreachableDefinitions: true, + }); + + // Strip the placeholder root type and keep only the definition-generated types + const strippedTs = compiled + .replace( + /\/\*\*\n \* This (?:interface|type) was referenced by `_RpcSchemaRoot`'s JSON-Schema\n \* via the `definition` "[^"]+"\.\n \*\/\n/g, + "\n" + ) + .replace(/export interface _RpcSchemaRoot\s*\{[^}]*\}\s*/g, "") + .replace(/export type _RpcSchemaRoot = [^;]+;\s*/g, "") + .trim(); + + if (strippedTs) { + // Add @experimental JSDoc annotations for types from experimental methods + let annotatedTs = strippedTs; + for (const expType of experimentalTypes) { + annotatedTs = annotatedTs.replace( + new RegExp(`(^|\\n)(export (?:interface|type) ${expType}\\b)`, "m"), + `$1/** @experimental */\n$2` + ); + } + lines.push(annotatedTs); + lines.push(""); + } + // Generate factory functions if (schema.server) { lines.push(`/** Create typed server-scoped RPC methods (no session required). */`); @@ -237,11 +457,14 @@ function emitGroup(node: Record, indent: string, isSession: boo for (const [key, value] of Object.entries(node)) { if (isRpcMethod(value)) { const { rpcMethod, params } = value; - const resultType = !isVoidSchema(value.result) ? resultTypeName(value) : "void"; + const resultType = !isVoidSchema(getMethodResultSchema(value)) ? resultTypeName(value) : "void"; const paramsType = paramsTypeName(value); + const effectiveParams = getMethodParamsSchema(value); - const paramEntries = params?.properties ? Object.entries(params.properties).filter(([k]) => k !== "sessionId") : []; - const hasParams = params?.properties && Object.keys(params.properties).length > 0; + const paramEntries = effectiveParams?.properties + ? Object.entries(effectiveParams.properties).filter(([k]) => k !== "sessionId") + : []; + const hasParams = hasSchemaPayload(effectiveParams); const hasNonSessionParams = paramEntries.length > 0; const sigParams: string[] = []; @@ -325,9 +548,9 @@ function emitClientSessionApiRegistration(clientSchema: Record) lines.push(`export interface ${interfaceName} {`); for (const method of methods) { const name = handlerMethodName(method.rpcMethod); - const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; + const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); const pType = hasParams ? paramsTypeName(method) : ""; - const rType = !isVoidSchema(method.result) ? resultTypeName(method) : "void"; + const rType = !isVoidSchema(getMethodResultSchema(method)) ? resultTypeName(method) : "void"; if (hasParams) { lines.push(` ${name}(params: ${pType}): Promise<${rType}>;`); @@ -365,7 +588,7 @@ function emitClientSessionApiRegistration(clientSchema: Record) for (const method of methods) { const name = handlerMethodName(method.rpcMethod); const pType = paramsTypeName(method); - const hasParams = method.params?.properties && Object.keys(method.params.properties).length > 0; + const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); if (hasParams) { lines.push(` connection.onRequest("${method.rpcMethod}", async (params: ${pType}) => {`); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 225e678b7..1931e8ac6 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -21,6 +21,20 @@ const __dirname = path.dirname(__filename); /** Root of the copilot-sdk repo */ export const REPO_ROOT = path.resolve(__dirname, "../.."); +/** Event types to exclude from generation (internal/legacy types) */ +export const EXCLUDED_EVENT_TYPES = new Set(["session.import_legacy"]); + +export interface DefinitionCollections { + definitions?: Record; + $defs?: Record; +} + +export interface JSONSchema7WithDefs extends JSONSchema7, DefinitionCollections {} + +export type SchemaWithSharedDefinitions = T & { + definitions: Record; + $defs: Record; +}; // ── Schema paths ──────────────────────────────────────────────────────────── export async function getSessionEventsSchemaPath(): Promise { @@ -51,7 +65,7 @@ export async function getApiSchemaPath(cliArg?: string): Promise { export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { if (typeof schema !== "object" || schema === null) return schema; - const processed: JSONSchema7 = { ...schema }; + const processed = { ...schema } as JSONSchema7WithDefs; if ("const" in processed && typeof processed.const === "boolean") { processed.enum = [processed.const]; @@ -84,13 +98,28 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { } } - if (processed.definitions) { - const newDefs: Record = {}; - for (const [key, value] of Object.entries(processed.definitions)) { + const { definitions, $defs } = collectDefinitionCollections(processed as Record); + let newDefs: Record | undefined; + if (Object.keys(definitions).length > 0) { + newDefs = {}; + for (const [key, value] of Object.entries(definitions)) { newDefs[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value; } processed.definitions = newDefs; } + let newDraftDefs: Record | undefined; + if (Object.keys($defs).length > 0) { + newDraftDefs = {}; + for (const [key, value] of Object.entries($defs)) { + newDraftDefs[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value; + } + processed.$defs = newDraftDefs; + } + if (processed.definitions && !processed.$defs) { + processed.$defs = { ...(newDefs ?? processed.definitions) }; + } else if (processed.$defs && !processed.definitions) { + processed.definitions = { ...processed.$defs }; + } if (typeof processed.additionalProperties === "object") { processed.additionalProperties = postProcessSchema(processed.additionalProperties as JSONSchema7); @@ -282,6 +311,8 @@ function sortJsonValue(value: unknown): unknown { } export interface ApiSchema { + definitions?: Record; + $defs?: Record; server?: Record; session?: Record; clientSession?: Record; @@ -291,6 +322,135 @@ export function isRpcMethod(node: unknown): node is RpcMethod { return typeof node === "object" && node !== null && "rpcMethod" in node; } +function normalizeSchemaDefinitionTitles(definition: JSONSchema7Definition): JSONSchema7Definition { + return typeof definition === "object" && definition !== null + ? normalizeSchemaTitles(definition as JSONSchema7) + : definition; +} + +export function normalizeSchemaTitles(schema: JSONSchema7): JSONSchema7 { + if (typeof schema !== "object" || schema === null) return schema; + + const normalized = { ...schema } as JSONSchema7WithDefs & Record; + delete normalized.title; + delete normalized.titleSource; + + if (normalized.properties) { + const newProps: Record = {}; + for (const [key, value] of Object.entries(normalized.properties)) { + newProps[key] = normalizeSchemaDefinitionTitles(value); + } + normalized.properties = newProps; + } + + if (normalized.items) { + if (typeof normalized.items === "object" && !Array.isArray(normalized.items)) { + normalized.items = normalizeSchemaTitles(normalized.items as JSONSchema7); + } else if (Array.isArray(normalized.items)) { + normalized.items = normalized.items.map((item) => normalizeSchemaDefinitionTitles(item)) as JSONSchema7Definition[]; + } + } + + for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { + if (normalized[combiner]) { + normalized[combiner] = normalized[combiner]!.map((item) => normalizeSchemaDefinitionTitles(item)) as JSONSchema7Definition[]; + } + } + + if (normalized.additionalProperties && typeof normalized.additionalProperties === "object") { + normalized.additionalProperties = normalizeSchemaTitles(normalized.additionalProperties as JSONSchema7); + } + + if (normalized.propertyNames && typeof normalized.propertyNames === "object" && !Array.isArray(normalized.propertyNames)) { + normalized.propertyNames = normalizeSchemaTitles(normalized.propertyNames as JSONSchema7); + } + + if (normalized.contains && typeof normalized.contains === "object" && !Array.isArray(normalized.contains)) { + normalized.contains = normalizeSchemaTitles(normalized.contains as JSONSchema7); + } + + if (normalized.not && typeof normalized.not === "object" && !Array.isArray(normalized.not)) { + normalized.not = normalizeSchemaTitles(normalized.not as JSONSchema7); + } + + if (normalized.if && typeof normalized.if === "object" && !Array.isArray(normalized.if)) { + normalized.if = normalizeSchemaTitles(normalized.if as JSONSchema7); + } + if (normalized.then && typeof normalized.then === "object" && !Array.isArray(normalized.then)) { + normalized.then = normalizeSchemaTitles(normalized.then as JSONSchema7); + } + if (normalized.else && typeof normalized.else === "object" && !Array.isArray(normalized.else)) { + normalized.else = normalizeSchemaTitles(normalized.else as JSONSchema7); + } + + if (normalized.patternProperties) { + const newPatternProps: Record = {}; + for (const [key, value] of Object.entries(normalized.patternProperties)) { + newPatternProps[key] = normalizeSchemaDefinitionTitles(value); + } + normalized.patternProperties = newPatternProps; + } + + const { definitions, $defs } = collectDefinitionCollections(normalized as Record); + if (Object.keys(definitions).length > 0) { + const newDefs: Record = {}; + for (const [key, value] of Object.entries(definitions)) { + newDefs[key] = normalizeSchemaDefinitionTitles(value); + } + normalized.definitions = newDefs; + } + if (Object.keys($defs).length > 0) { + const newDraftDefs: Record = {}; + for (const [key, value] of Object.entries($defs)) { + newDraftDefs[key] = normalizeSchemaDefinitionTitles(value); + } + normalized.$defs = newDraftDefs; + } + + return normalized; +} + +function normalizeApiNode(node: Record | undefined): Record | undefined { + if (!node) return undefined; + + const normalizedNode: Record = {}; + for (const [key, value] of Object.entries(node)) { + if (isRpcMethod(value)) { + const method = value as RpcMethod; + normalizedNode[key] = { + ...method, + params: method.params ? normalizeSchemaTitles(method.params) : method.params, + result: method.result ? normalizeSchemaTitles(method.result) : method.result, + }; + } else if (typeof value === "object" && value !== null) { + normalizedNode[key] = normalizeApiNode(value as Record); + } else { + normalizedNode[key] = value; + } + } + + return normalizedNode; +} + +export function normalizeApiSchema(schema: ApiSchema): ApiSchema { + return { + ...schema, + definitions: schema.definitions + ? Object.fromEntries( + Object.entries(schema.definitions).map(([key, value]) => [key, normalizeSchemaDefinitionTitles(value)]) + ) + : schema.definitions, + $defs: schema.$defs + ? Object.fromEntries( + Object.entries(schema.$defs).map(([key, value]) => [key, normalizeSchemaDefinitionTitles(value)]) + ) + : schema.$defs, + server: normalizeApiNode(schema.server), + session: normalizeApiNode(schema.session), + clientSession: normalizeApiNode(schema.clientSession), + }; +} + /** Returns true when every leaf RPC method inside `node` is marked experimental. */ export function isNodeFullyExperimental(node: Record): boolean { const methods: RpcMethod[] = []; @@ -305,3 +465,163 @@ export function isNodeFullyExperimental(node: Record): boolean })(node); return methods.length > 0 && methods.every(m => m.stability === "experimental"); } + +// ── $ref resolution ───────────────────────────────────────────────────────── + +/** Extract the generated type name from a `$ref` path (e.g. "#/definitions/Model" → "Model"). */ +export function refTypeName(ref: string, definitions?: DefinitionCollections): string { + const baseName = ref.split("/").pop()!; + const match = ref.match(/^#\/(definitions|\$defs)\/(.+)$/); + if (!match || match[1] !== "$defs" || !definitions) return baseName; + + const key = match[2]; + const legacyDefinition = definitions.definitions?.[key]; + const draftDefinition = definitions.$defs?.[key]; + if ( + legacyDefinition !== undefined && + draftDefinition !== undefined && + stableStringify(legacyDefinition) !== stableStringify(draftDefinition) + ) { + return `Draft${baseName}`; + } + + return baseName; +} + +/** Resolve a `$ref` path against a definitions map, returning the referenced schema. */ +export function resolveRef( + ref: string, + definitions: DefinitionCollections | undefined +): JSONSchema7 | undefined { + const match = ref.match(/^#\/(definitions|\$defs)\/(.+)$/); + if (!match || !definitions) return undefined; + const [, namespace, key] = match; + const primary = namespace === "$defs" ? definitions.$defs : definitions.definitions; + const fallback = namespace === "$defs" ? definitions.definitions : definitions.$defs; + const def = primary?.[key] ?? fallback?.[key]; + return typeof def === "object" ? (def as JSONSchema7) : undefined; +} + +export function resolveSchema( + schema: JSONSchema7 | null | undefined, + definitions: DefinitionCollections | undefined +): JSONSchema7 | undefined { + let current = schema ?? undefined; + const seenRefs = new Set(); + while (current?.$ref) { + if (seenRefs.has(current.$ref)) break; + seenRefs.add(current.$ref); + const resolved = resolveRef(current.$ref, definitions); + if (!resolved) break; + current = resolved; + } + return current; +} + +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; + + if (resolved.allOf) { + const mergedProperties: Record = {}; + const mergedRequired = new Set(); + const merged: JSONSchema7 = { + type: "object", + description: resolved.description, + }; + let hasObjectShape = false; + + 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; + } + } + + if (!hasObjectShape) return resolved; + if (Object.keys(mergedProperties).length > 0) { + merged.properties = mergedProperties; + } + if (mergedRequired.size > 0) { + merged.required = [...mergedRequired]; + } + return merged; + } + + const singleBranch = (resolved.anyOf ?? resolved.oneOf) + ?.filter((item): item is JSONSchema7 => typeof item === "object" && (item as JSONSchema7).type !== "null"); + if (singleBranch && singleBranch.length === 1) { + return resolveObjectSchema(singleBranch[0], definitions); + } + + return resolved; +} + +export function hasSchemaPayload(schema: JSONSchema7 | null | undefined): boolean { + if (!schema) return false; + if (schema.properties) return Object.keys(schema.properties).length > 0; + if (schema.additionalProperties) return true; + if (schema.items) return true; + if (schema.anyOf || schema.oneOf || schema.allOf) return true; + if (schema.enum && schema.enum.length > 0) return true; + if (schema.const !== undefined) return true; + if (schema.$ref) return true; + if (Array.isArray(schema.type)) return schema.type.length > 0 && !(schema.type.length === 1 && schema.type[0] === "object"); + return schema.type !== undefined && schema.type !== "object"; +} + +export function collectDefinitionCollections( + schema: Record +): Required { + return { + definitions: { ...((schema.definitions ?? {}) as Record) }, + $defs: { ...((schema.$defs ?? {}) as Record) }, + }; +} + +/** Collect the shared definitions from a schema (handles both `definitions` and `$defs`). */ +export function collectDefinitions( + schema: Record +): Record { + const { definitions, $defs } = collectDefinitionCollections(schema); + return { ...$defs, ...definitions }; +} + +export function withSharedDefinitions( + schema: T, + definitions: DefinitionCollections +): SchemaWithSharedDefinitions { + const legacyDefinitions = { ...(definitions.definitions ?? {}) }; + const draft2019Definitions = { ...(definitions.$defs ?? {}) }; + + const sharedLegacyDefinitions = + Object.keys(legacyDefinitions).length > 0 ? legacyDefinitions : { ...draft2019Definitions }; + const sharedDraftDefinitions = + Object.keys(draft2019Definitions).length > 0 ? draft2019Definitions : { ...legacyDefinitions }; + + return { + ...schema, + definitions: sharedLegacyDefinitions, + $defs: sharedDraftDefinitions, + }; +} From 06e4964811122b4f05fd8a4cfdfdce42b1ede357 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:11:48 -0400 Subject: [PATCH 16/34] Update @github/copilot to 1.0.26 (#1076) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update @github/copilot to 1.0.26 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Fix build: update .NET test Workspace→Workspaces, fix Python rpc.py lint errors Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/89ca7d21-b99f-493f-9155-4ba940ad7293 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * placeholder Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/a6ce684b-599e-45bd-956a-c351c5699f53 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix Python codegen: strip quicktype duplicate imports and trailing whitespace Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/a6ce684b-599e-45bd-956a-c351c5699f53 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix build for workspaces RPC update Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 249 +++++++++++++++++++++++++---- dotnet/test/RpcTests.cs | 12 +- go/internal/e2e/rpc_test.go | 12 +- go/rpc/generated_rpc.go | 133 +++++++++++++--- nodejs/package-lock.json | 56 +++---- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 67 ++++++-- nodejs/test/e2e/rpc.test.ts | 12 +- python/copilot/generated/rpc.py | 258 +++++++++++++++++++++++++------ python/e2e/test_rpc.py | 22 +-- scripts/codegen/csharp.ts | 5 + scripts/codegen/python.ts | 18 ++- test/harness/package-lock.json | 56 +++---- test/harness/package.json | 2 +- 15 files changed, 708 insertions(+), 198 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 0bea6e8db..342d35c25 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -493,6 +493,37 @@ internal sealed class ModeSetRequest public SessionMode Mode { get; set; } } +/// RPC data type for NameGet operations. +public sealed class NameGetResult +{ + /// The session name, falling back to the auto-generated summary, or null if neither exists. + [JsonPropertyName("name")] + public string? Name { get; set; } +} + +/// RPC data type for SessionNameGet operations. +internal sealed class SessionNameGetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for NameSet operations. +internal sealed class NameSetRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + + /// New session name (1–100 characters, trimmed of leading/trailing whitespace). + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [MaxLength(100)] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + /// RPC data type for PlanRead operations. public sealed class PlanReadResult { @@ -537,32 +568,113 @@ internal sealed class SessionPlanDeleteRequest public string SessionId { get; set; } = string.Empty; } -/// RPC data type for WorkspaceListFiles operations. -public sealed class WorkspaceListFilesResult +/// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. +public sealed class WorkspacesGetWorkspaceResultWorkspace +{ + /// Gets or sets the id value. + [JsonPropertyName("id")] + public Guid Id { get; set; } + + /// Gets or sets the cwd value. + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } + + /// Gets or sets the git_root value. + [JsonPropertyName("git_root")] + public string? GitRoot { get; set; } + + /// Gets or sets the repository value. + [JsonPropertyName("repository")] + public string? Repository { get; set; } + + /// Gets or sets the host_type value. + [JsonPropertyName("host_type")] + public WorkspacesGetWorkspaceResultWorkspaceHostType? HostType { get; set; } + + /// Gets or sets the branch value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + + /// Gets or sets the summary value. + [JsonPropertyName("summary")] + public string? Summary { get; set; } + + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string? Name { get; set; } + + /// Gets or sets the summary_count value. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("summary_count")] + public long? SummaryCount { get; set; } + + /// Gets or sets the created_at value. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } + + /// Gets or sets the updated_at value. + [JsonPropertyName("updated_at")] + public DateTimeOffset? UpdatedAt { get; set; } + + /// Gets or sets the mc_task_id value. + [JsonPropertyName("mc_task_id")] + public string? McTaskId { get; set; } + + /// Gets or sets the mc_session_id value. + [JsonPropertyName("mc_session_id")] + public string? McSessionId { get; set; } + + /// Gets or sets the mc_last_event_id value. + [JsonPropertyName("mc_last_event_id")] + public string? McLastEventId { get; set; } + + /// Gets or sets the session_sync_level value. + [JsonPropertyName("session_sync_level")] + public WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel? SessionSyncLevel { get; set; } +} + +/// RPC data type for WorkspacesGetWorkspace operations. +public sealed class WorkspacesGetWorkspaceResult +{ + /// Current workspace metadata, or null if not available. + [JsonPropertyName("workspace")] + public WorkspacesGetWorkspaceResultWorkspace? Workspace { get; set; } +} + +/// RPC data type for SessionWorkspacesGetWorkspace operations. +internal sealed class SessionWorkspacesGetWorkspaceRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for WorkspacesListFiles operations. +public sealed class WorkspacesListFilesResult { /// Relative file paths in the workspace files directory. [JsonPropertyName("files")] public IList Files { get => field ??= []; set; } } -/// RPC data type for SessionWorkspaceListFiles operations. -internal sealed class SessionWorkspaceListFilesRequest +/// RPC data type for SessionWorkspacesListFiles operations. +internal sealed class SessionWorkspacesListFilesRequest { /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; } -/// RPC data type for WorkspaceReadFile operations. -public sealed class WorkspaceReadFileResult +/// RPC data type for WorkspacesReadFile operations. +public sealed class WorkspacesReadFileResult { /// File content as a UTF-8 string. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; } -/// RPC data type for WorkspaceReadFile operations. -internal sealed class WorkspaceReadFileRequest +/// RPC data type for WorkspacesReadFile operations. +internal sealed class WorkspacesReadFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -573,8 +685,8 @@ internal sealed class WorkspaceReadFileRequest public string Path { get; set; } = string.Empty; } -/// RPC data type for WorkspaceCreateFile operations. -internal sealed class WorkspaceCreateFileRequest +/// RPC data type for WorkspacesCreateFile operations. +internal sealed class WorkspacesCreateFileRequest { /// Target session identifier. [JsonPropertyName("sessionId")] @@ -1754,6 +1866,35 @@ public enum SessionMode } +/// Defines the allowed values. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum WorkspacesGetWorkspaceResultWorkspaceHostType +{ + /// The github variant. + [JsonStringEnumMemberName("github")] + Github, + /// The ado variant. + [JsonStringEnumMemberName("ado")] + Ado, +} + + +/// Defines the allowed values. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel +{ + /// The local variant. + [JsonStringEnumMemberName("local")] + Local, + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The repo_and_user variant. + [JsonStringEnumMemberName("repo_and_user")] + RepoAndUser, +} + + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonConverter(typeof(JsonStringEnumConverter))] public enum McpServerStatus @@ -2036,8 +2177,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId) _sessionId = sessionId; Model = new ModelApi(rpc, sessionId); Mode = new ModeApi(rpc, sessionId); + Name = new NameApi(rpc, sessionId); Plan = new PlanApi(rpc, sessionId); - Workspace = new WorkspaceApi(rpc, sessionId); + Workspaces = new WorkspacesApi(rpc, sessionId); Fleet = new FleetApi(rpc, sessionId); Agent = new AgentApi(rpc, sessionId); Skills = new SkillsApi(rpc, sessionId); @@ -2059,11 +2201,14 @@ internal SessionRpc(JsonRpc rpc, string sessionId) /// Mode APIs. public ModeApi Mode { get; } + /// Name APIs. + public NameApi Name { get; } + /// Plan APIs. public PlanApi Plan { get; } - /// Workspace APIs. - public WorkspaceApi Workspace { get; } + /// Workspaces APIs. + public WorkspacesApi Workspaces { get; } /// Fleet APIs. public FleetApi Fleet { get; } @@ -2166,6 +2311,33 @@ public async Task SetAsync(SessionMode mode, CancellationToken cancellationToken } } +/// Provides session-scoped Name APIs. +public sealed class NameApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal NameApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.name.get". + public async Task GetAsync(CancellationToken cancellationToken = default) + { + var request = new SessionNameGetRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.name.get", [request], cancellationToken); + } + + /// Calls "session.name.set". + public async Task SetAsync(string name, CancellationToken cancellationToken = default) + { + var request = new NameSetRequest { SessionId = _sessionId, Name = name }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.name.set", [request], cancellationToken); + } +} + /// Provides session-scoped Plan APIs. public sealed class PlanApi { @@ -2200,37 +2372,44 @@ public async Task DeleteAsync(CancellationToken cancellationToken = default) } } -/// Provides session-scoped Workspace APIs. -public sealed class WorkspaceApi +/// Provides session-scoped Workspaces APIs. +public sealed class WorkspacesApi { private readonly JsonRpc _rpc; private readonly string _sessionId; - internal WorkspaceApi(JsonRpc rpc, string sessionId) + internal WorkspacesApi(JsonRpc rpc, string sessionId) { _rpc = rpc; _sessionId = sessionId; } - /// Calls "session.workspace.listFiles". - public async Task ListFilesAsync(CancellationToken cancellationToken = default) + /// Calls "session.workspaces.getWorkspace". + public async Task GetWorkspaceAsync(CancellationToken cancellationToken = default) + { + var request = new SessionWorkspacesGetWorkspaceRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspaces.getWorkspace", [request], cancellationToken); + } + + /// Calls "session.workspaces.listFiles". + public async Task ListFilesAsync(CancellationToken cancellationToken = default) { - var request = new SessionWorkspaceListFilesRequest { SessionId = _sessionId }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.listFiles", [request], cancellationToken); + var request = new SessionWorkspacesListFilesRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspaces.listFiles", [request], cancellationToken); } - /// Calls "session.workspace.readFile". - public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) + /// Calls "session.workspaces.readFile". + public async Task ReadFileAsync(string path, CancellationToken cancellationToken = default) { - var request = new WorkspaceReadFileRequest { SessionId = _sessionId, Path = path }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.readFile", [request], cancellationToken); + var request = new WorkspacesReadFileRequest { SessionId = _sessionId, Path = path }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.workspaces.readFile", [request], cancellationToken); } - /// Calls "session.workspace.createFile". + /// Calls "session.workspaces.createFile". public async Task CreateFileAsync(string path, string content, CancellationToken cancellationToken = default) { - var request = new WorkspaceCreateFileRequest { SessionId = _sessionId, Path = path, Content = content }; - await CopilotClient.InvokeRpcAsync(_rpc, "session.workspace.createFile", [request], cancellationToken); + var request = new WorkspacesCreateFileRequest { SessionId = _sessionId, Path = path, Content = content }; + await CopilotClient.InvokeRpcAsync(_rpc, "session.workspaces.createFile", [request], cancellationToken); } } @@ -2812,6 +2991,8 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func f.Contains("nested.txt")); } diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index 5a79a7509..819e8ccca 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -307,7 +307,7 @@ func TestSessionRpc(t *testing.T) { } // Initially no files - initialFiles, err := session.RPC.Workspace.ListFiles(t.Context()) + initialFiles, err := session.RPC.Workspaces.ListFiles(t.Context()) if err != nil { t.Fatalf("Failed to list files: %v", err) } @@ -317,7 +317,7 @@ func TestSessionRpc(t *testing.T) { // Create a file fileContent := "Hello, workspace!" - _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.WorkspaceCreateFileRequest{ + _, err = session.RPC.Workspaces.CreateFile(t.Context(), &rpc.WorkspacesCreateFileRequest{ Path: "test.txt", Content: fileContent, }) @@ -326,7 +326,7 @@ func TestSessionRpc(t *testing.T) { } // List files - afterCreate, err := session.RPC.Workspace.ListFiles(t.Context()) + afterCreate, err := session.RPC.Workspaces.ListFiles(t.Context()) if err != nil { t.Fatalf("Failed to list files after create: %v", err) } @@ -335,7 +335,7 @@ func TestSessionRpc(t *testing.T) { } // Read file - readResult, err := session.RPC.Workspace.ReadFile(t.Context(), &rpc.WorkspaceReadFileRequest{ + readResult, err := session.RPC.Workspaces.ReadFile(t.Context(), &rpc.WorkspacesReadFileRequest{ Path: "test.txt", }) if err != nil { @@ -346,7 +346,7 @@ func TestSessionRpc(t *testing.T) { } // Create nested file - _, err = session.RPC.Workspace.CreateFile(t.Context(), &rpc.WorkspaceCreateFileRequest{ + _, err = session.RPC.Workspaces.CreateFile(t.Context(), &rpc.WorkspacesCreateFileRequest{ Path: "subdir/nested.txt", Content: "Nested content", }) @@ -354,7 +354,7 @@ func TestSessionRpc(t *testing.T) { t.Fatalf("Failed to create nested file: %v", err) } - afterNested, err := session.RPC.Workspace.ListFiles(t.Context()) + afterNested, err := session.RPC.Workspaces.ListFiles(t.Context()) if err != nil { t.Fatalf("Failed to list files after nested: %v", err) } diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 75660a0e0..39478b0c4 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -344,6 +344,19 @@ type ModeSetRequest struct { Mode SessionMode `json:"mode"` } +type NameGetResult struct { + // The session name, falling back to the auto-generated summary, or null if neither exists + Name *string `json:"name"` +} + +type NameSetResult struct { +} + +type NameSetRequest struct { + // New session name (1–100 characters, trimmed of leading/trailing whitespace) + Name string `json:"name"` +} + type PlanReadResult struct { // The content of the plan file, or null if it does not exist Content *string `json:"content"` @@ -364,25 +377,48 @@ type PlanUpdateRequest struct { type PlanDeleteResult struct { } -type WorkspaceListFilesResult struct { +type WorkspacesGetWorkspaceResult struct { + // Current workspace metadata, or null if not available + Workspace *WorkspaceClass `json:"workspace"` +} + +type WorkspaceClass struct { + Branch *string `json:"branch,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Cwd *string `json:"cwd,omitempty"` + GitRoot *string `json:"git_root,omitempty"` + HostType *HostType `json:"host_type,omitempty"` + ID string `json:"id"` + McLastEventID *string `json:"mc_last_event_id,omitempty"` + McSessionID *string `json:"mc_session_id,omitempty"` + McTaskID *string `json:"mc_task_id,omitempty"` + Name *string `json:"name,omitempty"` + Repository *string `json:"repository,omitempty"` + SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` + Summary *string `json:"summary,omitempty"` + SummaryCount *int64 `json:"summary_count,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +type WorkspacesListFilesResult struct { // Relative file paths in the workspace files directory Files []string `json:"files"` } -type WorkspaceReadFileResult struct { +type WorkspacesReadFileResult struct { // File content as a UTF-8 string Content string `json:"content"` } -type WorkspaceReadFileRequest struct { +type WorkspacesReadFileRequest struct { // Relative path within the workspace files directory Path string `json:"path"` } -type WorkspaceCreateFileResult struct { +type WorkspacesCreateFileResult struct { } -type WorkspaceCreateFileRequest struct { +type WorkspacesCreateFileRequest struct { // File content to write as a UTF-8 string Content string `json:"content"` // Relative path within the workspace files directory @@ -1058,8 +1094,8 @@ const ( type MCPConfigType string const ( - MCPConfigTypeLocal MCPConfigType = "local" MCPConfigTypeHTTP MCPConfigType = "http" + MCPConfigTypeLocal MCPConfigType = "local" MCPConfigTypeSSE MCPConfigType = "sse" MCPConfigTypeStdio MCPConfigType = "stdio" ) @@ -1103,6 +1139,21 @@ const ( SessionModePlan SessionMode = "plan" ) +type HostType string + +const ( + HostTypeAdo HostType = "ado" + HostTypeGithub HostType = "github" +) + +type SessionSyncLevel string + +const ( + SessionSyncLevelRepoAndUser SessionSyncLevel = "repo_and_user" + SessionSyncLevelLocal SessionSyncLevel = "local" + SessionSyncLevelUser SessionSyncLevel = "user" +) + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured type MCPServerStatus string @@ -1427,6 +1478,37 @@ func (a *ModeApi) Set(ctx context.Context, params *ModeSetRequest) (*ModeSetResu return &result, nil } +type NameApi sessionApi + +func (a *NameApi) Get(ctx context.Context) (*NameGetResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.name.get", req) + if err != nil { + return nil, err + } + var result NameGetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *NameApi) Set(ctx context.Context, params *NameSetRequest) (*NameSetResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["name"] = params.Name + } + raw, err := a.client.Request("session.name.set", req) + if err != nil { + return nil, err + } + var result NameSetResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + type PlanApi sessionApi func (a *PlanApi) Read(ctx context.Context) (*PlanReadResult, error) { @@ -1471,48 +1553,61 @@ func (a *PlanApi) Delete(ctx context.Context) (*PlanDeleteResult, error) { return &result, nil } -type WorkspaceApi sessionApi +type WorkspacesApi sessionApi + +func (a *WorkspacesApi) GetWorkspace(ctx context.Context) (*WorkspacesGetWorkspaceResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.workspaces.getWorkspace", req) + if err != nil { + return nil, err + } + var result WorkspacesGetWorkspaceResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} -func (a *WorkspaceApi) ListFiles(ctx context.Context) (*WorkspaceListFilesResult, error) { +func (a *WorkspacesApi) ListFiles(ctx context.Context) (*WorkspacesListFilesResult, error) { req := map[string]any{"sessionId": a.sessionID} - raw, err := a.client.Request("session.workspace.listFiles", req) + raw, err := a.client.Request("session.workspaces.listFiles", req) if err != nil { return nil, err } - var result WorkspaceListFilesResult + var result WorkspacesListFilesResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *WorkspaceApi) ReadFile(ctx context.Context, params *WorkspaceReadFileRequest) (*WorkspaceReadFileResult, error) { +func (a *WorkspacesApi) ReadFile(ctx context.Context, params *WorkspacesReadFileRequest) (*WorkspacesReadFileResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["path"] = params.Path } - raw, err := a.client.Request("session.workspace.readFile", req) + raw, err := a.client.Request("session.workspaces.readFile", req) if err != nil { return nil, err } - var result WorkspaceReadFileResult + var result WorkspacesReadFileResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } return &result, nil } -func (a *WorkspaceApi) CreateFile(ctx context.Context, params *WorkspaceCreateFileRequest) (*WorkspaceCreateFileResult, error) { +func (a *WorkspacesApi) CreateFile(ctx context.Context, params *WorkspacesCreateFileRequest) (*WorkspacesCreateFileResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["path"] = params.Path req["content"] = params.Content } - raw, err := a.client.Request("session.workspace.createFile", req) + raw, err := a.client.Request("session.workspaces.createFile", req) if err != nil { return nil, err } - var result WorkspaceCreateFileResult + var result WorkspacesCreateFileResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } @@ -2007,8 +2102,9 @@ type SessionRpc struct { Model *ModelApi Mode *ModeApi + Name *NameApi Plan *PlanApi - Workspace *WorkspaceApi + Workspaces *WorkspacesApi Fleet *FleetApi Agent *AgentApi Skills *SkillsApi @@ -2054,8 +2150,9 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { r.common = sessionApi{client: client, sessionID: sessionID} r.Model = (*ModelApi)(&r.common) r.Mode = (*ModeApi)(&r.common) + r.Name = (*NameApi)(&r.common) r.Plan = (*PlanApi)(&r.common) - r.Workspace = (*WorkspaceApi)(&r.common) + r.Workspaces = (*WorkspacesApi)(&r.common) r.Fleet = (*FleetApi)(&r.common) r.Agent = (*AgentApi)(&r.common) r.Skills = (*SkillsApi)(&r.common) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index cc4407bbb..1c84c1d71 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.26-0", + "@github/copilot": "^1.0.26", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26-0.tgz", - "integrity": "sha512-MHeddlLZCi5OFeuzKRtj7kmJVm1o/teNwgrL5/FHU9x0H6VioG+KGlY6gd1H/cTJ763dtYQyACMPYFUNVVY52g==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26.tgz", + "integrity": "sha512-F7P6yimFzjvWxOF/A0F6k//vcpSVcVusQjaybb3IKyrEDhnd/LOv2tD+x6W0IoxCftGDDhkzBA2aon3rL9lPhQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.26-0", - "@github/copilot-darwin-x64": "1.0.26-0", - "@github/copilot-linux-arm64": "1.0.26-0", - "@github/copilot-linux-x64": "1.0.26-0", - "@github/copilot-win32-arm64": "1.0.26-0", - "@github/copilot-win32-x64": "1.0.26-0" + "@github/copilot-darwin-arm64": "1.0.26", + "@github/copilot-darwin-x64": "1.0.26", + "@github/copilot-linux-arm64": "1.0.26", + "@github/copilot-linux-x64": "1.0.26", + "@github/copilot-win32-arm64": "1.0.26", + "@github/copilot-win32-x64": "1.0.26" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26-0.tgz", - "integrity": "sha512-C1GP4qrKjCjPoKr485o0IbcP3n1q/4LxKwAhpga0V+9ZHlvggZ58YB9AaUFySJ+Alpu1vBlw/FFpD9amroasvw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26.tgz", + "integrity": "sha512-eV+jDMj4vnjdGcG+c4zg11zZKVAp94Hm4sK4f9LnyWw8MumTfS5F2Yyse9zt7A3oGlegyczmJopKwuwZbQd4ww==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26-0.tgz", - "integrity": "sha512-A/HSuoCe8i5+yc5yCi4ZMi6PQfOOExA0wwpN13zFKwmqDwdNdogb4/wX42DoGr7JwuOGhZSzXCEZirt/lqqxjQ==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26.tgz", + "integrity": "sha512-2AAgu19F3scDlYhsiHxCn0cz4ZkINq8gxnqW0an8VQn6p15lDcah6PqHw+RJ+12qiYX5L5NNACty9UOkIK7Kzg==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26-0.tgz", - "integrity": "sha512-goMPZkMi5dCqA1JHbgsxaUKOmtZ6juBAeUfVomtKmdKee1KC74TFXlEuP8qJMGkeug2yivPOptAfQQXSyJJnHw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26.tgz", + "integrity": "sha512-SnM7+TGAZ/i9dim5FfHM7+ii01hdpHJzzh8vnnA1Fa7RPFJaQ2KTOdTDJFgfv6e/jLhKXZEelYIidgCA3vSQCQ==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26-0.tgz", - "integrity": "sha512-oK6uQ0Q0ZUO9IM3B+KJb9wyRHG5ZGP5qoTOOTN7JcC+p8ZveNSGCAHUAtzLSflUREJUFYfRZauUKcfV31/Y2LA==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26.tgz", + "integrity": "sha512-x76vcwVbi0j03hFMhiQs+Eqefd9Xmc4qJoaj44YA2VsJuDbZw2Yv7ZBq7Vyxd/shJwJZjaKv36MHcx5bVUMBJQ==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26-0.tgz", - "integrity": "sha512-VXwm8xryO3cUHydVkzmSzb0M3WonwGDHCcgwI2GGS2YkHB9VjmRbdpVeLYeDB5EzmyZLSd7Nr4+i2X0gsU93ow==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26.tgz", + "integrity": "sha512-enVRcy7W9RD1bwYkF+mcxR+biXsG/X5m46XBaD0opvfDeiBHceDnI8hEI0O1A5PYvRo88AZFvDEmEW3Gdj6+rQ==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.26-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26-0.tgz", - "integrity": "sha512-+4IFUZbYSg5jxchEFdgVEgSDJzDE/P3nRDtEBcIhpYlVb7/zAw2JCkCJr+i4Aruo4zysJnEybL0wM3TpcWTt/g==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26.tgz", + "integrity": "sha512-DpJF6C1x4+sYIXUx5+vWCu6cFAbD2YlrXQ/BRttf2MMdc0DHwdgJxrttBBF2qCvmpfzjSE8cr5G0kt5EUk7FGw==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index f4a3a2188..ca2e0afe7 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.26-0", + "@github/copilot": "^1.0.26", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 3c5ebfd97..d85bc1e5e 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.22", + "@github/copilot": "^1.0.26", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 8214dec4e..9b70619f8 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -595,6 +595,20 @@ export interface ModeSetRequest { mode: SessionMode; } +export interface NameGetResult { + /** + * The session name, falling back to the auto-generated summary, or null if neither exists + */ + name: string | null; +} + +export interface NameSetRequest { + /** + * New session name (1–100 characters, trimmed of leading/trailing whitespace) + */ + name: string; +} + export interface PlanReadResult { /** * Whether the plan file exists in the workspace @@ -617,28 +631,51 @@ export interface PlanUpdateRequest { content: string; } -export interface WorkspaceListFilesResult { +export interface WorkspacesGetWorkspaceResult { + /** + * Current workspace metadata, or null if not available + */ + workspace: { + id: string; + cwd?: string; + git_root?: string; + repository?: string; + host_type?: "github" | "ado"; + branch?: string; + summary?: string; + name?: string; + summary_count?: number; + created_at?: string; + updated_at?: string; + mc_task_id?: string; + mc_session_id?: string; + mc_last_event_id?: string; + session_sync_level?: "local" | "user" | "repo_and_user"; + } | null; +} + +export interface WorkspacesListFilesResult { /** * Relative file paths in the workspace files directory */ files: string[]; } -export interface WorkspaceReadFileResult { +export interface WorkspacesReadFileResult { /** * File content as a UTF-8 string */ content: string; } -export interface WorkspaceReadFileRequest { +export interface WorkspacesReadFileRequest { /** * Relative path within the workspace files directory */ path: string; } -export interface WorkspaceCreateFileRequest { +export interface WorkspacesCreateFileRequest { /** * Relative path within the workspace files directory */ @@ -1593,6 +1630,12 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin set: async (params: Omit): Promise => connection.sendRequest("session.mode.set", { sessionId, ...params }), }, + name: { + get: async (): Promise => + connection.sendRequest("session.name.get", { sessionId }), + set: async (params: Omit): Promise => + connection.sendRequest("session.name.set", { sessionId, ...params }), + }, plan: { read: async (): Promise => connection.sendRequest("session.plan.read", { sessionId }), @@ -1601,13 +1644,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin delete: async (): Promise => connection.sendRequest("session.plan.delete", { sessionId }), }, - workspace: { - listFiles: async (): Promise => - connection.sendRequest("session.workspace.listFiles", { sessionId }), - readFile: async (params: Omit): Promise => - connection.sendRequest("session.workspace.readFile", { sessionId, ...params }), - createFile: async (params: Omit): Promise => - connection.sendRequest("session.workspace.createFile", { sessionId, ...params }), + workspaces: { + getWorkspace: async (): Promise => + connection.sendRequest("session.workspaces.getWorkspace", { sessionId }), + listFiles: async (): Promise => + connection.sendRequest("session.workspaces.listFiles", { sessionId }), + readFile: async (params: Omit): Promise => + connection.sendRequest("session.workspaces.readFile", { sessionId, ...params }), + createFile: async (params: Omit): Promise => + connection.sendRequest("session.workspaces.createFile", { sessionId, ...params }), }, /** @experimental */ fleet: { diff --git a/nodejs/test/e2e/rpc.test.ts b/nodejs/test/e2e/rpc.test.ts index bca4e8cd7..a4c333139 100644 --- a/nodejs/test/e2e/rpc.test.ts +++ b/nodejs/test/e2e/rpc.test.ts @@ -156,28 +156,28 @@ describe("Session RPC", async () => { const session = await client.createSession({ onPermissionRequest: approveAll }); // Initially no files - const initialFiles = await session.rpc.workspace.listFiles(); + const initialFiles = await session.rpc.workspaces.listFiles(); expect(initialFiles.files).toEqual([]); // Create a file const fileContent = "Hello, workspace!"; - await session.rpc.workspace.createFile({ path: "test.txt", content: fileContent }); + await session.rpc.workspaces.createFile({ path: "test.txt", content: fileContent }); // List files - const afterCreate = await session.rpc.workspace.listFiles(); + const afterCreate = await session.rpc.workspaces.listFiles(); expect(afterCreate.files).toContain("test.txt"); // Read file - const readResult = await session.rpc.workspace.readFile({ path: "test.txt" }); + const readResult = await session.rpc.workspaces.readFile({ path: "test.txt" }); expect(readResult.content).toBe(fileContent); // Create nested file - await session.rpc.workspace.createFile({ + await session.rpc.workspaces.createFile({ path: "subdir/nested.txt", content: "Nested content", }); - const afterNested = await session.rpc.workspace.listFiles(); + const afterNested = await session.rpc.workspaces.listFiles(); expect(afterNested.files).toContain("test.txt"); expect(afterNested.files.some((f) => f.includes("nested.txt"))).toBe(true); }); diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index b24f74e51..a4f15d9e2 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -10,19 +10,17 @@ from collections.abc import Callable from dataclasses import dataclass -from typing import Protocol - - -from dataclasses import dataclass -from typing import Any, TypeVar, Callable, cast from datetime import datetime from enum import Enum +from typing import Any, Protocol, TypeVar, cast from uuid import UUID + import dateutil.parser T = TypeVar("T") EnumT = TypeVar("EnumT", bound=Enum) + def from_str(x: Any) -> str: assert isinstance(x, str) return x @@ -766,7 +764,7 @@ def to_dict(self) -> dict: class MCPServerSource(Enum): """Configuration source - + Configuration source: user, workspace, plugin, or builtin """ BUILTIN = "builtin" @@ -1130,6 +1128,38 @@ def to_dict(self) -> dict: result["mode"] = to_enum(SessionMode, self.mode) return result +@dataclass +class NameGetResult: + name: str | None = None + """The session name, falling back to the auto-generated summary, or null if neither exists""" + + @staticmethod + def from_dict(obj: Any) -> 'NameGetResult': + assert isinstance(obj, dict) + name = from_union([from_none, from_str], obj.get("name")) + return NameGetResult(name) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_union([from_none, from_str], self.name) + return result + +@dataclass +class NameSetRequest: + name: str + """New session name (1–100 characters, trimmed of leading/trailing whitespace)""" + + @staticmethod + def from_dict(obj: Any) -> 'NameSetRequest': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + return NameSetRequest(name) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + return result + @dataclass class PlanReadResult: exists: bool @@ -1172,16 +1202,112 @@ def to_dict(self) -> dict: result["content"] = from_str(self.content) return result +class HostType(Enum): + ADO = "ado" + GITHUB = "github" + +class SessionSyncLevel(Enum): + LOCAL = "local" + REPO_AND_USER = "repo_and_user" + USER = "user" + @dataclass -class WorkspaceListFilesResult: +class Workspace: + id: UUID + branch: str | None = None + created_at: datetime | None = None + cwd: str | None = None + git_root: str | None = None + host_type: HostType | None = None + mc_last_event_id: str | None = None + mc_session_id: str | None = None + mc_task_id: str | None = None + name: str | None = None + repository: str | None = None + session_sync_level: SessionSyncLevel | None = None + summary: str | None = None + summary_count: int | None = None + updated_at: datetime | None = None + + @staticmethod + def from_dict(obj: Any) -> 'Workspace': + assert isinstance(obj, dict) + id = UUID(obj.get("id")) + branch = from_union([from_str, from_none], obj.get("branch")) + created_at = from_union([from_datetime, from_none], obj.get("created_at")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + git_root = from_union([from_str, from_none], obj.get("git_root")) + host_type = from_union([HostType, from_none], obj.get("host_type")) + mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) + mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) + mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) + name = from_union([from_str, from_none], obj.get("name")) + repository = from_union([from_str, from_none], obj.get("repository")) + session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) + summary = from_union([from_str, from_none], obj.get("summary")) + summary_count = from_union([from_int, from_none], obj.get("summary_count")) + updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) + return Workspace(id, branch, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, repository, session_sync_level, summary, summary_count, updated_at) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = str(self.id) + if self.branch is not None: + result["branch"] = from_union([from_str, from_none], self.branch) + if self.created_at is not None: + result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.git_root is not None: + result["git_root"] = from_union([from_str, from_none], self.git_root) + if self.host_type is not None: + result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + if self.mc_last_event_id is not None: + result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) + if self.mc_session_id is not None: + result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) + if self.mc_task_id is not None: + result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.repository is not None: + result["repository"] = from_union([from_str, from_none], self.repository) + if self.session_sync_level is not None: + result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) + if self.summary is not None: + result["summary"] = from_union([from_str, from_none], self.summary) + if self.summary_count is not None: + result["summary_count"] = from_union([from_int, from_none], self.summary_count) + if self.updated_at is not None: + result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + return result + +@dataclass +class WorkspacesGetWorkspaceResult: + workspace: Workspace | None = None + """Current workspace metadata, or null if not available""" + + @staticmethod + def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': + assert isinstance(obj, dict) + workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) + return WorkspacesGetWorkspaceResult(workspace) + + def to_dict(self) -> dict: + result: dict = {} + result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) + return result + +@dataclass +class WorkspacesListFilesResult: files: list[str] """Relative file paths in the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'WorkspaceListFilesResult': + def from_dict(obj: Any) -> 'WorkspacesListFilesResult': assert isinstance(obj, dict) files = from_list(from_str, obj.get("files")) - return WorkspaceListFilesResult(files) + return WorkspacesListFilesResult(files) def to_dict(self) -> dict: result: dict = {} @@ -1189,15 +1315,15 @@ def to_dict(self) -> dict: return result @dataclass -class WorkspaceReadFileResult: +class WorkspacesReadFileResult: content: str """File content as a UTF-8 string""" @staticmethod - def from_dict(obj: Any) -> 'WorkspaceReadFileResult': + def from_dict(obj: Any) -> 'WorkspacesReadFileResult': assert isinstance(obj, dict) content = from_str(obj.get("content")) - return WorkspaceReadFileResult(content) + return WorkspacesReadFileResult(content) def to_dict(self) -> dict: result: dict = {} @@ -1205,15 +1331,15 @@ def to_dict(self) -> dict: return result @dataclass -class WorkspaceReadFileRequest: +class WorkspacesReadFileRequest: path: str """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'WorkspaceReadFileRequest': + def from_dict(obj: Any) -> 'WorkspacesReadFileRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) - return WorkspaceReadFileRequest(path) + return WorkspacesReadFileRequest(path) def to_dict(self) -> dict: result: dict = {} @@ -1221,7 +1347,7 @@ def to_dict(self) -> dict: return result @dataclass -class WorkspaceCreateFileRequest: +class WorkspacesCreateFileRequest: content: str """File content to write as a UTF-8 string""" @@ -1229,11 +1355,11 @@ class WorkspaceCreateFileRequest: """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'WorkspaceCreateFileRequest': + def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) path = from_str(obj.get("path")) - return WorkspaceCreateFileRequest(content, path) + return WorkspacesCreateFileRequest(content, path) def to_dict(self) -> dict: result: dict = {} @@ -2216,15 +2342,15 @@ class Kind(Enum): class PermissionDecision: kind: Kind """The permission request was approved - + Denied because approval rules explicitly blocked it - + Denied because no approval rule matched and user confirmation was unavailable - + Denied by the user during an interactive prompt - + Denied by the organization's content exclusion policy - + Denied by a permission request hook registered by an extension or plugin """ rules: list[Any] | None = None @@ -2235,7 +2361,7 @@ class PermissionDecision: message: str | None = None """Human-readable explanation of why the path was excluded - + Optional message from the hook explaining the denial """ path: str | None = None @@ -3235,6 +3361,18 @@ def mode_set_request_from_dict(s: Any) -> ModeSetRequest: def mode_set_request_to_dict(x: ModeSetRequest) -> Any: return to_class(ModeSetRequest, x) +def name_get_result_from_dict(s: Any) -> NameGetResult: + return NameGetResult.from_dict(s) + +def name_get_result_to_dict(x: NameGetResult) -> Any: + return to_class(NameGetResult, x) + +def name_set_request_from_dict(s: Any) -> NameSetRequest: + return NameSetRequest.from_dict(s) + +def name_set_request_to_dict(x: NameSetRequest) -> Any: + return to_class(NameSetRequest, x) + def plan_read_result_from_dict(s: Any) -> PlanReadResult: return PlanReadResult.from_dict(s) @@ -3247,29 +3385,35 @@ def plan_update_request_from_dict(s: Any) -> PlanUpdateRequest: def plan_update_request_to_dict(x: PlanUpdateRequest) -> Any: return to_class(PlanUpdateRequest, x) -def workspace_list_files_result_from_dict(s: Any) -> WorkspaceListFilesResult: - return WorkspaceListFilesResult.from_dict(s) +def workspaces_get_workspace_result_from_dict(s: Any) -> WorkspacesGetWorkspaceResult: + return WorkspacesGetWorkspaceResult.from_dict(s) + +def workspaces_get_workspace_result_to_dict(x: WorkspacesGetWorkspaceResult) -> Any: + return to_class(WorkspacesGetWorkspaceResult, x) -def workspace_list_files_result_to_dict(x: WorkspaceListFilesResult) -> Any: - return to_class(WorkspaceListFilesResult, x) +def workspaces_list_files_result_from_dict(s: Any) -> WorkspacesListFilesResult: + return WorkspacesListFilesResult.from_dict(s) -def workspace_read_file_result_from_dict(s: Any) -> WorkspaceReadFileResult: - return WorkspaceReadFileResult.from_dict(s) +def workspaces_list_files_result_to_dict(x: WorkspacesListFilesResult) -> Any: + return to_class(WorkspacesListFilesResult, x) -def workspace_read_file_result_to_dict(x: WorkspaceReadFileResult) -> Any: - return to_class(WorkspaceReadFileResult, x) +def workspaces_read_file_result_from_dict(s: Any) -> WorkspacesReadFileResult: + return WorkspacesReadFileResult.from_dict(s) -def workspace_read_file_request_from_dict(s: Any) -> WorkspaceReadFileRequest: - return WorkspaceReadFileRequest.from_dict(s) +def workspaces_read_file_result_to_dict(x: WorkspacesReadFileResult) -> Any: + return to_class(WorkspacesReadFileResult, x) -def workspace_read_file_request_to_dict(x: WorkspaceReadFileRequest) -> Any: - return to_class(WorkspaceReadFileRequest, x) +def workspaces_read_file_request_from_dict(s: Any) -> WorkspacesReadFileRequest: + return WorkspacesReadFileRequest.from_dict(s) -def workspace_create_file_request_from_dict(s: Any) -> WorkspaceCreateFileRequest: - return WorkspaceCreateFileRequest.from_dict(s) +def workspaces_read_file_request_to_dict(x: WorkspacesReadFileRequest) -> Any: + return to_class(WorkspacesReadFileRequest, x) -def workspace_create_file_request_to_dict(x: WorkspaceCreateFileRequest) -> Any: - return to_class(WorkspaceCreateFileRequest, x) +def workspaces_create_file_request_from_dict(s: Any) -> WorkspacesCreateFileRequest: + return WorkspacesCreateFileRequest.from_dict(s) + +def workspaces_create_file_request_to_dict(x: WorkspacesCreateFileRequest) -> Any: + return to_class(WorkspacesCreateFileRequest, x) def fleet_start_result_from_dict(s: Any) -> FleetStartResult: return FleetStartResult.from_dict(s) @@ -3709,6 +3853,20 @@ async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> await self._client.request("session.mode.set", params_dict, **_timeout_kwargs(timeout)) +class NameApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get(self, *, timeout: float | None = None) -> NameGetResult: + return NameGetResult.from_dict(await self._client.request("session.name.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def set(self, params: NameSetRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict["sessionId"] = self._session_id + await self._client.request("session.name.set", params_dict, **_timeout_kwargs(timeout)) + + class PlanApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client @@ -3726,23 +3884,26 @@ async def delete(self, *, timeout: float | None = None) -> None: await self._client.request("session.plan.delete", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)) -class WorkspaceApi: +class WorkspacesApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def list_files(self, *, timeout: float | None = None) -> WorkspaceListFilesResult: - return WorkspaceListFilesResult.from_dict(await self._client.request("session.workspace.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get_workspace(self, *, timeout: float | None = None) -> WorkspacesGetWorkspaceResult: + return WorkspacesGetWorkspaceResult.from_dict(await self._client.request("session.workspaces.getWorkspace", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + async def list_files(self, *, timeout: float | None = None) -> WorkspacesListFilesResult: + return WorkspacesListFilesResult.from_dict(await self._client.request("session.workspaces.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) - async def read_file(self, params: WorkspaceReadFileRequest, *, timeout: float | None = None) -> WorkspaceReadFileResult: + async def read_file(self, params: WorkspacesReadFileRequest, *, timeout: float | None = None) -> WorkspacesReadFileResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return WorkspaceReadFileResult.from_dict(await self._client.request("session.workspace.readFile", params_dict, **_timeout_kwargs(timeout))) + return WorkspacesReadFileResult.from_dict(await self._client.request("session.workspaces.readFile", params_dict, **_timeout_kwargs(timeout))) - async def create_file(self, params: WorkspaceCreateFileRequest, *, timeout: float | None = None) -> None: + async def create_file(self, params: WorkspacesCreateFileRequest, *, timeout: float | None = None) -> None: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - await self._client.request("session.workspace.createFile", params_dict, **_timeout_kwargs(timeout)) + await self._client.request("session.workspaces.createFile", params_dict, **_timeout_kwargs(timeout)) # Experimental: this API group is experimental and may change or be removed. @@ -3957,8 +4118,9 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id self.model = ModelApi(client, session_id) self.mode = ModeApi(client, session_id) + self.name = NameApi(client, session_id) self.plan = PlanApi(client, session_id) - self.workspace = WorkspaceApi(client, session_id) + self.workspaces = WorkspacesApi(client, session_id) self.fleet = FleetApi(client, session_id) self.agent = AgentApi(client, session_id) self.skills = SkillsApi(client, session_id) diff --git a/python/e2e/test_rpc.py b/python/e2e/test_rpc.py index 0d9f9a4eb..c5e9a7b79 100644 --- a/python/e2e/test_rpc.py +++ b/python/e2e/test_rpc.py @@ -187,8 +187,8 @@ async def test_read_update_and_delete_plan(self): async def test_create_list_and_read_workspace_files(self): """Test creating, listing, and reading workspace files""" from copilot.generated.rpc import ( - WorkspaceCreateFileRequest, - WorkspaceReadFileRequest, + WorkspacesCreateFileRequest, + WorkspacesReadFileRequest, ) client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, use_stdio=True)) @@ -200,31 +200,31 @@ async def test_create_list_and_read_workspace_files(self): ) # Initially no files - initial_files = await session.rpc.workspace.list_files() + initial_files = await session.rpc.workspaces.list_files() assert initial_files.files == [] # Create a file file_content = "Hello, workspace!" - await session.rpc.workspace.create_file( - WorkspaceCreateFileRequest(content=file_content, path="test.txt") + await session.rpc.workspaces.create_file( + WorkspacesCreateFileRequest(content=file_content, path="test.txt") ) # List files - after_create = await session.rpc.workspace.list_files() + after_create = await session.rpc.workspaces.list_files() assert "test.txt" in after_create.files # Read file - read_result = await session.rpc.workspace.read_file( - WorkspaceReadFileRequest(path="test.txt") + read_result = await session.rpc.workspaces.read_file( + WorkspacesReadFileRequest(path="test.txt") ) assert read_result.content == file_content # Create nested file - await session.rpc.workspace.create_file( - WorkspaceCreateFileRequest(content="Nested content", path="subdir/nested.txt") + await session.rpc.workspaces.create_file( + WorkspacesCreateFileRequest(content="Nested content", path="subdir/nested.txt") ) - after_nested = await session.rpc.workspace.list_files() + after_nested = await session.rpc.workspaces.list_files() assert "test.txt" in after_nested.files assert any("nested.txt" in f for f in after_nested.files) diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 9e63b68ea..243047fb6 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -258,6 +258,11 @@ function emitDataAnnotations(schema: JSONSchema7, indent: string): string[] { } // [MinLength] / [MaxLength] for string constraints + if (typeof schema.minLength === "number" || typeof schema.maxLength === "number") { + attrs.push( + `${indent}[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")]` + ); + } if (typeof schema.minLength === "number") { attrs.push(`${indent}[MinLength(${schema.minLength})]`); } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 62b53e1e6..46d11de83 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -1454,6 +1454,14 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = modernizePython(typesCode); typesCode = collapsePlaceholderPythonDataclasses(typesCode); + // Strip quicktype's import block and preamble — we provide our own unified header. + // The preamble ends just before the first helper function (e.g. "def from_str") + // or class definition. + typesCode = typesCode.replace(/^[\s\S]*?(?=^(?:def |@dataclass|class )\w)/m, ""); + + // Strip trailing whitespace from blank lines (e.g. inside multi-line docstrings) + typesCode = typesCode.replace(/^\s+$/gm, ""); + // Annotate experimental data types const experimentalTypeNames = new Set(); for (const method of allMethods) { @@ -1494,7 +1502,15 @@ if TYPE_CHECKING: from collections.abc import Callable from dataclasses import dataclass -from typing import Protocol +from datetime import datetime +from enum import Enum +from typing import Any, Protocol, TypeVar, cast +from uuid import UUID + +import dateutil.parser + +T = TypeVar("T") +EnumT = TypeVar("EnumT", bound=Enum) `); lines.push(typesCode); diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 691d66bf9..ba4ed7d10 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.22", + "@github/copilot": "^1.0.26", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.22.tgz", - "integrity": "sha512-BR9oTJ1tQ51RV81xcxmlZe0zB3Tf8i/vFsKSTm2f5wRLJgtuVl2LgaFStoI/peTFcmgtZbhrqsnWTu5GkEPK5Q==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26.tgz", + "integrity": "sha512-F7P6yimFzjvWxOF/A0F6k//vcpSVcVusQjaybb3IKyrEDhnd/LOv2tD+x6W0IoxCftGDDhkzBA2aon3rL9lPhQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.22", - "@github/copilot-darwin-x64": "1.0.22", - "@github/copilot-linux-arm64": "1.0.22", - "@github/copilot-linux-x64": "1.0.22", - "@github/copilot-win32-arm64": "1.0.22", - "@github/copilot-win32-x64": "1.0.22" + "@github/copilot-darwin-arm64": "1.0.26", + "@github/copilot-darwin-x64": "1.0.26", + "@github/copilot-linux-arm64": "1.0.26", + "@github/copilot-linux-x64": "1.0.26", + "@github/copilot-win32-arm64": "1.0.26", + "@github/copilot-win32-x64": "1.0.26" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.22.tgz", - "integrity": "sha512-cK42uX+oz46Cjsb7z+rdPw+DIGczfVSFWlc1WDcdVlwBW4cEfV0pzFXExpN1r1z179TFgAaVMbhkgLqhOZ/PeQ==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26.tgz", + "integrity": "sha512-eV+jDMj4vnjdGcG+c4zg11zZKVAp94Hm4sK4f9LnyWw8MumTfS5F2Yyse9zt7A3oGlegyczmJopKwuwZbQd4ww==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.22.tgz", - "integrity": "sha512-Pmw0ipF+yeLbP6JctsEoMS2LUCpVdC2r557BnCoe48BN8lO8i9JLnkpuDDrJ1AZuCk1VjnujFKEQywOOdfVlpA==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26.tgz", + "integrity": "sha512-2AAgu19F3scDlYhsiHxCn0cz4ZkINq8gxnqW0an8VQn6p15lDcah6PqHw+RJ+12qiYX5L5NNACty9UOkIK7Kzg==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.22.tgz", - "integrity": "sha512-WVgG67VmZgHoD7GMlkTxEVe1qK8k9Ek9A02/Da7obpsDdtBInt3nJTwBEgm4cNDM4XaenQH17/jmwVtTwXB6lw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26.tgz", + "integrity": "sha512-SnM7+TGAZ/i9dim5FfHM7+ii01hdpHJzzh8vnnA1Fa7RPFJaQ2KTOdTDJFgfv6e/jLhKXZEelYIidgCA3vSQCQ==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.22.tgz", - "integrity": "sha512-XRkHVFmdC7FMrczXOdPjbNKiknMr13asKtwJoErJO/Xdy4cmzKQHSvNsBk8VNrr7oyWrUcB1F6mbIxb2LFxPOw==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26.tgz", + "integrity": "sha512-x76vcwVbi0j03hFMhiQs+Eqefd9Xmc4qJoaj44YA2VsJuDbZw2Yv7ZBq7Vyxd/shJwJZjaKv36MHcx5bVUMBJQ==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.22.tgz", - "integrity": "sha512-Ao6gv1f2ZV+HVlkB1MV7YFdCuaB3NcFCnNu0a6/WLl2ypsfP1vWosPPkIB32jQJeBkT9ku3exOZLRj+XC0P3Mg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26.tgz", + "integrity": "sha512-enVRcy7W9RD1bwYkF+mcxR+biXsG/X5m46XBaD0opvfDeiBHceDnI8hEI0O1A5PYvRo88AZFvDEmEW3Gdj6+rQ==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.22", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.22.tgz", - "integrity": "sha512-EppcL+3TpxC+X/eQEIYtkN0PaA3/cvtI9UJqldLIkKDPXNYk/0mw877Ru9ypRcBWBWokDN6iKIWk5IxYH+JIvg==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26.tgz", + "integrity": "sha512-DpJF6C1x4+sYIXUx5+vWCu6cFAbD2YlrXQ/BRttf2MMdc0DHwdgJxrttBBF2qCvmpfzjSE8cr5G0kt5EUk7FGw==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index def9f09cf..527c036b7 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.22", + "@github/copilot": "^1.0.26", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From 3e1b65e56a80557b796df90f3292f34d56bf32e1 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Wed, 15 Apr 2026 08:50:05 -0700 Subject: [PATCH 17/34] feat: add per-agent skills support (#995) * feat: add per-agent skills support to SDK types and docs (#958) Add a 'skills' field to CustomAgentConfig across all four SDK languages (Node.js, Python, Go, .NET) to support scoping skills to individual subagents. Skills are opt-in: agents get no skills by default. Changes: - Add skills?: string[] to CustomAgentConfig in all SDKs - Update custom-agents.md with skills in config table and new section - Update skills.md with per-agent skills example and opt-in note - Update streaming-events.md with agentName on skill.invoked event - Add E2E tests for agent-scoped skills in all four SDKs - Add snapshot YAML files for new test scenarios Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: update skills semantics to eager injection model Update type comments, docs, and test descriptions to reflect that per-agent skills are eagerly injected into the agent's context at startup rather than filtered for invocation. Sub-agents do not inherit skills from the parent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: remove agentName from skill.invoked event table The runtime does not emit agentName on the skill.invoked event. The agent name is used only for internal logging during eager skill loading, not as event data. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback for per-agent skills (#995) - Add skills field to Python wire format converter - Explicitly select agents in all E2E tests for deterministic behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: update Go skills tests to use typed SessionEventData after rebase The generated_session_events.go on main changed from a flat Data struct to a SessionEventData interface with per-event typed structs. The agent skills test cases added in this PR were using the old message.Data.Content pattern instead of the type assertion pattern used elsewhere. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * chore: revert unintentional package-lock.json changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: update proxy and snapshots for eager skill injection The runtime now eagerly injects skill content into in the user message instead of using a skill tool call. Update the replay proxy to strip during normalization, and simplify the snapshot for agent-with-skills to match the new flow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: add agent_instructions normalization tests for replay proxy Add two regression tests validating that blocks are properly stripped during user message normalization, including the case where skill-context is nested inside agent_instructions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use IList for Skills property in .NET SDK Match the established convention used by Tools, SkillDirectories, DisabledSkills, and other collection properties in the codebase. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: restore original skillDirectories path in skills.md sample Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: use see cref for SkillDirectories in XML doc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 28 +++++++ docs/features/skills.md | 4 +- dotnet/src/Types.cs | 10 +++ dotnet/test/SkillsTests.cs | 63 +++++++++++++++ go/internal/e2e/skills_test.go | 77 +++++++++++++++++++ go/types.go | 2 + nodejs/src/types.ts | 8 ++ nodejs/test/e2e/skills.test.ts | 60 +++++++++++++++ python/copilot/client.py | 2 + python/copilot/session.py | 2 + python/e2e/test_skills.py | 59 +++++++++++++- test/harness/replayingCapiProxy.test.ts | 46 +++++++++++ test/harness/replayingCapiProxy.ts | 1 + ...low_agent_with_skills_to_invoke_skill.yaml | 10 +++ ..._skills_to_agent_without_skills_field.yaml | 10 +++ 15 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml create mode 100644 test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 6c6455a02..f3c508922 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -252,6 +252,7 @@ try (var client = new CopilotClient()) { | `prompt` | `string` | ✅ | System prompt for the agent | | `mcpServers` | `object` | | MCP server configurations specific to this agent | | `infer` | `boolean` | | Whether the runtime can auto-select this agent (default: `true`) | +| `skills` | `string[]` | | Skill names to preload into the agent's context at startup | > **Tip:** A good `description` helps the runtime match user intent to the right agent. Be specific about the agent's expertise and capabilities. @@ -261,6 +262,33 @@ In addition to per-agent configuration above, you can set `agent` on the **sessi |-------------------------|------|-------------| | `agent` | `string` | Name of the custom agent to pre-select at session creation. Must match a `name` in `customAgents`. | +## Per-Agent Skills + +You can preload skills into an agent's context using the `skills` property. When specified, the **full content** of each listed skill is eagerly injected into the agent's context at startup — the agent doesn't need to invoke a skill tool; the instructions are already present. Skills are **opt-in**: agents receive no skills by default, and sub-agents do not inherit skills from the parent. Skill names are resolved from the session-level `skillDirectories`. + +```typescript +const session = await client.createSession({ + skillDirectories: ["./skills"], + customAgents: [ + { + name: "security-auditor", + description: "Security-focused code reviewer", + prompt: "Focus on OWASP Top 10 vulnerabilities", + skills: ["security-scan", "dependency-check"], + }, + { + name: "docs-writer", + description: "Technical documentation writer", + prompt: "Write clear, concise documentation", + skills: ["markdown-lint"], + }, + ], + onPermissionRequest: async () => ({ kind: "approved" }), +}); +``` + +In this example, `security-auditor` starts with `security-scan` and `dependency-check` already injected into its context, while `docs-writer` starts with `markdown-lint`. An agent without a `skills` field receives no skill content. + ## Selecting an Agent at Session Creation You can pass `agent` in the session config to pre-select which custom agent should be active when the session starts. The value must match the `name` of one of the agents defined in `customAgents`. diff --git a/docs/features/skills.md b/docs/features/skills.md index 882580fd4..6c3888eb8 100644 --- a/docs/features/skills.md +++ b/docs/features/skills.md @@ -364,7 +364,7 @@ The markdown body contains the instructions that are injected into the session c ### Skills + Custom Agents -Skills work alongside custom agents: +Skills listed in an agent's `skills` field are **eagerly preloaded** — their full content is injected into the agent's context at startup, so the agent has access to the skill instructions immediately without needing to invoke a skill tool. Skill names are resolved from the session-level `skillDirectories`. ```typescript const session = await client.createSession({ @@ -373,10 +373,12 @@ const session = await client.createSession({ name: "security-auditor", description: "Security-focused code reviewer", prompt: "Focus on OWASP Top 10 vulnerabilities", + skills: ["security-scan", "dependency-check"], }], onPermissionRequest: async () => ({ kind: "approved" }), }); ``` +> **Note:** Skills are opt-in — when `skills` is omitted, no skill content is injected. Sub-agents do not inherit skills from the parent; you must list them explicitly per agent. ### Skills + MCP Servers diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index f88d84eb6..978defcfb 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1638,6 +1638,16 @@ public class CustomAgentConfig /// [JsonPropertyName("infer")] public bool? Infer { get; set; } + + /// + /// List of skill names to preload into this agent's context. + /// When set, the full content of each listed skill is eagerly injected into + /// the agent's context at startup. Skills are resolved by name from the + /// session's configured skill directories (). + /// When omitted, no skills are injected (opt-in model). + /// + [JsonPropertyName("skills")] + public IList? Skills { get; set; } } /// diff --git a/dotnet/test/SkillsTests.cs b/dotnet/test/SkillsTests.cs index d68eed79d..0cae1f58f 100644 --- a/dotnet/test/SkillsTests.cs +++ b/dotnet/test/SkillsTests.cs @@ -87,6 +87,69 @@ public async Task Should_Not_Apply_Skill_When_Disabled_Via_DisabledSkills() await session.DisposeAsync(); } + [Fact] + public async Task Should_Allow_Agent_With_Skills_To_Invoke_Skill() + { + var skillsDir = CreateSkillDir(); + var customAgents = new List + { + new CustomAgentConfig + { + Name = "skill-agent", + Description = "An agent with access to test-skill", + Prompt = "You are a helpful test agent.", + Skills = ["test-skill"] + } + }; + + var session = await CreateSessionAsync(new SessionConfig + { + SkillDirectories = [skillsDir], + CustomAgents = customAgents, + Agent = "skill-agent" + }); + + Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); + + // The agent has Skills = ["test-skill"], so the skill content is preloaded into its context + var message = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say hello briefly using the test skill." }); + Assert.NotNull(message); + Assert.Contains(SkillMarker, message!.Data.Content); + + await session.DisposeAsync(); + } + + [Fact] + public async Task Should_Not_Provide_Skills_To_Agent_Without_Skills_Field() + { + var skillsDir = CreateSkillDir(); + var customAgents = new List + { + new CustomAgentConfig + { + Name = "no-skill-agent", + Description = "An agent without skills access", + Prompt = "You are a helpful test agent." + } + }; + + var session = await CreateSessionAsync(new SessionConfig + { + SkillDirectories = [skillsDir], + CustomAgents = customAgents, + Agent = "no-skill-agent" + }); + + Assert.Matches(@"^[a-f0-9-]+$", session.SessionId); + + // The agent has no Skills field, so no skill content is injected + var message = await session.SendAndWaitAsync(new MessageOptions { Prompt = "Say hello briefly using the test skill." }); + Assert.NotNull(message); + Assert.DoesNotContain(SkillMarker, message!.Data.Content); + + await session.DisposeAsync(); + } + [Fact(Skip = "See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.")] public async Task Should_Apply_Skill_On_Session_Resume_With_SkillDirectories() { diff --git a/go/internal/e2e/skills_test.go b/go/internal/e2e/skills_test.go index f6943fef9..b91592d9d 100644 --- a/go/internal/e2e/skills_test.go +++ b/go/internal/e2e/skills_test.go @@ -108,6 +108,83 @@ func TestSkills(t *testing.T) { session.Disconnect() }) + t.Run("should allow agent with skills to invoke skill", func(t *testing.T) { + ctx.ConfigureForTest(t) + cleanSkillsDir(t, ctx.WorkDir) + skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker) + + customAgents := []copilot.CustomAgentConfig{ + { + Name: "skill-agent", + Description: "An agent with access to test-skill", + Prompt: "You are a helpful test agent.", + Skills: []string{"test-skill"}, + }, + } + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + SkillDirectories: []string{skillsDir}, + CustomAgents: customAgents, + Agent: "skill-agent", + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // The agent has Skills: ["test-skill"], so the skill content is preloaded into its context + message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Say hello briefly using the test skill.", + }) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to contain skill marker '%s', got: %v", skillMarker, message.Data) + } + + session.Disconnect() + }) + + t.Run("should not provide skills to agent without skills field", func(t *testing.T) { + ctx.ConfigureForTest(t) + cleanSkillsDir(t, ctx.WorkDir) + skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker) + + customAgents := []copilot.CustomAgentConfig{ + { + Name: "no-skill-agent", + Description: "An agent without skills access", + Prompt: "You are a helpful test agent.", + }, + } + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + SkillDirectories: []string{skillsDir}, + CustomAgents: customAgents, + Agent: "no-skill-agent", + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // The agent has no Skills field, so no skill content is injected + message, err := session.SendAndWait(t.Context(), copilot.MessageOptions{ + Prompt: "Say hello briefly using the test skill.", + }) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + if md, ok := message.Data.(*copilot.AssistantMessageData); ok && strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to NOT contain skill marker '%s' when agent has no skills, got: %v", skillMarker, md.Content) + } + + session.Disconnect() + }) + t.Run("should apply skill on session resume with skillDirectories", func(t *testing.T) { t.Skip("See the big comment around the equivalent test in the Node SDK. Skipped because the feature doesn't work correctly yet.") ctx.ConfigureForTest(t) diff --git a/go/types.go b/go/types.go index 0e0370ed2..d609ce00a 100644 --- a/go/types.go +++ b/go/types.go @@ -450,6 +450,8 @@ type CustomAgentConfig struct { MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` // Infer indicates whether the agent should be available for model inference Infer *bool `json:"infer,omitempty"` + // Skills is the list of skill names to preload into this agent's context at startup (opt-in; omit for none) + Skills []string `json:"skills,omitempty"` } // InfiniteSessionConfig configures infinite sessions with automatic context compaction diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 1318b3df4..a4cb77fa2 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1104,6 +1104,14 @@ export interface CustomAgentConfig { * @default true */ infer?: boolean; + /** + * List of skill names to preload into this agent's context. + * When set, the full content of each listed skill is eagerly injected into + * the agent's context at startup. Skills are resolved by name from the + * session's configured skill directories (`skillDirectories`). + * When omitted, no skills are injected (opt-in model). + */ + skills?: string[]; } /** diff --git a/nodejs/test/e2e/skills.test.ts b/nodejs/test/e2e/skills.test.ts index a2173648f..973e2f329 100644 --- a/nodejs/test/e2e/skills.test.ts +++ b/nodejs/test/e2e/skills.test.ts @@ -5,6 +5,7 @@ import * as fs from "fs"; import * as path from "path"; import { beforeEach, describe, expect, it } from "vitest"; +import type { CustomAgentConfig } from "../../src/index.js"; import { approveAll } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -92,6 +93,65 @@ IMPORTANT: You MUST include the exact text "${SKILL_MARKER}" somewhere in EVERY // Also, if this test runs FIRST and then the "should load and apply skill from skillDirectories" test runs second // within the same run (i.e., sharing the same Client instance), then the second test fails too. There's definitely // some state being shared or cached incorrectly. + it("should allow agent with skills to invoke skill", async () => { + const skillsDir = createSkillDir(); + const customAgents: CustomAgentConfig[] = [ + { + name: "skill-agent", + description: "An agent with access to test-skill", + prompt: "You are a helpful test agent.", + skills: ["test-skill"], + }, + ]; + + const session = await client.createSession({ + onPermissionRequest: approveAll, + skillDirectories: [skillsDir], + customAgents, + agent: "skill-agent", + }); + + expect(session.sessionId).toBeDefined(); + + // The agent has skills: ["test-skill"], so the skill content is preloaded into its context + const message = await session.sendAndWait({ + prompt: "Say hello briefly using the test skill.", + }); + + expect(message?.data.content).toContain(SKILL_MARKER); + + await session.disconnect(); + }); + + it("should not provide skills to agent without skills field", async () => { + const skillsDir = createSkillDir(); + const customAgents: CustomAgentConfig[] = [ + { + name: "no-skill-agent", + description: "An agent without skills access", + prompt: "You are a helpful test agent.", + }, + ]; + + const session = await client.createSession({ + onPermissionRequest: approveAll, + skillDirectories: [skillsDir], + customAgents, + agent: "no-skill-agent", + }); + + expect(session.sessionId).toBeDefined(); + + // The agent has no skills field, so no skill content is injected + const message = await session.sendAndWait({ + prompt: "Say hello briefly using the test skill.", + }); + + expect(message?.data.content).not.toContain(SKILL_MARKER); + + await session.disconnect(); + }); + it.skip("should apply skill on session resume with skillDirectories", async () => { const skillsDir = createSkillDir(); diff --git a/python/copilot/client.py b/python/copilot/client.py index f59816d6e..407ad1673 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2156,6 +2156,8 @@ def _convert_custom_agent_to_wire_format( wire_agent["mcpServers"] = agent["mcp_servers"] if "infer" in agent: wire_agent["infer"] = agent["infer"] + if "skills" in agent: + wire_agent["skills"] = agent["skills"] return wire_agent async def _start_cli_server(self) -> None: diff --git a/python/copilot/session.py b/python/copilot/session.py index 443cfc969..9fd9f79bd 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -777,6 +777,8 @@ class CustomAgentConfig(TypedDict, total=False): # MCP servers specific to agent mcp_servers: NotRequired[dict[str, MCPServerConfig]] infer: NotRequired[bool] # Whether agent is available for model inference + # Skill names to preload into this agent's context at startup (opt-in; omit for none) + skills: NotRequired[list[str]] class InfiniteSessionConfig(TypedDict, total=False): diff --git a/python/e2e/test_skills.py b/python/e2e/test_skills.py index feacae73b..b5c5e6e7c 100644 --- a/python/e2e/test_skills.py +++ b/python/e2e/test_skills.py @@ -7,7 +7,7 @@ import pytest -from copilot.session import PermissionHandler +from copilot.session import CustomAgentConfig, PermissionHandler from .testharness import E2ETestContext @@ -88,6 +88,63 @@ async def test_should_not_apply_skill_when_disabled_via_disabledskills( await session.disconnect() + async def test_should_allow_agent_with_skills_to_invoke_skill(self, ctx: E2ETestContext): + """Test that an agent with skills gets skill content preloaded into context""" + skills_dir = create_skill_dir(ctx.work_dir) + custom_agents: list[CustomAgentConfig] = [ + { + "name": "skill-agent", + "description": "An agent with access to test-skill", + "prompt": "You are a helpful test agent.", + "skills": ["test-skill"], + } + ] + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + skill_directories=[skills_dir], + custom_agents=custom_agents, + agent="skill-agent", + ) + + assert session.session_id is not None + + # The agent has skills: ["test-skill"], so the skill content is preloaded into its context + message = await session.send_and_wait("Say hello briefly using the test skill.") + assert message is not None + assert SKILL_MARKER in message.data.content + + await session.disconnect() + + async def test_should_not_provide_skills_to_agent_without_skills_field( + self, ctx: E2ETestContext + ): + """Test that an agent without skills field gets no skill content (opt-in model)""" + skills_dir = create_skill_dir(ctx.work_dir) + custom_agents: list[CustomAgentConfig] = [ + { + "name": "no-skill-agent", + "description": "An agent without skills access", + "prompt": "You are a helpful test agent.", + } + ] + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + skill_directories=[skills_dir], + custom_agents=custom_agents, + agent="no-skill-agent", + ) + + assert session.session_id is not None + + # The agent has no skills field, so no skill content is injected + message = await session.send_and_wait("Say hello briefly using the test skill.") + assert message is not None + assert SKILL_MARKER not in message.data.content + + await session.disconnect() + @pytest.mark.skip( reason="See the big comment around the equivalent test in the Node SDK. " "Skipped because the feature doesn't work correctly yet." diff --git a/test/harness/replayingCapiProxy.test.ts b/test/harness/replayingCapiProxy.test.ts index 6fcaed5e2..f19674052 100644 --- a/test/harness/replayingCapiProxy.test.ts +++ b/test/harness/replayingCapiProxy.test.ts @@ -302,6 +302,52 @@ describe("ReplayingCapiProxy", () => { ); }); + test("strips agent_instructions from user messages", async () => { + const requestBody = JSON.stringify({ + messages: [ + { + role: "user", + content: + "\nYou are a helpful test agent.\n\n\n\n\nSay hello briefly.", + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Hello!" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + expect(result.conversations[0].messages[0].content).toBe( + "Say hello briefly.", + ); + }); + + test("strips agent_instructions containing skill-context from user messages", async () => { + const requestBody = JSON.stringify({ + messages: [ + { + role: "user", + content: + '\n\nSkill content here\n\nYou are a helpful agent.\n\n\nSay hello.', + }, + ], + }); + const responseBody = JSON.stringify({ + choices: [{ message: { role: "assistant", content: "Hi!" } }], + }); + + const outputPath = await createProxy([ + { url: "/chat/completions", requestBody, responseBody }, + ]); + + const result = await readYamlOutput(outputPath); + expect(result.conversations[0].messages[0].content).toBe("Say hello."); + }); + test("applies tool result normalizers to tool response content", async () => { const requestBody = JSON.stringify({ messages: [ diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index 03dcd190f..a63c5b123 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -805,6 +805,7 @@ function normalizeUserMessage(content: string): string { return content .replace(/.*?<\/current_datetime>/g, "") .replace(/[\s\S]*?<\/reminder>/g, "") + .replace(/[\s\S]*?<\/agent_instructions>/g, "") .replace( /Please create a detailed summary of the conversation so far\. The history is being compacted[\s\S]*/, "${compaction_prompt}", diff --git a/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml b/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml new file mode 100644 index 000000000..007c5c1c5 --- /dev/null +++ b/test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Say hello briefly using the test skill. + - role: assistant + content: Hello! PINEAPPLE_COCONUT_42 - I'm ready to help you with your tasks today. diff --git a/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml b/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml new file mode 100644 index 000000000..0c678deab --- /dev/null +++ b/test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Say hello briefly using the test skill. + - role: assistant + content: Hello! I'm GitHub Copilot CLI, ready to help you with your software engineering tasks. From d519ac42acb634f72f2f9b439e07a93b91416985 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Apr 2026 18:32:02 +0100 Subject: [PATCH 18/34] Update @github/copilot to 1.0.27 (#1078) - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- nodejs/package-lock.json | 56 ++++++++++++++++---------------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- test/harness/package-lock.json | 56 ++++++++++++++++---------------- test/harness/package.json | 2 +- 5 files changed, 59 insertions(+), 59 deletions(-) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 1c84c1d71..bdfbd1aff 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.26", + "@github/copilot": "^1.0.27", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26.tgz", - "integrity": "sha512-F7P6yimFzjvWxOF/A0F6k//vcpSVcVusQjaybb3IKyrEDhnd/LOv2tD+x6W0IoxCftGDDhkzBA2aon3rL9lPhQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.27.tgz", + "integrity": "sha512-f9rlylQWzXRWyK+KkCOmC/wCKXbqQUwfwRkgT8p5JqHlTBvmJ6CS8M9aPo4ycv0aJjtbasLlkYHdrfITMA1cjg==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.26", - "@github/copilot-darwin-x64": "1.0.26", - "@github/copilot-linux-arm64": "1.0.26", - "@github/copilot-linux-x64": "1.0.26", - "@github/copilot-win32-arm64": "1.0.26", - "@github/copilot-win32-x64": "1.0.26" + "@github/copilot-darwin-arm64": "1.0.27", + "@github/copilot-darwin-x64": "1.0.27", + "@github/copilot-linux-arm64": "1.0.27", + "@github/copilot-linux-x64": "1.0.27", + "@github/copilot-win32-arm64": "1.0.27", + "@github/copilot-win32-x64": "1.0.27" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26.tgz", - "integrity": "sha512-eV+jDMj4vnjdGcG+c4zg11zZKVAp94Hm4sK4f9LnyWw8MumTfS5F2Yyse9zt7A3oGlegyczmJopKwuwZbQd4ww==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.27.tgz", + "integrity": "sha512-F0mzfLTGngGugSfTuDtG4MMsAK4U8u+Okcb2ftrn9ObHakz/Fzr3DOMld2T8GyzQIbhOnmOYwOk2UvOAZTq/Vg==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26.tgz", - "integrity": "sha512-2AAgu19F3scDlYhsiHxCn0cz4ZkINq8gxnqW0an8VQn6p15lDcah6PqHw+RJ+12qiYX5L5NNACty9UOkIK7Kzg==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.27.tgz", + "integrity": "sha512-Nn1KME4kZDsve+HOMbwvO0XfCznyZN9mzh+DRL+Q5e2CF0PIxIcJC7zP9t1/dBux/CUOyDppniUd5OVTuqbWVQ==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26.tgz", - "integrity": "sha512-SnM7+TGAZ/i9dim5FfHM7+ii01hdpHJzzh8vnnA1Fa7RPFJaQ2KTOdTDJFgfv6e/jLhKXZEelYIidgCA3vSQCQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.27.tgz", + "integrity": "sha512-tg91mQQIChPDdSZCJ2e6iNIvjaOhBAT78o0jkxjF2Hn9bmNt8Iu/ywDUorugtPM+0t82PZY8AwUPkyMmuYokTQ==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26.tgz", - "integrity": "sha512-x76vcwVbi0j03hFMhiQs+Eqefd9Xmc4qJoaj44YA2VsJuDbZw2Yv7ZBq7Vyxd/shJwJZjaKv36MHcx5bVUMBJQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.27.tgz", + "integrity": "sha512-E2cJLoiT5hWtuLPbVS04fxTM5F7yJL2Xazlf44PLXWPzbp5LQvQ+0SDSxnaAkRVT/DqtrtKitYMCxuDQpkdH7Q==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26.tgz", - "integrity": "sha512-enVRcy7W9RD1bwYkF+mcxR+biXsG/X5m46XBaD0opvfDeiBHceDnI8hEI0O1A5PYvRo88AZFvDEmEW3Gdj6+rQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.27.tgz", + "integrity": "sha512-/V530uFEHf3Pl6itJX4nJjx5fX9RAEIejDiqCDoKvuL8prFHGvx2CoKEz00+1QGpQHN0Z2PA0spN9a8V8o+/KA==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26.tgz", - "integrity": "sha512-DpJF6C1x4+sYIXUx5+vWCu6cFAbD2YlrXQ/BRttf2MMdc0DHwdgJxrttBBF2qCvmpfzjSE8cr5G0kt5EUk7FGw==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.27.tgz", + "integrity": "sha512-ifRG64DAWG09AV6TIvkd5X08DaVMdyvrBC0Iavr75XVA1B9dKldocJAfVtQzhZTkjo/PLHRFTaAaPMNhGTfziA==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index ca2e0afe7..6c2f44c1c 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.26", + "@github/copilot": "^1.0.27", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index d85bc1e5e..1ccbb8dbb 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.26", + "@github/copilot": "^1.0.27", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index ba4ed7d10..34405734b 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.26", + "@github/copilot": "^1.0.27", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.26.tgz", - "integrity": "sha512-F7P6yimFzjvWxOF/A0F6k//vcpSVcVusQjaybb3IKyrEDhnd/LOv2tD+x6W0IoxCftGDDhkzBA2aon3rL9lPhQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.27.tgz", + "integrity": "sha512-f9rlylQWzXRWyK+KkCOmC/wCKXbqQUwfwRkgT8p5JqHlTBvmJ6CS8M9aPo4ycv0aJjtbasLlkYHdrfITMA1cjg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.26", - "@github/copilot-darwin-x64": "1.0.26", - "@github/copilot-linux-arm64": "1.0.26", - "@github/copilot-linux-x64": "1.0.26", - "@github/copilot-win32-arm64": "1.0.26", - "@github/copilot-win32-x64": "1.0.26" + "@github/copilot-darwin-arm64": "1.0.27", + "@github/copilot-darwin-x64": "1.0.27", + "@github/copilot-linux-arm64": "1.0.27", + "@github/copilot-linux-x64": "1.0.27", + "@github/copilot-win32-arm64": "1.0.27", + "@github/copilot-win32-x64": "1.0.27" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.26.tgz", - "integrity": "sha512-eV+jDMj4vnjdGcG+c4zg11zZKVAp94Hm4sK4f9LnyWw8MumTfS5F2Yyse9zt7A3oGlegyczmJopKwuwZbQd4ww==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.27.tgz", + "integrity": "sha512-F0mzfLTGngGugSfTuDtG4MMsAK4U8u+Okcb2ftrn9ObHakz/Fzr3DOMld2T8GyzQIbhOnmOYwOk2UvOAZTq/Vg==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.26.tgz", - "integrity": "sha512-2AAgu19F3scDlYhsiHxCn0cz4ZkINq8gxnqW0an8VQn6p15lDcah6PqHw+RJ+12qiYX5L5NNACty9UOkIK7Kzg==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.27.tgz", + "integrity": "sha512-Nn1KME4kZDsve+HOMbwvO0XfCznyZN9mzh+DRL+Q5e2CF0PIxIcJC7zP9t1/dBux/CUOyDppniUd5OVTuqbWVQ==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.26.tgz", - "integrity": "sha512-SnM7+TGAZ/i9dim5FfHM7+ii01hdpHJzzh8vnnA1Fa7RPFJaQ2KTOdTDJFgfv6e/jLhKXZEelYIidgCA3vSQCQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.27.tgz", + "integrity": "sha512-tg91mQQIChPDdSZCJ2e6iNIvjaOhBAT78o0jkxjF2Hn9bmNt8Iu/ywDUorugtPM+0t82PZY8AwUPkyMmuYokTQ==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.26.tgz", - "integrity": "sha512-x76vcwVbi0j03hFMhiQs+Eqefd9Xmc4qJoaj44YA2VsJuDbZw2Yv7ZBq7Vyxd/shJwJZjaKv36MHcx5bVUMBJQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.27.tgz", + "integrity": "sha512-E2cJLoiT5hWtuLPbVS04fxTM5F7yJL2Xazlf44PLXWPzbp5LQvQ+0SDSxnaAkRVT/DqtrtKitYMCxuDQpkdH7Q==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.26.tgz", - "integrity": "sha512-enVRcy7W9RD1bwYkF+mcxR+biXsG/X5m46XBaD0opvfDeiBHceDnI8hEI0O1A5PYvRo88AZFvDEmEW3Gdj6+rQ==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.27.tgz", + "integrity": "sha512-/V530uFEHf3Pl6itJX4nJjx5fX9RAEIejDiqCDoKvuL8prFHGvx2CoKEz00+1QGpQHN0Z2PA0spN9a8V8o+/KA==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.26.tgz", - "integrity": "sha512-DpJF6C1x4+sYIXUx5+vWCu6cFAbD2YlrXQ/BRttf2MMdc0DHwdgJxrttBBF2qCvmpfzjSE8cr5G0kt5EUk7FGw==", + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.27.tgz", + "integrity": "sha512-ifRG64DAWG09AV6TIvkd5X08DaVMdyvrBC0Iavr75XVA1B9dKldocJAfVtQzhZTkjo/PLHRFTaAaPMNhGTfziA==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 527c036b7..c1e90d1b7 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.26", + "@github/copilot": "^1.0.27", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From 97c10ae95785d822e223b2be4b50804bae5e6fb1 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:48:42 -0400 Subject: [PATCH 19/34] Fix CI not triggering on automated dependency update PRs (#1086) * Fix CI not triggering on automated dependency update PRs Create dependency update PRs as drafts so maintainers can trigger CI by clicking "Ready for review". Add ready_for_review to pull_request types in all CI workflows. Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/4e36e957-26c2-47a9-88c2-7b630ff3a33b Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .github/workflows/codegen-check.yml | 1 + .github/workflows/corrections-tests.yml | 1 + .github/workflows/docs-validation.yml | 1 + .github/workflows/dotnet-sdk-tests.yml | 1 + .github/workflows/go-sdk-tests.yml | 1 + .github/workflows/nodejs-sdk-tests.yml | 1 + .github/workflows/python-sdk-tests.yml | 1 + .github/workflows/scenario-builds.yml | 1 + .github/workflows/update-copilot-dependency.yml | 11 ++++++++++- .github/workflows/verify-compiled.yml | 1 + 10 files changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml index c7d295221..9fd7f0542 100644 --- a/.github/workflows/codegen-check.yml +++ b/.github/workflows/codegen-check.yml @@ -5,6 +5,7 @@ on: branches: - main pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'scripts/codegen/**' - 'nodejs/src/generated/**' diff --git a/.github/workflows/corrections-tests.yml b/.github/workflows/corrections-tests.yml index a67840e6d..7654f3c9b 100644 --- a/.github/workflows/corrections-tests.yml +++ b/.github/workflows/corrections-tests.yml @@ -6,6 +6,7 @@ on: paths: - 'scripts/corrections/**' pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'scripts/corrections/**' diff --git a/.github/workflows/docs-validation.yml b/.github/workflows/docs-validation.yml index 89d2fa2a9..4c26e9ec1 100644 --- a/.github/workflows/docs-validation.yml +++ b/.github/workflows/docs-validation.yml @@ -2,6 +2,7 @@ name: "Documentation Validation" on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'docs/**' - 'nodejs/src/**' diff --git a/.github/workflows/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml index 3ca9d1de9..872f06668 100644 --- a/.github/workflows/dotnet-sdk-tests.yml +++ b/.github/workflows/dotnet-sdk-tests.yml @@ -5,6 +5,7 @@ on: branches: - main pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'dotnet/**' - 'test/**' diff --git a/.github/workflows/go-sdk-tests.yml b/.github/workflows/go-sdk-tests.yml index ed75bcb0c..733954f1d 100644 --- a/.github/workflows/go-sdk-tests.yml +++ b/.github/workflows/go-sdk-tests.yml @@ -5,6 +5,7 @@ on: branches: - main pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'go/**' - 'test/**' diff --git a/.github/workflows/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml index 9dec01667..141b161b6 100644 --- a/.github/workflows/nodejs-sdk-tests.yml +++ b/.github/workflows/nodejs-sdk-tests.yml @@ -8,6 +8,7 @@ on: branches: - main pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'nodejs/**' - 'test/**' diff --git a/.github/workflows/python-sdk-tests.yml b/.github/workflows/python-sdk-tests.yml index 941f08183..5b305ed09 100644 --- a/.github/workflows/python-sdk-tests.yml +++ b/.github/workflows/python-sdk-tests.yml @@ -8,6 +8,7 @@ on: branches: - main pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - 'python/**' - 'test/**' diff --git a/.github/workflows/scenario-builds.yml b/.github/workflows/scenario-builds.yml index 54d7257e5..ae368075c 100644 --- a/.github/workflows/scenario-builds.yml +++ b/.github/workflows/scenario-builds.yml @@ -2,6 +2,7 @@ name: "Scenario Build Verification" on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - "test/scenarios/**" - "nodejs/src/**" diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index b1d3cae6d..49b003fd4 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -100,9 +100,15 @@ jobs: git push origin "$BRANCH" --force-with-lease if gh pr view "$BRANCH" >/dev/null 2>&1; then - echo "Pull request for branch '$BRANCH' already exists; updated branch only." + if [ "$(gh pr view "$BRANCH" --json isDraft --jq '.isDraft')" = "false" ]; then + gh pr ready "$BRANCH" --undo + echo "Pull request for branch '$BRANCH' already existed and was moved back to draft after updating the branch." + else + echo "Pull request for branch '$BRANCH' already exists and is already a draft; updated branch only." + fi else gh pr create \ + --draft \ --title "Update @github/copilot to $VERSION" \ --body "Automated update of \`@github/copilot\` to version \`$VERSION\`. @@ -111,6 +117,9 @@ jobs: - Re-ran all code generators (\`scripts/codegen\`) - Formatted generated output + ### Next steps + When ready, click **Ready for review** to trigger CI checks. + > Created by the **Update @github/copilot Dependency** workflow." \ --base main \ --head "$BRANCH" diff --git a/.github/workflows/verify-compiled.yml b/.github/workflows/verify-compiled.yml index b78c4a85f..792dac172 100644 --- a/.github/workflows/verify-compiled.yml +++ b/.github/workflows/verify-compiled.yml @@ -2,6 +2,7 @@ name: Verify compiled workflows on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: - '.github/workflows/*.md' - '.github/workflows/*.lock.yml' From a5810d374512718cd894f97071cdf5e044b9ff85 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 23:11:13 -0400 Subject: [PATCH 20/34] Fix update-copilot-dependency workflow to handle closed PRs (#1087) The workflow failed when gh pr view found a closed PR (#1085) and tried to run gh pr ready --undo on it. Fix by checking PR state is OPEN before attempting to interact with it. Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/eb5269ed-52c5-4510-9249-65d2eef53de2 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .github/workflows/update-copilot-dependency.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index 49b003fd4..bc5c843b5 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -99,7 +99,8 @@ jobs: - Formatted generated code" git push origin "$BRANCH" --force-with-lease - if gh pr view "$BRANCH" >/dev/null 2>&1; then + PR_STATE="$(gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null || echo "")" + if [ "$PR_STATE" = "OPEN" ]; then if [ "$(gh pr view "$BRANCH" --json isDraft --jq '.isDraft')" = "false" ]; then gh pr ready "$BRANCH" --undo echo "Pull request for branch '$BRANCH' already existed and was moved back to draft after updating the branch." From a6662b7e03318e4ea5afd8104d2d82804a4df4f9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:04:04 -0400 Subject: [PATCH 21/34] fix: fetch remote branch before checkout in update-copilot-dependency workflow (#1088) * Initial plan * fix: fetch remote branch before checkout to fix --force-with-lease stale info error Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/f1827486-b704-48bd-851e-bd6584dd6ae7 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .github/workflows/update-copilot-dependency.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index bc5c843b5..a39d0575e 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -79,8 +79,9 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" if git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1; then + git fetch origin "$BRANCH" git checkout "$BRANCH" - git reset --hard HEAD + git reset --hard "origin/$BRANCH" else git checkout -b "$BRANCH" fi From 21c6d5e5bacd301d7fda6aad99a034321aeaeab1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 08:39:56 -0400 Subject: [PATCH 22/34] Update @github/copilot to 1.0.28 (#1089) - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 8 ++++ dotnet/src/Generated/SessionEvents.cs | 4 ++ go/rpc/generated_rpc.go | 32 ++++++++------- nodejs/package-lock.json | 56 +++++++++++++------------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 2 + nodejs/src/generated/session-events.ts | 4 ++ python/copilot/generated/rpc.py | 10 ++++- test/harness/package-lock.json | 56 +++++++++++++------------- test/harness/package.json | 2 +- 11 files changed, 103 insertions(+), 75 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 342d35c25..4f77933f9 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -631,6 +631,14 @@ public sealed class WorkspacesGetWorkspaceResultWorkspace /// Gets or sets the session_sync_level value. [JsonPropertyName("session_sync_level")] public WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel? SessionSyncLevel { get; set; } + + /// Gets or sets the pr_create_sync_dismissed value. + [JsonPropertyName("pr_create_sync_dismissed")] + public bool? PrCreateSyncDismissed { get; set; } + + /// Gets or sets the chronicle_sync_dismissed value. + [JsonPropertyName("chronicle_sync_dismissed")] + public bool? ChronicleSyncDismissed { get; set; } } /// RPC data type for WorkspacesGetWorkspace operations. diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index a0dbf3c28..74f470471 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -3492,6 +3492,10 @@ public partial class PermissionRequestWrite : PermissionRequest [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("newFileContents")] public string? NewFileContents { get; set; } + + /// Whether the UI can offer session-wide approval for file write operations. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } } /// File or directory read permission request. diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 39478b0c4..d15555a35 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -383,21 +383,23 @@ type WorkspacesGetWorkspaceResult struct { } type WorkspaceClass struct { - Branch *string `json:"branch,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Cwd *string `json:"cwd,omitempty"` - GitRoot *string `json:"git_root,omitempty"` - HostType *HostType `json:"host_type,omitempty"` - ID string `json:"id"` - McLastEventID *string `json:"mc_last_event_id,omitempty"` - McSessionID *string `json:"mc_session_id,omitempty"` - McTaskID *string `json:"mc_task_id,omitempty"` - Name *string `json:"name,omitempty"` - Repository *string `json:"repository,omitempty"` - SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` - Summary *string `json:"summary,omitempty"` - SummaryCount *int64 `json:"summary_count,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` + Branch *string `json:"branch,omitempty"` + ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Cwd *string `json:"cwd,omitempty"` + GitRoot *string `json:"git_root,omitempty"` + HostType *HostType `json:"host_type,omitempty"` + ID string `json:"id"` + McLastEventID *string `json:"mc_last_event_id,omitempty"` + McSessionID *string `json:"mc_session_id,omitempty"` + McTaskID *string `json:"mc_task_id,omitempty"` + Name *string `json:"name,omitempty"` + PRCreateSyncDismissed *bool `json:"pr_create_sync_dismissed,omitempty"` + Repository *string `json:"repository,omitempty"` + SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` + Summary *string `json:"summary,omitempty"` + SummaryCount *int64 `json:"summary_count,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` } type WorkspacesListFilesResult struct { diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index bdfbd1aff..ca788723b 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.27", + "@github/copilot": "^1.0.28", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.27.tgz", - "integrity": "sha512-f9rlylQWzXRWyK+KkCOmC/wCKXbqQUwfwRkgT8p5JqHlTBvmJ6CS8M9aPo4ycv0aJjtbasLlkYHdrfITMA1cjg==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.28.tgz", + "integrity": "sha512-S1Y+KnhywjIsK1DzskoCqPVC3uURohvCRyDkGPWXvMw+lXO5ryOJvHFZDDw7MSRjT7ea7T0m8e3yKdK0OxJhnw==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.27", - "@github/copilot-darwin-x64": "1.0.27", - "@github/copilot-linux-arm64": "1.0.27", - "@github/copilot-linux-x64": "1.0.27", - "@github/copilot-win32-arm64": "1.0.27", - "@github/copilot-win32-x64": "1.0.27" + "@github/copilot-darwin-arm64": "1.0.28", + "@github/copilot-darwin-x64": "1.0.28", + "@github/copilot-linux-arm64": "1.0.28", + "@github/copilot-linux-x64": "1.0.28", + "@github/copilot-win32-arm64": "1.0.28", + "@github/copilot-win32-x64": "1.0.28" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.27.tgz", - "integrity": "sha512-F0mzfLTGngGugSfTuDtG4MMsAK4U8u+Okcb2ftrn9ObHakz/Fzr3DOMld2T8GyzQIbhOnmOYwOk2UvOAZTq/Vg==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.28.tgz", + "integrity": "sha512-Bkis5dkOsdgaK95j/8mgIGSxHlRuL211Wa3S4MeeYGrilZweaG20sa0jktzagL6XFxfPRKBC87E+fDFyXz1L3g==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.27.tgz", - "integrity": "sha512-Nn1KME4kZDsve+HOMbwvO0XfCznyZN9mzh+DRL+Q5e2CF0PIxIcJC7zP9t1/dBux/CUOyDppniUd5OVTuqbWVQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.28.tgz", + "integrity": "sha512-0RIabmr05KgPPUcD4kpKNBGg/eRwJF2NrYtibDUCIRFWKZu7q0m9c9EURpW0wOO32cXZtAQ+BmJIGlqfCkt6gA==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.27.tgz", - "integrity": "sha512-tg91mQQIChPDdSZCJ2e6iNIvjaOhBAT78o0jkxjF2Hn9bmNt8Iu/ywDUorugtPM+0t82PZY8AwUPkyMmuYokTQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.28.tgz", + "integrity": "sha512-A/zQ4ifN+FSSEHdPHajv5UwygS5BOQ8l1AJMYdVBnnuqVX9bCcRAJJ4S/F60AnaDimzDvVuYSe3lYXRYxz3M5A==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.27.tgz", - "integrity": "sha512-E2cJLoiT5hWtuLPbVS04fxTM5F7yJL2Xazlf44PLXWPzbp5LQvQ+0SDSxnaAkRVT/DqtrtKitYMCxuDQpkdH7Q==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.28.tgz", + "integrity": "sha512-0VqoW9hj7qKj+eH2un9E7zn9AbassTZHkKQPsd8yPvLsmPaNJgsHMYDrCCNZNol2ZSGt/XskTfmWQaQM6BoBfg==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.27.tgz", - "integrity": "sha512-/V530uFEHf3Pl6itJX4nJjx5fX9RAEIejDiqCDoKvuL8prFHGvx2CoKEz00+1QGpQHN0Z2PA0spN9a8V8o+/KA==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.28.tgz", + "integrity": "sha512-f28NKudBtIXTpIliHGJbRhEfCItsXKWNzXzgqgmP8FZB+JYrqG/ysU2qCUCxhpv3PLjMLWqnsWs+mIvVLTH9zw==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.27.tgz", - "integrity": "sha512-ifRG64DAWG09AV6TIvkd5X08DaVMdyvrBC0Iavr75XVA1B9dKldocJAfVtQzhZTkjo/PLHRFTaAaPMNhGTfziA==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.28.tgz", + "integrity": "sha512-b9ZEx2i5P7DZTP66FXTfwf81r5kbAqs2GEJjDdevCwxH7cRexqM9eBxQGj1zGtm4qXF7JGK2eH6Ay7NC28m1Iw==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 6c2f44c1c..4c00fcc04 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.27", + "@github/copilot": "^1.0.28", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 1ccbb8dbb..c8985d20e 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.27", + "@github/copilot": "^1.0.28", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 9b70619f8..e6b2f705c 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -651,6 +651,8 @@ export interface WorkspacesGetWorkspaceResult { mc_session_id?: string; mc_last_event_id?: string; session_sync_level?: "local" | "user" | "repo_and_user"; + pr_create_sync_dismissed?: boolean; + chronicle_sync_dismissed?: boolean; } | null; } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 2a5b08b21..65deaf2b3 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -2673,6 +2673,10 @@ export type SessionEvent = * Complete new file contents for newly created files */ newFileContents?: string; + /** + * Whether the UI can offer session-wide approval for file write operations + */ + canOfferSessionApproval: boolean; } | { /** diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index a4f15d9e2..62ae5d934 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -1215,6 +1215,7 @@ class SessionSyncLevel(Enum): class Workspace: id: UUID branch: str | None = None + chronicle_sync_dismissed: bool | None = None created_at: datetime | None = None cwd: str | None = None git_root: str | None = None @@ -1223,6 +1224,7 @@ class Workspace: mc_session_id: str | None = None mc_task_id: str | None = None name: str | None = None + pr_create_sync_dismissed: bool | None = None repository: str | None = None session_sync_level: SessionSyncLevel | None = None summary: str | None = None @@ -1234,6 +1236,7 @@ def from_dict(obj: Any) -> 'Workspace': assert isinstance(obj, dict) id = UUID(obj.get("id")) branch = from_union([from_str, from_none], obj.get("branch")) + chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) created_at = from_union([from_datetime, from_none], obj.get("created_at")) cwd = from_union([from_str, from_none], obj.get("cwd")) git_root = from_union([from_str, from_none], obj.get("git_root")) @@ -1242,18 +1245,21 @@ def from_dict(obj: Any) -> 'Workspace': mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) name = from_union([from_str, from_none], obj.get("name")) + pr_create_sync_dismissed = from_union([from_bool, from_none], obj.get("pr_create_sync_dismissed")) repository = from_union([from_str, from_none], obj.get("repository")) session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) summary = from_union([from_str, from_none], obj.get("summary")) summary_count = from_union([from_int, from_none], obj.get("summary_count")) updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, repository, session_sync_level, summary, summary_count, updated_at) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, pr_create_sync_dismissed, repository, session_sync_level, summary, summary_count, updated_at) def to_dict(self) -> dict: result: dict = {} result["id"] = str(self.id) if self.branch is not None: result["branch"] = from_union([from_str, from_none], self.branch) + if self.chronicle_sync_dismissed is not None: + result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) if self.created_at is not None: result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) if self.cwd is not None: @@ -1270,6 +1276,8 @@ def to_dict(self) -> dict: result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) + if self.pr_create_sync_dismissed is not None: + result["pr_create_sync_dismissed"] = from_union([from_bool, from_none], self.pr_create_sync_dismissed) if self.repository is not None: result["repository"] = from_union([from_str, from_none], self.repository) if self.session_sync_level is not None: diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 34405734b..28ecc71f2 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.27", + "@github/copilot": "^1.0.28", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.27.tgz", - "integrity": "sha512-f9rlylQWzXRWyK+KkCOmC/wCKXbqQUwfwRkgT8p5JqHlTBvmJ6CS8M9aPo4ycv0aJjtbasLlkYHdrfITMA1cjg==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.28.tgz", + "integrity": "sha512-S1Y+KnhywjIsK1DzskoCqPVC3uURohvCRyDkGPWXvMw+lXO5ryOJvHFZDDw7MSRjT7ea7T0m8e3yKdK0OxJhnw==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.27", - "@github/copilot-darwin-x64": "1.0.27", - "@github/copilot-linux-arm64": "1.0.27", - "@github/copilot-linux-x64": "1.0.27", - "@github/copilot-win32-arm64": "1.0.27", - "@github/copilot-win32-x64": "1.0.27" + "@github/copilot-darwin-arm64": "1.0.28", + "@github/copilot-darwin-x64": "1.0.28", + "@github/copilot-linux-arm64": "1.0.28", + "@github/copilot-linux-x64": "1.0.28", + "@github/copilot-win32-arm64": "1.0.28", + "@github/copilot-win32-x64": "1.0.28" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.27.tgz", - "integrity": "sha512-F0mzfLTGngGugSfTuDtG4MMsAK4U8u+Okcb2ftrn9ObHakz/Fzr3DOMld2T8GyzQIbhOnmOYwOk2UvOAZTq/Vg==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.28.tgz", + "integrity": "sha512-Bkis5dkOsdgaK95j/8mgIGSxHlRuL211Wa3S4MeeYGrilZweaG20sa0jktzagL6XFxfPRKBC87E+fDFyXz1L3g==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.27.tgz", - "integrity": "sha512-Nn1KME4kZDsve+HOMbwvO0XfCznyZN9mzh+DRL+Q5e2CF0PIxIcJC7zP9t1/dBux/CUOyDppniUd5OVTuqbWVQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.28.tgz", + "integrity": "sha512-0RIabmr05KgPPUcD4kpKNBGg/eRwJF2NrYtibDUCIRFWKZu7q0m9c9EURpW0wOO32cXZtAQ+BmJIGlqfCkt6gA==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.27.tgz", - "integrity": "sha512-tg91mQQIChPDdSZCJ2e6iNIvjaOhBAT78o0jkxjF2Hn9bmNt8Iu/ywDUorugtPM+0t82PZY8AwUPkyMmuYokTQ==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.28.tgz", + "integrity": "sha512-A/zQ4ifN+FSSEHdPHajv5UwygS5BOQ8l1AJMYdVBnnuqVX9bCcRAJJ4S/F60AnaDimzDvVuYSe3lYXRYxz3M5A==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.27.tgz", - "integrity": "sha512-E2cJLoiT5hWtuLPbVS04fxTM5F7yJL2Xazlf44PLXWPzbp5LQvQ+0SDSxnaAkRVT/DqtrtKitYMCxuDQpkdH7Q==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.28.tgz", + "integrity": "sha512-0VqoW9hj7qKj+eH2un9E7zn9AbassTZHkKQPsd8yPvLsmPaNJgsHMYDrCCNZNol2ZSGt/XskTfmWQaQM6BoBfg==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.27.tgz", - "integrity": "sha512-/V530uFEHf3Pl6itJX4nJjx5fX9RAEIejDiqCDoKvuL8prFHGvx2CoKEz00+1QGpQHN0Z2PA0spN9a8V8o+/KA==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.28.tgz", + "integrity": "sha512-f28NKudBtIXTpIliHGJbRhEfCItsXKWNzXzgqgmP8FZB+JYrqG/ysU2qCUCxhpv3PLjMLWqnsWs+mIvVLTH9zw==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.27", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.27.tgz", - "integrity": "sha512-ifRG64DAWG09AV6TIvkd5X08DaVMdyvrBC0Iavr75XVA1B9dKldocJAfVtQzhZTkjo/PLHRFTaAaPMNhGTfziA==", + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.28.tgz", + "integrity": "sha512-b9ZEx2i5P7DZTP66FXTfwf81r5kbAqs2GEJjDdevCwxH7cRexqM9eBxQGj1zGtm4qXF7JGK2eH6Ay7NC28m1Iw==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index c1e90d1b7..649cd5df6 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.27", + "@github/copilot": "^1.0.28", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From c04c8c0efced9f8d4961e9a253a1831cd7d5efc7 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 16 Apr 2026 15:30:57 -0400 Subject: [PATCH 23/34] Add runtime header options across SDKs (#1094) Expose provider headers and per-message requestHeaders across Node, Python, Go, and .NET, and add focused tests covering create, resume, and send request forwarding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Session.cs | 4 +- dotnet/src/Types.cs | 13 ++++ dotnet/test/SerializationTests.cs | 98 +++++++++++++++++++++++++++++++ go/session.go | 13 ++-- go/types.go | 17 ++++-- go/types_test.go | 57 ++++++++++++++++++ nodejs/src/session.ts | 1 + nodejs/src/types.ts | 10 ++++ nodejs/test/client.test.ts | 88 +++++++++++++++++++++++++++ python/copilot/client.py | 2 + python/copilot/session.py | 14 ++++- python/test_client.py | 97 ++++++++++++++++++++++++++++++ 12 files changed, 400 insertions(+), 14 deletions(-) diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 733b94a71..20d6525b8 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -192,7 +192,8 @@ public async Task SendAsync(MessageOptions options, CancellationToken ca Attachments = options.Attachments, Mode = options.Mode, Traceparent = traceparent, - Tracestate = tracestate + Tracestate = tracestate, + RequestHeaders = options.RequestHeaders, }; var response = await InvokeRpcAsync( @@ -1223,6 +1224,7 @@ internal record SendMessageRequest public string? Mode { get; init; } public string? Traceparent { get; init; } public string? Tracestate { get; init; } + public IDictionary? RequestHeaders { get; init; } } internal record SendMessageResponse diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 978defcfb..1fd8afa39 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1483,6 +1483,12 @@ public class ProviderConfig /// [JsonPropertyName("azure")] public AzureOptions? Azure { get; set; } + + /// + /// Custom HTTP headers to include in outbound provider requests. + /// + [JsonPropertyName("headers")] + public IDictionary? Headers { get; set; } } /// @@ -2157,6 +2163,9 @@ protected MessageOptions(MessageOptions? other) Attachments = other.Attachments is not null ? [.. other.Attachments] : null; Mode = other.Mode; Prompt = other.Prompt; + RequestHeaders = other.RequestHeaders is not null + ? new Dictionary(other.RequestHeaders) + : null; } /// @@ -2171,6 +2180,10 @@ protected MessageOptions(MessageOptions? other) /// Interaction mode for the message (e.g., "plan", "edit"). /// public string? Mode { get; set; } + /// + /// Custom per-turn HTTP headers for outbound model requests. + /// + public IDictionary? RequestHeaders { get; set; } /// /// Creates a shallow clone of this instance. diff --git a/dotnet/test/SerializationTests.cs b/dotnet/test/SerializationTests.cs index 6fb266be1..4a976d2bc 100644 --- a/dotnet/test/SerializationTests.cs +++ b/dotnet/test/SerializationTests.cs @@ -67,6 +67,74 @@ public void SerializerOptions_CanResolveRequestIdTypeInfo() Assert.Equal(typeof(RequestId), typeInfo.Type); } + [Fact] + public void ProviderConfig_CanSerializeHeaders_WithSdkOptions() + { + var options = GetSerializerOptions(); + var original = new ProviderConfig + { + BaseUrl = "https://example.com/provider", + Headers = new Dictionary { ["Authorization"] = "Bearer provider-token" } + }; + + var json = JsonSerializer.Serialize(original, options); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal("https://example.com/provider", root.GetProperty("baseUrl").GetString()); + Assert.Equal("Bearer provider-token", root.GetProperty("headers").GetProperty("Authorization").GetString()); + + var deserialized = JsonSerializer.Deserialize(json, options); + Assert.NotNull(deserialized); + Assert.Equal("https://example.com/provider", deserialized.BaseUrl); + Assert.Equal("Bearer provider-token", deserialized.Headers!["Authorization"]); + } + + [Fact] + public void MessageOptions_CanSerializeRequestHeaders_WithSdkOptions() + { + var options = GetSerializerOptions(); + var original = new MessageOptions + { + Prompt = "real prompt", + Mode = "plan", + RequestHeaders = new Dictionary { ["X-Trace"] = "trace-value" } + }; + + var json = JsonSerializer.Serialize(original, options); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal("real prompt", root.GetProperty("prompt").GetString()); + Assert.Equal("plan", root.GetProperty("mode").GetString()); + Assert.Equal("trace-value", root.GetProperty("requestHeaders").GetProperty("X-Trace").GetString()); + + var deserialized = JsonSerializer.Deserialize(json, options); + Assert.NotNull(deserialized); + Assert.Equal("real prompt", deserialized.Prompt); + Assert.Equal("plan", deserialized.Mode); + Assert.Equal("trace-value", deserialized.RequestHeaders!["X-Trace"]); + } + + [Fact] + public void SendMessageRequest_CanSerializeRequestHeaders_WithSdkOptions() + { + var options = GetSerializerOptions(); + var requestType = GetNestedType(typeof(CopilotSession), "SendMessageRequest"); + var request = CreateInternalRequest( + requestType, + ("SessionId", "session-id"), + ("Prompt", "real prompt"), + ("Mode", "plan"), + ("RequestHeaders", new Dictionary { ["X-Trace"] = "trace-value" })); + + var json = JsonSerializer.Serialize(request, requestType, options); + using var document = JsonDocument.Parse(json); + var root = document.RootElement; + Assert.Equal("session-id", root.GetProperty("sessionId").GetString()); + Assert.Equal("real prompt", root.GetProperty("prompt").GetString()); + Assert.Equal("plan", root.GetProperty("mode").GetString()); + Assert.Equal("trace-value", root.GetProperty("requestHeaders").GetProperty("X-Trace").GetString()); + } + private static JsonSerializerOptions GetSerializerOptions() { var prop = typeof(CopilotClient) @@ -77,4 +145,34 @@ private static JsonSerializerOptions GetSerializerOptions() Assert.NotNull(options); return options; } + + private static Type GetNestedType(Type containingType, string name) + { + var type = containingType.GetNestedType(name, System.Reflection.BindingFlags.NonPublic); + Assert.NotNull(type); + return type!; + } + + private static object CreateInternalRequest(Type type, params (string Name, object? Value)[] properties) + { + var instance = System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(type); + + foreach (var (name, value) in properties) + { + var property = type.GetProperty(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic); + Assert.NotNull(property); + + if (property!.SetMethod is not null) + { + property.SetValue(instance, value); + continue; + } + + var field = type.GetField($"<{name}>k__BackingField", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + Assert.NotNull(field); + field!.SetValue(instance, value); + } + + return instance; + } } diff --git a/go/session.go b/go/session.go index a2e52e72c..be8c78e2b 100644 --- a/go/session.go +++ b/go/session.go @@ -132,12 +132,13 @@ func newSession(sessionID string, client *jsonrpc2.Client, workspacePath string) func (s *Session) Send(ctx context.Context, options MessageOptions) (string, error) { traceparent, tracestate := getTraceContext(ctx) req := sessionSendRequest{ - SessionID: s.SessionID, - Prompt: options.Prompt, - Attachments: options.Attachments, - Mode: options.Mode, - Traceparent: traceparent, - Tracestate: tracestate, + SessionID: s.SessionID, + Prompt: options.Prompt, + Attachments: options.Attachments, + Mode: options.Mode, + Traceparent: traceparent, + Tracestate: tracestate, + RequestHeaders: options.RequestHeaders, } result, err := s.client.Request("session.send", req) diff --git a/go/types.go b/go/types.go index d609ce00a..f889d3e2a 100644 --- a/go/types.go +++ b/go/types.go @@ -783,6 +783,8 @@ type ProviderConfig struct { BearerToken string `json:"bearerToken,omitempty"` // Azure contains Azure-specific options Azure *AzureProviderOptions `json:"azure,omitempty"` + // Headers are custom HTTP headers included in outbound provider requests. + Headers map[string]string `json:"headers,omitempty"` } // AzureProviderOptions contains Azure-specific provider configuration @@ -807,6 +809,8 @@ type MessageOptions struct { Attachments []Attachment // Mode is the message delivery mode (default: "enqueue") Mode string + // RequestHeaders are custom per-turn HTTP headers for outbound model requests. + RequestHeaders map[string]string } // SessionEventHandler is a callback for session events @@ -1142,12 +1146,13 @@ type sessionAbortRequest struct { } type sessionSendRequest struct { - SessionID string `json:"sessionId"` - Prompt string `json:"prompt"` - Attachments []Attachment `json:"attachments,omitempty"` - Mode string `json:"mode,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` + SessionID string `json:"sessionId"` + Prompt string `json:"prompt"` + Attachments []Attachment `json:"attachments,omitempty"` + Mode string `json:"mode,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` + RequestHeaders map[string]string `json:"requestHeaders,omitempty"` } // sessionSendResponse is the response from session.send diff --git a/go/types_test.go b/go/types_test.go index 80b0cc545..b37e94f15 100644 --- a/go/types_test.go +++ b/go/types_test.go @@ -91,3 +91,60 @@ func TestPermissionRequestResult_JSONSerialize(t *testing.T) { t.Errorf("expected %s, got %s", expected, string(data)) } } + +func TestProviderConfig_JSONIncludesHeaders(t *testing.T) { + config := ProviderConfig{ + BaseURL: "https://example.com/provider", + Headers: map[string]string{"Authorization": "Bearer provider-token"}, + } + + data, err := json.Marshal(config) + if err != nil { + t.Fatalf("failed to marshal provider config: %v", err) + } + + var decoded map[string]any + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("failed to unmarshal provider config: %v", err) + } + + if decoded["baseUrl"] != "https://example.com/provider" { + t.Fatalf("expected baseUrl to round-trip, got %v", decoded["baseUrl"]) + } + headers, ok := decoded["headers"].(map[string]any) + if !ok { + t.Fatalf("expected headers object, got %T", decoded["headers"]) + } + if headers["Authorization"] != "Bearer provider-token" { + t.Fatalf("expected Authorization header, got %v", headers["Authorization"]) + } +} + +func TestSessionSendRequest_JSONIncludesRequestHeaders(t *testing.T) { + req := sessionSendRequest{ + SessionID: "session-1", + Prompt: "hello", + RequestHeaders: map[string]string{"Authorization": "Bearer turn-token"}, + } + + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("failed to marshal session send request: %v", err) + } + + var decoded map[string]any + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatalf("failed to unmarshal session send request: %v", err) + } + + if decoded["prompt"] != "hello" { + t.Fatalf("expected prompt to round-trip, got %v", decoded["prompt"]) + } + headers, ok := decoded["requestHeaders"].(map[string]any) + if !ok { + t.Fatalf("expected requestHeaders object, got %T", decoded["requestHeaders"]) + } + if headers["Authorization"] != "Bearer turn-token" { + t.Fatalf("expected Authorization header, got %v", headers["Authorization"]) + } +} diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index ffb2c045a..eae4cab94 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -184,6 +184,7 @@ export class CopilotSession { prompt: options.prompt, attachments: options.attachments, mode: options.mode, + requestHeaders: options.requestHeaders, }); return (response as { messageId: string }).messageId; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index a4cb77fa2..0c901f989 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1403,6 +1403,11 @@ export interface ProviderConfig { */ apiVersion?: string; }; + + /** + * Custom HTTP headers to include in outbound provider requests. + */ + headers?: Record; } /** @@ -1452,6 +1457,11 @@ export interface MessageOptions { * - "immediate": Send immediately */ mode?: "enqueue" | "immediate"; + + /** + * Custom HTTP headers to include in outbound model requests for this turn. + */ + requestHeaders?: Record; } /** diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 0c0611df8..870ccb1ed 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -98,6 +98,67 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("forwards provider headers in session.create request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.create") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.createSession({ + onPermissionRequest: approveAll, + provider: { + baseUrl: "https://example.com/provider", + headers: { Authorization: "Bearer provider-token" }, + }, + }); + + const payload = spy.mock.calls.find(([method]) => method === "session.create")![1] as any; + expect(payload.provider).toEqual( + expect.objectContaining({ + baseUrl: "https://example.com/provider", + headers: { Authorization: "Bearer provider-token" }, + }) + ); + spy.mockRestore(); + }); + + it("forwards provider headers in session.resume request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + provider: { + baseUrl: "https://example.com/provider", + headers: { Authorization: "Bearer resume-token" }, + }, + }); + + const payload = spy.mock.calls.find(([method]) => method === "session.resume")![1] as any; + expect(payload.provider).toEqual( + expect.objectContaining({ + baseUrl: "https://example.com/provider", + headers: { Authorization: "Bearer resume-token" }, + }) + ); + spy.mockRestore(); + }); + it("does not request permissions on session.resume when using the default joinSession handler", async () => { const client = new CopilotClient(); await client.start(); @@ -720,6 +781,33 @@ describe("CopilotClient", () => { ); }); + it("forwards requestHeaders in session.send request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string) => { + if (method === "session.send") return { messageId: "m1" }; + throw new Error(`Unexpected method: ${method}`); + }); + + await session.send({ + prompt: "hello", + requestHeaders: { Authorization: "Bearer turn-token" }, + }); + + expect(spy).toHaveBeenCalledWith( + "session.send", + expect.objectContaining({ + prompt: "hello", + requestHeaders: { Authorization: "Bearer turn-token" }, + }) + ); + }); + it("does not include trace context when no callback is provided", async () => { const client = new CopilotClient(); await client.start(); diff --git a/python/copilot/client.py b/python/copilot/client.py index 407ad1673..5d62db301 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2124,6 +2124,8 @@ def _convert_provider_to_wire_format( wire_provider["wireApi"] = provider["wire_api"] if "bearer_token" in provider: wire_provider["bearerToken"] = provider["bearer_token"] + if "headers" in provider: + wire_provider["headers"] = provider["headers"] if "azure" in provider: azure = provider["azure"] wire_azure: dict[str, Any] = {} diff --git a/python/copilot/session.py b/python/copilot/session.py index 9fd9f79bd..9552f75b6 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -825,6 +825,7 @@ class ProviderConfig(TypedDict, total=False): # Takes precedence over api_key when both are set. bearer_token: str azure: AzureProviderOptions # Azure-specific options + headers: dict[str, str] class SessionConfig(TypedDict, total=False): @@ -1066,6 +1067,7 @@ async def send( *, attachments: list[Attachment] | None = None, mode: Literal["enqueue", "immediate"] | None = None, + request_headers: dict[str, str] | None = None, ) -> str: """ Send a message to this session. @@ -1078,6 +1080,7 @@ async def send( prompt: The message text to send. attachments: Optional file, directory, or selection attachments. mode: Message delivery mode (``"enqueue"`` or ``"immediate"``). + request_headers: Optional per-turn HTTP headers for outbound model requests. Returns: The message ID assigned by the server, which can be used to correlate events. @@ -1099,6 +1102,8 @@ async def send( params["attachments"] = attachments if mode is not None: params["mode"] = mode + if request_headers is not None: + params["requestHeaders"] = request_headers params.update(get_trace_context()) response = await self._client.request("session.send", params) @@ -1110,6 +1115,7 @@ async def send_and_wait( *, attachments: list[Attachment] | None = None, mode: Literal["enqueue", "immediate"] | None = None, + request_headers: dict[str, str] | None = None, timeout: float = 60.0, ) -> SessionEvent | None: """ @@ -1125,6 +1131,7 @@ async def send_and_wait( prompt: The message text to send. attachments: Optional file, directory, or selection attachments. mode: Message delivery mode (``"enqueue"`` or ``"immediate"``). + request_headers: Optional per-turn HTTP headers for outbound model requests. timeout: Timeout in seconds (default: 60). Controls how long to wait; does not abort in-flight agent work. @@ -1160,7 +1167,12 @@ def handler(event: SessionEventTypeAlias) -> None: unsubscribe = self.on(handler) try: - await self.send(prompt, attachments=attachments, mode=mode) + await self.send( + prompt, + attachments=attachments, + mode=mode, + request_headers=request_headers, + ) await asyncio.wait_for(idle_event.wait(), timeout=timeout) if error_event: raise error_event diff --git a/python/test_client.py b/python/test_client.py index 5d0dc868e..0896b54e2 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -444,6 +444,103 @@ async def mock_request(method, params): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_session_forwards_provider_headers(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.create": + return {"sessionId": params["sessionId"]} + return await original_request(method, params) + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + provider={ + "base_url": "https://example.com/provider", + "headers": {"Authorization": "Bearer provider-token"}, + }, + ) + + provider = captured["session.create"]["provider"] + assert provider["baseUrl"] == "https://example.com/provider" + assert provider["headers"] == {"Authorization": "Bearer provider-token"} + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_forwards_provider_headers(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + provider={ + "base_url": "https://example.com/provider", + "headers": {"Authorization": "Bearer resume-token"}, + }, + ) + + provider = captured["session.resume"]["provider"] + assert provider["baseUrl"] == "https://example.com/provider" + assert provider["headers"] == {"Authorization": "Bearer resume-token"} + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_session_send_forwards_request_headers(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.send": + return {"messageId": "msg-1"} + return await original_request(method, params) + + client._client.request = mock_request + await session.send( + "hello", + request_headers={"Authorization": "Bearer turn-token"}, + ) + + assert captured["session.send"]["prompt"] == "hello" + assert captured["session.send"]["requestHeaders"] == { + "Authorization": "Bearer turn-token" + } + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_create_session_forwards_agent(self): client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) From 719beb0abf591cc343615885bc82f20320a7c6c8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:41:47 -0400 Subject: [PATCH 24/34] Update @github/copilot to 1.0.29 (#1092) * Update @github/copilot to 1.0.29 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Fix C#/Go/Python codegen to recurse into nested API sub-groups The code generators for C#, Go, and Python only processed one level of API groups, silently dropping nested sub-groups like skills.config.setDisabledSkills and mcp.config.*. TypeScript already handled these correctly via recursive emitGroup. - C#: emitServerApiClass/emitSessionApiClass now return string[] and recurse, creating sub-API classes (e.g. ServerSkillsConfigApi) - Go: new emitApiGroup helper recurses and emits zero-cost accessor methods on parent type aliases (e.g. rpc.Skills.Config()) - Python: new emitPyApiGroup helper emits sub-groups depth-first and adds sub-group instances as attributes on parent classes Regenerated all three outputs to pick up both mcp.config.* (pre-existing gap) and skills.config.setDisabledSkills (new in 1.0.29). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 203 ++++++++++++++++++++++++++++++- go/rpc/generated_rpc.go | 141 ++++++++++++++++++++- nodejs/package-lock.json | 56 ++++----- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/generated/rpc.ts | 84 ++++++++++++- python/copilot/generated/rpc.py | 191 +++++++++++++++++++++++++++-- scripts/codegen/csharp.ts | 58 +++++++-- scripts/codegen/go.ts | 48 ++++++-- scripts/codegen/python.ts | 72 +++++++---- test/harness/package-lock.json | 56 ++++----- test/harness/package.json | 2 +- 12 files changed, 793 insertions(+), 122 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 4f77933f9..e75ff861d 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -90,7 +90,7 @@ public sealed class ModelCapabilitiesLimits /// Maximum total context window size in tokens. [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_context_window_tokens")] - public long MaxContextWindowTokens { get; set; } + public long? MaxContextWindowTokens { get; set; } /// Vision-specific limits. [JsonPropertyName("vision")] @@ -102,11 +102,11 @@ public sealed class ModelCapabilities { /// Feature flags indicating what the model supports. [JsonPropertyName("supports")] - public ModelCapabilitiesSupports Supports { get => field ??= new(); set; } + public ModelCapabilitiesSupports? Supports { get; set; } /// Token limits for prompts, outputs, and context window. [JsonPropertyName("limits")] - public ModelCapabilitiesLimits Limits { get => field ??= new(); set; } + public ModelCapabilitiesLimits? Limits { get; set; } } /// Policy state (if applicable). @@ -284,6 +284,109 @@ internal sealed class McpDiscoverRequest public string? WorkingDirectory { get; set; } } +/// RPC data type for McpConfigList operations. +public sealed class McpConfigList +{ + /// All MCP servers from user config, keyed by name. + [JsonPropertyName("servers")] + public IDictionary Servers { get => field ??= new Dictionary(); set; } +} + +/// RPC data type for McpConfigAdd operations. +internal sealed class McpConfigAddRequest +{ + /// Unique name for the MCP server. + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// MCP server configuration (local/stdio or remote/http). + [JsonPropertyName("config")] + public object Config { get; set; } = null!; +} + +/// RPC data type for McpConfigUpdate operations. +internal sealed class McpConfigUpdateRequest +{ + /// Name of the MCP server to update. + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// MCP server configuration (local/stdio or remote/http). + [JsonPropertyName("config")] + public object Config { get; set; } = null!; +} + +/// RPC data type for McpConfigRemove operations. +internal sealed class McpConfigRemoveRequest +{ + /// Name of the MCP server to remove. + [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; +} + +/// RPC data type for ServerSkill operations. +public sealed class ServerSkill +{ + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } + + /// Whether the skill is currently enabled (based on global config). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } + + /// The project path this skill belongs to (only for project/inherited skills). + [JsonPropertyName("projectPath")] + public string? ProjectPath { get; set; } +} + +/// RPC data type for ServerSkillList operations. +public sealed class ServerSkillList +{ + /// All discovered skills across all sources. + [JsonPropertyName("skills")] + public IList Skills { get => field ??= []; set; } +} + +/// RPC data type for SkillsDiscover operations. +internal sealed class SkillsDiscoverRequest +{ + /// Optional list of project directory paths to scan for project-scoped skills. + [JsonPropertyName("projectPaths")] + public IList? ProjectPaths { get; set; } + + /// Optional list of additional skill directory paths to include. + [JsonPropertyName("skillDirectories")] + public IList? SkillDirectories { get; set; } +} + +/// RPC data type for SkillsConfigSetDisabledSkills operations. +internal sealed class SkillsConfigSetDisabledSkillsRequest +{ + /// List of skill names to disable. + [JsonPropertyName("disabledSkills")] + public IList DisabledSkills { get => field ??= []; set; } +} + /// RPC data type for SessionFsSetProvider operations. public sealed class SessionFsSetProviderResult { @@ -2036,6 +2139,7 @@ internal ServerRpc(JsonRpc rpc) Tools = new ServerToolsApi(rpc); Account = new ServerAccountApi(rpc); Mcp = new ServerMcpApi(rpc); + Skills = new ServerSkillsApi(rpc); SessionFs = new ServerSessionFsApi(rpc); Sessions = new ServerSessionsApi(rpc); } @@ -2059,6 +2163,9 @@ public async Task PingAsync(string? message = null, CancellationToke /// Mcp APIs. public ServerMcpApi Mcp { get; } + /// Skills APIs. + public ServerSkillsApi Skills { get; } + /// SessionFs APIs. public ServerSessionFsApi SessionFs { get; } @@ -2126,6 +2233,7 @@ public sealed class ServerMcpApi internal ServerMcpApi(JsonRpc rpc) { _rpc = rpc; + Config = new ServerMcpConfigApi(rpc); } /// Calls "mcp.discover". @@ -2134,6 +2242,87 @@ public async Task DiscoverAsync(string? workingDirectory = nu var request = new McpDiscoverRequest { WorkingDirectory = workingDirectory }; return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.discover", [request], cancellationToken); } + + /// Config APIs. + public ServerMcpConfigApi Config { get; } +} + +/// Provides server-scoped McpConfig APIs. +public sealed class ServerMcpConfigApi +{ + private readonly JsonRpc _rpc; + + internal ServerMcpConfigApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Calls "mcp.config.list". + public async Task ListAsync(CancellationToken cancellationToken = default) + { + return await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.list", [], cancellationToken); + } + + /// Calls "mcp.config.add". + public async Task AddAsync(string name, object config, CancellationToken cancellationToken = default) + { + var request = new McpConfigAddRequest { Name = name, Config = config }; + await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.add", [request], cancellationToken); + } + + /// Calls "mcp.config.update". + public async Task UpdateAsync(string name, object config, CancellationToken cancellationToken = default) + { + var request = new McpConfigUpdateRequest { Name = name, Config = config }; + await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.update", [request], cancellationToken); + } + + /// Calls "mcp.config.remove". + public async Task RemoveAsync(string name, CancellationToken cancellationToken = default) + { + var request = new McpConfigRemoveRequest { Name = name }; + await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.remove", [request], cancellationToken); + } +} + +/// Provides server-scoped Skills APIs. +public sealed class ServerSkillsApi +{ + private readonly JsonRpc _rpc; + + internal ServerSkillsApi(JsonRpc rpc) + { + _rpc = rpc; + Config = new ServerSkillsConfigApi(rpc); + } + + /// Calls "skills.discover". + public async Task DiscoverAsync(IList? projectPaths = null, IList? skillDirectories = null, CancellationToken cancellationToken = default) + { + var request = new SkillsDiscoverRequest { ProjectPaths = projectPaths, SkillDirectories = skillDirectories }; + return await CopilotClient.InvokeRpcAsync(_rpc, "skills.discover", [request], cancellationToken); + } + + /// Config APIs. + public ServerSkillsConfigApi Config { get; } +} + +/// Provides server-scoped SkillsConfig APIs. +public sealed class ServerSkillsConfigApi +{ + private readonly JsonRpc _rpc; + + internal ServerSkillsConfigApi(JsonRpc rpc) + { + _rpc = rpc; + } + + /// Calls "skills.config.setDisabledSkills". + public async Task SetDisabledSkillsAsync(IList disabledSkills, CancellationToken cancellationToken = default) + { + var request = new SkillsConfigSetDisabledSkillsRequest { DisabledSkills = disabledSkills }; + await CopilotClient.InvokeRpcAsync(_rpc, "skills.config.setDisabledSkills", [request], cancellationToken); + } } /// Provides server-scoped SessionFs APIs. @@ -2978,6 +3167,10 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func => connection.sendRequest("mcp.discover", params), }, + skills: { + config: { + setDisabledSkills: async (params: SkillsConfigSetDisabledSkillsRequest): Promise => + connection.sendRequest("skills.config.setDisabledSkills", params), + }, + discover: async (params: SkillsDiscoverRequest): Promise => + connection.sendRequest("skills.discover", params), + }, sessionFs: { setProvider: async (params: SessionFsSetProviderRequest): Promise => connection.sendRequest("sessionFs.setProvider", params), diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 62ae5d934..c99182b17 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -165,7 +165,7 @@ def to_dict(self) -> dict: class ModelCapabilitiesLimits: """Token limits for prompts, outputs, and context window""" - max_context_window_tokens: int + max_context_window_tokens: int | None = None """Maximum total context window size in tokens""" max_output_tokens: int | None = None @@ -180,7 +180,7 @@ class ModelCapabilitiesLimits: @staticmethod def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': assert isinstance(obj, dict) - max_context_window_tokens = from_int(obj.get("max_context_window_tokens")) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) @@ -188,7 +188,8 @@ def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': def to_dict(self) -> dict: result: dict = {} - result["max_context_window_tokens"] = from_int(self.max_context_window_tokens) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) if self.max_output_tokens is not None: result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) if self.max_prompt_tokens is not None: @@ -226,23 +227,25 @@ def to_dict(self) -> dict: class ModelCapabilities: """Model capabilities and limits""" - limits: ModelCapabilitiesLimits + limits: ModelCapabilitiesLimits | None = None """Token limits for prompts, outputs, and context window""" - supports: ModelCapabilitiesSupports + supports: ModelCapabilitiesSupports | None = None """Feature flags indicating what the model supports""" @staticmethod def from_dict(obj: Any) -> 'ModelCapabilities': assert isinstance(obj, dict) - limits = ModelCapabilitiesLimits.from_dict(obj.get("limits")) - supports = ModelCapabilitiesSupports.from_dict(obj.get("supports")) + limits = from_union([ModelCapabilitiesLimits.from_dict, from_none], obj.get("limits")) + supports = from_union([ModelCapabilitiesSupports.from_dict, from_none], obj.get("supports")) return ModelCapabilities(limits, supports) def to_dict(self) -> dict: result: dict = {} - result["limits"] = to_class(ModelCapabilitiesLimits, self.limits) - result["supports"] = to_class(ModelCapabilitiesSupports, self.supports) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimits, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) return result @dataclass @@ -473,6 +476,8 @@ class MCPConfigFilterMappingString(Enum): NONE = "none" class MCPConfigType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" + HTTP = "http" LOCAL = "local" SSE = "sse" @@ -495,6 +500,8 @@ class MCPConfigServer: """Tools to include. Defaults to all tools if not specified.""" type: MCPConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None @@ -581,6 +588,8 @@ class MCPConfigAddConfig: """Tools to include. Defaults to all tools if not specified.""" type: MCPConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None @@ -672,6 +681,8 @@ class MCPConfigUpdateConfig: """Tools to include. Defaults to all tools if not specified.""" type: MCPConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + headers: dict[str, str] | None = None oauth_client_id: str | None = None oauth_public_client: bool | None = None @@ -845,6 +856,109 @@ def to_dict(self) -> dict: result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) return result +@dataclass +class SkillsConfigSetDisabledSkillsRequest: + disabled_skills: list[str] + """List of skill names to disable""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': + assert isinstance(obj, dict) + disabled_skills = from_list(from_str, obj.get("disabledSkills")) + return SkillsConfigSetDisabledSkillsRequest(disabled_skills) + + def to_dict(self) -> dict: + result: dict = {} + result["disabledSkills"] = from_list(from_str, self.disabled_skills) + return result + +@dataclass +class ServerSkill: + description: str + """Description of what the skill does""" + + enabled: bool + """Whether the skill is currently enabled (based on global config)""" + + name: str + """Unique identifier for the skill""" + + source: str + """Source location type (e.g., project, personal-copilot, plugin, builtin)""" + + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" + + path: str | None = None + """Absolute path to the skill file""" + + project_path: str | None = None + """The project path this skill belongs to (only for project/inherited skills)""" + + @staticmethod + def from_dict(obj: Any) -> 'ServerSkill': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) + + def to_dict(self) -> dict: + result: dict = {} + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) + return result + +@dataclass +class ServerSkillList: + skills: list[ServerSkill] + """All discovered skills across all sources""" + + @staticmethod + def from_dict(obj: Any) -> 'ServerSkillList': + assert isinstance(obj, dict) + skills = from_list(ServerSkill.from_dict, obj.get("skills")) + return ServerSkillList(skills) + + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) + return result + +@dataclass +class SkillsDiscoverRequest: + project_paths: list[str] | None = None + """Optional list of project directory paths to scan for project-scoped skills""" + + skill_directories: list[str] | None = None + """Optional list of additional skill directory paths to include""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillsDiscoverRequest': + assert isinstance(obj, dict) + project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) + skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) + return SkillsDiscoverRequest(project_paths, skill_directories) + + def to_dict(self) -> dict: + result: dict = {} + if self.project_paths is not None: + result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) + if self.skill_directories is not None: + result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) + return result + @dataclass class SessionFSSetProviderResult: success: bool @@ -3315,6 +3429,24 @@ def mcp_discover_request_from_dict(s: Any) -> MCPDiscoverRequest: def mcp_discover_request_to_dict(x: MCPDiscoverRequest) -> Any: return to_class(MCPDiscoverRequest, x) +def skills_config_set_disabled_skills_request_from_dict(s: Any) -> SkillsConfigSetDisabledSkillsRequest: + return SkillsConfigSetDisabledSkillsRequest.from_dict(s) + +def skills_config_set_disabled_skills_request_to_dict(x: SkillsConfigSetDisabledSkillsRequest) -> Any: + return to_class(SkillsConfigSetDisabledSkillsRequest, x) + +def server_skill_list_from_dict(s: Any) -> ServerSkillList: + return ServerSkillList.from_dict(s) + +def server_skill_list_to_dict(x: ServerSkillList) -> Any: + return to_class(ServerSkillList, x) + +def skills_discover_request_from_dict(s: Any) -> SkillsDiscoverRequest: + return SkillsDiscoverRequest.from_dict(s) + +def skills_discover_request_to_dict(x: SkillsDiscoverRequest) -> Any: + return to_class(SkillsDiscoverRequest, x) + def session_fs_set_provider_result_from_dict(s: Any) -> SessionFSSetProviderResult: return SessionFSSetProviderResult.from_dict(s) @@ -3789,15 +3921,55 @@ async def get_quota(self, *, timeout: float | None = None) -> AccountGetQuotaRes return AccountGetQuotaResult.from_dict(await self._client.request("account.getQuota", {}, **_timeout_kwargs(timeout))) +class ServerMcpConfigApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def list(self, *, timeout: float | None = None) -> MCPConfigList: + return MCPConfigList.from_dict(await self._client.request("mcp.config.list", {}, **_timeout_kwargs(timeout))) + + async def add(self, params: MCPConfigAddRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("mcp.config.add", params_dict, **_timeout_kwargs(timeout)) + + async def update(self, params: MCPConfigUpdateRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("mcp.config.update", params_dict, **_timeout_kwargs(timeout)) + + async def remove(self, params: MCPConfigRemoveRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("mcp.config.remove", params_dict, **_timeout_kwargs(timeout)) + + class ServerMcpApi: def __init__(self, client: "JsonRpcClient"): self._client = client + self.config = ServerMcpConfigApi(client) async def discover(self, params: MCPDiscoverRequest, *, timeout: float | None = None) -> MCPDiscoverResult: params_dict = {k: v for k, v in params.to_dict().items() if v is not None} return MCPDiscoverResult.from_dict(await self._client.request("mcp.discover", params_dict, **_timeout_kwargs(timeout))) +class ServerSkillsConfigApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + + async def set_disabled_skills(self, params: SkillsConfigSetDisabledSkillsRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("skills.config.setDisabledSkills", params_dict, **_timeout_kwargs(timeout)) + + +class ServerSkillsApi: + def __init__(self, client: "JsonRpcClient"): + self._client = client + self.config = ServerSkillsConfigApi(client) + + async def discover(self, params: SkillsDiscoverRequest, *, timeout: float | None = None) -> ServerSkillList: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + return ServerSkillList.from_dict(await self._client.request("skills.discover", params_dict, **_timeout_kwargs(timeout))) + + class ServerSessionFsApi: def __init__(self, client: "JsonRpcClient"): self._client = client @@ -3825,6 +3997,7 @@ def __init__(self, client: "JsonRpcClient"): self.tools = ServerToolsApi(client) self.account = ServerAccountApi(client) self.mcp = ServerMcpApi(client) + self.skills = ServerSkillsApi(client) self.session_fs = ServerSessionFsApi(client) self.sessions = ServerSessionsApi(client) diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 243047fb6..08671ac1d 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -986,15 +986,18 @@ function emitServerRpcClasses(node: Record, classes: string[]): // Per-group API classes for (const [groupName, groupNode] of groups) { - result.push(emitServerApiClass(`Server${toPascalCase(groupName)}Api`, groupNode as Record, classes)); + result.push(...emitServerApiClass(`Server${toPascalCase(groupName)}Api`, groupNode as Record, classes)); } return result; } -function emitServerApiClass(className: string, node: Record, classes: string[]): string { +function emitServerApiClass(className: string, node: Record, classes: string[]): string[] { + const parts: string[] = []; const lines: string[] = []; const displayName = className.replace(/^Server/, "").replace(/Api$/, ""); + const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); + lines.push(`/// Provides server-scoped ${displayName} APIs.`); const groupExperimental = isNodeFullyExperimental(node); if (groupExperimental) { @@ -1007,6 +1010,10 @@ function emitServerApiClass(className: string, node: Record, cl lines.push(` internal ${className}(JsonRpc rpc)`); lines.push(` {`); lines.push(` _rpc = rpc;`); + for (const [subGroupName] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(` ${toPascalCase(subGroupName)} = new ${subClassName}(rpc);`); + } lines.push(` }`); for (const [key, value] of Object.entries(node)) { @@ -1014,8 +1021,22 @@ function emitServerApiClass(className: string, node: Record, cl emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental); } + for (const [subGroupName] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(""); + lines.push(` /// ${toPascalCase(subGroupName)} APIs.`); + lines.push(` public ${subClassName} ${toPascalCase(subGroupName)} { get; }`); + } + lines.push(`}`); - return lines.join("\n"); + parts.push(lines.join("\n")); + + for (const [subGroupName, subGroupNode] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + parts.push(...emitServerApiClass(subClassName, subGroupNode as Record, classes)); + } + + return parts; } function emitServerInstanceMethod( @@ -1116,7 +1137,7 @@ function emitSessionRpcClasses(node: Record, classes: string[]) result.push(srLines.join("\n")); for (const [groupName, groupNode] of groups) { - result.push(emitSessionApiClass(`${toPascalCase(groupName)}Api`, groupNode as Record, classes)); + result.push(...emitSessionApiClass(`${toPascalCase(groupName)}Api`, groupNode as Record, classes)); } return result; } @@ -1181,19 +1202,42 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas } } -function emitSessionApiClass(className: string, node: Record, classes: string[]): string { +function emitSessionApiClass(className: string, node: Record, classes: string[]): string[] { + const parts: string[] = []; const displayName = className.replace(/Api$/, ""); const groupExperimental = isNodeFullyExperimental(node); const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : ""; + const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); + const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; - lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`, ` }`); + lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`); + for (const [subGroupName] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(` ${toPascalCase(subGroupName)} = new ${subClassName}(rpc, sessionId);`); + } + lines.push(` }`); for (const [key, value] of Object.entries(node)) { if (!isRpcMethod(value)) continue; emitSessionMethod(key, value, lines, classes, " ", groupExperimental); } + + for (const [subGroupName] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(""); + lines.push(` /// ${toPascalCase(subGroupName)} APIs.`); + lines.push(` public ${subClassName} ${toPascalCase(subGroupName)} { get; }`); + } + lines.push(`}`); - return lines.join("\n"); + parts.push(lines.join("\n")); + + for (const [subGroupName, subGroupNode] of subGroups) { + const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + parts.push(...emitSessionApiClass(subClassName, subGroupNode as Record, classes)); + } + + return parts; } function collectClientGroups(node: Record): Array<{ groupName: string; groupNode: Record; methods: RpcMethod[] }> { diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index dd87f037b..be36b8e5f 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -1126,6 +1126,44 @@ async function generateRpc(schemaPath?: string): Promise { await formatGoFile(outPath); } +function emitApiGroup( + lines: string[], + apiName: string, + node: Record, + isSession: boolean, + serviceName: string, + resolveType: (name: string) => string, + fieldNames: Map>, + groupExperimental: boolean +): void { + const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); + + if (groupExperimental) { + lines.push(`// Experimental: ${apiName} contains experimental APIs that may change or be removed.`); + } + lines.push(`type ${apiName} ${serviceName}`); + lines.push(``); + + for (const [key, value] of Object.entries(node)) { + if (!isRpcMethod(value)) continue; + emitMethod(lines, apiName, key, value, isSession, resolveType, fieldNames, groupExperimental); + } + + for (const [subGroupName, subGroupNode] of subGroups) { + const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + const subGroupExperimental = isNodeFullyExperimental(subGroupNode as Record); + emitApiGroup(lines, subApiName, subGroupNode as Record, isSession, serviceName, resolveType, fieldNames, subGroupExperimental); + + if (subGroupExperimental) { + lines.push(`// Experimental: ${toPascalCase(subGroupName)} returns experimental APIs that may change or be removed.`); + } + lines.push(`func (s *${apiName}) ${toPascalCase(subGroupName)}() *${subApiName} {`); + lines.push(`\treturn (*${subApiName})(s)`); + lines.push(`}`); + lines.push(``); + } +} + function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>): void { const groups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const topLevelMethods = Object.entries(node).filter(([, v]) => isRpcMethod(v)); @@ -1146,15 +1184,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio const prefix = isSession ? "" : "Server"; const apiName = prefix + toPascalCase(groupName) + apiSuffix; const groupExperimental = isNodeFullyExperimental(groupNode as Record); - if (groupExperimental) { - lines.push(`// Experimental: ${apiName} contains experimental APIs that may change or be removed.`); - } - lines.push(`type ${apiName} ${serviceName}`); - lines.push(``); - for (const [key, value] of Object.entries(groupNode as Record)) { - if (!isRpcMethod(value)) continue; - emitMethod(lines, apiName, key, value, isSession, resolveType, fieldNames, groupExperimental); - } + emitApiGroup(lines, apiName, groupNode as Record, isSession, serviceName, resolveType, fieldNames, groupExperimental); } // Compute field name lengths for gofmt-compatible column alignment diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 46d11de83..659b777e9 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -1571,39 +1571,65 @@ def _patch_model_capabilities(data: dict) -> dict: console.log(` ✓ ${outPath}`); } +function emitPyApiGroup( + lines: string[], + apiName: string, + node: Record, + isSession: boolean, + resolveType: (name: string) => string, + groupExperimental: boolean +): void { + const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); + + // Emit sub-group classes first (Python needs definitions before use) + for (const [subGroupName, subGroupNode] of subGroups) { + const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + const subGroupExperimental = isNodeFullyExperimental(subGroupNode as Record); + emitPyApiGroup(lines, subApiName, subGroupNode as Record, isSession, resolveType, subGroupExperimental); + } + + // Emit this class + if (groupExperimental) { + lines.push(`# Experimental: this API group is experimental and may change or be removed.`); + } + lines.push(`class ${apiName}:`); + if (isSession) { + lines.push(` def __init__(self, client: "JsonRpcClient", session_id: str):`); + lines.push(` self._client = client`); + lines.push(` self._session_id = session_id`); + for (const [subGroupName] of subGroups) { + const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(` self.${toSnakeCase(subGroupName)} = ${subApiName}(client, session_id)`); + } + } else { + lines.push(` def __init__(self, client: "JsonRpcClient"):`); + lines.push(` self._client = client`); + for (const [subGroupName] of subGroups) { + const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; + lines.push(` self.${toSnakeCase(subGroupName)} = ${subApiName}(client)`); + } + } + lines.push(``); + + for (const [key, value] of Object.entries(node)) { + if (!isRpcMethod(value)) continue; + emitMethod(lines, key, value, isSession, resolveType, groupExperimental); + } + lines.push(``); +} + function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string): void { const groups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const topLevelMethods = Object.entries(node).filter(([, v]) => isRpcMethod(v)); const wrapperName = isSession ? "SessionRpc" : "ServerRpc"; - // Emit API classes for groups + // Emit API classes for groups (recursively handles sub-groups) for (const [groupName, groupNode] of groups) { const prefix = isSession ? "" : "Server"; const apiName = prefix + toPascalCase(groupName) + "Api"; const groupExperimental = isNodeFullyExperimental(groupNode as Record); - if (isSession) { - if (groupExperimental) { - lines.push(`# Experimental: this API group is experimental and may change or be removed.`); - } - lines.push(`class ${apiName}:`); - lines.push(` def __init__(self, client: "JsonRpcClient", session_id: str):`); - lines.push(` self._client = client`); - lines.push(` self._session_id = session_id`); - } else { - if (groupExperimental) { - lines.push(`# Experimental: this API group is experimental and may change or be removed.`); - } - lines.push(`class ${apiName}:`); - lines.push(` def __init__(self, client: "JsonRpcClient"):`); - lines.push(` self._client = client`); - } - lines.push(``); - for (const [key, value] of Object.entries(groupNode as Record)) { - if (!isRpcMethod(value)) continue; - emitMethod(lines, key, value, isSession, resolveType, groupExperimental); - } - lines.push(``); + emitPyApiGroup(lines, apiName, groupNode as Record, isSession, resolveType, groupExperimental); } // Emit wrapper class diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 28ecc71f2..378ff5d29 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.28", + "@github/copilot": "^1.0.29", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.28.tgz", - "integrity": "sha512-S1Y+KnhywjIsK1DzskoCqPVC3uURohvCRyDkGPWXvMw+lXO5ryOJvHFZDDw7MSRjT7ea7T0m8e3yKdK0OxJhnw==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.29.tgz", + "integrity": "sha512-d5MH4Wr5Xja7NlUt97w43kRTfChAhlLCDHhMxE0Gk5kcAMoK1zOwYgz+HrxddViT/MKJMxQIWrgMaeLKROAEZg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.28", - "@github/copilot-darwin-x64": "1.0.28", - "@github/copilot-linux-arm64": "1.0.28", - "@github/copilot-linux-x64": "1.0.28", - "@github/copilot-win32-arm64": "1.0.28", - "@github/copilot-win32-x64": "1.0.28" + "@github/copilot-darwin-arm64": "1.0.29", + "@github/copilot-darwin-x64": "1.0.29", + "@github/copilot-linux-arm64": "1.0.29", + "@github/copilot-linux-x64": "1.0.29", + "@github/copilot-win32-arm64": "1.0.29", + "@github/copilot-win32-x64": "1.0.29" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.28.tgz", - "integrity": "sha512-Bkis5dkOsdgaK95j/8mgIGSxHlRuL211Wa3S4MeeYGrilZweaG20sa0jktzagL6XFxfPRKBC87E+fDFyXz1L3g==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.29.tgz", + "integrity": "sha512-2r0XZDXX3TXKe3BaDHxAL2MVVxl/2kURfIwugu/NN2lpvGsFgZAnk4f8SntN3zvOmwiX2+KvEkHHlPj9Tee3RA==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.28.tgz", - "integrity": "sha512-0RIabmr05KgPPUcD4kpKNBGg/eRwJF2NrYtibDUCIRFWKZu7q0m9c9EURpW0wOO32cXZtAQ+BmJIGlqfCkt6gA==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.29.tgz", + "integrity": "sha512-T+KqCTeVZW17gKkur37BTnZ7RSFGnqx2dX5ieJ5YS8uTCugNUx44TQVDtbKpMSLyvgzDVM6l80Atp+KnrG8PbA==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.28.tgz", - "integrity": "sha512-A/zQ4ifN+FSSEHdPHajv5UwygS5BOQ8l1AJMYdVBnnuqVX9bCcRAJJ4S/F60AnaDimzDvVuYSe3lYXRYxz3M5A==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.29.tgz", + "integrity": "sha512-7Gkj8Rc++5whPvBs/jxcyKClvTR/t9Qb3vdISkR5Teq6LadT468qJR1rtBxWBSFRXl7mbOw6Bo6EESAbwhhArw==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.28.tgz", - "integrity": "sha512-0VqoW9hj7qKj+eH2un9E7zn9AbassTZHkKQPsd8yPvLsmPaNJgsHMYDrCCNZNol2ZSGt/XskTfmWQaQM6BoBfg==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.29.tgz", + "integrity": "sha512-fyeuSLfEo+4Rqgj5koNTVx3CtHeVE2n3VZdsLsSGV5mkSg2/pTkr7mqaxbMJEhaMriXdW4/DO7h0dg/rmci8tQ==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.28.tgz", - "integrity": "sha512-f28NKudBtIXTpIliHGJbRhEfCItsXKWNzXzgqgmP8FZB+JYrqG/ysU2qCUCxhpv3PLjMLWqnsWs+mIvVLTH9zw==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.29.tgz", + "integrity": "sha512-Pi7o5fffATE+2g/bXIQInegfhevYTYGT1ysLR3QGSEbFYjapchjrRSRC5xBQz0WkuGjSl+0gpAHxAJ78tfhJ0w==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.28.tgz", - "integrity": "sha512-b9ZEx2i5P7DZTP66FXTfwf81r5kbAqs2GEJjDdevCwxH7cRexqM9eBxQGj1zGtm4qXF7JGK2eH6Ay7NC28m1Iw==", + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.29.tgz", + "integrity": "sha512-ITC0/vzgM2uX2FmMQDo1Mcec1M43Ae7njnLRdfPXdAgLKKP+i4b9r1bnczVb4CFoyG1fca6ss+0rCPOK6xUISw==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 649cd5df6..c43c33f26 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.28", + "@github/copilot": "^1.0.29", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From 883cc0238485dae0c2e6ee978da6e7e30fdb4aba Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 18:06:32 -0400 Subject: [PATCH 25/34] Update @github/copilot to 1.0.30 (#1096) - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- nodejs/package-lock.json | 56 ++++++++++++++++---------------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- test/harness/package-lock.json | 56 ++++++++++++++++---------------- test/harness/package.json | 2 +- 5 files changed, 59 insertions(+), 59 deletions(-) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 6845a143e..002edfbf3 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.29", + "@github/copilot": "^1.0.30", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.29.tgz", - "integrity": "sha512-d5MH4Wr5Xja7NlUt97w43kRTfChAhlLCDHhMxE0Gk5kcAMoK1zOwYgz+HrxddViT/MKJMxQIWrgMaeLKROAEZg==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.30.tgz", + "integrity": "sha512-JYZNMM6hteAE6tIMbHobRjpAaXzvqeeglXgGlDCr26rRq3K6h5ul2GN27qzhMBaWyujUQN402KLKdrhDPqcL7A==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.29", - "@github/copilot-darwin-x64": "1.0.29", - "@github/copilot-linux-arm64": "1.0.29", - "@github/copilot-linux-x64": "1.0.29", - "@github/copilot-win32-arm64": "1.0.29", - "@github/copilot-win32-x64": "1.0.29" + "@github/copilot-darwin-arm64": "1.0.30", + "@github/copilot-darwin-x64": "1.0.30", + "@github/copilot-linux-arm64": "1.0.30", + "@github/copilot-linux-x64": "1.0.30", + "@github/copilot-win32-arm64": "1.0.30", + "@github/copilot-win32-x64": "1.0.30" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.29.tgz", - "integrity": "sha512-2r0XZDXX3TXKe3BaDHxAL2MVVxl/2kURfIwugu/NN2lpvGsFgZAnk4f8SntN3zvOmwiX2+KvEkHHlPj9Tee3RA==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.30.tgz", + "integrity": "sha512-qhLMhAY7nskG6yabbsWSqErxPWcZLX1ixJBdQX3RLqgw5dyNvZRNzG2evUnABo5bqgndztsFXjE3u4XtfX0WkA==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.29.tgz", - "integrity": "sha512-T+KqCTeVZW17gKkur37BTnZ7RSFGnqx2dX5ieJ5YS8uTCugNUx44TQVDtbKpMSLyvgzDVM6l80Atp+KnrG8PbA==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.30.tgz", + "integrity": "sha512-nsjGRt1jLBzCaVd6eb3ok75zqePr8eU8GSTqu1KVf5KUrnvvfIlsvESkEAE8l+lkR14f7SGQLfMJ2EEbcJMGcg==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.29.tgz", - "integrity": "sha512-7Gkj8Rc++5whPvBs/jxcyKClvTR/t9Qb3vdISkR5Teq6LadT468qJR1rtBxWBSFRXl7mbOw6Bo6EESAbwhhArw==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.30.tgz", + "integrity": "sha512-7wOrOKm9MHnglyzzGeZnXSkfRi4sXB2Db7rK/CgUenxS+dwwIuXhT4rgkH/DIOiDbGCxYjigICxln28Jvbs+cA==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.29.tgz", - "integrity": "sha512-fyeuSLfEo+4Rqgj5koNTVx3CtHeVE2n3VZdsLsSGV5mkSg2/pTkr7mqaxbMJEhaMriXdW4/DO7h0dg/rmci8tQ==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.30.tgz", + "integrity": "sha512-OSJtP7mV9vnDzGFjBkI3sgbNOcxsRcq7vXrT4PNrjJw4Mc71aaW55hc5F1j2fElfGWIb+Jubm3AB8nb6AoufnA==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.29.tgz", - "integrity": "sha512-Pi7o5fffATE+2g/bXIQInegfhevYTYGT1ysLR3QGSEbFYjapchjrRSRC5xBQz0WkuGjSl+0gpAHxAJ78tfhJ0w==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.30.tgz", + "integrity": "sha512-5nCz/+9VWJdNvW2uRYeMmnRdQq/gpuSlmYMvRv8fIsFF8KH0mdJndJn8xN6GeJtx0fKJrLzgKqJHWdgb5MtLgA==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.29.tgz", - "integrity": "sha512-ITC0/vzgM2uX2FmMQDo1Mcec1M43Ae7njnLRdfPXdAgLKKP+i4b9r1bnczVb4CFoyG1fca6ss+0rCPOK6xUISw==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.30.tgz", + "integrity": "sha512-tJvgCsWLJVQvHLvFyQZ0P5MQ7YGX51/bl9kbXDUFCGATtPpELul3NyHWwEYGjRv+VDPvhFxjbf+V7Bf/VzYZ7w==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index d529e57bf..7576406df 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.29", + "@github/copilot": "^1.0.30", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 6ec70bffb..574f9878b 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.29", + "@github/copilot": "^1.0.30", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 378ff5d29..2c82d7b87 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.29", + "@github/copilot": "^1.0.30", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.29.tgz", - "integrity": "sha512-d5MH4Wr5Xja7NlUt97w43kRTfChAhlLCDHhMxE0Gk5kcAMoK1zOwYgz+HrxddViT/MKJMxQIWrgMaeLKROAEZg==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.30.tgz", + "integrity": "sha512-JYZNMM6hteAE6tIMbHobRjpAaXzvqeeglXgGlDCr26rRq3K6h5ul2GN27qzhMBaWyujUQN402KLKdrhDPqcL7A==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.29", - "@github/copilot-darwin-x64": "1.0.29", - "@github/copilot-linux-arm64": "1.0.29", - "@github/copilot-linux-x64": "1.0.29", - "@github/copilot-win32-arm64": "1.0.29", - "@github/copilot-win32-x64": "1.0.29" + "@github/copilot-darwin-arm64": "1.0.30", + "@github/copilot-darwin-x64": "1.0.30", + "@github/copilot-linux-arm64": "1.0.30", + "@github/copilot-linux-x64": "1.0.30", + "@github/copilot-win32-arm64": "1.0.30", + "@github/copilot-win32-x64": "1.0.30" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.29.tgz", - "integrity": "sha512-2r0XZDXX3TXKe3BaDHxAL2MVVxl/2kURfIwugu/NN2lpvGsFgZAnk4f8SntN3zvOmwiX2+KvEkHHlPj9Tee3RA==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.30.tgz", + "integrity": "sha512-qhLMhAY7nskG6yabbsWSqErxPWcZLX1ixJBdQX3RLqgw5dyNvZRNzG2evUnABo5bqgndztsFXjE3u4XtfX0WkA==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.29.tgz", - "integrity": "sha512-T+KqCTeVZW17gKkur37BTnZ7RSFGnqx2dX5ieJ5YS8uTCugNUx44TQVDtbKpMSLyvgzDVM6l80Atp+KnrG8PbA==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.30.tgz", + "integrity": "sha512-nsjGRt1jLBzCaVd6eb3ok75zqePr8eU8GSTqu1KVf5KUrnvvfIlsvESkEAE8l+lkR14f7SGQLfMJ2EEbcJMGcg==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.29.tgz", - "integrity": "sha512-7Gkj8Rc++5whPvBs/jxcyKClvTR/t9Qb3vdISkR5Teq6LadT468qJR1rtBxWBSFRXl7mbOw6Bo6EESAbwhhArw==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.30.tgz", + "integrity": "sha512-7wOrOKm9MHnglyzzGeZnXSkfRi4sXB2Db7rK/CgUenxS+dwwIuXhT4rgkH/DIOiDbGCxYjigICxln28Jvbs+cA==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.29.tgz", - "integrity": "sha512-fyeuSLfEo+4Rqgj5koNTVx3CtHeVE2n3VZdsLsSGV5mkSg2/pTkr7mqaxbMJEhaMriXdW4/DO7h0dg/rmci8tQ==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.30.tgz", + "integrity": "sha512-OSJtP7mV9vnDzGFjBkI3sgbNOcxsRcq7vXrT4PNrjJw4Mc71aaW55hc5F1j2fElfGWIb+Jubm3AB8nb6AoufnA==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.29.tgz", - "integrity": "sha512-Pi7o5fffATE+2g/bXIQInegfhevYTYGT1ysLR3QGSEbFYjapchjrRSRC5xBQz0WkuGjSl+0gpAHxAJ78tfhJ0w==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.30.tgz", + "integrity": "sha512-5nCz/+9VWJdNvW2uRYeMmnRdQq/gpuSlmYMvRv8fIsFF8KH0mdJndJn8xN6GeJtx0fKJrLzgKqJHWdgb5MtLgA==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.29", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.29.tgz", - "integrity": "sha512-ITC0/vzgM2uX2FmMQDo1Mcec1M43Ae7njnLRdfPXdAgLKKP+i4b9r1bnczVb4CFoyG1fca6ss+0rCPOK6xUISw==", + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.30.tgz", + "integrity": "sha512-tJvgCsWLJVQvHLvFyQZ0P5MQ7YGX51/bl9kbXDUFCGATtPpELul3NyHWwEYGjRv+VDPvhFxjbf+V7Bf/VzYZ7w==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index c43c33f26..94fe9d8c5 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.29", + "@github/copilot": "^1.0.30", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From dbcea815e3604d8c1d535d1a084a7c9c935b9117 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Thu, 16 Apr 2026 22:08:05 -0400 Subject: [PATCH 26/34] Add deprecated schema support to all four code generators (#1099) * Add deprecated schema support to all four code generators Translate deprecated: true from JSON Schema nodes into language-specific deprecation markers during code generation: - C#: [Obsolete] attribute on types, properties, methods, enums, and API groups. Added #pragma warning disable CS0612/CS0618 to generated file headers to avoid TreatWarningsAsErrors build failures. - TypeScript: /** @deprecated */ JSDoc on types, methods, and handler interfaces. - Python: # Deprecated: comments on types, methods, fields, enums, and handler Protocol methods. - Go: // Deprecated: comments on types, methods, fields, enums, and handler interface methods. Shared utilities added to utils.ts: deprecated field on RpcMethod, isSchemaDeprecated() for property/type-level checks, and isNodeFullyDeprecated() for API group-level checks (mirrors the existing experimental pattern). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Avoid deprecating shared types referenced via \ Only add types to the deprecated annotation set when the method's params/result schema is inline (not a \ to a shared definition). This prevents shared types used by both deprecated and non-deprecated methods from being incorrectly tagged as deprecated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Regenerate output files after codegen script changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 3 ++ dotnet/src/Generated/SessionEvents.cs | 3 ++ scripts/codegen/csharp.ts | 66 ++++++++++++++++++----- scripts/codegen/go.ts | 71 ++++++++++++++++++++++--- scripts/codegen/python.ts | 75 +++++++++++++++++++++++---- scripts/codegen/typescript.ts | 40 ++++++++++++-- scripts/codegen/utils.ts | 21 ++++++++ 7 files changed, 246 insertions(+), 33 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index e75ff861d..295fb8bfa 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -5,6 +5,9 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete (with message) + using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 74f470471..2e2724aed 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -5,6 +5,9 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: session-events.schema.json +#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete (with message) + using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 08671ac1d..f8bcfad1c 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -25,6 +25,8 @@ import { refTypeName, isRpcMethod, isNodeFullyExperimental, + isNodeFullyDeprecated, + isSchemaDeprecated, isObjectSchema, isVoidSchema, REPO_ROOT, @@ -316,7 +318,7 @@ let generatedEnums = new Map(); /** Schema definitions available during session event generation (for $ref resolution). */ let sessionDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; -function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string): string { +function getOrCreateEnum(parentClassName: string, propName: string, values: string[], enumOutput: string[], description?: string, explicitName?: string, deprecated?: boolean): string { const enumName = explicitName ?? `${parentClassName}${propName}`; const existing = generatedEnums.get(enumName); if (existing) return existing.enumName; @@ -324,6 +326,7 @@ function getOrCreateEnum(parentClassName: string, propName: string, values: stri const lines: string[] = []; lines.push(...xmlDocEnumComment(description, "")); + if (deprecated) lines.push(`[Obsolete]`); lines.push(`[JsonConverter(typeof(JsonStringEnumConverter<${enumName}>))]`, `public enum ${enumName}`, `{`); for (const value of values) { lines.push(` /// The ${escapeXml(value)} variant.`); @@ -458,6 +461,7 @@ function generateDerivedClass( const required = new Set(schema.required || []); lines.push(...xmlDocCommentWithFallback(schema.description, `The ${escapeXml(discriminatorValue)} variant of .`, "")); + if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`); lines.push(`public partial class ${className} : ${baseClassName}`); lines.push(`{`); lines.push(` /// `); @@ -476,6 +480,7 @@ function generateDerivedClass( lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " ")); lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " ")); + if (isSchemaDeprecated(propSchema as JSONSchema7)) lines.push(` [Obsolete]`); if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); @@ -499,6 +504,7 @@ function generateNestedClass( const required = new Set(schema.required || []); const lines: string[] = []; lines.push(...xmlDocCommentWithFallback(schema.description, `Nested data type for ${className}.`, "")); + if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`); lines.push(`public partial class ${className}`, `{`); for (const [propName, propSchema] of Object.entries(schema.properties || {})) { @@ -510,6 +516,7 @@ function generateNestedClass( lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); lines.push(...emitDataAnnotations(prop, " ")); + if (isSchemaDeprecated(prop)) lines.push(` [Obsolete]`); if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); @@ -539,7 +546,7 @@ function resolveSessionPropertyType( } if (refSchema.enum && Array.isArray(refSchema.enum)) { - const enumName = getOrCreateEnum(className, "", refSchema.enum as string[], enumOutput, refSchema.description); + const enumName = getOrCreateEnum(className, "", refSchema.enum as string[], enumOutput, refSchema.description, undefined, isSchemaDeprecated(refSchema)); return isRequired ? enumName : `${enumName}?`; } @@ -573,7 +580,7 @@ function resolveSessionPropertyType( return hasNull || !isRequired ? "object?" : "object"; } if (propSchema.enum && Array.isArray(propSchema.enum)) { - const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined); + const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined, isSchemaDeprecated(propSchema)); return isRequired ? enumName : `${enumName}?`; } if (propSchema.type === "object" && propSchema.properties) { @@ -607,6 +614,9 @@ function generateDataClass(variant: EventVariant, knownTypes: Map.`, "")); } + if (isSchemaDeprecated(variant.dataSchema)) { + lines.push(`[Obsolete]`); + } lines.push(`public partial class ${variant.dataClassName}`, `{`); for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties)) { @@ -617,6 +627,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map, classes: string[]): // Top-level methods (like ping) for (const [key, value] of topLevelMethods) { if (!isRpcMethod(value)) continue; - emitServerInstanceMethod(key, value, srLines, classes, " ", false); + emitServerInstanceMethod(key, value, srLines, classes, " ", false, false); } // Group properties @@ -1000,9 +1019,13 @@ function emitServerApiClass(className: string, node: Record, cl lines.push(`/// Provides server-scoped ${displayName} APIs.`); const groupExperimental = isNodeFullyExperimental(node); + const groupDeprecated = isNodeFullyDeprecated(node); if (groupExperimental) { lines.push(`[Experimental(Diagnostics.Experimental)]`); } + if (groupDeprecated) { + lines.push(`[Obsolete]`); + } lines.push(`public sealed class ${className}`); lines.push(`{`); lines.push(` private readonly JsonRpc _rpc;`); @@ -1018,7 +1041,7 @@ function emitServerApiClass(className: string, node: Record, cl for (const [key, value] of Object.entries(node)) { if (!isRpcMethod(value)) continue; - emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental); + emitServerInstanceMethod(key, value, lines, classes, " ", groupExperimental, groupDeprecated); } for (const [subGroupName] of subGroups) { @@ -1045,7 +1068,8 @@ function emitServerInstanceMethod( lines: string[], classes: string[], indent: string, - groupExperimental: boolean + groupExperimental: boolean, + groupDeprecated: boolean ): void { const methodName = toPascalCase(name); const resultSchema = getMethodResultSchema(method); @@ -1079,6 +1103,9 @@ function emitServerInstanceMethod( if (method.stability === "experimental" && !groupExperimental) { lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`); } + if (method.deprecated && !groupDeprecated) { + lines.push(`${indent}[Obsolete]`); + } const sigParams: string[] = []; const bodyAssignments: string[] = []; @@ -1129,7 +1156,7 @@ function emitSessionRpcClasses(node: Record, classes: string[]) // Emit top-level session RPC methods directly on the SessionRpc class const topLevelLines: string[] = []; for (const [key, value] of topLevelMethods) { - emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ", false); + emitSessionMethod(key, value as RpcMethod, topLevelLines, classes, " ", false, false); } srLines.push(...topLevelLines); @@ -1142,7 +1169,7 @@ function emitSessionRpcClasses(node: Record, classes: string[]) return result; } -function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean): void { +function emitSessionMethod(key: string, method: RpcMethod, lines: string[], classes: string[], indent: string, groupExperimental: boolean, groupDeprecated: boolean): void { const methodName = toPascalCase(key); const resultSchema = getMethodResultSchema(method); let resultClassName = !isVoidSchema(resultSchema) ? resultTypeName(method) : ""; @@ -1180,6 +1207,9 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas if (method.stability === "experimental" && !groupExperimental) { lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`); } + if (method.deprecated && !groupDeprecated) { + lines.push(`${indent}[Obsolete]`); + } const sigParams: string[] = []; const bodyAssignments = [`SessionId = _sessionId`]; @@ -1206,10 +1236,12 @@ function emitSessionApiClass(className: string, node: Record, c const parts: string[] = []; const displayName = className.replace(/Api$/, ""); const groupExperimental = isNodeFullyExperimental(node); + const groupDeprecated = isNodeFullyDeprecated(node); const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : ""; + const deprecatedAttr = groupDeprecated ? `[Obsolete]\n` : ""; const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); - const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; + const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}${deprecatedAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; lines.push(` internal ${className}(JsonRpc rpc, string sessionId)`, ` {`, ` _rpc = rpc;`, ` _sessionId = sessionId;`); for (const [subGroupName] of subGroups) { const subClassName = className.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; @@ -1219,7 +1251,7 @@ function emitSessionApiClass(className: string, node: Record, c for (const [key, value] of Object.entries(node)) { if (!isRpcMethod(value)) continue; - emitSessionMethod(key, value, lines, classes, " ", groupExperimental); + emitSessionMethod(key, value, lines, classes, " ", groupExperimental, groupDeprecated); } for (const [subGroupName] of subGroups) { @@ -1290,10 +1322,14 @@ function emitClientSessionApiRegistration(clientSchema: Record, for (const { groupName, groupNode, methods } of groups) { const interfaceName = clientHandlerInterfaceName(groupName); const groupExperimental = isNodeFullyExperimental(groupNode); + const groupDeprecated = isNodeFullyDeprecated(groupNode); lines.push(`/// Handles \`${groupName}\` client session API methods.`); if (groupExperimental) { lines.push(`[Experimental(Diagnostics.Experimental)]`); } + if (groupDeprecated) { + lines.push(`[Obsolete]`); + } lines.push(`public interface ${interfaceName}`); lines.push(`{`); for (const method of methods) { @@ -1305,6 +1341,9 @@ function emitClientSessionApiRegistration(clientSchema: Record, if (method.stability === "experimental" && !groupExperimental) { lines.push(` [Experimental(Diagnostics.Experimental)]`); } + if (method.deprecated && !groupDeprecated) { + lines.push(` [Obsolete]`); + } if (hasParams) { lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(${paramsTypeName(method)} request, CancellationToken cancellationToken = default);`); } else { @@ -1400,6 +1439,9 @@ function generateRpcCode(schema: ApiSchema): string { // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: api.schema.json +#pragma warning disable CS0612 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete (with message) + using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json; diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index be36b8e5f..fa21aa703 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -19,6 +19,8 @@ import { hoistTitledSchemas, hasSchemaPayload, isNodeFullyExperimental, + isNodeFullyDeprecated, + isSchemaDeprecated, isVoidSchema, isRpcMethod, postProcessSchema, @@ -355,7 +357,8 @@ function getOrCreateGoEnum( enumName: string, values: string[], ctx: GoCodegenCtx, - description?: string + description?: string, + deprecated?: boolean ): string { const existing = ctx.enumsByName.get(enumName); if (existing) return existing; @@ -366,6 +369,9 @@ function getOrCreateGoEnum( lines.push(`// ${line}`); } } + if (deprecated) { + lines.push(`// Deprecated: ${enumName} is deprecated and will be removed in a future version.`); + } lines.push(`type ${enumName} string`); lines.push(``); lines.push(`const (`); @@ -406,7 +412,7 @@ function resolveGoPropertyType( const resolved = resolveRef(propSchema.$ref, ctx.definitions); if (resolved) { if (resolved.enum) { - const enumType = getOrCreateGoEnum(typeName, resolved.enum as string[], ctx, resolved.description); + const enumType = getOrCreateGoEnum(typeName, resolved.enum as string[], ctx, resolved.description, isSchemaDeprecated(resolved)); return isRequired ? enumType : `*${enumType}`; } if (isNamedGoObjectSchema(resolved)) { @@ -450,7 +456,7 @@ function resolveGoPropertyType( // Handle enum if (propSchema.enum && Array.isArray(propSchema.enum)) { - const enumType = getOrCreateGoEnum((propSchema.title as string) || nestedName, propSchema.enum as string[], ctx, propSchema.description); + const enumType = getOrCreateGoEnum((propSchema.title as string) || nestedName, propSchema.enum as string[], ctx, propSchema.description, isSchemaDeprecated(propSchema)); return isRequired ? enumType : `*${enumType}`; } @@ -559,6 +565,9 @@ function emitGoStruct( lines.push(`// ${line}`); } } + if (isSchemaDeprecated(schema)) { + lines.push(`// Deprecated: ${typeName} is deprecated and will be removed in a future version.`); + } lines.push(`type ${typeName} struct {`); for (const [propName, propSchema] of Object.entries(schema.properties || {})) { @@ -572,6 +581,9 @@ function emitGoStruct( if (prop.description) { lines.push(`\t// ${prop.description}`); } + if (isSchemaDeprecated(prop)) { + lines.push(`\t// Deprecated: ${goName} is deprecated.`); + } lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); } @@ -662,6 +674,9 @@ function emitGoFlatDiscriminatedUnion( if (info.schema.description) { lines.push(`\t// ${info.schema.description}`); } + if (isSchemaDeprecated(info.schema)) { + lines.push(`\t// Deprecated: ${goName} is deprecated.`); + } lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); } @@ -708,6 +723,9 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { if (prop.description) { lines.push(`\t// ${prop.description}`); } + if (isSchemaDeprecated(prop)) { + lines.push(`\t// Deprecated: ${goName} is deprecated.`); + } lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); } @@ -1073,6 +1091,27 @@ async function generateRpc(schemaPath?: string): Promise { `// Experimental: ${typeName} is part of an experimental API and may change or be removed.\n$1` ); } + + // Annotate deprecated data types + const deprecatedTypeNames = new Set(); + for (const method of allMethods) { + if (!method.deprecated) continue; + if (!method.result?.$ref) { + deprecatedTypeNames.add(goResultTypeName(method)); + } + if (!method.params?.$ref) { + const paramsTypeName = goParamsTypeName(method); + if (rootDefinitions[paramsTypeName]) { + deprecatedTypeNames.add(paramsTypeName); + } + } + } + for (const typeName of deprecatedTypeNames) { + qtCode = qtCode.replace( + new RegExp(`^(type ${typeName} struct)`, "m"), + `// Deprecated: ${typeName} is deprecated and will be removed in a future version.\n$1` + ); + } // Remove trailing blank lines from quicktype output before appending qtCode = qtCode.replace(/\n+$/, ""); // Replace interface{} with any (quicktype emits the pre-1.18 form) @@ -1134,10 +1173,14 @@ function emitApiGroup( serviceName: string, resolveType: (name: string) => string, fieldNames: Map>, - groupExperimental: boolean + groupExperimental: boolean, + groupDeprecated: boolean = false ): void { const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); + if (groupDeprecated) { + lines.push(`// Deprecated: ${apiName} contains deprecated APIs that will be removed in a future version.`); + } if (groupExperimental) { lines.push(`// Experimental: ${apiName} contains experimental APIs that may change or be removed.`); } @@ -1146,13 +1189,14 @@ function emitApiGroup( for (const [key, value] of Object.entries(node)) { if (!isRpcMethod(value)) continue; - emitMethod(lines, apiName, key, value, isSession, resolveType, fieldNames, groupExperimental); + emitMethod(lines, apiName, key, value, isSession, resolveType, fieldNames, groupExperimental, false, groupDeprecated); } for (const [subGroupName, subGroupNode] of subGroups) { const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; const subGroupExperimental = isNodeFullyExperimental(subGroupNode as Record); - emitApiGroup(lines, subApiName, subGroupNode as Record, isSession, serviceName, resolveType, fieldNames, subGroupExperimental); + const subGroupDeprecated = isNodeFullyDeprecated(subGroupNode as Record); + emitApiGroup(lines, subApiName, subGroupNode as Record, isSession, serviceName, resolveType, fieldNames, subGroupExperimental, subGroupDeprecated); if (subGroupExperimental) { lines.push(`// Experimental: ${toPascalCase(subGroupName)} returns experimental APIs that may change or be removed.`); @@ -1184,7 +1228,8 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio const prefix = isSession ? "" : "Server"; const apiName = prefix + toPascalCase(groupName) + apiSuffix; const groupExperimental = isNodeFullyExperimental(groupNode as Record); - emitApiGroup(lines, apiName, groupNode as Record, isSession, serviceName, resolveType, fieldNames, groupExperimental); + const groupDeprecated = isNodeFullyDeprecated(groupNode as Record); + emitApiGroup(lines, apiName, groupNode as Record, isSession, serviceName, resolveType, fieldNames, groupExperimental, groupDeprecated); } // Compute field name lengths for gofmt-compatible column alignment @@ -1229,7 +1274,7 @@ 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, fieldNames: Map>, groupExperimental = false, isWrapper = false): void { +function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>, groupExperimental = false, isWrapper = false, groupDeprecated = false): void { const methodName = toPascalCase(name); const resultType = resolveType(goResultTypeName(method)); @@ -1244,6 +1289,9 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc const clientRef = isWrapper ? "a.common.client" : "a.client"; const sessionIDRef = isWrapper ? "a.common.sessionID" : "a.sessionID"; + if (method.deprecated && !groupDeprecated) { + lines.push(`// Deprecated: ${methodName} is deprecated and will be removed in a future version.`); + } if (method.stability === "experimental" && !groupExperimental) { lines.push(`// Experimental: ${methodName} is an experimental API and may change or be removed in future versions.`); } @@ -1323,11 +1371,18 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< for (const { groupName, groupNode, methods } of groups) { const interfaceName = clientHandlerInterfaceName(groupName); const groupExperimental = isNodeFullyExperimental(groupNode); + const groupDeprecated = isNodeFullyDeprecated(groupNode); + if (groupDeprecated) { + lines.push(`// Deprecated: ${interfaceName} contains deprecated APIs that will be removed in a future version.`); + } if (groupExperimental) { lines.push(`// Experimental: ${interfaceName} contains experimental APIs that may change or be removed.`); } lines.push(`type ${interfaceName} interface {`); for (const method of methods) { + if (method.deprecated && !groupDeprecated) { + lines.push(`\t// Deprecated: ${clientHandlerMethodName(method.rpcMethod)} is deprecated and will be removed in a future version.`); + } if (method.stability === "experimental" && !groupExperimental) { lines.push(`\t// Experimental: ${clientHandlerMethodName(method.rpcMethod)} is an experimental API and may change or be removed in future versions.`); } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 659b777e9..8c437b191 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -20,6 +20,8 @@ import { isVoidSchema, isRpcMethod, isNodeFullyExperimental, + isNodeFullyDeprecated, + isSchemaDeprecated, postProcessSchema, writeGeneratedFile, collectDefinitionCollections, @@ -507,7 +509,8 @@ function getOrCreatePyEnum( enumName: string, values: string[], ctx: PyCodegenCtx, - description?: string + description?: string, + deprecated?: boolean ): string { const existing = ctx.enumsByName.get(enumName); if (existing) { @@ -515,6 +518,9 @@ function getOrCreatePyEnum( } const lines: string[] = []; + if (deprecated) { + lines.push(`# Deprecated: this enum is deprecated and will be removed in a future version.`); + } if (description) { lines.push(`class ${enumName}(Enum):`); lines.push(` ${pyDocstringLiteral(description)}`); @@ -543,7 +549,7 @@ function resolvePyPropertyType( const resolved = resolveSchema(propSchema, ctx.definitions); if (resolved && resolved !== propSchema) { if (resolved.enum && Array.isArray(resolved.enum) && resolved.enum.every((value) => typeof value === "string")) { - const enumType = getOrCreatePyEnum(typeName, resolved.enum as string[], ctx, resolved.description); + const enumType = getOrCreatePyEnum(typeName, resolved.enum as string[], ctx, resolved.description, isSchemaDeprecated(resolved)); const enumResolved: PyResolvedType = { annotation: enumType, fromExpr: (expr) => `parse_enum(${enumType}, ${expr})`, @@ -621,7 +627,8 @@ function resolvePyPropertyType( nestedName, propSchema.enum as string[], ctx, - propSchema.description + propSchema.description, + isSchemaDeprecated(propSchema) ); const resolved: PyResolvedType = { annotation: enumType, @@ -842,6 +849,9 @@ function emitPyClass( }); const lines: string[] = []; + if (isSchemaDeprecated(schema)) { + lines.push(`# Deprecated: this type is deprecated and will be removed in a future version.`); + } lines.push(`@dataclass`); lines.push(`class ${typeName}:`); if (description || schema.description) { @@ -862,6 +872,9 @@ function emitPyClass( for (const field of fieldInfos) { const suffix = field.isRequired ? "" : " = None"; + if (isSchemaDeprecated(orderedFieldEntries.find(([n]) => n === field.jsonName)?.[1] as JSONSchema7)) { + lines.push(` # Deprecated: this field is deprecated.`); + } lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); } @@ -997,6 +1010,10 @@ function emitPyFlatDiscriminatedUnion( } for (const field of fieldInfos) { const suffix = field.isRequired ? "" : " = None"; + const fieldSchema = orderedFieldEntries.find(([n]) => n === field.jsonName)?.[1]; + if (fieldSchema && isSchemaDeprecated(fieldSchema)) { + lines.push(` # Deprecated: this field is deprecated.`); + } lines.push(` ${field.fieldName}: ${field.resolved.annotation}${suffix}`); } lines.push(``); @@ -1479,6 +1496,27 @@ async function generateRpc(schemaPath?: string): Promise { ); } + // Annotate deprecated data types + const deprecatedTypeNames = new Set(); + for (const method of allMethods) { + if (!method.deprecated) continue; + if (!method.result?.$ref) { + deprecatedTypeNames.add(pythonResultTypeName(method)); + } + if (!method.params?.$ref) { + const paramsTypeName = pythonParamsTypeName(method); + if (rootDefinitions[paramsTypeName]) { + deprecatedTypeNames.add(paramsTypeName); + } + } + } + for (const typeName of deprecatedTypeNames) { + typesCode = typesCode.replace( + new RegExp(`^(@dataclass\\n)?class ${typeName}[:(]`, "m"), + (match) => `# Deprecated: this type is part of a deprecated API and will be removed in a future version.\n${match}` + ); + } + // Extract actual class names generated by quicktype (may differ from toPascalCase, // e.g. quicktype produces "SessionMCPList" not "SessionMcpList") const actualTypeNames = new Map(); @@ -1577,7 +1615,8 @@ function emitPyApiGroup( node: Record, isSession: boolean, resolveType: (name: string) => string, - groupExperimental: boolean + groupExperimental: boolean, + groupDeprecated: boolean = false ): void { const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); @@ -1585,10 +1624,14 @@ function emitPyApiGroup( for (const [subGroupName, subGroupNode] of subGroups) { const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api"; const subGroupExperimental = isNodeFullyExperimental(subGroupNode as Record); - emitPyApiGroup(lines, subApiName, subGroupNode as Record, isSession, resolveType, subGroupExperimental); + const subGroupDeprecated = isNodeFullyDeprecated(subGroupNode as Record); + emitPyApiGroup(lines, subApiName, subGroupNode as Record, isSession, resolveType, subGroupExperimental, subGroupDeprecated); } // Emit this class + if (groupDeprecated) { + lines.push(`# Deprecated: this API group is deprecated and will be removed in a future version.`); + } if (groupExperimental) { lines.push(`# Experimental: this API group is experimental and may change or be removed.`); } @@ -1613,7 +1656,7 @@ function emitPyApiGroup( for (const [key, value] of Object.entries(node)) { if (!isRpcMethod(value)) continue; - emitMethod(lines, key, value, isSession, resolveType, groupExperimental); + emitMethod(lines, key, value, isSession, resolveType, groupExperimental, groupDeprecated); } lines.push(``); } @@ -1629,7 +1672,8 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio const prefix = isSession ? "" : "Server"; const apiName = prefix + toPascalCase(groupName) + "Api"; const groupExperimental = isNodeFullyExperimental(groupNode as Record); - emitPyApiGroup(lines, apiName, groupNode as Record, isSession, resolveType, groupExperimental); + const groupDeprecated = isNodeFullyDeprecated(groupNode as Record); + emitPyApiGroup(lines, apiName, groupNode as Record, isSession, resolveType, groupExperimental, groupDeprecated); } // Emit wrapper class @@ -1661,7 +1705,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio lines.push(``); } -function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false): void { +function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false, groupDeprecated = false): void { const methodName = toSnakeCase(name); const resultSchema = getMethodResultSchema(method); const hasResult = !isVoidSchema(resultSchema); @@ -1681,6 +1725,9 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: lines.push(sig); + if (method.deprecated && !groupDeprecated) { + lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`); + } if (method.stability === "experimental" && !groupExperimental) { lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); } @@ -1734,13 +1781,17 @@ function emitClientSessionApiRegistration( for (const [groupName, groupNode] of groups) { const handlerName = `${toPascalCase(groupName)}Handler`; const groupExperimental = isNodeFullyExperimental(groupNode as Record); + const groupDeprecated = isNodeFullyDeprecated(groupNode as Record); + if (groupDeprecated) { + lines.push(`# Deprecated: this API group is deprecated and will be removed in a future version.`); + } if (groupExperimental) { lines.push(`# Experimental: this API group is experimental and may change or be removed.`); } lines.push(`class ${handlerName}(Protocol):`); for (const [methodName, value] of Object.entries(groupNode as Record)) { if (!isRpcMethod(value)) continue; - emitClientSessionHandlerMethod(lines, methodName, value, resolveType, groupExperimental); + emitClientSessionHandlerMethod(lines, methodName, value, resolveType, groupExperimental, groupDeprecated); } lines.push(``); } @@ -1785,12 +1836,16 @@ function emitClientSessionHandlerMethod( name: string, method: RpcMethod, resolveType: (name: string) => string, - groupExperimental = false + groupExperimental = false, + groupDeprecated = false ): void { const paramsType = resolveType(pythonParamsTypeName(method)); const resultSchema = getMethodResultSchema(method); const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : "None"; lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`); + if (method.deprecated && !groupDeprecated) { + lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`); + } if (method.stability === "experimental" && !groupExperimental) { lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); } diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index c18108573..8cc3e4078 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -23,6 +23,7 @@ import { withSharedDefinitions, isRpcMethod, isNodeFullyExperimental, + isNodeFullyDeprecated, isVoidSchema, stripNonAnnotationTitles, type ApiSchema, @@ -347,6 +348,8 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; // Track which type names come from experimental methods for JSDoc annotations. const experimentalTypes = new Set(); + // Track which type names come from deprecated methods for JSDoc annotations. + const deprecatedTypes = new Set(); for (const method of [...allMethods, ...clientSessionMethods]) { const resultSchema = getMethodResultSchema(method); @@ -358,6 +361,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; if (method.stability === "experimental") { experimentalTypes.add(resultTypeName(method)); } + if (method.deprecated && !method.result?.$ref) { + deprecatedTypes.add(resultTypeName(method)); + } } const resolvedParams = getMethodParamsSchema(method); @@ -378,6 +384,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; if (method.stability === "experimental") { experimentalTypes.add(paramsTypeName(method)); } + if (method.deprecated) { + deprecatedTypes.add(paramsTypeName(method)); + } } } else { combinedSchema.definitions![paramsTypeName(method)] = withRootTitle( @@ -387,6 +396,9 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; if (method.stability === "experimental") { experimentalTypes.add(paramsTypeName(method)); } + if (method.deprecated && !method.params?.$ref) { + deprecatedTypes.add(paramsTypeName(method)); + } } } } @@ -418,6 +430,13 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; `$1/** @experimental */\n$2` ); } + // Add @deprecated JSDoc annotations for types from deprecated methods + for (const depType of deprecatedTypes) { + annotatedTs = annotatedTs.replace( + new RegExp(`(^|\\n)(export (?:interface|type) ${depType}\\b)`, "m"), + `$1/** @deprecated */\n$2` + ); + } lines.push(annotatedTs); lines.push(""); } @@ -452,7 +471,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; console.log(` ✓ ${outPath}`); } -function emitGroup(node: Record, indent: string, isSession: boolean, parentExperimental = false): string[] { +function emitGroup(node: Record, indent: string, isSession: boolean, parentExperimental = false, parentDeprecated = false): string[] { const lines: string[] = []; for (const [key, value] of Object.entries(node)) { if (isRpcMethod(value)) { @@ -486,6 +505,9 @@ function emitGroup(node: Record, indent: string, isSession: boo } } + if ((value as RpcMethod).deprecated && !parentDeprecated) { + lines.push(`${indent}/** @deprecated */`); + } if ((value as RpcMethod).stability === "experimental" && !parentExperimental) { lines.push(`${indent}/** @experimental */`); } @@ -493,11 +515,15 @@ function emitGroup(node: Record, indent: string, isSession: boo lines.push(`${indent} connection.sendRequest("${rpcMethod}", ${bodyArg}),`); } else if (typeof value === "object" && value !== null) { const groupExperimental = isNodeFullyExperimental(value as Record); + const groupDeprecated = isNodeFullyDeprecated(value as Record); + if (groupDeprecated) { + lines.push(`${indent}/** @deprecated */`); + } if (groupExperimental) { lines.push(`${indent}/** @experimental */`); } lines.push(`${indent}${key}: {`); - lines.push(...emitGroup(value as Record, indent + " ", isSession, groupExperimental)); + lines.push(...emitGroup(value as Record, indent + " ", isSession, groupExperimental, groupDeprecated)); lines.push(`${indent}},`); } } @@ -544,7 +570,12 @@ function emitClientSessionApiRegistration(clientSchema: Record) // Emit a handler interface per group for (const [groupName, methods] of groups) { const interfaceName = toPascalCase(groupName) + "Handler"; - lines.push(`/** Handler for \`${groupName}\` client session API methods. */`); + const groupDeprecated = isNodeFullyDeprecated(clientSchema[groupName] as Record); + if (groupDeprecated) { + lines.push(`/** @deprecated Handler for \`${groupName}\` client session API methods. */`); + } else { + lines.push(`/** Handler for \`${groupName}\` client session API methods. */`); + } lines.push(`export interface ${interfaceName} {`); for (const method of methods) { const name = handlerMethodName(method.rpcMethod); @@ -552,6 +583,9 @@ function emitClientSessionApiRegistration(clientSchema: Record) const pType = hasParams ? paramsTypeName(method) : ""; const rType = !isVoidSchema(getMethodResultSchema(method)) ? resultTypeName(method) : "void"; + if (method.deprecated && !groupDeprecated) { + lines.push(` /** @deprecated */`); + } if (hasParams) { lines.push(` ${name}(params: ${pType}): Promise<${rType}>;`); } else { diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 1931e8ac6..d6083adec 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -144,6 +144,7 @@ export interface RpcMethod { params: JSONSchema7 | null; result: JSONSchema7 | null; stability?: string; + deprecated?: boolean; } export function getRpcSchemaTypeName(schema: JSONSchema7 | null | undefined, fallback: string): string { @@ -466,6 +467,26 @@ export function isNodeFullyExperimental(node: Record): boolean return methods.length > 0 && methods.every(m => m.stability === "experimental"); } +/** Returns true when every leaf RPC method inside `node` is marked deprecated. */ +export function isNodeFullyDeprecated(node: Record): boolean { + const methods: RpcMethod[] = []; + (function collect(n: Record) { + for (const value of Object.values(n)) { + if (isRpcMethod(value)) { + methods.push(value); + } else if (typeof value === "object" && value !== null) { + collect(value as Record); + } + } + })(node); + return methods.length > 0 && methods.every(m => m.deprecated === true); +} + +/** Returns true when a JSON Schema node is marked as deprecated. */ +export function isSchemaDeprecated(schema: JSONSchema7 | null | undefined): boolean { + return typeof schema === "object" && schema !== null && (schema as Record).deprecated === true; +} + // ── $ref resolution ───────────────────────────────────────────────────────── /** Extract the generated type name from a `$ref` path (e.g. "#/definitions/Model" → "Model"). */ From 48e244dba5ade6e1ad0143ba80f56c48bab3c85a Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 17 Apr 2026 15:58:07 -0400 Subject: [PATCH 27/34] Clean up redundant Python codegen lambdas (#1104) Unwrap redundant passthrough lambdas in the Python generator, add a regression test, and regenerate the Python session events output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/test/python-codegen.test.ts | 49 +- python/copilot/generated/session_events.py | 856 ++++++++++----------- scripts/codegen/python.ts | 14 +- 3 files changed, 489 insertions(+), 430 deletions(-) diff --git a/nodejs/test/python-codegen.test.ts b/nodejs/test/python-codegen.test.ts index 4032ce2cc..dc404ea19 100644 --- a/nodejs/test/python-codegen.test.ts +++ b/nodejs/test/python-codegen.test.ts @@ -74,7 +74,7 @@ describe("python session event codegen", () => { 'action = from_union([from_none, lambda x: parse_enum(SessionSyntheticDataAction, x)], obj.get("action", "store"))' ); expect(code).toContain( - 'summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary", ""))' + 'summary = from_union([from_none, from_str], obj.get("summary", ""))' ); expect(code).toContain("uri: str"); expect(code).toContain("pattern: str"); @@ -83,6 +83,53 @@ describe("python session event codegen", () => { expect(code).toContain("count: int"); }); + it("collapses redundant callable wrapper lambdas", () => { + const schema: JSONSchema7 = { + definitions: { + SessionEvent: { + anyOf: [ + { + type: "object", + required: ["type", "data"], + properties: { + type: { const: "session.synthetic" }, + data: { + type: "object", + properties: { + summary: { type: "string" }, + tags: { + type: "array", + items: { type: "string" }, + }, + context: { + type: "object", + properties: { + gitRoot: { type: "string" }, + }, + }, + }, + }, + }, + }, + ], + }, + }, + }; + + const code = generatePythonSessionEventsCode(schema); + + expect(code).toContain('summary = from_union([from_none, from_str], obj.get("summary"))'); + expect(code).toContain( + 'tags = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tags"))' + ); + expect(code).toContain( + 'context = from_union([from_none, SessionSyntheticDataContext.from_dict], obj.get("context"))' + ); + expect(code).not.toContain("lambda x: from_str(x)"); + expect(code).not.toContain("lambda x: SessionSyntheticDataContext.from_dict(x)"); + expect(code).not.toContain("from_list(lambda x: from_str(x), x)"); + }); + it("preserves key shortened nested type names", () => { const schema: JSONSchema7 = { definitions: { diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 400883850..784b0bb52 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -266,12 +266,12 @@ class SessionStartDataContext: def from_dict(obj: Any) -> "SessionStartDataContext": assert isinstance(obj, dict) cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) - repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + git_root = from_union([from_none, from_str], obj.get("gitRoot")) + repository = from_union([from_none, from_str], obj.get("repository")) host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) - branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) - head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) - base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + branch = from_union([from_none, from_str], obj.get("branch")) + head_commit = from_union([from_none, from_str], obj.get("headCommit")) + base_commit = from_union([from_none, from_str], obj.get("baseCommit")) return SessionStartDataContext( cwd=cwd, git_root=git_root, @@ -286,17 +286,17 @@ def to_dict(self) -> dict: result: dict = {} result["cwd"] = from_str(self.cwd) if self.git_root is not None: - result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + result["gitRoot"] = from_union([from_none, from_str], self.git_root) if self.repository is not None: - result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) if self.branch is not None: - result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: - result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + result["headCommit"] = from_union([from_none, from_str], self.head_commit) if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) + result["baseCommit"] = from_union([from_none, from_str], self.base_commit) return result @@ -322,11 +322,11 @@ def from_dict(obj: Any) -> "SessionStartData": producer = from_str(obj.get("producer")) copilot_version = from_str(obj.get("copilotVersion")) start_time = from_datetime(obj.get("startTime")) - selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) - reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) - context = from_union([from_none, lambda x: SessionStartDataContext.from_dict(x)], obj.get("context")) - already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) - remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + selected_model = from_union([from_none, from_str], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + context = from_union([from_none, SessionStartDataContext.from_dict], obj.get("context")) + already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) return SessionStartData( session_id=session_id, version=version, @@ -348,15 +348,15 @@ def to_dict(self) -> dict: result["copilotVersion"] = from_str(self.copilot_version) result["startTime"] = to_datetime(self.start_time) if self.selected_model is not None: - result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + result["selectedModel"] = from_union([from_none, from_str], self.selected_model) if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(SessionStartDataContext, x)], self.context) if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) + result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) return result @@ -375,12 +375,12 @@ class SessionResumeDataContext: def from_dict(obj: Any) -> "SessionResumeDataContext": assert isinstance(obj, dict) cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) - repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + git_root = from_union([from_none, from_str], obj.get("gitRoot")) + repository = from_union([from_none, from_str], obj.get("repository")) host_type = from_union([from_none, lambda x: parse_enum(SessionResumeDataContextHostType, x)], obj.get("hostType")) - branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) - head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) - base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + branch = from_union([from_none, from_str], obj.get("branch")) + head_commit = from_union([from_none, from_str], obj.get("headCommit")) + base_commit = from_union([from_none, from_str], obj.get("baseCommit")) return SessionResumeDataContext( cwd=cwd, git_root=git_root, @@ -395,17 +395,17 @@ def to_dict(self) -> dict: result: dict = {} result["cwd"] = from_str(self.cwd) if self.git_root is not None: - result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + result["gitRoot"] = from_union([from_none, from_str], self.git_root) if self.repository is not None: - result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(SessionResumeDataContextHostType, x)], self.host_type) if self.branch is not None: - result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: - result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + result["headCommit"] = from_union([from_none, from_str], self.head_commit) if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) + result["baseCommit"] = from_union([from_none, from_str], self.base_commit) return result @@ -425,11 +425,11 @@ def from_dict(obj: Any) -> "SessionResumeData": assert isinstance(obj, dict) resume_time = from_datetime(obj.get("resumeTime")) event_count = from_float(obj.get("eventCount")) - selected_model = from_union([from_none, lambda x: from_str(x)], obj.get("selectedModel")) - reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) - context = from_union([from_none, lambda x: SessionResumeDataContext.from_dict(x)], obj.get("context")) - already_in_use = from_union([from_none, lambda x: from_bool(x)], obj.get("alreadyInUse")) - remote_steerable = from_union([from_none, lambda x: from_bool(x)], obj.get("remoteSteerable")) + selected_model = from_union([from_none, from_str], obj.get("selectedModel")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + context = from_union([from_none, SessionResumeDataContext.from_dict], obj.get("context")) + already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) return SessionResumeData( resume_time=resume_time, event_count=event_count, @@ -445,15 +445,15 @@ def to_dict(self) -> dict: result["resumeTime"] = to_datetime(self.resume_time) result["eventCount"] = to_float(self.event_count) if self.selected_model is not None: - result["selectedModel"] = from_union([from_none, lambda x: from_str(x)], self.selected_model) + result["selectedModel"] = from_union([from_none, from_str], self.selected_model) if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(SessionResumeDataContext, x)], self.context) if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_none, lambda x: from_bool(x)], self.already_in_use) + result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_none, lambda x: from_bool(x)], self.remote_steerable) + result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) return result @@ -491,10 +491,10 @@ def from_dict(obj: Any) -> "SessionErrorData": assert isinstance(obj, dict) error_type = from_str(obj.get("errorType")) message = from_str(obj.get("message")) - stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) - status_code = from_union([from_none, lambda x: from_int(x)], obj.get("statusCode")) - provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + stack = from_union([from_none, from_str], obj.get("stack")) + status_code = from_union([from_none, from_int], obj.get("statusCode")) + provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) + url = from_union([from_none, from_str], obj.get("url")) return SessionErrorData( error_type=error_type, message=message, @@ -509,13 +509,13 @@ def to_dict(self) -> dict: result["errorType"] = from_str(self.error_type) result["message"] = from_str(self.message) if self.stack is not None: - result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + result["stack"] = from_union([from_none, from_str], self.stack) if self.status_code is not None: - result["statusCode"] = from_union([from_none, lambda x: to_int(x)], self.status_code) + result["statusCode"] = from_union([from_none, to_int], self.status_code) if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) + result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) return result @@ -527,7 +527,7 @@ class SessionIdleData: @staticmethod def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) - aborted = from_union([from_none, lambda x: from_bool(x)], obj.get("aborted")) + aborted = from_union([from_none, from_bool], obj.get("aborted")) return SessionIdleData( aborted=aborted, ) @@ -535,7 +535,7 @@ def from_dict(obj: Any) -> "SessionIdleData": def to_dict(self) -> dict: result: dict = {} if self.aborted is not None: - result["aborted"] = from_union([from_none, lambda x: from_bool(x)], self.aborted) + result["aborted"] = from_union([from_none, from_bool], self.aborted) return result @@ -570,7 +570,7 @@ def from_dict(obj: Any) -> "SessionInfoData": assert isinstance(obj, dict) info_type = from_str(obj.get("infoType")) message = from_str(obj.get("message")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + url = from_union([from_none, from_str], obj.get("url")) return SessionInfoData( info_type=info_type, message=message, @@ -582,7 +582,7 @@ def to_dict(self) -> dict: result["infoType"] = from_str(self.info_type) result["message"] = from_str(self.message) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) return result @@ -598,7 +598,7 @@ def from_dict(obj: Any) -> "SessionWarningData": assert isinstance(obj, dict) warning_type = from_str(obj.get("warningType")) message = from_str(obj.get("message")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + url = from_union([from_none, from_str], obj.get("url")) return SessionWarningData( warning_type=warning_type, message=message, @@ -610,7 +610,7 @@ def to_dict(self) -> dict: result["warningType"] = from_str(self.warning_type) result["message"] = from_str(self.message) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) return result @@ -626,9 +626,9 @@ class SessionModelChangeData: def from_dict(obj: Any) -> "SessionModelChangeData": assert isinstance(obj, dict) new_model = from_str(obj.get("newModel")) - previous_model = from_union([from_none, lambda x: from_str(x)], obj.get("previousModel")) - previous_reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("previousReasoningEffort")) - reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + previous_model = from_union([from_none, from_str], obj.get("previousModel")) + previous_reasoning_effort = from_union([from_none, from_str], obj.get("previousReasoningEffort")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) return SessionModelChangeData( new_model=new_model, previous_model=previous_model, @@ -640,11 +640,11 @@ def to_dict(self) -> dict: result: dict = {} result["newModel"] = from_str(self.new_model) if self.previous_model is not None: - result["previousModel"] = from_union([from_none, lambda x: from_str(x)], self.previous_model) + result["previousModel"] = from_union([from_none, from_str], self.previous_model) if self.previous_reasoning_effort is not None: - result["previousReasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.previous_reasoning_effort) + result["previousReasoningEffort"] = from_union([from_none, from_str], self.previous_reasoning_effort) if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) return result @@ -725,7 +725,7 @@ def from_dict(obj: Any) -> "HandoffRepository": assert isinstance(obj, dict) owner = from_str(obj.get("owner")) name = from_str(obj.get("name")) - branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) + branch = from_union([from_none, from_str], obj.get("branch")) return HandoffRepository( owner=owner, name=name, @@ -737,7 +737,7 @@ def to_dict(self) -> dict: result["owner"] = from_str(self.owner) result["name"] = from_str(self.name) if self.branch is not None: - result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + result["branch"] = from_union([from_none, from_str], self.branch) return result @@ -757,11 +757,11 @@ def from_dict(obj: Any) -> "SessionHandoffData": assert isinstance(obj, dict) handoff_time = from_datetime(obj.get("handoffTime")) source_type = parse_enum(HandoffSourceType, obj.get("sourceType")) - repository = from_union([from_none, lambda x: HandoffRepository.from_dict(x)], obj.get("repository")) - context = from_union([from_none, lambda x: from_str(x)], obj.get("context")) - summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary")) - remote_session_id = from_union([from_none, lambda x: from_str(x)], obj.get("remoteSessionId")) - host = from_union([from_none, lambda x: from_str(x)], obj.get("host")) + repository = from_union([from_none, HandoffRepository.from_dict], obj.get("repository")) + context = from_union([from_none, from_str], obj.get("context")) + summary = from_union([from_none, from_str], obj.get("summary")) + remote_session_id = from_union([from_none, from_str], obj.get("remoteSessionId")) + host = from_union([from_none, from_str], obj.get("host")) return SessionHandoffData( handoff_time=handoff_time, source_type=source_type, @@ -779,13 +779,13 @@ def to_dict(self) -> dict: if self.repository is not None: result["repository"] = from_union([from_none, lambda x: to_class(HandoffRepository, x)], self.repository) if self.context is not None: - result["context"] = from_union([from_none, lambda x: from_str(x)], self.context) + result["context"] = from_union([from_none, from_str], self.context) if self.summary is not None: - result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + result["summary"] = from_union([from_none, from_str], self.summary) if self.remote_session_id is not None: - result["remoteSessionId"] = from_union([from_none, lambda x: from_str(x)], self.remote_session_id) + result["remoteSessionId"] = from_union([from_none, from_str], self.remote_session_id) if self.host is not None: - result["host"] = from_union([from_none, lambda x: from_str(x)], self.host) + result["host"] = from_union([from_none, from_str], self.host) return result @@ -871,7 +871,7 @@ def from_dict(obj: Any) -> "ShutdownCodeChanges": assert isinstance(obj, dict) lines_added = from_float(obj.get("linesAdded")) lines_removed = from_float(obj.get("linesRemoved")) - files_modified = from_list(lambda x: from_str(x), obj.get("filesModified")) + files_modified = from_list(from_str, obj.get("filesModified")) return ShutdownCodeChanges( lines_added=lines_added, lines_removed=lines_removed, @@ -882,7 +882,7 @@ def to_dict(self) -> dict: result: dict = {} result["linesAdded"] = to_float(self.lines_added) result["linesRemoved"] = to_float(self.lines_removed) - result["filesModified"] = from_list(lambda x: from_str(x), self.files_modified) + result["filesModified"] = from_list(from_str, self.files_modified) return result @@ -925,7 +925,7 @@ def from_dict(obj: Any) -> "ShutdownModelMetricUsage": output_tokens = from_float(obj.get("outputTokens")) cache_read_tokens = from_float(obj.get("cacheReadTokens")) cache_write_tokens = from_float(obj.get("cacheWriteTokens")) - reasoning_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("reasoningTokens")) + reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) return ShutdownModelMetricUsage( input_tokens=input_tokens, output_tokens=output_tokens, @@ -941,7 +941,7 @@ def to_dict(self) -> dict: result["cacheReadTokens"] = to_float(self.cache_read_tokens) result["cacheWriteTokens"] = to_float(self.cache_write_tokens) if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_none, lambda x: to_float(x)], self.reasoning_tokens) + result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) return result @@ -991,13 +991,13 @@ def from_dict(obj: Any) -> "SessionShutdownData": total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) session_start_time = from_float(obj.get("sessionStartTime")) code_changes = ShutdownCodeChanges.from_dict(obj.get("codeChanges")) - model_metrics = from_dict(lambda x: ShutdownModelMetric.from_dict(x), obj.get("modelMetrics")) - error_reason = from_union([from_none, lambda x: from_str(x)], obj.get("errorReason")) - current_model = from_union([from_none, lambda x: from_str(x)], obj.get("currentModel")) - current_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("currentTokens")) - system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + model_metrics = from_dict(ShutdownModelMetric.from_dict, obj.get("modelMetrics")) + error_reason = from_union([from_none, from_str], obj.get("errorReason")) + current_model = from_union([from_none, from_str], obj.get("currentModel")) + current_tokens = from_union([from_none, from_float], obj.get("currentTokens")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) return SessionShutdownData( shutdown_type=shutdown_type, total_premium_requests=total_premium_requests, @@ -1022,17 +1022,17 @@ def to_dict(self) -> dict: result["codeChanges"] = to_class(ShutdownCodeChanges, self.code_changes) result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) if self.error_reason is not None: - result["errorReason"] = from_union([from_none, lambda x: from_str(x)], self.error_reason) + result["errorReason"] = from_union([from_none, from_str], self.error_reason) if self.current_model is not None: - result["currentModel"] = from_union([from_none, lambda x: from_str(x)], self.current_model) + result["currentModel"] = from_union([from_none, from_str], self.current_model) if self.current_tokens is not None: - result["currentTokens"] = from_union([from_none, lambda x: to_float(x)], self.current_tokens) + result["currentTokens"] = from_union([from_none, to_float], self.current_tokens) if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @@ -1051,12 +1051,12 @@ class SessionContextChangedData: def from_dict(obj: Any) -> "SessionContextChangedData": assert isinstance(obj, dict) cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, lambda x: from_str(x)], obj.get("gitRoot")) - repository = from_union([from_none, lambda x: from_str(x)], obj.get("repository")) + git_root = from_union([from_none, from_str], obj.get("gitRoot")) + repository = from_union([from_none, from_str], obj.get("repository")) host_type = from_union([from_none, lambda x: parse_enum(SessionContextChangedDataHostType, x)], obj.get("hostType")) - branch = from_union([from_none, lambda x: from_str(x)], obj.get("branch")) - head_commit = from_union([from_none, lambda x: from_str(x)], obj.get("headCommit")) - base_commit = from_union([from_none, lambda x: from_str(x)], obj.get("baseCommit")) + branch = from_union([from_none, from_str], obj.get("branch")) + head_commit = from_union([from_none, from_str], obj.get("headCommit")) + base_commit = from_union([from_none, from_str], obj.get("baseCommit")) return SessionContextChangedData( cwd=cwd, git_root=git_root, @@ -1071,17 +1071,17 @@ def to_dict(self) -> dict: result: dict = {} result["cwd"] = from_str(self.cwd) if self.git_root is not None: - result["gitRoot"] = from_union([from_none, lambda x: from_str(x)], self.git_root) + result["gitRoot"] = from_union([from_none, from_str], self.git_root) if self.repository is not None: - result["repository"] = from_union([from_none, lambda x: from_str(x)], self.repository) + result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(SessionContextChangedDataHostType, x)], self.host_type) if self.branch is not None: - result["branch"] = from_union([from_none, lambda x: from_str(x)], self.branch) + result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: - result["headCommit"] = from_union([from_none, lambda x: from_str(x)], self.head_commit) + result["headCommit"] = from_union([from_none, from_str], self.head_commit) if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, lambda x: from_str(x)], self.base_commit) + result["baseCommit"] = from_union([from_none, from_str], self.base_commit) return result @@ -1102,10 +1102,10 @@ def from_dict(obj: Any) -> "SessionUsageInfoData": token_limit = from_float(obj.get("tokenLimit")) current_tokens = from_float(obj.get("currentTokens")) messages_length = from_float(obj.get("messagesLength")) - system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) - is_initial = from_union([from_none, lambda x: from_bool(x)], obj.get("isInitial")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + is_initial = from_union([from_none, from_bool], obj.get("isInitial")) return SessionUsageInfoData( token_limit=token_limit, current_tokens=current_tokens, @@ -1122,13 +1122,13 @@ def to_dict(self) -> dict: result["currentTokens"] = to_float(self.current_tokens) result["messagesLength"] = to_float(self.messages_length) if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) if self.is_initial is not None: - result["isInitial"] = from_union([from_none, lambda x: from_bool(x)], self.is_initial) + result["isInitial"] = from_union([from_none, from_bool], self.is_initial) return result @@ -1142,9 +1142,9 @@ class SessionCompactionStartData: @staticmethod def from_dict(obj: Any) -> "SessionCompactionStartData": assert isinstance(obj, dict) - system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) return SessionCompactionStartData( system_tokens=system_tokens, conversation_tokens=conversation_tokens, @@ -1154,11 +1154,11 @@ def from_dict(obj: Any) -> "SessionCompactionStartData": def to_dict(self) -> dict: result: dict = {} if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @@ -1212,20 +1212,20 @@ class SessionCompactionCompleteData: def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) success = from_bool(obj.get("success")) - error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) - pre_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionTokens")) - post_compaction_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("postCompactionTokens")) - pre_compaction_messages_length = from_union([from_none, lambda x: from_float(x)], obj.get("preCompactionMessagesLength")) - messages_removed = from_union([from_none, lambda x: from_float(x)], obj.get("messagesRemoved")) - tokens_removed = from_union([from_none, lambda x: from_float(x)], obj.get("tokensRemoved")) - summary_content = from_union([from_none, lambda x: from_str(x)], obj.get("summaryContent")) - checkpoint_number = from_union([from_none, lambda x: from_float(x)], obj.get("checkpointNumber")) - checkpoint_path = from_union([from_none, lambda x: from_str(x)], obj.get("checkpointPath")) - compaction_tokens_used = from_union([from_none, lambda x: CompactionCompleteCompactionTokensUsed.from_dict(x)], obj.get("compactionTokensUsed")) - request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) - system_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("toolDefinitionsTokens")) + error = from_union([from_none, from_str], obj.get("error")) + pre_compaction_tokens = from_union([from_none, from_float], obj.get("preCompactionTokens")) + post_compaction_tokens = from_union([from_none, from_float], obj.get("postCompactionTokens")) + pre_compaction_messages_length = from_union([from_none, from_float], obj.get("preCompactionMessagesLength")) + messages_removed = from_union([from_none, from_float], obj.get("messagesRemoved")) + tokens_removed = from_union([from_none, from_float], obj.get("tokensRemoved")) + summary_content = from_union([from_none, from_str], obj.get("summaryContent")) + checkpoint_number = from_union([from_none, from_float], obj.get("checkpointNumber")) + checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) + compaction_tokens_used = from_union([from_none, CompactionCompleteCompactionTokensUsed.from_dict], obj.get("compactionTokensUsed")) + request_id = from_union([from_none, from_str], obj.get("requestId")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) return SessionCompactionCompleteData( success=success, error=error, @@ -1248,33 +1248,33 @@ def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) if self.error is not None: - result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + result["error"] = from_union([from_none, from_str], self.error) if self.pre_compaction_tokens is not None: - result["preCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_tokens) + result["preCompactionTokens"] = from_union([from_none, to_float], self.pre_compaction_tokens) if self.post_compaction_tokens is not None: - result["postCompactionTokens"] = from_union([from_none, lambda x: to_float(x)], self.post_compaction_tokens) + result["postCompactionTokens"] = from_union([from_none, to_float], self.post_compaction_tokens) if self.pre_compaction_messages_length is not None: - result["preCompactionMessagesLength"] = from_union([from_none, lambda x: to_float(x)], self.pre_compaction_messages_length) + result["preCompactionMessagesLength"] = from_union([from_none, to_float], self.pre_compaction_messages_length) if self.messages_removed is not None: - result["messagesRemoved"] = from_union([from_none, lambda x: to_float(x)], self.messages_removed) + result["messagesRemoved"] = from_union([from_none, to_float], self.messages_removed) if self.tokens_removed is not None: - result["tokensRemoved"] = from_union([from_none, lambda x: to_float(x)], self.tokens_removed) + result["tokensRemoved"] = from_union([from_none, to_float], self.tokens_removed) if self.summary_content is not None: - result["summaryContent"] = from_union([from_none, lambda x: from_str(x)], self.summary_content) + result["summaryContent"] = from_union([from_none, from_str], self.summary_content) if self.checkpoint_number is not None: - result["checkpointNumber"] = from_union([from_none, lambda x: to_float(x)], self.checkpoint_number) + result["checkpointNumber"] = from_union([from_none, to_float], self.checkpoint_number) if self.checkpoint_path is not None: - result["checkpointPath"] = from_union([from_none, lambda x: from_str(x)], self.checkpoint_path) + result["checkpointPath"] = from_union([from_none, from_str], self.checkpoint_path) if self.compaction_tokens_used is not None: result["compactionTokensUsed"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsed, x)], self.compaction_tokens_used) if self.request_id is not None: - result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + result["requestId"] = from_union([from_none, from_str], self.request_id) if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, lambda x: to_float(x)], self.system_tokens) + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, lambda x: to_float(x)], self.conversation_tokens) + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, lambda x: to_float(x)], self.tool_definitions_tokens) + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @@ -1287,8 +1287,8 @@ class SessionTaskCompleteData: @staticmethod def from_dict(obj: Any) -> "SessionTaskCompleteData": assert isinstance(obj, dict) - summary = from_union([from_none, lambda x: from_str(x)], obj.get("summary", "")) - success = from_union([from_none, lambda x: from_bool(x)], obj.get("success")) + summary = from_union([from_none, from_str], obj.get("summary", "")) + success = from_union([from_none, from_bool], obj.get("success")) return SessionTaskCompleteData( summary=summary, success=success, @@ -1297,9 +1297,9 @@ def from_dict(obj: Any) -> "SessionTaskCompleteData": def to_dict(self) -> dict: result: dict = {} if self.summary is not None: - result["summary"] = from_union([from_none, lambda x: from_str(x)], self.summary) + result["summary"] = from_union([from_none, from_str], self.summary) if self.success is not None: - result["success"] = from_union([from_none, lambda x: from_bool(x)], self.success) + result["success"] = from_union([from_none, from_bool], self.success) return result @@ -1417,19 +1417,19 @@ class UserMessageAttachment: def from_dict(obj: Any) -> "UserMessageAttachment": assert isinstance(obj, dict) type = parse_enum(UserMessageAttachmentType, obj.get("type")) - path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) - display_name = from_union([from_none, lambda x: from_str(x)], obj.get("displayName")) - line_range = from_union([from_none, lambda x: UserMessageAttachmentFileLineRange.from_dict(x)], obj.get("lineRange")) - file_path = from_union([from_none, lambda x: from_str(x)], obj.get("filePath")) - text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) - selection = from_union([from_none, lambda x: UserMessageAttachmentSelectionDetails.from_dict(x)], obj.get("selection")) - number = from_union([from_none, lambda x: from_float(x)], obj.get("number")) - title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) + path = from_union([from_none, from_str], obj.get("path")) + display_name = from_union([from_none, from_str], obj.get("displayName")) + line_range = from_union([from_none, UserMessageAttachmentFileLineRange.from_dict], obj.get("lineRange")) + file_path = from_union([from_none, from_str], obj.get("filePath")) + text = from_union([from_none, from_str], obj.get("text")) + selection = from_union([from_none, UserMessageAttachmentSelectionDetails.from_dict], obj.get("selection")) + number = from_union([from_none, from_float], obj.get("number")) + title = from_union([from_none, from_str], obj.get("title")) reference_type = from_union([from_none, lambda x: parse_enum(UserMessageAttachmentGithubReferenceType, x)], obj.get("referenceType")) - state = from_union([from_none, lambda x: from_str(x)], obj.get("state")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) - data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) - mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) + state = from_union([from_none, from_str], obj.get("state")) + url = from_union([from_none, from_str], obj.get("url")) + data = from_union([from_none, from_str], obj.get("data")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) return UserMessageAttachment( type=type, path=path, @@ -1451,31 +1451,31 @@ def to_dict(self) -> dict: result: dict = {} result["type"] = to_enum(UserMessageAttachmentType, self.type) if self.path is not None: - result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + result["path"] = from_union([from_none, from_str], self.path) if self.display_name is not None: - result["displayName"] = from_union([from_none, lambda x: from_str(x)], self.display_name) + result["displayName"] = from_union([from_none, from_str], self.display_name) if self.line_range is not None: result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentFileLineRange, x)], self.line_range) if self.file_path is not None: - result["filePath"] = from_union([from_none, lambda x: from_str(x)], self.file_path) + result["filePath"] = from_union([from_none, from_str], self.file_path) if self.text is not None: - result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + result["text"] = from_union([from_none, from_str], self.text) if self.selection is not None: result["selection"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentSelectionDetails, x)], self.selection) if self.number is not None: - result["number"] = from_union([from_none, lambda x: to_float(x)], self.number) + result["number"] = from_union([from_none, to_float], self.number) if self.title is not None: - result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + result["title"] = from_union([from_none, from_str], self.title) if self.reference_type is not None: result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x)], self.reference_type) if self.state is not None: - result["state"] = from_union([from_none, lambda x: from_str(x)], self.state) + result["state"] = from_union([from_none, from_str], self.state) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) if self.data is not None: - result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + result["data"] = from_union([from_none, from_str], self.data) if self.mime_type is not None: - result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + result["mimeType"] = from_union([from_none, from_str], self.mime_type) return result @@ -1492,11 +1492,11 @@ class UserMessageData: def from_dict(obj: Any) -> "UserMessageData": assert isinstance(obj, dict) content = from_str(obj.get("content")) - transformed_content = from_union([from_none, lambda x: from_str(x)], obj.get("transformedContent")) + transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments")) - source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) + source = from_union([from_none, from_str], obj.get("source")) agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode")) - interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) return UserMessageData( content=content, transformed_content=transformed_content, @@ -1510,15 +1510,15 @@ def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) if self.transformed_content is not None: - result["transformedContent"] = from_union([from_none, lambda x: from_str(x)], self.transformed_content) + result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) if self.attachments is not None: result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments) if self.source is not None: - result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + result["source"] = from_union([from_none, from_str], self.source) if self.agent_mode is not None: result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode) if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) return result @@ -1544,7 +1544,7 @@ class AssistantTurnStartData: def from_dict(obj: Any) -> "AssistantTurnStartData": assert isinstance(obj, dict) turn_id = from_str(obj.get("turnId")) - interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) return AssistantTurnStartData( turn_id=turn_id, interaction_id=interaction_id, @@ -1554,7 +1554,7 @@ def to_dict(self) -> dict: result: dict = {} result["turnId"] = from_str(self.turn_id) if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) return result @@ -1660,9 +1660,9 @@ def from_dict(obj: Any) -> "AssistantMessageToolRequest": name = from_str(obj.get("name")) arguments = obj.get("arguments") type = from_union([from_none, lambda x: parse_enum(AssistantMessageToolRequestType, x)], obj.get("type")) - tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) - mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) - intention_summary = from_union([from_none, lambda x: from_str(x)], obj.get("intentionSummary")) + tool_title = from_union([from_none, from_str], obj.get("toolTitle")) + mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) + intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) return AssistantMessageToolRequest( tool_call_id=tool_call_id, name=name, @@ -1682,11 +1682,11 @@ def to_dict(self) -> dict: if self.type is not None: result["type"] = from_union([from_none, lambda x: to_enum(AssistantMessageToolRequestType, x)], self.type) if self.tool_title is not None: - result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + result["toolTitle"] = from_union([from_none, from_str], self.tool_title) if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) if self.intention_summary is not None: - result["intentionSummary"] = from_union([from_none, lambda x: from_str(x)], self.intention_summary) + result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) return result @@ -1710,15 +1710,15 @@ def from_dict(obj: Any) -> "AssistantMessageData": assert isinstance(obj, dict) message_id = from_str(obj.get("messageId")) content = from_str(obj.get("content")) - tool_requests = from_union([from_none, lambda x: from_list(lambda x: AssistantMessageToolRequest.from_dict(x), x)], obj.get("toolRequests")) - reasoning_opaque = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningOpaque")) - reasoning_text = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningText")) - encrypted_content = from_union([from_none, lambda x: from_str(x)], obj.get("encryptedContent")) - phase = from_union([from_none, lambda x: from_str(x)], obj.get("phase")) - output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) - interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) - request_id = from_union([from_none, lambda x: from_str(x)], obj.get("requestId")) - parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + tool_requests = from_union([from_none, lambda x: from_list(AssistantMessageToolRequest.from_dict, x)], obj.get("toolRequests")) + reasoning_opaque = from_union([from_none, from_str], obj.get("reasoningOpaque")) + reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) + encrypted_content = from_union([from_none, from_str], obj.get("encryptedContent")) + phase = from_union([from_none, from_str], obj.get("phase")) + output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + request_id = from_union([from_none, from_str], obj.get("requestId")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return AssistantMessageData( message_id=message_id, content=content, @@ -1740,21 +1740,21 @@ def to_dict(self) -> dict: if self.tool_requests is not None: result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x)], self.tool_requests) if self.reasoning_opaque is not None: - result["reasoningOpaque"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_opaque) + result["reasoningOpaque"] = from_union([from_none, from_str], self.reasoning_opaque) if self.reasoning_text is not None: - result["reasoningText"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_text) + result["reasoningText"] = from_union([from_none, from_str], self.reasoning_text) if self.encrypted_content is not None: - result["encryptedContent"] = from_union([from_none, lambda x: from_str(x)], self.encrypted_content) + result["encryptedContent"] = from_union([from_none, from_str], self.encrypted_content) if self.phase is not None: - result["phase"] = from_union([from_none, lambda x: from_str(x)], self.phase) + result["phase"] = from_union([from_none, from_str], self.phase) if self.output_tokens is not None: - result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.request_id is not None: - result["requestId"] = from_union([from_none, lambda x: from_str(x)], self.request_id) + result["requestId"] = from_union([from_none, from_str], self.request_id) if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -1770,7 +1770,7 @@ def from_dict(obj: Any) -> "AssistantMessageDeltaData": assert isinstance(obj, dict) message_id = from_str(obj.get("messageId")) delta_content = from_str(obj.get("deltaContent")) - parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return AssistantMessageDeltaData( message_id=message_id, delta_content=delta_content, @@ -1782,7 +1782,7 @@ def to_dict(self) -> dict: result["messageId"] = from_str(self.message_id) result["deltaContent"] = from_str(self.delta_content) if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -1826,7 +1826,7 @@ def from_dict(obj: Any) -> "AssistantUsageQuotaSnapshot": overage = from_float(obj.get("overage")) overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) remaining_percentage = from_float(obj.get("remainingPercentage")) - reset_date = from_union([from_none, lambda x: from_datetime(x)], obj.get("resetDate")) + reset_date = from_union([from_none, from_datetime], obj.get("resetDate")) return AssistantUsageQuotaSnapshot( is_unlimited_entitlement=is_unlimited_entitlement, entitlement_requests=entitlement_requests, @@ -1848,7 +1848,7 @@ def to_dict(self) -> dict: result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) result["remainingPercentage"] = to_float(self.remaining_percentage) if self.reset_date is not None: - result["resetDate"] = from_union([from_none, lambda x: to_datetime(x)], self.reset_date) + result["resetDate"] = from_union([from_none, to_datetime], self.reset_date) return result @@ -1892,7 +1892,7 @@ class AssistantUsageCopilotUsage: @staticmethod def from_dict(obj: Any) -> "AssistantUsageCopilotUsage": assert isinstance(obj, dict) - token_details = from_list(lambda x: AssistantUsageCopilotUsageTokenDetail.from_dict(x), obj.get("tokenDetails")) + token_details = from_list(AssistantUsageCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) total_nano_aiu = from_float(obj.get("totalNanoAiu")) return AssistantUsageCopilotUsage( token_details=token_details, @@ -1931,22 +1931,22 @@ class AssistantUsageData: def from_dict(obj: Any) -> "AssistantUsageData": assert isinstance(obj, dict) model = from_str(obj.get("model")) - input_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("inputTokens")) - output_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("outputTokens")) - cache_read_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheReadTokens")) - cache_write_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("cacheWriteTokens")) - reasoning_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("reasoningTokens")) - cost = from_union([from_none, lambda x: from_float(x)], obj.get("cost")) - duration = from_union([from_none, lambda x: from_float(x)], obj.get("duration")) - ttft_ms = from_union([from_none, lambda x: from_float(x)], obj.get("ttftMs")) - inter_token_latency_ms = from_union([from_none, lambda x: from_float(x)], obj.get("interTokenLatencyMs")) - initiator = from_union([from_none, lambda x: from_str(x)], obj.get("initiator")) - api_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("apiCallId")) - provider_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("providerCallId")) - parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) - quota_snapshots = from_union([from_none, lambda x: from_dict(lambda x: AssistantUsageQuotaSnapshot.from_dict(x), x)], obj.get("quotaSnapshots")) - copilot_usage = from_union([from_none, lambda x: AssistantUsageCopilotUsage.from_dict(x)], obj.get("copilotUsage")) - reasoning_effort = from_union([from_none, lambda x: from_str(x)], obj.get("reasoningEffort")) + input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) + output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) + cache_read_tokens = from_union([from_none, from_float], obj.get("cacheReadTokens")) + cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens")) + reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) + cost = from_union([from_none, from_float], obj.get("cost")) + duration = from_union([from_none, from_float], obj.get("duration")) + ttft_ms = from_union([from_none, from_float], obj.get("ttftMs")) + inter_token_latency_ms = from_union([from_none, from_float], obj.get("interTokenLatencyMs")) + initiator = from_union([from_none, from_str], obj.get("initiator")) + api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) + provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + quota_snapshots = from_union([from_none, lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) + copilot_usage = from_union([from_none, AssistantUsageCopilotUsage.from_dict], obj.get("copilotUsage")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) return AssistantUsageData( model=model, input_tokens=input_tokens, @@ -1971,37 +1971,37 @@ def to_dict(self) -> dict: result: dict = {} result["model"] = from_str(self.model) if self.input_tokens is not None: - result["inputTokens"] = from_union([from_none, lambda x: to_float(x)], self.input_tokens) + result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) if self.output_tokens is not None: - result["outputTokens"] = from_union([from_none, lambda x: to_float(x)], self.output_tokens) + result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) if self.cache_read_tokens is not None: - result["cacheReadTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_read_tokens) + result["cacheReadTokens"] = from_union([from_none, to_float], self.cache_read_tokens) if self.cache_write_tokens is not None: - result["cacheWriteTokens"] = from_union([from_none, lambda x: to_float(x)], self.cache_write_tokens) + result["cacheWriteTokens"] = from_union([from_none, to_float], self.cache_write_tokens) if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_none, lambda x: to_float(x)], self.reasoning_tokens) + result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) if self.cost is not None: - result["cost"] = from_union([from_none, lambda x: to_float(x)], self.cost) + result["cost"] = from_union([from_none, to_float], self.cost) if self.duration is not None: - result["duration"] = from_union([from_none, lambda x: to_float(x)], self.duration) + result["duration"] = from_union([from_none, to_float], self.duration) if self.ttft_ms is not None: - result["ttftMs"] = from_union([from_none, lambda x: to_float(x)], self.ttft_ms) + result["ttftMs"] = from_union([from_none, to_float], self.ttft_ms) if self.inter_token_latency_ms is not None: - result["interTokenLatencyMs"] = from_union([from_none, lambda x: to_float(x)], self.inter_token_latency_ms) + result["interTokenLatencyMs"] = from_union([from_none, to_float], self.inter_token_latency_ms) if self.initiator is not None: - result["initiator"] = from_union([from_none, lambda x: from_str(x)], self.initiator) + result["initiator"] = from_union([from_none, from_str], self.initiator) if self.api_call_id is not None: - result["apiCallId"] = from_union([from_none, lambda x: from_str(x)], self.api_call_id) + result["apiCallId"] = from_union([from_none, from_str], self.api_call_id) if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_none, lambda x: from_str(x)], self.provider_call_id) + result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) if self.quota_snapshots is not None: result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x)], self.quota_snapshots) if self.copilot_usage is not None: result["copilotUsage"] = from_union([from_none, lambda x: to_class(AssistantUsageCopilotUsage, x)], self.copilot_usage) if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, lambda x: from_str(x)], self.reasoning_effort) + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) return result @@ -2068,9 +2068,9 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": tool_call_id = from_str(obj.get("toolCallId")) tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") - mcp_server_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpServerName")) - mcp_tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("mcpToolName")) - parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) + mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return ToolExecutionStartData( tool_call_id=tool_call_id, tool_name=tool_name, @@ -2087,11 +2087,11 @@ def to_dict(self) -> dict: if self.arguments is not None: result["arguments"] = self.arguments if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_server_name) + result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) if self.mcp_tool_name is not None: - result["mcpToolName"] = from_union([from_none, lambda x: from_str(x)], self.mcp_tool_name) + result["mcpToolName"] = from_union([from_none, from_str], self.mcp_tool_name) if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -2153,8 +2153,8 @@ class ToolExecutionCompleteDataResultContentsItemIconsItem: def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItemIconsItem": assert isinstance(obj, dict) src = from_str(obj.get("src")) - mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) - sizes = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("sizes")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) + sizes = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("sizes")) theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], obj.get("theme")) return ToolExecutionCompleteDataResultContentsItemIconsItem( src=src, @@ -2167,9 +2167,9 @@ def to_dict(self) -> dict: result: dict = {} result["src"] = from_str(self.src) if self.mime_type is not None: - result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + result["mimeType"] = from_union([from_none, from_str], self.mime_type) if self.sizes is not None: - result["sizes"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.sizes) + result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes) if self.theme is not None: result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], self.theme) return result @@ -2196,17 +2196,17 @@ class ToolExecutionCompleteDataResultContentsItem: def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": assert isinstance(obj, dict) type = parse_enum(ToolExecutionCompleteDataResultContentsItemType, obj.get("type")) - text = from_union([from_none, lambda x: from_str(x)], obj.get("text")) - exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) - cwd = from_union([from_none, lambda x: from_str(x)], obj.get("cwd")) - data = from_union([from_none, lambda x: from_str(x)], obj.get("data")) - mime_type = from_union([from_none, lambda x: from_str(x)], obj.get("mimeType")) - icons = from_union([from_none, lambda x: from_list(lambda x: ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict(x), x)], obj.get("icons")) - name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) - title = from_union([from_none, lambda x: from_str(x)], obj.get("title")) - uri = from_union([from_none, lambda x: from_str(x)], obj.get("uri")) - description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) - size = from_union([from_none, lambda x: from_float(x)], obj.get("size")) + text = from_union([from_none, from_str], obj.get("text")) + exit_code = from_union([from_none, from_float], obj.get("exitCode")) + cwd = from_union([from_none, from_str], obj.get("cwd")) + data = from_union([from_none, from_str], obj.get("data")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) + icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict, x)], obj.get("icons")) + name = from_union([from_none, from_str], obj.get("name")) + title = from_union([from_none, from_str], obj.get("title")) + uri = from_union([from_none, from_str], obj.get("uri")) + description = from_union([from_none, from_str], obj.get("description")) + size = from_union([from_none, from_float], obj.get("size")) resource = obj.get("resource") return ToolExecutionCompleteDataResultContentsItem( type=type, @@ -2228,27 +2228,27 @@ def to_dict(self) -> dict: result: dict = {} result["type"] = to_enum(ToolExecutionCompleteDataResultContentsItemType, self.type) if self.text is not None: - result["text"] = from_union([from_none, lambda x: from_str(x)], self.text) + result["text"] = from_union([from_none, from_str], self.text) if self.exit_code is not None: - result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + result["exitCode"] = from_union([from_none, to_float], self.exit_code) if self.cwd is not None: - result["cwd"] = from_union([from_none, lambda x: from_str(x)], self.cwd) + result["cwd"] = from_union([from_none, from_str], self.cwd) if self.data is not None: - result["data"] = from_union([from_none, lambda x: from_str(x)], self.data) + result["data"] = from_union([from_none, from_str], self.data) if self.mime_type is not None: - result["mimeType"] = from_union([from_none, lambda x: from_str(x)], self.mime_type) + result["mimeType"] = from_union([from_none, from_str], self.mime_type) if self.icons is not None: result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItemIconsItem, x), x)], self.icons) if self.name is not None: - result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + result["name"] = from_union([from_none, from_str], self.name) if self.title is not None: - result["title"] = from_union([from_none, lambda x: from_str(x)], self.title) + result["title"] = from_union([from_none, from_str], self.title) if self.uri is not None: - result["uri"] = from_union([from_none, lambda x: from_str(x)], self.uri) + result["uri"] = from_union([from_none, from_str], self.uri) if self.description is not None: - result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + result["description"] = from_union([from_none, from_str], self.description) if self.size is not None: - result["size"] = from_union([from_none, lambda x: to_float(x)], self.size) + result["size"] = from_union([from_none, to_float], self.size) if self.resource is not None: result["resource"] = self.resource return result @@ -2265,7 +2265,7 @@ class ToolExecutionCompleteDataResult: def from_dict(obj: Any) -> "ToolExecutionCompleteDataResult": assert isinstance(obj, dict) content = from_str(obj.get("content")) - detailed_content = from_union([from_none, lambda x: from_str(x)], obj.get("detailedContent")) + detailed_content = from_union([from_none, from_str], obj.get("detailedContent")) contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItem.from_dict, x)], obj.get("contents")) return ToolExecutionCompleteDataResult( content=content, @@ -2277,7 +2277,7 @@ def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) if self.detailed_content is not None: - result["detailedContent"] = from_union([from_none, lambda x: from_str(x)], self.detailed_content) + result["detailedContent"] = from_union([from_none, from_str], self.detailed_content) if self.contents is not None: result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItem, x), x)], self.contents) return result @@ -2293,7 +2293,7 @@ class ToolExecutionCompleteDataError: def from_dict(obj: Any) -> "ToolExecutionCompleteDataError": assert isinstance(obj, dict) message = from_str(obj.get("message")) - code = from_union([from_none, lambda x: from_str(x)], obj.get("code")) + code = from_union([from_none, from_str], obj.get("code")) return ToolExecutionCompleteDataError( message=message, code=code, @@ -2303,7 +2303,7 @@ def to_dict(self) -> dict: result: dict = {} result["message"] = from_str(self.message) if self.code is not None: - result["code"] = from_union([from_none, lambda x: from_str(x)], self.code) + result["code"] = from_union([from_none, from_str], self.code) return result @@ -2325,13 +2325,13 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": assert isinstance(obj, dict) tool_call_id = from_str(obj.get("toolCallId")) success = from_bool(obj.get("success")) - model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) - interaction_id = from_union([from_none, lambda x: from_str(x)], obj.get("interactionId")) - is_user_requested = from_union([from_none, lambda x: from_bool(x)], obj.get("isUserRequested")) - result = from_union([from_none, lambda x: ToolExecutionCompleteDataResult.from_dict(x)], obj.get("result")) - error = from_union([from_none, lambda x: ToolExecutionCompleteDataError.from_dict(x)], obj.get("error")) + model = from_union([from_none, from_str], obj.get("model")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) + result = from_union([from_none, ToolExecutionCompleteDataResult.from_dict], obj.get("result")) + error = from_union([from_none, ToolExecutionCompleteDataError.from_dict], obj.get("error")) tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) - parent_tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("parentToolCallId")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return ToolExecutionCompleteData( tool_call_id=tool_call_id, success=success, @@ -2349,11 +2349,11 @@ def to_dict(self) -> dict: result["toolCallId"] = from_str(self.tool_call_id) result["success"] = from_bool(self.success) if self.model is not None: - result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + result["model"] = from_union([from_none, from_str], self.model) if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, lambda x: from_str(x)], self.interaction_id) + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.is_user_requested is not None: - result["isUserRequested"] = from_union([from_none, lambda x: from_bool(x)], self.is_user_requested) + result["isUserRequested"] = from_union([from_none, from_bool], self.is_user_requested) if self.result is not None: result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataResult, x)], self.result) if self.error is not None: @@ -2361,7 +2361,7 @@ def to_dict(self) -> dict: if self.tool_telemetry is not None: result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, lambda x: from_str(x)], self.parent_tool_call_id) + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @@ -2382,10 +2382,10 @@ def from_dict(obj: Any) -> "SkillInvokedData": name = from_str(obj.get("name")) path = from_str(obj.get("path")) content = from_str(obj.get("content")) - allowed_tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("allowedTools")) - plugin_name = from_union([from_none, lambda x: from_str(x)], obj.get("pluginName")) - plugin_version = from_union([from_none, lambda x: from_str(x)], obj.get("pluginVersion")) - description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + allowed_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("allowedTools")) + plugin_name = from_union([from_none, from_str], obj.get("pluginName")) + plugin_version = from_union([from_none, from_str], obj.get("pluginVersion")) + description = from_union([from_none, from_str], obj.get("description")) return SkillInvokedData( name=name, path=path, @@ -2402,13 +2402,13 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) result["content"] = from_str(self.content) if self.allowed_tools is not None: - result["allowedTools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.allowed_tools) + result["allowedTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.allowed_tools) if self.plugin_name is not None: - result["pluginName"] = from_union([from_none, lambda x: from_str(x)], self.plugin_name) + result["pluginName"] = from_union([from_none, from_str], self.plugin_name) if self.plugin_version is not None: - result["pluginVersion"] = from_union([from_none, lambda x: from_str(x)], self.plugin_version) + result["pluginVersion"] = from_union([from_none, from_str], self.plugin_version) if self.description is not None: - result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + result["description"] = from_union([from_none, from_str], self.description) return result @@ -2460,10 +2460,10 @@ def from_dict(obj: Any) -> "SubagentCompletedData": tool_call_id = from_str(obj.get("toolCallId")) agent_name = from_str(obj.get("agentName")) agent_display_name = from_str(obj.get("agentDisplayName")) - model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) - total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) - total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) - duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + model = from_union([from_none, from_str], obj.get("model")) + total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) + duration_ms = from_union([from_none, from_float], obj.get("durationMs")) return SubagentCompletedData( tool_call_id=tool_call_id, agent_name=agent_name, @@ -2480,13 +2480,13 @@ def to_dict(self) -> dict: result["agentName"] = from_str(self.agent_name) result["agentDisplayName"] = from_str(self.agent_display_name) if self.model is not None: - result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + result["model"] = from_union([from_none, from_str], self.model) if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) if self.total_tokens is not None: - result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + result["durationMs"] = from_union([from_none, to_float], self.duration_ms) return result @@ -2509,10 +2509,10 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name = from_str(obj.get("agentName")) agent_display_name = from_str(obj.get("agentDisplayName")) error = from_str(obj.get("error")) - model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) - total_tool_calls = from_union([from_none, lambda x: from_float(x)], obj.get("totalToolCalls")) - total_tokens = from_union([from_none, lambda x: from_float(x)], obj.get("totalTokens")) - duration_ms = from_union([from_none, lambda x: from_float(x)], obj.get("durationMs")) + model = from_union([from_none, from_str], obj.get("model")) + total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) + total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) + duration_ms = from_union([from_none, from_float], obj.get("durationMs")) return SubagentFailedData( tool_call_id=tool_call_id, agent_name=agent_name, @@ -2531,13 +2531,13 @@ def to_dict(self) -> dict: result["agentDisplayName"] = from_str(self.agent_display_name) result["error"] = from_str(self.error) if self.model is not None: - result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + result["model"] = from_union([from_none, from_str], self.model) if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([from_none, lambda x: to_float(x)], self.total_tool_calls) + result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) if self.total_tokens is not None: - result["totalTokens"] = from_union([from_none, lambda x: to_float(x)], self.total_tokens) + result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, lambda x: to_float(x)], self.duration_ms) + result["durationMs"] = from_union([from_none, to_float], self.duration_ms) return result @@ -2553,7 +2553,7 @@ def from_dict(obj: Any) -> "SubagentSelectedData": assert isinstance(obj, dict) agent_name = from_str(obj.get("agentName")) agent_display_name = from_str(obj.get("agentDisplayName")) - tools = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("tools")) + tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tools")) return SubagentSelectedData( agent_name=agent_name, agent_display_name=agent_display_name, @@ -2564,7 +2564,7 @@ def to_dict(self) -> dict: result: dict = {} result["agentName"] = from_str(self.agent_name) result["agentDisplayName"] = from_str(self.agent_display_name) - result["tools"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.tools) + result["tools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.tools) return result @@ -2618,7 +2618,7 @@ class HookEndDataError: def from_dict(obj: Any) -> "HookEndDataError": assert isinstance(obj, dict) message = from_str(obj.get("message")) - stack = from_union([from_none, lambda x: from_str(x)], obj.get("stack")) + stack = from_union([from_none, from_str], obj.get("stack")) return HookEndDataError( message=message, stack=stack, @@ -2628,7 +2628,7 @@ def to_dict(self) -> dict: result: dict = {} result["message"] = from_str(self.message) if self.stack is not None: - result["stack"] = from_union([from_none, lambda x: from_str(x)], self.stack) + result["stack"] = from_union([from_none, from_str], self.stack) return result @@ -2648,7 +2648,7 @@ def from_dict(obj: Any) -> "HookEndData": hook_type = from_str(obj.get("hookType")) success = from_bool(obj.get("success")) output = obj.get("output") - error = from_union([from_none, lambda x: HookEndDataError.from_dict(x)], obj.get("error")) + error = from_union([from_none, HookEndDataError.from_dict], obj.get("error")) return HookEndData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, @@ -2678,7 +2678,7 @@ class SystemMessageDataMetadata: @staticmethod def from_dict(obj: Any) -> "SystemMessageDataMetadata": assert isinstance(obj, dict) - prompt_version = from_union([from_none, lambda x: from_str(x)], obj.get("promptVersion")) + prompt_version = from_union([from_none, from_str], obj.get("promptVersion")) variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) return SystemMessageDataMetadata( prompt_version=prompt_version, @@ -2688,7 +2688,7 @@ def from_dict(obj: Any) -> "SystemMessageDataMetadata": def to_dict(self) -> dict: result: dict = {} if self.prompt_version is not None: - result["promptVersion"] = from_union([from_none, lambda x: from_str(x)], self.prompt_version) + result["promptVersion"] = from_union([from_none, from_str], self.prompt_version) if self.variables is not None: result["variables"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.variables) return result @@ -2707,8 +2707,8 @@ def from_dict(obj: Any) -> "SystemMessageData": assert isinstance(obj, dict) content = from_str(obj.get("content")) role = parse_enum(SystemMessageDataRole, obj.get("role")) - name = from_union([from_none, lambda x: from_str(x)], obj.get("name")) - metadata = from_union([from_none, lambda x: SystemMessageDataMetadata.from_dict(x)], obj.get("metadata")) + name = from_union([from_none, from_str], obj.get("name")) + metadata = from_union([from_none, SystemMessageDataMetadata.from_dict], obj.get("metadata")) return SystemMessageData( content=content, role=role, @@ -2721,7 +2721,7 @@ def to_dict(self) -> dict: result["content"] = from_str(self.content) result["role"] = to_enum(SystemMessageDataRole, self.role) if self.name is not None: - result["name"] = from_union([from_none, lambda x: from_str(x)], self.name) + result["name"] = from_union([from_none, from_str], self.name) if self.metadata is not None: result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageDataMetadata, x)], self.metadata) return result @@ -2743,13 +2743,13 @@ class SystemNotificationDataKind: def from_dict(obj: Any) -> "SystemNotificationDataKind": assert isinstance(obj, dict) type = parse_enum(SystemNotificationDataKindType, obj.get("type")) - agent_id = from_union([from_none, lambda x: from_str(x)], obj.get("agentId")) - agent_type = from_union([from_none, lambda x: from_str(x)], obj.get("agentType")) + agent_id = from_union([from_none, from_str], obj.get("agentId")) + agent_type = from_union([from_none, from_str], obj.get("agentType")) status = from_union([from_none, lambda x: parse_enum(SystemNotificationDataKindStatus, x)], obj.get("status")) - description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) - prompt = from_union([from_none, lambda x: from_str(x)], obj.get("prompt")) - shell_id = from_union([from_none, lambda x: from_str(x)], obj.get("shellId")) - exit_code = from_union([from_none, lambda x: from_float(x)], obj.get("exitCode")) + description = from_union([from_none, from_str], obj.get("description")) + prompt = from_union([from_none, from_str], obj.get("prompt")) + shell_id = from_union([from_none, from_str], obj.get("shellId")) + exit_code = from_union([from_none, from_float], obj.get("exitCode")) return SystemNotificationDataKind( type=type, agent_id=agent_id, @@ -2765,19 +2765,19 @@ def to_dict(self) -> dict: result: dict = {} result["type"] = to_enum(SystemNotificationDataKindType, self.type) if self.agent_id is not None: - result["agentId"] = from_union([from_none, lambda x: from_str(x)], self.agent_id) + result["agentId"] = from_union([from_none, from_str], self.agent_id) if self.agent_type is not None: - result["agentType"] = from_union([from_none, lambda x: from_str(x)], self.agent_type) + result["agentType"] = from_union([from_none, from_str], self.agent_type) if self.status is not None: result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationDataKindStatus, x)], self.status) if self.description is not None: - result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + result["description"] = from_union([from_none, from_str], self.description) if self.prompt is not None: - result["prompt"] = from_union([from_none, lambda x: from_str(x)], self.prompt) + result["prompt"] = from_union([from_none, from_str], self.prompt) if self.shell_id is not None: - result["shellId"] = from_union([from_none, lambda x: from_str(x)], self.shell_id) + result["shellId"] = from_union([from_none, from_str], self.shell_id) if self.exit_code is not None: - result["exitCode"] = from_union([from_none, lambda x: to_float(x)], self.exit_code) + result["exitCode"] = from_union([from_none, to_float], self.exit_code) return result @@ -2881,34 +2881,34 @@ class PermissionRequest: def from_dict(obj: Any) -> "PermissionRequest": assert isinstance(obj, dict) kind = parse_enum(PermissionRequestedDataPermissionRequestKind, obj.get("kind")) - tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) - full_command_text = from_union([from_none, lambda x: from_str(x)], obj.get("fullCommandText")) - intention = from_union([from_none, lambda x: from_str(x)], obj.get("intention")) - commands = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestShellCommand.from_dict(x), x)], obj.get("commands")) - possible_paths = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("possiblePaths")) - possible_urls = from_union([from_none, lambda x: from_list(lambda x: PermissionRequestShellPossibleURL.from_dict(x), x)], obj.get("possibleUrls")) - has_write_file_redirection = from_union([from_none, lambda x: from_bool(x)], obj.get("hasWriteFileRedirection")) - can_offer_session_approval = from_union([from_none, lambda x: from_bool(x)], obj.get("canOfferSessionApproval")) - warning = from_union([from_none, lambda x: from_str(x)], obj.get("warning")) - file_name = from_union([from_none, lambda x: from_str(x)], obj.get("fileName")) - diff = from_union([from_none, lambda x: from_str(x)], obj.get("diff")) - new_file_contents = from_union([from_none, lambda x: from_str(x)], obj.get("newFileContents")) - path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) - server_name = from_union([from_none, lambda x: from_str(x)], obj.get("serverName")) - tool_name = from_union([from_none, lambda x: from_str(x)], obj.get("toolName")) - tool_title = from_union([from_none, lambda x: from_str(x)], obj.get("toolTitle")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + full_command_text = from_union([from_none, from_str], obj.get("fullCommandText")) + intention = from_union([from_none, from_str], obj.get("intention")) + commands = from_union([from_none, lambda x: from_list(PermissionRequestShellCommand.from_dict, x)], obj.get("commands")) + possible_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("possiblePaths")) + possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleURL.from_dict, x)], obj.get("possibleUrls")) + has_write_file_redirection = from_union([from_none, from_bool], obj.get("hasWriteFileRedirection")) + can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) + warning = from_union([from_none, from_str], obj.get("warning")) + file_name = from_union([from_none, from_str], obj.get("fileName")) + diff = from_union([from_none, from_str], obj.get("diff")) + new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) + path = from_union([from_none, from_str], obj.get("path")) + server_name = from_union([from_none, from_str], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + tool_title = from_union([from_none, from_str], obj.get("toolTitle")) args = obj.get("args") - read_only = from_union([from_none, lambda x: from_bool(x)], obj.get("readOnly")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + read_only = from_union([from_none, from_bool], obj.get("readOnly")) + url = from_union([from_none, from_str], obj.get("url")) action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store")) - subject = from_union([from_none, lambda x: from_str(x)], obj.get("subject")) - fact = from_union([from_none, lambda x: from_str(x)], obj.get("fact")) - citations = from_union([from_none, lambda x: from_str(x)], obj.get("citations")) + subject = from_union([from_none, from_str], obj.get("subject")) + fact = from_union([from_none, from_str], obj.get("fact")) + citations = from_union([from_none, from_str], obj.get("citations")) direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) - reason = from_union([from_none, lambda x: from_str(x)], obj.get("reason")) - tool_description = from_union([from_none, lambda x: from_str(x)], obj.get("toolDescription")) + reason = from_union([from_none, from_str], obj.get("reason")) + tool_description = from_union([from_none, from_str], obj.get("toolDescription")) tool_args = obj.get("toolArgs") - hook_message = from_union([from_none, lambda x: from_str(x)], obj.get("hookMessage")) + hook_message = from_union([from_none, from_str], obj.get("hookMessage")) return PermissionRequest( kind=kind, tool_call_id=tool_call_id, @@ -2945,61 +2945,61 @@ def to_dict(self) -> dict: result: dict = {} result["kind"] = to_enum(PermissionRequestedDataPermissionRequestKind, self.kind) if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) if self.full_command_text is not None: - result["fullCommandText"] = from_union([from_none, lambda x: from_str(x)], self.full_command_text) + result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text) if self.intention is not None: - result["intention"] = from_union([from_none, lambda x: from_str(x)], self.intention) + result["intention"] = from_union([from_none, from_str], self.intention) if self.commands is not None: result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x)], self.commands) if self.possible_paths is not None: - result["possiblePaths"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.possible_paths) + result["possiblePaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.possible_paths) if self.possible_urls is not None: result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x)], self.possible_urls) if self.has_write_file_redirection is not None: - result["hasWriteFileRedirection"] = from_union([from_none, lambda x: from_bool(x)], self.has_write_file_redirection) + result["hasWriteFileRedirection"] = from_union([from_none, from_bool], self.has_write_file_redirection) if self.can_offer_session_approval is not None: - result["canOfferSessionApproval"] = from_union([from_none, lambda x: from_bool(x)], self.can_offer_session_approval) + result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval) if self.warning is not None: - result["warning"] = from_union([from_none, lambda x: from_str(x)], self.warning) + result["warning"] = from_union([from_none, from_str], self.warning) if self.file_name is not None: - result["fileName"] = from_union([from_none, lambda x: from_str(x)], self.file_name) + result["fileName"] = from_union([from_none, from_str], self.file_name) if self.diff is not None: - result["diff"] = from_union([from_none, lambda x: from_str(x)], self.diff) + result["diff"] = from_union([from_none, from_str], self.diff) if self.new_file_contents is not None: - result["newFileContents"] = from_union([from_none, lambda x: from_str(x)], self.new_file_contents) + result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) if self.path is not None: - result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + result["path"] = from_union([from_none, from_str], self.path) if self.server_name is not None: - result["serverName"] = from_union([from_none, lambda x: from_str(x)], self.server_name) + result["serverName"] = from_union([from_none, from_str], self.server_name) if self.tool_name is not None: - result["toolName"] = from_union([from_none, lambda x: from_str(x)], self.tool_name) + result["toolName"] = from_union([from_none, from_str], self.tool_name) if self.tool_title is not None: - result["toolTitle"] = from_union([from_none, lambda x: from_str(x)], self.tool_title) + result["toolTitle"] = from_union([from_none, from_str], self.tool_title) if self.args is not None: result["args"] = self.args if self.read_only is not None: - result["readOnly"] = from_union([from_none, lambda x: from_bool(x)], self.read_only) + result["readOnly"] = from_union([from_none, from_bool], self.read_only) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) if self.action is not None: result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) if self.subject is not None: - result["subject"] = from_union([from_none, lambda x: from_str(x)], self.subject) + result["subject"] = from_union([from_none, from_str], self.subject) if self.fact is not None: - result["fact"] = from_union([from_none, lambda x: from_str(x)], self.fact) + result["fact"] = from_union([from_none, from_str], self.fact) if self.citations is not None: - result["citations"] = from_union([from_none, lambda x: from_str(x)], self.citations) + result["citations"] = from_union([from_none, from_str], self.citations) if self.direction is not None: result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) if self.reason is not None: - result["reason"] = from_union([from_none, lambda x: from_str(x)], self.reason) + result["reason"] = from_union([from_none, from_str], self.reason) if self.tool_description is not None: - result["toolDescription"] = from_union([from_none, lambda x: from_str(x)], self.tool_description) + result["toolDescription"] = from_union([from_none, from_str], self.tool_description) if self.tool_args is not None: result["toolArgs"] = self.tool_args if self.hook_message is not None: - result["hookMessage"] = from_union([from_none, lambda x: from_str(x)], self.hook_message) + result["hookMessage"] = from_union([from_none, from_str], self.hook_message) return result @@ -3015,7 +3015,7 @@ def from_dict(obj: Any) -> "PermissionRequestedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) permission_request = PermissionRequest.from_dict(obj.get("permissionRequest")) - resolved_by_hook = from_union([from_none, lambda x: from_bool(x)], obj.get("resolvedByHook")) + resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook")) return PermissionRequestedData( request_id=request_id, permission_request=permission_request, @@ -3027,7 +3027,7 @@ def to_dict(self) -> dict: result["requestId"] = from_str(self.request_id) result["permissionRequest"] = to_class(PermissionRequest, self.permission_request) if self.resolved_by_hook is not None: - result["resolvedByHook"] = from_union([from_none, lambda x: from_bool(x)], self.resolved_by_hook) + result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook) return result @@ -3087,9 +3087,9 @@ def from_dict(obj: Any) -> "UserInputRequestedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) question = from_str(obj.get("question")) - choices = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("choices")) - allow_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("allowFreeform")) - tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) + choices = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("choices")) + allow_freeform = from_union([from_none, from_bool], obj.get("allowFreeform")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return UserInputRequestedData( request_id=request_id, question=question, @@ -3103,11 +3103,11 @@ def to_dict(self) -> dict: result["requestId"] = from_str(self.request_id) result["question"] = from_str(self.question) if self.choices is not None: - result["choices"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.choices) + result["choices"] = from_union([from_none, lambda x: from_list(from_str, x)], self.choices) if self.allow_freeform is not None: - result["allowFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.allow_freeform) + result["allowFreeform"] = from_union([from_none, from_bool], self.allow_freeform) if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -3122,8 +3122,8 @@ class UserInputCompletedData: def from_dict(obj: Any) -> "UserInputCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - answer = from_union([from_none, lambda x: from_str(x)], obj.get("answer")) - was_freeform = from_union([from_none, lambda x: from_bool(x)], obj.get("wasFreeform")) + answer = from_union([from_none, from_str], obj.get("answer")) + was_freeform = from_union([from_none, from_bool], obj.get("wasFreeform")) return UserInputCompletedData( request_id=request_id, answer=answer, @@ -3134,9 +3134,9 @@ def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) if self.answer is not None: - result["answer"] = from_union([from_none, lambda x: from_str(x)], self.answer) + result["answer"] = from_union([from_none, from_str], self.answer) if self.was_freeform is not None: - result["wasFreeform"] = from_union([from_none, lambda x: from_bool(x)], self.was_freeform) + result["wasFreeform"] = from_union([from_none, from_bool], self.was_freeform) return result @@ -3152,7 +3152,7 @@ def from_dict(obj: Any) -> "ElicitationRequestedSchema": assert isinstance(obj, dict) type = from_str(obj.get("type")) properties = from_dict(lambda x: x, obj.get("properties")) - required = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], obj.get("required")) + required = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("required")) return ElicitationRequestedSchema( type=type, properties=properties, @@ -3164,7 +3164,7 @@ def to_dict(self) -> dict: result["type"] = from_str(self.type) result["properties"] = from_dict(lambda x: x, self.properties) if self.required is not None: - result["required"] = from_union([from_none, lambda x: from_list(lambda x: from_str(x), x)], self.required) + result["required"] = from_union([from_none, lambda x: from_list(from_str, x)], self.required) return result @@ -3184,11 +3184,11 @@ def from_dict(obj: Any) -> "ElicitationRequestedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) message = from_str(obj.get("message")) - tool_call_id = from_union([from_none, lambda x: from_str(x)], obj.get("toolCallId")) - elicitation_source = from_union([from_none, lambda x: from_str(x)], obj.get("elicitationSource")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + elicitation_source = from_union([from_none, from_str], obj.get("elicitationSource")) mode = from_union([from_none, lambda x: parse_enum(ElicitationRequestedMode, x)], obj.get("mode")) - requested_schema = from_union([from_none, lambda x: ElicitationRequestedSchema.from_dict(x)], obj.get("requestedSchema")) - url = from_union([from_none, lambda x: from_str(x)], obj.get("url")) + requested_schema = from_union([from_none, ElicitationRequestedSchema.from_dict], obj.get("requestedSchema")) + url = from_union([from_none, from_str], obj.get("url")) return ElicitationRequestedData( request_id=request_id, message=message, @@ -3204,15 +3204,15 @@ def to_dict(self) -> dict: result["requestId"] = from_str(self.request_id) result["message"] = from_str(self.message) if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, lambda x: from_str(x)], self.tool_call_id) + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) if self.elicitation_source is not None: - result["elicitationSource"] = from_union([from_none, lambda x: from_str(x)], self.elicitation_source) + result["elicitationSource"] = from_union([from_none, from_str], self.elicitation_source) if self.mode is not None: result["mode"] = from_union([from_none, lambda x: to_enum(ElicitationRequestedMode, x)], self.mode) if self.requested_schema is not None: result["requestedSchema"] = from_union([from_none, lambda x: to_class(ElicitationRequestedSchema, x)], self.requested_schema) if self.url is not None: - result["url"] = from_union([from_none, lambda x: from_str(x)], self.url) + result["url"] = from_union([from_none, from_str], self.url) return result @@ -3301,7 +3301,7 @@ class MCPOauthRequiredStaticClientConfig: def from_dict(obj: Any) -> "MCPOauthRequiredStaticClientConfig": assert isinstance(obj, dict) client_id = from_str(obj.get("clientId")) - public_client = from_union([from_none, lambda x: from_bool(x)], obj.get("publicClient")) + public_client = from_union([from_none, from_bool], obj.get("publicClient")) return MCPOauthRequiredStaticClientConfig( client_id=client_id, public_client=public_client, @@ -3311,7 +3311,7 @@ def to_dict(self) -> dict: result: dict = {} result["clientId"] = from_str(self.client_id) if self.public_client is not None: - result["publicClient"] = from_union([from_none, lambda x: from_bool(x)], self.public_client) + result["publicClient"] = from_union([from_none, from_bool], self.public_client) return result @@ -3329,7 +3329,7 @@ def from_dict(obj: Any) -> "McpOauthRequiredData": request_id = from_str(obj.get("requestId")) server_name = from_str(obj.get("serverName")) server_url = from_str(obj.get("serverUrl")) - static_client_config = from_union([from_none, lambda x: MCPOauthRequiredStaticClientConfig.from_dict(x)], obj.get("staticClientConfig")) + static_client_config = from_union([from_none, MCPOauthRequiredStaticClientConfig.from_dict], obj.get("staticClientConfig")) return McpOauthRequiredData( request_id=request_id, server_name=server_name, @@ -3385,8 +3385,8 @@ def from_dict(obj: Any) -> "ExternalToolRequestedData": tool_call_id = from_str(obj.get("toolCallId")) tool_name = from_str(obj.get("toolName")) arguments = obj.get("arguments") - traceparent = from_union([from_none, lambda x: from_str(x)], obj.get("traceparent")) - tracestate = from_union([from_none, lambda x: from_str(x)], obj.get("tracestate")) + traceparent = from_union([from_none, from_str], obj.get("traceparent")) + tracestate = from_union([from_none, from_str], obj.get("tracestate")) return ExternalToolRequestedData( request_id=request_id, session_id=session_id, @@ -3406,9 +3406,9 @@ def to_dict(self) -> dict: if self.arguments is not None: result["arguments"] = self.arguments if self.traceparent is not None: - result["traceparent"] = from_union([from_none, lambda x: from_str(x)], self.traceparent) + result["traceparent"] = from_union([from_none, from_str], self.traceparent) if self.tracestate is not None: - result["tracestate"] = from_union([from_none, lambda x: from_str(x)], self.tracestate) + result["tracestate"] = from_union([from_none, from_str], self.tracestate) return result @@ -3513,7 +3513,7 @@ class CommandsChangedCommand: def from_dict(obj: Any) -> "CommandsChangedCommand": assert isinstance(obj, dict) name = from_str(obj.get("name")) - description = from_union([from_none, lambda x: from_str(x)], obj.get("description")) + description = from_union([from_none, from_str], obj.get("description")) return CommandsChangedCommand( name=name, description=description, @@ -3523,7 +3523,7 @@ def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) if self.description is not None: - result["description"] = from_union([from_none, lambda x: from_str(x)], self.description) + result["description"] = from_union([from_none, from_str], self.description) return result @@ -3535,7 +3535,7 @@ class CommandsChangedData: @staticmethod def from_dict(obj: Any) -> "CommandsChangedData": assert isinstance(obj, dict) - commands = from_list(lambda x: CommandsChangedCommand.from_dict(x), obj.get("commands")) + commands = from_list(CommandsChangedCommand.from_dict, obj.get("commands")) return CommandsChangedData( commands=commands, ) @@ -3554,7 +3554,7 @@ class CapabilitiesChangedUI: @staticmethod def from_dict(obj: Any) -> "CapabilitiesChangedUI": assert isinstance(obj, dict) - elicitation = from_union([from_none, lambda x: from_bool(x)], obj.get("elicitation")) + elicitation = from_union([from_none, from_bool], obj.get("elicitation")) return CapabilitiesChangedUI( elicitation=elicitation, ) @@ -3562,7 +3562,7 @@ def from_dict(obj: Any) -> "CapabilitiesChangedUI": def to_dict(self) -> dict: result: dict = {} if self.elicitation is not None: - result["elicitation"] = from_union([from_none, lambda x: from_bool(x)], self.elicitation) + result["elicitation"] = from_union([from_none, from_bool], self.elicitation) return result @@ -3574,7 +3574,7 @@ class CapabilitiesChangedData: @staticmethod def from_dict(obj: Any) -> "CapabilitiesChangedData": assert isinstance(obj, dict) - ui = from_union([from_none, lambda x: CapabilitiesChangedUI.from_dict(x)], obj.get("ui")) + ui = from_union([from_none, CapabilitiesChangedUI.from_dict], obj.get("ui")) return CapabilitiesChangedData( ui=ui, ) @@ -3601,7 +3601,7 @@ def from_dict(obj: Any) -> "ExitPlanModeRequestedData": request_id = from_str(obj.get("requestId")) summary = from_str(obj.get("summary")) plan_content = from_str(obj.get("planContent")) - actions = from_list(lambda x: from_str(x), obj.get("actions")) + actions = from_list(from_str, obj.get("actions")) recommended_action = from_str(obj.get("recommendedAction")) return ExitPlanModeRequestedData( request_id=request_id, @@ -3616,7 +3616,7 @@ def to_dict(self) -> dict: result["requestId"] = from_str(self.request_id) result["summary"] = from_str(self.summary) result["planContent"] = from_str(self.plan_content) - result["actions"] = from_list(lambda x: from_str(x), self.actions) + result["actions"] = from_list(from_str, self.actions) result["recommendedAction"] = from_str(self.recommended_action) return result @@ -3634,10 +3634,10 @@ class ExitPlanModeCompletedData: def from_dict(obj: Any) -> "ExitPlanModeCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - approved = from_union([from_none, lambda x: from_bool(x)], obj.get("approved")) - selected_action = from_union([from_none, lambda x: from_str(x)], obj.get("selectedAction")) - auto_approve_edits = from_union([from_none, lambda x: from_bool(x)], obj.get("autoApproveEdits")) - feedback = from_union([from_none, lambda x: from_str(x)], obj.get("feedback")) + approved = from_union([from_none, from_bool], obj.get("approved")) + selected_action = from_union([from_none, from_str], obj.get("selectedAction")) + auto_approve_edits = from_union([from_none, from_bool], obj.get("autoApproveEdits")) + feedback = from_union([from_none, from_str], obj.get("feedback")) return ExitPlanModeCompletedData( request_id=request_id, approved=approved, @@ -3650,13 +3650,13 @@ def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) if self.approved is not None: - result["approved"] = from_union([from_none, lambda x: from_bool(x)], self.approved) + result["approved"] = from_union([from_none, from_bool], self.approved) if self.selected_action is not None: - result["selectedAction"] = from_union([from_none, lambda x: from_str(x)], self.selected_action) + result["selectedAction"] = from_union([from_none, from_str], self.selected_action) if self.auto_approve_edits is not None: - result["autoApproveEdits"] = from_union([from_none, lambda x: from_bool(x)], self.auto_approve_edits) + result["autoApproveEdits"] = from_union([from_none, from_bool], self.auto_approve_edits) if self.feedback is not None: - result["feedback"] = from_union([from_none, lambda x: from_str(x)], self.feedback) + result["feedback"] = from_union([from_none, from_str], self.feedback) return result @@ -3706,7 +3706,7 @@ def from_dict(obj: Any) -> "SkillsLoadedSkill": source = from_str(obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) enabled = from_bool(obj.get("enabled")) - path = from_union([from_none, lambda x: from_str(x)], obj.get("path")) + path = from_union([from_none, from_str], obj.get("path")) return SkillsLoadedSkill( name=name, description=description, @@ -3724,7 +3724,7 @@ def to_dict(self) -> dict: result["userInvocable"] = from_bool(self.user_invocable) result["enabled"] = from_bool(self.enabled) if self.path is not None: - result["path"] = from_union([from_none, lambda x: from_str(x)], self.path) + result["path"] = from_union([from_none, from_str], self.path) return result @@ -3735,7 +3735,7 @@ class SessionSkillsLoadedData: @staticmethod def from_dict(obj: Any) -> "SessionSkillsLoadedData": assert isinstance(obj, dict) - skills = from_list(lambda x: SkillsLoadedSkill.from_dict(x), obj.get("skills")) + skills = from_list(SkillsLoadedSkill.from_dict, obj.get("skills")) return SessionSkillsLoadedData( skills=skills, ) @@ -3765,9 +3765,9 @@ def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": display_name = from_str(obj.get("displayName")) description = from_str(obj.get("description")) source = from_str(obj.get("source")) - tools = from_list(lambda x: from_str(x), obj.get("tools")) + tools = from_list(from_str, obj.get("tools")) user_invocable = from_bool(obj.get("userInvocable")) - model = from_union([from_none, lambda x: from_str(x)], obj.get("model")) + model = from_union([from_none, from_str], obj.get("model")) return CustomAgentsUpdatedAgent( id=id, name=name, @@ -3786,10 +3786,10 @@ def to_dict(self) -> dict: result["displayName"] = from_str(self.display_name) result["description"] = from_str(self.description) result["source"] = from_str(self.source) - result["tools"] = from_list(lambda x: from_str(x), self.tools) + result["tools"] = from_list(from_str, self.tools) result["userInvocable"] = from_bool(self.user_invocable) if self.model is not None: - result["model"] = from_union([from_none, lambda x: from_str(x)], self.model) + result["model"] = from_union([from_none, from_str], self.model) return result @@ -3802,9 +3802,9 @@ class SessionCustomAgentsUpdatedData: @staticmethod def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": assert isinstance(obj, dict) - agents = from_list(lambda x: CustomAgentsUpdatedAgent.from_dict(x), obj.get("agents")) - warnings = from_list(lambda x: from_str(x), obj.get("warnings")) - errors = from_list(lambda x: from_str(x), obj.get("errors")) + agents = from_list(CustomAgentsUpdatedAgent.from_dict, obj.get("agents")) + warnings = from_list(from_str, obj.get("warnings")) + errors = from_list(from_str, obj.get("errors")) return SessionCustomAgentsUpdatedData( agents=agents, warnings=warnings, @@ -3814,8 +3814,8 @@ def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": def to_dict(self) -> dict: result: dict = {} result["agents"] = from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), self.agents) - result["warnings"] = from_list(lambda x: from_str(x), self.warnings) - result["errors"] = from_list(lambda x: from_str(x), self.errors) + result["warnings"] = from_list(from_str, self.warnings) + result["errors"] = from_list(from_str, self.errors) return result @@ -3831,8 +3831,8 @@ def from_dict(obj: Any) -> "MCPServersLoadedServer": assert isinstance(obj, dict) name = from_str(obj.get("name")) status = parse_enum(MCPServerStatus, obj.get("status")) - source = from_union([from_none, lambda x: from_str(x)], obj.get("source")) - error = from_union([from_none, lambda x: from_str(x)], obj.get("error")) + source = from_union([from_none, from_str], obj.get("source")) + error = from_union([from_none, from_str], obj.get("error")) return MCPServersLoadedServer( name=name, status=status, @@ -3845,9 +3845,9 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) result["status"] = to_enum(MCPServerStatus, self.status) if self.source is not None: - result["source"] = from_union([from_none, lambda x: from_str(x)], self.source) + result["source"] = from_union([from_none, from_str], self.source) if self.error is not None: - result["error"] = from_union([from_none, lambda x: from_str(x)], self.error) + result["error"] = from_union([from_none, from_str], self.error) return result @@ -3858,7 +3858,7 @@ class SessionMcpServersLoadedData: @staticmethod def from_dict(obj: Any) -> "SessionMcpServersLoadedData": assert isinstance(obj, dict) - servers = from_list(lambda x: MCPServersLoadedServer.from_dict(x), obj.get("servers")) + servers = from_list(MCPServersLoadedServer.from_dict, obj.get("servers")) return SessionMcpServersLoadedData( servers=servers, ) @@ -3928,7 +3928,7 @@ class SessionExtensionsLoadedData: @staticmethod def from_dict(obj: Any) -> "SessionExtensionsLoadedData": assert isinstance(obj, dict) - extensions = from_list(lambda x: ExtensionsLoadedExtension.from_dict(x), obj.get("extensions")) + extensions = from_list(ExtensionsLoadedExtension.from_dict, obj.get("extensions")) return SessionExtensionsLoadedData( extensions=extensions, ) diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 8c437b191..bb1f56e0d 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -140,6 +140,17 @@ function modernizePython(code: string): string { return code; } +/** + * Collapse lambdas that only forward their single argument into another callable. + * This keeps the generated Python readable and avoids CodeQL "unnecessary lambda" findings. + */ +function unwrapRedundantPythonLambdas(code: string): string { + return code.replace( + /lambda\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*((?:[A-Za-z_][A-Za-z0-9_]*)(?:\.[A-Za-z_][A-Za-z0-9_]*)*)\(\1\)/g, + "$2" + ); +} + function collapsePlaceholderPythonDataclasses(code: string): string { const classBlockRe = /(@dataclass\r?\nclass\s+(\w+):[\s\S]*?)(?=^@dataclass|^class\s+\w+|^def\s+\w+|\Z)/gm; const matches = [...code.matchAll(classBlockRe)].map((match) => ({ @@ -363,7 +374,7 @@ function postProcessPythonSessionEventCode(code: string): string { )) { code = code.replace(new RegExp(`\\b${from}\\b`, "g"), to); } - return code; + return unwrapRedundantPythonLambdas(code); } function pyPrimitiveResolvedType(annotation: string, fromFn: string, toFn = fromFn): PyResolvedType { @@ -1604,6 +1615,7 @@ def _patch_model_capabilities(data: dict) -> dict: /(_patch_model_capabilities\(await self\._client\.request\("models\.list",\s*\{[^)]*\)[^)]*\))/, "$1)", ); + finalCode = unwrapRedundantPythonLambdas(finalCode); const outPath = await writeGeneratedFile("python/copilot/generated/rpc.py", finalCode); console.log(` ✓ ${outPath}`); From cf5694c8d0f6ec73033359219de2ff3aa03e24ff Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 20:58:38 -0400 Subject: [PATCH 28/34] Update @github/copilot to 1.0.32-1 (#1105) * Update @github/copilot to 1.0.32-1 - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code * Add deprecation message to [Obsolete] attributes in C# generator Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/35a08e5f-ea8a-4494-9e24-084aad8ec639 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Restore accidentally modified snapshot file Agent-Logs-Url: https://github.com/github/copilot-sdk/sessions/35a08e5f-ea8a-4494-9e24-084aad8ec639 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> * Fix CI failures for @github/copilot 1.0.32-1 update - Add shared nullable-required schema normalization in codegen utils (converts required \ properties with null descriptions to anyOf pattern) - Add Python forward-reference topological sort in codegen - Update Go type alias for renamed PurpleModelCapabilitiesOverrideLimitsVision - Add Go type conversion helper for duplicate quicktype-generated types - Add Node.js model capabilities normalization (backfill supports/limits) - Regenerate all language bindings with fixes applied Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix TypeScript type errors in model capabilities normalization Use 'any' cast instead of 'Record' to avoid TS2352 errors with strict interface types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Go formatting in session.go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix Python ModelSwitchToRequest type mismatch Import ModelCapabilitiesClass (used by generated ModelSwitchToRequest) instead of ModelCapabilitiesOverride which is now a separate duplicate type. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix flaky TestMultiClient permission event assertions Client 2's event handler receives events asynchronously, so checking immediately after SendAndWait may miss them. Replace instant checks with polling (waitForEventsByType) that retries for up to 5 seconds. Applied to both 'approves' and 'rejects' subtests for consistency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 185 +- dotnet/src/Generated/SessionEvents.cs | 107 +- go/generated_session_events.go | 65 +- go/internal/e2e/multi_client_test.go | 33 +- go/rpc/generated_rpc.go | 802 ++-- go/session.go | 31 +- go/types.go | 2 +- nodejs/package-lock.json | 56 +- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- nodejs/src/client.ts | 20 + nodejs/src/generated/rpc.ts | 1583 +++++-- nodejs/src/generated/session-events.ts | 512 ++- python/copilot/generated/rpc.py | 4848 ++++++++++++-------- python/copilot/generated/session_events.py | 100 +- python/copilot/session.py | 2 +- scripts/codegen/csharp.ts | 33 +- scripts/codegen/go.ts | 3 +- scripts/codegen/python.ts | 158 +- scripts/codegen/typescript.ts | 3 +- scripts/codegen/utils.ts | 118 + test/harness/package-lock.json | 56 +- test/harness/package.json | 2 +- 23 files changed, 5533 insertions(+), 3190 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 295fb8bfa..0c9880de6 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -815,6 +815,58 @@ internal sealed class WorkspacesCreateFileRequest public string Content { get; set; } = string.Empty; } +/// RPC data type for InstructionsSources operations. +public sealed class InstructionsSources +{ + /// Unique identifier for this source (used for toggling). + [JsonPropertyName("id")] + public string Id { get; set; } = string.Empty; + + /// Human-readable label. + [JsonPropertyName("label")] + public string Label { get; set; } = string.Empty; + + /// File path relative to repo or absolute for home. + [JsonPropertyName("sourcePath")] + public string SourcePath { get; set; } = string.Empty; + + /// Raw content of the instruction file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Category of instruction source — used for merge logic. + [JsonPropertyName("type")] + public InstructionsSourcesType Type { get; set; } + + /// Where this source lives — used for UI grouping. + [JsonPropertyName("location")] + public InstructionsSourcesLocation Location { get; set; } + + /// Glob pattern from frontmatter — when set, this instruction applies only to matching files. + [JsonPropertyName("applyTo")] + public string? ApplyTo { get; set; } + + /// Short description (body after frontmatter) for use in instruction tables. + [JsonPropertyName("description")] + public string? Description { get; set; } +} + +/// RPC data type for InstructionsGetSources operations. +public sealed class InstructionsGetSourcesResult +{ + /// Instruction sources for the session. + [JsonPropertyName("sources")] + public IList Sources { get => field ??= []; set; } +} + +/// RPC data type for SessionInstructionsGetSources operations. +internal sealed class SessionInstructionsGetSourcesRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// RPC data type for FleetStart operations. [Experimental(Diagnostics.Experimental)] public sealed class FleetStartResult @@ -837,8 +889,8 @@ internal sealed class FleetStartRequest public string? Prompt { get; set; } } -/// RPC data type for Agent operations. -public sealed class Agent +/// RPC data type for AgentInfo operations. +public sealed class AgentInfo { /// Unique identifier of the custom agent. [JsonPropertyName("name")] @@ -859,7 +911,7 @@ public sealed class AgentList { /// Available custom agents. [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentList operations. @@ -871,29 +923,13 @@ internal sealed class SessionAgentListRequest public string SessionId { get; set; } = string.Empty; } -/// RPC data type for AgentGetCurrentResultAgent operations. -public sealed class AgentGetCurrentResultAgent -{ - /// Unique identifier of the custom agent. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Human-readable display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; -} - /// RPC data type for AgentGetCurrent operations. [Experimental(Diagnostics.Experimental)] public sealed class AgentGetCurrentResult { /// Currently selected custom agent, or null if using the default agent. [JsonPropertyName("agent")] - public AgentGetCurrentResultAgent? Agent { get; set; } + public AgentInfo? Agent { get; set; } } /// RPC data type for SessionAgentGetCurrent operations. @@ -905,29 +941,13 @@ internal sealed class SessionAgentGetCurrentRequest public string SessionId { get; set; } = string.Empty; } -/// The newly selected custom agent. -public sealed class AgentSelectAgent -{ - /// Unique identifier of the custom agent. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Human-readable display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; -} - /// RPC data type for AgentSelect operations. [Experimental(Diagnostics.Experimental)] public sealed class AgentSelectResult { /// The newly selected custom agent. [JsonPropertyName("agent")] - public AgentSelectAgent Agent { get => field ??= new(); set; } + public AgentInfo Agent { get => field ??= new(); set; } } /// RPC data type for AgentSelect operations. @@ -952,29 +972,13 @@ internal sealed class SessionAgentDeselectRequest public string SessionId { get; set; } = string.Empty; } -/// RPC data type for AgentReloadAgent operations. -public sealed class AgentReloadAgent -{ - /// Unique identifier of the custom agent. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// Human-readable display name. - [JsonPropertyName("displayName")] - public string DisplayName { get; set; } = string.Empty; - - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; -} - /// RPC data type for AgentReload operations. [Experimental(Diagnostics.Experimental)] public sealed class AgentReloadResult { /// Reloaded custom agents. [JsonPropertyName("agents")] - public IList Agents { get => field ??= []; set; } + public IList Agents { get => field ??= []; set; } } /// RPC data type for SessionAgentReload operations. @@ -2009,6 +2013,47 @@ public enum WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel } +/// Category of instruction source — used for merge logic. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum InstructionsSourcesType +{ + /// The home variant. + [JsonStringEnumMemberName("home")] + Home, + /// The repo variant. + [JsonStringEnumMemberName("repo")] + Repo, + /// The model variant. + [JsonStringEnumMemberName("model")] + Model, + /// The vscode variant. + [JsonStringEnumMemberName("vscode")] + Vscode, + /// The nested-agents variant. + [JsonStringEnumMemberName("nested-agents")] + NestedAgents, + /// The child-instructions variant. + [JsonStringEnumMemberName("child-instructions")] + ChildInstructions, +} + + +/// Where this source lives — used for UI grouping. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum InstructionsSourcesLocation +{ + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The repository variant. + [JsonStringEnumMemberName("repository")] + Repository, + /// The working-directory variant. + [JsonStringEnumMemberName("working-directory")] + WorkingDirectory, +} + + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonConverter(typeof(JsonStringEnumConverter))] public enum McpServerStatus @@ -2380,6 +2425,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId) Name = new NameApi(rpc, sessionId); Plan = new PlanApi(rpc, sessionId); Workspaces = new WorkspacesApi(rpc, sessionId); + Instructions = new InstructionsApi(rpc, sessionId); Fleet = new FleetApi(rpc, sessionId); Agent = new AgentApi(rpc, sessionId); Skills = new SkillsApi(rpc, sessionId); @@ -2410,6 +2456,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId) /// Workspaces APIs. public WorkspacesApi Workspaces { get; } + /// Instructions APIs. + public InstructionsApi Instructions { get; } + /// Fleet APIs. public FleetApi Fleet { get; } @@ -2613,6 +2662,26 @@ public async Task CreateFileAsync(string path, string content, CancellationToken } } +/// Provides session-scoped Instructions APIs. +public sealed class InstructionsApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal InstructionsApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.instructions.getSources". + public async Task GetSourcesAsync(CancellationToken cancellationToken = default) + { + var request = new SessionInstructionsGetSourcesRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.instructions.getSources", [request], cancellationToken); + } +} + /// Provides session-scoped Fleet APIs. [Experimental(Diagnostics.Experimental)] public sealed class FleetApi @@ -3144,13 +3213,10 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncUpdated working directory and git context after the change. +/// Working directory and git context at session start. /// Represents the session.context_changed event. public partial class SessionContextChangedEvent : SessionEvent { @@ -731,7 +731,7 @@ public partial class HookEndEvent : SessionEvent public required HookEndData Data { get; set; } } -/// System or developer message content with role and optional template metadata. +/// System/developer instruction content with role and optional template metadata. /// Represents the system.message event. public partial class SystemMessageEvent : SessionEvent { @@ -1124,7 +1124,7 @@ public partial class SessionStartData /// Working directory and git context at session start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] - public StartContext? Context { get; set; } + public WorkingDirectoryContext? Context { get; set; } /// Whether the session was already in use by another client at start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1161,7 +1161,7 @@ public partial class SessionResumeData /// Updated working directory and git context at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("context")] - public ResumeContext? Context { get; set; } + public WorkingDirectoryContext? Context { get; set; } /// Whether the session was already in use by another client at resume time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1469,7 +1469,7 @@ public partial class SessionShutdownData public double? ToolDefinitionsTokens { get; set; } } -/// Updated working directory and git context after the change. +/// Working directory and git context at session start. public partial class SessionContextChangedData { /// Current working directory path. @@ -1489,7 +1489,7 @@ public partial class SessionContextChangedData /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] - public ContextChangedHostType? HostType { get; set; } + public WorkingDirectoryContextHostType? HostType { get; set; } /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1671,6 +1671,16 @@ public partial class UserMessageData [JsonPropertyName("attachments")] public UserMessageAttachment[]? Attachments { get; set; } + /// Normalized document MIME types that were sent natively instead of through tagged_files XML. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("supportedNativeDocumentMimeTypes")] + public string[]? SupportedNativeDocumentMimeTypes { 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")] + public string[]? NativeDocumentPathFallbackPaths { get; set; } + /// Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] @@ -1797,6 +1807,7 @@ public partial class AssistantMessageData public string? RequestId { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -1814,6 +1825,7 @@ public partial class AssistantMessageDeltaData public required string DeltaContent { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -1895,6 +1907,7 @@ public partial class AssistantUsageData public string? ProviderCallId { get; set; } /// Parent tool call ID when this usage originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -1967,6 +1980,7 @@ public partial class ToolExecutionStartData public string? McpToolName { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -2038,6 +2052,7 @@ public partial class ToolExecutionCompleteData public IDictionary? ToolTelemetry { get; set; } /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -2239,10 +2254,10 @@ public partial class HookEndData public HookEndError? Error { get; set; } } -/// System or developer message content with role and optional template metadata. +/// System/developer instruction content with role and optional template metadata. public partial class SystemMessageData { - /// The system or developer prompt text. + /// The system or developer prompt text sent as model input. [JsonPropertyName("content")] public required string Content { get; set; } @@ -2673,8 +2688,8 @@ public partial class SessionExtensionsLoadedData } /// Working directory and git context at session start. -/// Nested data type for StartContext. -public partial class StartContext +/// Nested data type for WorkingDirectoryContext. +public partial class WorkingDirectoryContext { /// Current working directory path. [JsonPropertyName("cwd")] @@ -2693,46 +2708,7 @@ public partial class StartContext /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] - public StartContextHostType? HostType { get; set; } - - /// Current git branch name. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Head commit of current git branch at session start time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } - - /// Base commit of current git branch at session start time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } -} - -/// Updated working directory and git context at resume time. -/// Nested data type for ResumeContext. -public partial class ResumeContext -{ - /// Current working directory path. - [JsonPropertyName("cwd")] - public required string Cwd { get; set; } - - /// Root directory of the git repository, resolved via git rev-parse. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("gitRoot")] - public string? GitRoot { get; set; } - - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("repository")] - public string? Repository { get; set; } - - /// Hosting platform type of the repository (github or ado). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hostType")] - public ResumeContextHostType? HostType { get; set; } + public WorkingDirectoryContextHostType? HostType { get; set; } /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3872,20 +3848,8 @@ public partial class ExtensionsLoadedExtension } /// Hosting platform type of the repository (github or ado). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum StartContextHostType -{ - /// The github variant. - [JsonStringEnumMemberName("github")] - Github, - /// The ado variant. - [JsonStringEnumMemberName("ado")] - Ado, -} - -/// Hosting platform type of the repository (github or ado). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ResumeContextHostType +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum WorkingDirectoryContextHostType { /// The github variant. [JsonStringEnumMemberName("github")] @@ -3946,18 +3910,6 @@ public enum ShutdownType Error, } -/// Hosting platform type of the repository (github or ado). -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum ContextChangedHostType -{ - /// The github variant. - [JsonStringEnumMemberName("github")] - Github, - /// The ado variant. - [JsonStringEnumMemberName("ado")] - Ado, -} - /// Type of GitHub reference. [JsonConverter(typeof(JsonStringEnumConverter))] public enum UserMessageAttachmentGithubReferenceType @@ -4278,7 +4230,6 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(PermissionRequestWrite))] [JsonSerializable(typeof(PermissionRequestedData))] [JsonSerializable(typeof(PermissionRequestedEvent))] -[JsonSerializable(typeof(ResumeContext))] [JsonSerializable(typeof(SamplingCompletedData))] [JsonSerializable(typeof(SamplingCompletedEvent))] [JsonSerializable(typeof(SamplingRequestedData))] @@ -4344,7 +4295,6 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] [JsonSerializable(typeof(SkillsLoadedSkill))] -[JsonSerializable(typeof(StartContext))] [JsonSerializable(typeof(SubagentCompletedData))] [JsonSerializable(typeof(SubagentCompletedEvent))] [JsonSerializable(typeof(SubagentDeselectedData))] @@ -4401,5 +4351,6 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(UserMessageAttachmentSelectionDetailsStart))] [JsonSerializable(typeof(UserMessageData))] [JsonSerializable(typeof(UserMessageEvent))] +[JsonSerializable(typeof(WorkingDirectoryContext))] [JsonSerializable(typeof(JsonElement))] internal partial class SessionEventsJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 01a6a0811..95ace9123 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -637,7 +637,7 @@ type SessionStartData struct { // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Working directory and git context at session start - Context *StartContext `json:"context,omitempty"` + Context *WorkingDirectoryContext `json:"context,omitempty"` // Whether the session was already in use by another client at start time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Whether this session supports remote steering via Mission Control @@ -657,7 +657,7 @@ type SessionResumeData struct { // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Updated working directory and git context at resume time - Context *ResumeContext `json:"context,omitempty"` + Context *WorkingDirectoryContext `json:"context,omitempty"` // Whether the session was already in use by another client at resume time AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Whether this session supports remote steering via Mission Control @@ -856,7 +856,7 @@ type SessionShutdownData struct { func (*SessionShutdownData) sessionEventData() {} -// Updated working directory and git context after the change +// Working directory and git context at session start type SessionContextChangedData struct { // Current working directory path Cwd string `json:"cwd"` @@ -865,7 +865,7 @@ type SessionContextChangedData struct { // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) - HostType *ContextChangedHostType `json:"hostType,omitempty"` + HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -962,6 +962,10 @@ type UserMessageData struct { TransformedContent *string `json:"transformedContent,omitempty"` // Files, selections, or GitHub references attached to the message Attachments []UserMessageAttachment `json:"attachments,omitempty"` + // Normalized document MIME types that were sent natively instead of through tagged_files XML + SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,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"` // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) Source *string `json:"source,omitempty"` // The agent mode that was active when this message was sent @@ -1047,6 +1051,7 @@ type AssistantMessageData struct { // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs RequestID *string `json:"requestId,omitempty"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` } @@ -1059,6 +1064,7 @@ type AssistantMessageDeltaData struct { // Incremental text chunk to append to the message content DeltaContent string `json:"deltaContent"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` } @@ -1101,6 +1107,7 @@ type AssistantUsageData struct { // GitHub request tracing ID (x-github-request-id header) for server-side log correlation ProviderCallID *string `json:"providerCallId,omitempty"` // Parent tool call ID when this usage originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Per-quota resource usage snapshots, keyed by quota identifier QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitempty"` @@ -1145,6 +1152,7 @@ type ToolExecutionStartData struct { // Original tool name on the MCP server, when the tool is an MCP tool McpToolName *string `json:"mcpToolName,omitempty"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` } @@ -1189,6 +1197,7 @@ type ToolExecutionCompleteData struct { // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` } @@ -1316,9 +1325,9 @@ type HookEndData struct { func (*HookEndData) sessionEventData() {} -// System or developer message content with role and optional template metadata +// System/developer instruction content with role and optional template metadata type SystemMessageData struct { - // The system or developer prompt text + // The system or developer prompt text sent as model input Content string `json:"content"` // Message role: "system" for system prompts, "developer" for developer-injected instructions Role SystemMessageRole `json:"role"` @@ -1632,25 +1641,7 @@ type SessionExtensionsLoadedData struct { func (*SessionExtensionsLoadedData) sessionEventData() {} // Working directory and git context at session start -type StartContext struct { - // Current working directory path - Cwd string `json:"cwd"` - // Root directory of the git repository, resolved via git rev-parse - GitRoot *string `json:"gitRoot,omitempty"` - // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - Repository *string `json:"repository,omitempty"` - // Hosting platform type of the repository (github or ado) - HostType *StartContextHostType `json:"hostType,omitempty"` - // Current git branch name - Branch *string `json:"branch,omitempty"` - // Head commit of current git branch at session start time - HeadCommit *string `json:"headCommit,omitempty"` - // Base commit of current git branch at session start time - BaseCommit *string `json:"baseCommit,omitempty"` -} - -// Updated working directory and git context at resume time -type ResumeContext struct { +type WorkingDirectoryContext struct { // Current working directory path Cwd string `json:"cwd"` // Root directory of the git repository, resolved via git rev-parse @@ -1658,7 +1649,7 @@ type ResumeContext struct { // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) - HostType *ResumeContextHostType `json:"hostType,omitempty"` + HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -2109,19 +2100,11 @@ type ExtensionsLoadedExtension struct { } // Hosting platform type of the repository (github or ado) -type StartContextHostType string +type WorkingDirectoryContextHostType string const ( - StartContextHostTypeGithub StartContextHostType = "github" - StartContextHostTypeAdo StartContextHostType = "ado" -) - -// Hosting platform type of the repository (github or ado) -type ResumeContextHostType string - -const ( - ResumeContextHostTypeGithub ResumeContextHostType = "github" - ResumeContextHostTypeAdo ResumeContextHostType = "ado" + WorkingDirectoryContextHostTypeGithub WorkingDirectoryContextHostType = "github" + WorkingDirectoryContextHostTypeAdo WorkingDirectoryContextHostType = "ado" ) // The type of operation performed on the plan file @@ -2157,14 +2140,6 @@ const ( ShutdownTypeError ShutdownType = "error" ) -// Hosting platform type of the repository (github or ado) -type ContextChangedHostType string - -const ( - ContextChangedHostTypeGithub ContextChangedHostType = "github" - ContextChangedHostTypeAdo ContextChangedHostType = "ado" -) - // Type discriminator for UserMessageAttachment. type UserMessageAttachmentType string diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index 389912284..3b009e898 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -200,9 +200,7 @@ func TestMultiClient(t *testing.T) { mu1.Lock() c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested) mu1.Unlock() - mu2.Lock() - c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested) - mu2.Unlock() + c2PermRequested := waitForEventsByType(t, &mu2, &client2Events, copilot.SessionEventTypePermissionRequested, 5*time.Second) if len(c1PermRequested) == 0 { t.Errorf("Expected client 1 to see permission.requested events") @@ -215,9 +213,7 @@ func TestMultiClient(t *testing.T) { mu1.Lock() c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted) mu1.Unlock() - mu2.Lock() - c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted) - mu2.Unlock() + c2PermCompleted := waitForEventsByType(t, &mu2, &client2Events, copilot.SessionEventTypePermissionCompleted, 5*time.Second) if len(c1PermCompleted) == 0 { t.Errorf("Expected client 1 to see permission.completed events") @@ -297,9 +293,7 @@ func TestMultiClient(t *testing.T) { mu1.Lock() c1PermRequested := filterEventsByType(client1Events, copilot.SessionEventTypePermissionRequested) mu1.Unlock() - mu2.Lock() - c2PermRequested := filterEventsByType(client2Events, copilot.SessionEventTypePermissionRequested) - mu2.Unlock() + c2PermRequested := waitForEventsByType(t, &mu2, &client2Events, copilot.SessionEventTypePermissionRequested, 5*time.Second) if len(c1PermRequested) == 0 { t.Errorf("Expected client 1 to see permission.requested events") @@ -312,9 +306,7 @@ func TestMultiClient(t *testing.T) { mu1.Lock() c1PermCompleted := filterEventsByType(client1Events, copilot.SessionEventTypePermissionCompleted) mu1.Unlock() - mu2.Lock() - c2PermCompleted := filterEventsByType(client2Events, copilot.SessionEventTypePermissionCompleted) - mu2.Unlock() + c2PermCompleted := waitForEventsByType(t, &mu2, &client2Events, copilot.SessionEventTypePermissionCompleted, 5*time.Second) if len(c1PermCompleted) == 0 { t.Errorf("Expected client 1 to see permission.completed events") @@ -519,3 +511,20 @@ func filterEventsByType(events []copilot.SessionEvent, eventType copilot.Session } return filtered } + +// waitForEventsByType polls the event slice until at least one event of the given type appears +// or the timeout is reached. This avoids flaky assertions on async event delivery. +func waitForEventsByType(t *testing.T, mu *sync.Mutex, events *[]copilot.SessionEvent, eventType copilot.SessionEventType, timeout time.Duration) []copilot.SessionEvent { + t.Helper() + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + mu.Lock() + filtered := filterEventsByType(*events, eventType) + mu.Unlock() + if len(filtered) > 0 { + return filtered + } + time.Sleep(50 * time.Millisecond) + } + return nil +} diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 23bdfb618..528a933b5 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -12,6 +12,319 @@ import ( "time" ) +type UIElicitationResponseContent map[string]*UIElicitationFieldValue + +// Model capabilities and limits +type ModelCapabilities struct { + // Token limits for prompts, outputs, and context window + Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` + // Feature flags indicating what the model supports + Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` +} + +// Token limits for prompts, outputs, and context window +type ModelCapabilitiesLimits struct { + // Maximum total context window size in tokens + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + // Maximum number of output/completion tokens + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + // Maximum number of prompt/input tokens + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` + // Vision-specific limits + Vision *PurpleModelCapabilitiesLimitsVision `json:"vision,omitempty"` +} + +// Vision-specific limits +type PurpleModelCapabilitiesLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize int64 `json:"max_prompt_image_size"` + // Maximum number of images per prompt + MaxPromptImages int64 `json:"max_prompt_images"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types"` +} + +// Feature flags indicating what the model supports +type ModelCapabilitiesSupports struct { + // Whether this model supports reasoning effort configuration + ReasoningEffort *bool `json:"reasoningEffort,omitempty"` + // Whether this model supports vision/image input + Vision *bool `json:"vision,omitempty"` +} + +// Vision-specific limits +type ModelCapabilitiesLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize int64 `json:"max_prompt_image_size"` + // Maximum number of images per prompt + MaxPromptImages int64 `json:"max_prompt_images"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types"` +} + +// MCP server configuration (local/stdio or remote/http) +type MCPServerConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + // Remote transport type. Defaults to "http" when omitted. + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` +} + +type DiscoveredMCPServer struct { + // Whether the server is enabled (not in the disabled list) + Enabled bool `json:"enabled"` + // Server name (config key) + Name string `json:"name"` + // Configuration source + Source MCPServerSource `json:"source"` + // Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + Type *DiscoveredMCPServerType `json:"type,omitempty"` +} + +type ServerSkillList struct { + // All discovered skills across all sources + Skills []SkillElement `json:"skills"` +} + +type SkillElement struct { + // Description of what the skill does + Description string `json:"description"` + // Whether the skill is currently enabled (based on global config) + Enabled bool `json:"enabled"` + // Unique identifier for the skill + Name string `json:"name"` + // Absolute path to the skill file + Path *string `json:"path,omitempty"` + // The project path this skill belongs to (only for project/inherited skills) + ProjectPath *string `json:"projectPath,omitempty"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source string `json:"source"` + // Whether the skill can be invoked by the user as a slash command + UserInvocable bool `json:"userInvocable"` +} + +type ServerSkill struct { + // Description of what the skill does + Description string `json:"description"` + // Whether the skill is currently enabled (based on global config) + Enabled bool `json:"enabled"` + // Unique identifier for the skill + Name string `json:"name"` + // Absolute path to the skill file + Path *string `json:"path,omitempty"` + // The project path this skill belongs to (only for project/inherited skills) + ProjectPath *string `json:"projectPath,omitempty"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source string `json:"source"` + // Whether the skill can be invoked by the user as a slash command + UserInvocable bool `json:"userInvocable"` +} + +type CurrentModel struct { + // Currently active model identifier + ModelID *string `json:"modelId,omitempty"` +} + +// Override individual model capabilities resolved by the runtime +type ModelCapabilitiesOverride struct { + // Token limits for prompts, outputs, and context window + Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` + // Feature flags indicating what the model supports + Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` +} + +// Token limits for prompts, outputs, and context window +type ModelCapabilitiesOverrideLimits struct { + // Maximum total context window size in tokens + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` + Vision *PurpleModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` +} + +type PurpleModelCapabilitiesOverrideLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` + // Maximum number of images per prompt + MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types,omitempty"` +} + +// Feature flags indicating what the model supports +type ModelCapabilitiesOverrideSupports struct { + ReasoningEffort *bool `json:"reasoningEffort,omitempty"` + Vision *bool `json:"vision,omitempty"` +} + +type AgentInfo struct { + // Description of the agent's purpose + Description string `json:"description"` + // Human-readable display name + DisplayName string `json:"displayName"` + // Unique identifier of the custom agent + Name string `json:"name"` +} + +type MCPServerList struct { + // Configured MCP servers + Servers []MCPServer `json:"servers"` +} + +type MCPServer struct { + // Error message if the server failed to connect + Error *string `json:"error,omitempty"` + // Server name (config key) + Name string `json:"name"` + // Configuration source: user, workspace, plugin, or builtin + Source *MCPServerSource `json:"source,omitempty"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status MCPServerStatus `json:"status"` +} + +type ToolCallResult struct { + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Type of the tool result + ResultType *string `json:"resultType,omitempty"` + // Text result to send back to the LLM + TextResultForLlm string `json:"textResultForLlm"` + // Telemetry data from tool execution + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` +} + +type HandleToolCallResult struct { + // Whether the tool call result was handled successfully + Success bool `json:"success"` +} + +type UIElicitationStringEnumField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Enum []string `json:"enum"` + EnumNames []string `json:"enumNames,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationStringEnumFieldType `json:"type"` +} + +type UIElicitationStringOneOfField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf"` + Title *string `json:"title,omitempty"` + Type UIElicitationStringEnumFieldType `json:"type"` +} + +type UIElicitationStringOneOfFieldOneOf struct { + Const string `json:"const"` + Title string `json:"title"` +} + +type UIElicitationArrayEnumField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayEnumFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldType `json:"type"` +} + +type UIElicitationArrayEnumFieldItems struct { + Enum []string `json:"enum"` + Type UIElicitationStringEnumFieldType `json:"type"` +} + +type UIElicitationArrayAnyOfField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayAnyOfFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldType `json:"type"` +} + +type UIElicitationArrayAnyOfFieldItems struct { + AnyOf []PurpleUIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf"` +} + +type PurpleUIElicitationArrayAnyOfFieldItemsAnyOf struct { + Const string `json:"const"` + Title string `json:"title"` +} + +// The elicitation response (accept with form values, decline, or cancel) +type UIElicitationResponse struct { + // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + Action UIElicitationResponseAction `json:"action"` + // The form values submitted by the user (present when action is 'accept') + Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` +} + +type UIHandlePendingElicitationRequest struct { + // The unique request ID from the elicitation.requested event + RequestID string `json:"requestId"` + // The elicitation response (accept with form values, decline, or cancel) + Result UIElicitationResponse `json:"result"` +} + +type UIElicitationResult struct { + // Whether the response was accepted. False if the request was already resolved by another + // client. + Success bool `json:"success"` +} + +type PermissionDecisionRequest struct { + // Request ID of the pending permission request + RequestID string `json:"requestId"` + Result PermissionDecision `json:"result"` +} + +type PermissionDecision struct { + // The permission request was approved + // + // Denied because approval rules explicitly blocked it + // + // Denied because no approval rule matched and user confirmation was unavailable + // + // Denied by the user during an interactive prompt + // + // Denied by the organization's content exclusion policy + // + // Denied by a permission request hook registered by an extension or plugin + Kind Kind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules,omitempty"` + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Human-readable explanation of why the path was excluded + // + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` + // File path that triggered the exclusion + Path *string `json:"path,omitempty"` + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` +} + +type PermissionRequestResult struct { + // Whether the permission request was handled successfully + Success bool `json:"success"` +} + type PingResult struct { // Echoed message (or default greeting) Message string `json:"message"` @@ -28,14 +341,14 @@ type PingRequest struct { type ModelList struct { // List of available models with full metadata - Models []Model `json:"models"` + Models []ModelElement `json:"models"` } -type Model struct { +type ModelElement struct { // Billing information Billing *ModelBilling `json:"billing,omitempty"` // Model capabilities and limits - Capabilities ModelCapabilities `json:"capabilities"` + Capabilities CapabilitiesClass `json:"capabilities"` // Default reasoning effort level (only present if model supports reasoning effort) DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` // Model identifier (e.g., "claude-sonnet-4.5") @@ -55,15 +368,15 @@ type ModelBilling struct { } // Model capabilities and limits -type ModelCapabilities struct { +type CapabilitiesClass struct { // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` + Limits *CapabilitiesLimits `json:"limits,omitempty"` // Feature flags indicating what the model supports - Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` + Supports *CapabilitiesSupports `json:"supports,omitempty"` } // Token limits for prompts, outputs, and context window -type ModelCapabilitiesLimits struct { +type CapabilitiesLimits struct { // Maximum total context window size in tokens MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` // Maximum number of output/completion tokens @@ -71,11 +384,11 @@ type ModelCapabilitiesLimits struct { // Maximum number of prompt/input tokens MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` // Vision-specific limits - Vision *ModelCapabilitiesLimitsVision `json:"vision,omitempty"` + Vision *FluffyModelCapabilitiesLimitsVision `json:"vision,omitempty"` } // Vision-specific limits -type ModelCapabilitiesLimitsVision struct { +type FluffyModelCapabilitiesLimitsVision struct { // Maximum image size in bytes MaxPromptImageSize int64 `json:"max_prompt_image_size"` // Maximum number of images per prompt @@ -85,7 +398,7 @@ type ModelCapabilitiesLimitsVision struct { } // Feature flags indicating what the model supports -type ModelCapabilitiesSupports struct { +type CapabilitiesSupports struct { // Whether this model supports reasoning effort configuration ReasoningEffort *bool `json:"reasoningEffort,omitempty"` // Whether this model supports vision/image input @@ -147,27 +460,27 @@ type AccountQuotaSnapshot struct { type MCPConfigList struct { // All MCP servers from user config, keyed by name - Servers map[string]MCPConfigServer `json:"servers"` + Servers map[string]MCPServerConfigValue `json:"servers"` } // MCP server configuration (local/stdio or remote/http) -type MCPConfigServer struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` +type MCPServerConfigValue struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` // Timeout in milliseconds for tool calls to this server. Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` // Remote transport type. Defaults to "http" when omitted. - Type *MCPConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` } type MCPConfigAddResult struct { @@ -175,29 +488,29 @@ type MCPConfigAddResult struct { type MCPConfigAddRequest struct { // MCP server configuration (local/stdio or remote/http) - Config MCPConfigAddConfig `json:"config"` + Config MCPConfigAddRequestMCPServerConfig `json:"config"` // Unique name for the MCP server Name string `json:"name"` } // MCP server configuration (local/stdio or remote/http) -type MCPConfigAddConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` +type MCPConfigAddRequestMCPServerConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` // Timeout in milliseconds for tool calls to this server. Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` // Remote transport type. Defaults to "http" when omitted. - Type *MCPConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` } type MCPConfigUpdateResult struct { @@ -205,29 +518,29 @@ type MCPConfigUpdateResult struct { type MCPConfigUpdateRequest struct { // MCP server configuration (local/stdio or remote/http) - Config MCPConfigUpdateConfig `json:"config"` + Config MCPConfigUpdateRequestMCPServerConfig `json:"config"` // Name of the MCP server to update Name string `json:"name"` } // MCP server configuration (local/stdio or remote/http) -type MCPConfigUpdateConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *MCPConfigFilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` +type MCPConfigUpdateRequestMCPServerConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` // Timeout in milliseconds for tool calls to this server. Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` // Remote transport type. Defaults to "http" when omitted. - Type *MCPConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` } type MCPConfigRemoveResult struct { @@ -240,10 +553,10 @@ type MCPConfigRemoveRequest struct { type MCPDiscoverResult struct { // MCP servers discovered from all sources - Servers []DiscoveredMCPServer `json:"servers"` + Servers []ServerElement `json:"servers"` } -type DiscoveredMCPServer struct { +type ServerElement struct { // Whether the server is enabled (not in the disabled list) Enabled bool `json:"enabled"` // Server name (config key) @@ -267,28 +580,6 @@ type SkillsConfigSetDisabledSkillsRequest struct { DisabledSkills []string `json:"disabledSkills"` } -type ServerSkillList struct { - // All discovered skills across all sources - Skills []ServerSkill `json:"skills"` -} - -type ServerSkill struct { - // Description of what the skill does - Description string `json:"description"` - // Whether the skill is currently enabled (based on global config) - Enabled bool `json:"enabled"` - // Unique identifier for the skill - Name string `json:"name"` - // Absolute path to the skill file - Path *string `json:"path,omitempty"` - // The project path this skill belongs to (only for project/inherited skills) - ProjectPath *string `json:"projectPath,omitempty"` - // Source location type (e.g., project, personal-copilot, plugin, builtin) - Source string `json:"source"` - // Whether the skill can be invoked by the user as a slash command - UserInvocable bool `json:"userInvocable"` -} - type SkillsDiscoverRequest struct { // Optional list of project directory paths to scan for project-scoped skills ProjectPaths []string `json:"projectPaths,omitempty"` @@ -325,11 +616,6 @@ type SessionsForkRequest struct { ToEventID *string `json:"toEventId,omitempty"` } -type CurrentModel struct { - // Currently active model identifier - ModelID *string `json:"modelId,omitempty"` -} - type ModelSwitchToResult struct { // Currently active model identifier after the switch ModelID *string `json:"modelId,omitempty"` @@ -337,7 +623,7 @@ type ModelSwitchToResult struct { type ModelSwitchToRequest struct { // Override individual model capabilities resolved by the runtime - ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + ModelCapabilities *ModelCapabilitiesClass `json:"modelCapabilities,omitempty"` // Model identifier to switch to ModelID string `json:"modelId"` // Reasoning effort level to use for the model @@ -345,23 +631,23 @@ type ModelSwitchToRequest struct { } // Override individual model capabilities resolved by the runtime -type ModelCapabilitiesOverride struct { +type ModelCapabilitiesClass struct { // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` + Limits *ModelCapabilitiesLimitsClass `json:"limits,omitempty"` // Feature flags indicating what the model supports Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` } // Token limits for prompts, outputs, and context window -type ModelCapabilitiesOverrideLimits struct { +type ModelCapabilitiesLimitsClass struct { // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - Vision *ModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` + Vision *FluffyModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` } -type ModelCapabilitiesOverrideLimitsVision struct { +type FluffyModelCapabilitiesOverrideLimitsVision struct { // Maximum image size in bytes MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` // Maximum number of images per prompt @@ -370,12 +656,6 @@ type ModelCapabilitiesOverrideLimitsVision struct { SupportedMediaTypes []string `json:"supported_media_types,omitempty"` } -// Feature flags indicating what the model supports -type ModelCapabilitiesOverrideSupports struct { - ReasoningEffort *bool `json:"reasoningEffort,omitempty"` - Vision *bool `json:"vision,omitempty"` -} - type ModeSetResult struct { } @@ -467,6 +747,30 @@ type WorkspacesCreateFileRequest struct { Path string `json:"path"` } +type InstructionsGetSourcesResult struct { + // Instruction sources for the session + Sources []InstructionsSources `json:"sources"` +} + +type InstructionsSources struct { + // Glob pattern from frontmatter — when set, this instruction applies only to matching files + ApplyTo *string `json:"applyTo,omitempty"` + // Raw content of the instruction file + Content string `json:"content"` + // Short description (body after frontmatter) for use in instruction tables + Description *string `json:"description,omitempty"` + // Unique identifier for this source (used for toggling) + ID string `json:"id"` + // Human-readable label + Label string `json:"label"` + // Where this source lives — used for UI grouping + Location InstructionsSourcesLocation `json:"location"` + // File path relative to repo or absolute for home + SourcePath string `json:"sourcePath"` + // Category of instruction source — used for merge logic + Type InstructionsSourcesType `json:"type"` +} + // Experimental: FleetStartResult is part of an experimental API and may change or be removed. type FleetStartResult struct { // Whether fleet mode was successfully activated @@ -482,10 +786,10 @@ type FleetStartRequest struct { // Experimental: AgentList is part of an experimental API and may change or be removed. type AgentList struct { // Available custom agents - Agents []Agent `json:"agents"` + Agents []AgentListAgent `json:"agents"` } -type Agent struct { +type AgentListAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -497,26 +801,17 @@ type Agent struct { // Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. type AgentGetCurrentResult struct { // Currently selected custom agent, or null if using the default agent - Agent *AgentGetCurrentResultAgent `json:"agent"` -} - -type AgentGetCurrentResultAgent struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` + Agent *AgentReloadResultAgent `json:"agent"` } // Experimental: AgentSelectResult is part of an experimental API and may change or be removed. type AgentSelectResult struct { // The newly selected custom agent - Agent AgentSelectAgent `json:"agent"` + Agent AgentSelectResultAgent `json:"agent"` } // The newly selected custom agent -type AgentSelectAgent struct { +type AgentSelectResultAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -538,10 +833,10 @@ type AgentDeselectResult struct { // Experimental: AgentReloadResult is part of an experimental API and may change or be removed. type AgentReloadResult struct { // Reloaded custom agents - Agents []AgentReloadAgent `json:"agents"` + Agents []AgentReloadResultAgent `json:"agents"` } -type AgentReloadAgent struct { +type AgentReloadResultAgent struct { // Description of the agent's purpose Description string `json:"description"` // Human-readable display name @@ -595,22 +890,6 @@ type SkillsDisableRequest struct { type SkillsReloadResult struct { } -type MCPServerList struct { - // Configured MCP servers - Servers []MCPServer `json:"servers"` -} - -type MCPServer struct { - // Error message if the server failed to connect - Error *string `json:"error,omitempty"` - // Server name (config key) - Name string `json:"name"` - // Configuration source: user, workspace, plugin, or builtin - Source *MCPServerSource `json:"source,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status MCPServerStatus `json:"status"` -} - type MCPEnableResult struct { } @@ -690,11 +969,6 @@ type ExtensionsDisableRequest struct { type ExtensionsReloadResult struct { } -type HandleToolCallResult struct { - // Whether the tool call result was handled successfully - Success bool `json:"success"` -} - type ToolsHandlePendingToolCallRequest struct { // Error message if the tool call failed Error *string `json:"error,omitempty"` @@ -704,17 +978,6 @@ type ToolsHandlePendingToolCallRequest struct { Result *ToolsHandlePendingToolCall `json:"result"` } -type ToolCallResult struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Type of the tool result - ResultType *string `json:"resultType,omitempty"` - // Text result to send back to the LLM - TextResultForLlm string `json:"textResultForLlm"` - // Telemetry data from tool execution - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` -} - type CommandsHandlePendingCommandResult struct { // Whether the command was handled successfully Success bool `json:"success"` @@ -727,14 +990,6 @@ type CommandsHandlePendingCommandRequest struct { RequestID string `json:"requestId"` } -// The elicitation response (accept with form values, decline, or cancel) -type UIElicitationResponse struct { - // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - Action UIElicitationResponseAction `json:"action"` - // The form values submitted by the user (present when action is 'accept') - Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` -} - type UIElicitationRequest struct { // Message describing what information is needed from the user Message string `json:"message"` @@ -759,7 +1014,7 @@ type UIElicitationSchemaProperty struct { EnumNames []string `json:"enumNames,omitempty"` Title *string `json:"title,omitempty"` Type UIElicitationSchemaPropertyNumberType `json:"type"` - OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf,omitempty"` + OneOf []UIElicitationSchemaPropertyOneOf `json:"oneOf,omitempty"` Items *UIElicitationArrayFieldItems `json:"items,omitempty"` MaxItems *float64 `json:"maxItems,omitempty"` MinItems *float64 `json:"minItems,omitempty"` @@ -771,72 +1026,21 @@ type UIElicitationSchemaProperty struct { } type UIElicitationArrayFieldItems struct { - Enum []string `json:"enum,omitempty"` - Type *ItemsType `json:"type,omitempty"` - AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` + Enum []string `json:"enum,omitempty"` + Type *UIElicitationStringEnumFieldType `json:"type,omitempty"` + AnyOf []FluffyUIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` } -type UIElicitationArrayAnyOfFieldItemsAnyOf struct { +type FluffyUIElicitationArrayAnyOfFieldItemsAnyOf struct { Const string `json:"const"` Title string `json:"title"` } -type UIElicitationStringOneOfFieldOneOf struct { +type UIElicitationSchemaPropertyOneOf struct { Const string `json:"const"` Title string `json:"title"` } -type UIElicitationResult struct { - // Whether the response was accepted. False if the request was already resolved by another - // client. - Success bool `json:"success"` -} - -type UIHandlePendingElicitationRequest struct { - // The unique request ID from the elicitation.requested event - RequestID string `json:"requestId"` - // The elicitation response (accept with form values, decline, or cancel) - Result UIElicitationResponse `json:"result"` -} - -type PermissionRequestResult struct { - // Whether the permission request was handled successfully - Success bool `json:"success"` -} - -type PermissionDecisionRequest struct { - // Request ID of the pending permission request - RequestID string `json:"requestId"` - Result PermissionDecision `json:"result"` -} - -type PermissionDecision struct { - // The permission request was approved - // - // Denied because approval rules explicitly blocked it - // - // Denied because no approval rule matched and user confirmation was unavailable - // - // Denied by the user during an interactive prompt - // - // Denied by the organization's content exclusion policy - // - // Denied by a permission request hook registered by an extension or plugin - Kind Kind `json:"kind"` - // Rules that denied the request - Rules []any `json:"rules,omitempty"` - // Optional feedback from the user explaining the denial - Feedback *string `json:"feedback,omitempty"` - // Human-readable explanation of why the path was excluded - // - // Optional message from the hook explaining the denial - Message *string `json:"message,omitempty"` - // File path that triggered the exclusion - Path *string `json:"path,omitempty"` - // Whether to interrupt the current agent turn - Interrupt *bool `json:"interrupt,omitempty"` -} - type LogResult struct { // The unique identifier of the emitted session event EventID string `json:"eventId"` @@ -1125,22 +1329,22 @@ type SessionFSRenameRequest struct { Src string `json:"src"` } -type MCPConfigFilterMappingString string +type FilterMappingString string const ( - MCPConfigFilterMappingStringHiddenCharacters MCPConfigFilterMappingString = "hidden_characters" - MCPConfigFilterMappingStringMarkdown MCPConfigFilterMappingString = "markdown" - MCPConfigFilterMappingStringNone MCPConfigFilterMappingString = "none" + FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" + FilterMappingStringMarkdown FilterMappingString = "markdown" + FilterMappingStringNone FilterMappingString = "none" ) // Remote transport type. Defaults to "http" when omitted. -type MCPConfigType string +type MCPServerConfigType string const ( - MCPConfigTypeHTTP MCPConfigType = "http" - MCPConfigTypeLocal MCPConfigType = "local" - MCPConfigTypeSSE MCPConfigType = "sse" - MCPConfigTypeStdio MCPConfigType = "stdio" + MCPServerConfigTypeHTTP MCPServerConfigType = "http" + MCPServerConfigTypeLocal MCPServerConfigType = "local" + MCPServerConfigTypeSSE MCPServerConfigType = "sse" + MCPServerConfigTypeStdio MCPServerConfigType = "stdio" ) // Configuration source @@ -1165,6 +1369,50 @@ const ( DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" ) +// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +type MCPServerStatus string + +const ( + MCPServerStatusConnected MCPServerStatus = "connected" + MCPServerStatusDisabled MCPServerStatus = "disabled" + MCPServerStatusFailed MCPServerStatus = "failed" + MCPServerStatusNeedsAuth MCPServerStatus = "needs-auth" + MCPServerStatusNotConfigured MCPServerStatus = "not_configured" + MCPServerStatusPending MCPServerStatus = "pending" +) + +type UIElicitationStringEnumFieldType string + +const ( + UIElicitationStringEnumFieldTypeString UIElicitationStringEnumFieldType = "string" +) + +type UIElicitationArrayEnumFieldType string + +const ( + UIElicitationArrayEnumFieldTypeArray UIElicitationArrayEnumFieldType = "array" +) + +// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) +type UIElicitationResponseAction string + +const ( + UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" + UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" + UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" +) + +type Kind string + +const ( + KindApproved Kind = "approved" + KindDeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" + KindDeniedByPermissionRequestHook Kind = "denied-by-permission-request-hook" + KindDeniedByRules Kind = "denied-by-rules" + KindDeniedInteractivelyByUser Kind = "denied-interactively-by-user" + KindDeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" +) + // Path conventions used by this filesystem type SessionFSSetProviderConventions string @@ -1197,16 +1445,25 @@ const ( SessionSyncLevelUser SessionSyncLevel = "user" ) -// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type MCPServerStatus string +// Where this source lives — used for UI grouping +type InstructionsSourcesLocation string const ( - MCPServerStatusConnected MCPServerStatus = "connected" - MCPServerStatusDisabled MCPServerStatus = "disabled" - MCPServerStatusFailed MCPServerStatus = "failed" - MCPServerStatusNeedsAuth MCPServerStatus = "needs-auth" - MCPServerStatusNotConfigured MCPServerStatus = "not_configured" - MCPServerStatusPending MCPServerStatus = "pending" + InstructionsSourcesLocationUser InstructionsSourcesLocation = "user" + InstructionsSourcesLocationRepository InstructionsSourcesLocation = "repository" + InstructionsSourcesLocationWorkingDirectory InstructionsSourcesLocation = "working-directory" +) + +// Category of instruction source — used for merge logic +type InstructionsSourcesType string + +const ( + InstructionsSourcesTypeChildInstructions InstructionsSourcesType = "child-instructions" + InstructionsSourcesTypeHome InstructionsSourcesType = "home" + InstructionsSourcesTypeModel InstructionsSourcesType = "model" + InstructionsSourcesTypeNestedAgents InstructionsSourcesType = "nested-agents" + InstructionsSourcesTypeRepo InstructionsSourcesType = "repo" + InstructionsSourcesTypeVscode InstructionsSourcesType = "vscode" ) // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) @@ -1227,15 +1484,6 @@ const ( ExtensionStatusStarting ExtensionStatus = "starting" ) -// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) -type UIElicitationResponseAction string - -const ( - UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" - UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" - UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" -) - type UIElicitationSchemaPropertyStringFormat string const ( @@ -1245,19 +1493,13 @@ const ( UIElicitationSchemaPropertyStringFormatURI UIElicitationSchemaPropertyStringFormat = "uri" ) -type ItemsType string - -const ( - ItemsTypeString ItemsType = "string" -) - type UIElicitationSchemaPropertyNumberType string const ( - UIElicitationSchemaPropertyNumberTypeArray UIElicitationSchemaPropertyNumberType = "array" UIElicitationSchemaPropertyNumberTypeBoolean UIElicitationSchemaPropertyNumberType = "boolean" UIElicitationSchemaPropertyNumberTypeInteger UIElicitationSchemaPropertyNumberType = "integer" UIElicitationSchemaPropertyNumberTypeNumber UIElicitationSchemaPropertyNumberType = "number" + UIElicitationSchemaPropertyNumberTypeArray UIElicitationSchemaPropertyNumberType = "array" UIElicitationSchemaPropertyNumberTypeString UIElicitationSchemaPropertyNumberType = "string" ) @@ -1267,17 +1509,6 @@ const ( RequestedSchemaTypeObject RequestedSchemaType = "object" ) -type Kind string - -const ( - KindApproved Kind = "approved" - KindDeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" - KindDeniedByPermissionRequestHook Kind = "denied-by-permission-request-hook" - KindDeniedByRules Kind = "denied-by-rules" - KindDeniedInteractivelyByUser Kind = "denied-interactively-by-user" - KindDeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" -) - // Log severity level. Determines how the message is displayed in the timeline. Defaults to // "info". type SessionLogLevel string @@ -1305,15 +1536,9 @@ const ( SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" ) -type MCPConfigFilterMapping struct { - Enum *MCPConfigFilterMappingString - EnumMap map[string]MCPConfigFilterMappingString -} - -// Tool call result (string or expanded result object) -type ToolsHandlePendingToolCall struct { - String *string - ToolCallResult *ToolCallResult +type FilterMapping struct { + Enum *FilterMappingString + EnumMap map[string]FilterMappingString } type UIElicitationFieldValue struct { @@ -1323,6 +1548,12 @@ type UIElicitationFieldValue struct { StringArray []string } +// Tool call result (string or expanded result object) +type ToolsHandlePendingToolCall struct { + String *string + ToolCallResult *ToolCallResult +} + type serverApi struct { client *jsonrpc2.Client } @@ -1745,6 +1976,21 @@ func (a *WorkspacesApi) CreateFile(ctx context.Context, params *WorkspacesCreate return &result, nil } +type InstructionsApi sessionApi + +func (a *InstructionsApi) GetSources(ctx context.Context) (*InstructionsGetSourcesResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.instructions.getSources", req) + if err != nil { + return nil, err + } + var result InstructionsGetSourcesResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + // Experimental: FleetApi contains experimental APIs that may change or be removed. type FleetApi sessionApi @@ -2231,24 +2477,25 @@ func (a *UsageApi) GetMetrics(ctx context.Context) (*UsageGetMetricsResult, erro type SessionRpc struct { common sessionApi // Reuse a single struct instead of allocating one for each service on the heap. - Model *ModelApi - Mode *ModeApi - Name *NameApi - Plan *PlanApi - Workspaces *WorkspacesApi - Fleet *FleetApi - Agent *AgentApi - Skills *SkillsApi - Mcp *McpApi - Plugins *PluginsApi - Extensions *ExtensionsApi - Tools *ToolsApi - Commands *CommandsApi - UI *UIApi - Permissions *PermissionsApi - Shell *ShellApi - History *HistoryApi - Usage *UsageApi + Model *ModelApi + Mode *ModeApi + Name *NameApi + Plan *PlanApi + Workspaces *WorkspacesApi + Instructions *InstructionsApi + Fleet *FleetApi + Agent *AgentApi + Skills *SkillsApi + Mcp *McpApi + Plugins *PluginsApi + Extensions *ExtensionsApi + Tools *ToolsApi + Commands *CommandsApi + UI *UIApi + Permissions *PermissionsApi + Shell *ShellApi + History *HistoryApi + Usage *UsageApi } func (a *SessionRpc) Log(ctx context.Context, params *LogRequest) (*LogResult, error) { @@ -2284,6 +2531,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { r.Name = (*NameApi)(&r.common) r.Plan = (*PlanApi)(&r.common) r.Workspaces = (*WorkspacesApi)(&r.common) + r.Instructions = (*InstructionsApi)(&r.common) r.Fleet = (*FleetApi)(&r.common) r.Agent = (*AgentApi)(&r.common) r.Skills = (*SkillsApi)(&r.common) diff --git a/go/session.go b/go/session.go index be8c78e2b..bf42bf03a 100644 --- a/go/session.go +++ b/go/session.go @@ -1213,7 +1213,7 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti params := &rpc.ModelSwitchToRequest{ModelID: model} if opts != nil { params.ReasoningEffort = opts.ReasoningEffort - params.ModelCapabilities = opts.ModelCapabilities + params.ModelCapabilities = convertModelCapabilitiesToClass(opts.ModelCapabilities) } _, err := s.RPC.Model.SwitchTo(ctx, params) if err != nil { @@ -1223,7 +1223,34 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti return nil } -// LogOptions configures optional parameters for [Session.Log]. +// convertModelCapabilitiesToClass converts from ModelCapabilitiesOverride +// (used in the public API) to ModelCapabilitiesClass (used internally by +// the ModelSwitchToRequest RPC). The two types are structurally identical +// but have different Go types due to code generation. +func convertModelCapabilitiesToClass(src *rpc.ModelCapabilitiesOverride) *rpc.ModelCapabilitiesClass { + if src == nil { + return nil + } + dst := &rpc.ModelCapabilitiesClass{ + Supports: src.Supports, + } + if src.Limits != nil { + dst.Limits = &rpc.ModelCapabilitiesLimitsClass{ + MaxContextWindowTokens: src.Limits.MaxContextWindowTokens, + MaxOutputTokens: src.Limits.MaxOutputTokens, + MaxPromptTokens: src.Limits.MaxPromptTokens, + } + if src.Limits.Vision != nil { + dst.Limits.Vision = &rpc.FluffyModelCapabilitiesOverrideLimitsVision{ + MaxPromptImageSize: src.Limits.Vision.MaxPromptImageSize, + MaxPromptImages: src.Limits.Vision.MaxPromptImages, + SupportedMediaTypes: src.Limits.Vision.SupportedMediaTypes, + } + } + } + return dst +} + type LogOptions struct { // Level sets the log severity. Valid values are [rpc.SessionLogLevelInfo] (default), // [rpc.SessionLogLevelWarning], and [rpc.SessionLogLevelError]. diff --git a/go/types.go b/go/types.go index f889d3e2a..15c62cec0 100644 --- a/go/types.go +++ b/go/types.go @@ -848,7 +848,7 @@ type ( ModelCapabilitiesOverride = rpc.ModelCapabilitiesOverride ModelCapabilitiesOverrideSupports = rpc.ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideLimits = rpc.ModelCapabilitiesOverrideLimits - ModelCapabilitiesOverrideLimitsVision = rpc.ModelCapabilitiesOverrideLimitsVision + ModelCapabilitiesOverrideLimitsVision = rpc.PurpleModelCapabilitiesOverrideLimitsVision ) // ModelPolicy contains model policy state diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 002edfbf3..9ccf85c04 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.30", + "@github/copilot": "^1.0.32-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.30.tgz", - "integrity": "sha512-JYZNMM6hteAE6tIMbHobRjpAaXzvqeeglXgGlDCr26rRq3K6h5ul2GN27qzhMBaWyujUQN402KLKdrhDPqcL7A==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32-1.tgz", + "integrity": "sha512-uJgZWkd+gYS6t8NeWgZd+KDlQ41RFvAydOPdJqMDdB8aBwJYKQA75AVQzJyIne/CaMmv2Cy24X+IeRsMXvg+YA==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.30", - "@github/copilot-darwin-x64": "1.0.30", - "@github/copilot-linux-arm64": "1.0.30", - "@github/copilot-linux-x64": "1.0.30", - "@github/copilot-win32-arm64": "1.0.30", - "@github/copilot-win32-x64": "1.0.30" + "@github/copilot-darwin-arm64": "1.0.32-1", + "@github/copilot-darwin-x64": "1.0.32-1", + "@github/copilot-linux-arm64": "1.0.32-1", + "@github/copilot-linux-x64": "1.0.32-1", + "@github/copilot-win32-arm64": "1.0.32-1", + "@github/copilot-win32-x64": "1.0.32-1" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.30.tgz", - "integrity": "sha512-qhLMhAY7nskG6yabbsWSqErxPWcZLX1ixJBdQX3RLqgw5dyNvZRNzG2evUnABo5bqgndztsFXjE3u4XtfX0WkA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32-1.tgz", + "integrity": "sha512-MGz9kKJYqrfZ94DOVsKy8c0sTFn1Gax60hM3TjMt6K+Tt7n8vGhrpBn+KjFYOb+6+r7fp3E7fc6tTtwjgaURVw==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.30.tgz", - "integrity": "sha512-nsjGRt1jLBzCaVd6eb3ok75zqePr8eU8GSTqu1KVf5KUrnvvfIlsvESkEAE8l+lkR14f7SGQLfMJ2EEbcJMGcg==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32-1.tgz", + "integrity": "sha512-HSLJXMVk2yf6Xb6NhNxEYvD57hBGdWs5zQ7EOHrFYO+qA5/iD4JVGgQNg7sS88+qsTR5PtEcxwbtQPid1KZJnQ==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.30.tgz", - "integrity": "sha512-7wOrOKm9MHnglyzzGeZnXSkfRi4sXB2Db7rK/CgUenxS+dwwIuXhT4rgkH/DIOiDbGCxYjigICxln28Jvbs+cA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32-1.tgz", + "integrity": "sha512-XBiX4947+ygPugwsZrrVOwftIWWASoknq1FzehIpj7BqPxjwTpzDXPDJNleHf+6a1cGm8cUutDn/wslHjJEW9A==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.30.tgz", - "integrity": "sha512-OSJtP7mV9vnDzGFjBkI3sgbNOcxsRcq7vXrT4PNrjJw4Mc71aaW55hc5F1j2fElfGWIb+Jubm3AB8nb6AoufnA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32-1.tgz", + "integrity": "sha512-iJkcWKSoaDY5GKtOZtoZV5YhuOqvVSdENashNKjXzkIoFN0mqonIhsbAv3OB2Kr34ZwoQF3CfNoOCNBs2tg8pg==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.30.tgz", - "integrity": "sha512-5nCz/+9VWJdNvW2uRYeMmnRdQq/gpuSlmYMvRv8fIsFF8KH0mdJndJn8xN6GeJtx0fKJrLzgKqJHWdgb5MtLgA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32-1.tgz", + "integrity": "sha512-U/lfmWAqOIxucqotmsOsJtOjfAhNIYAFeqxyaKo+V35YkurXZGTNjB2YxqUlmKm/7fuOgAACHKvrK+tWs+Mlvg==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.30.tgz", - "integrity": "sha512-tJvgCsWLJVQvHLvFyQZ0P5MQ7YGX51/bl9kbXDUFCGATtPpELul3NyHWwEYGjRv+VDPvhFxjbf+V7Bf/VzYZ7w==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32-1.tgz", + "integrity": "sha512-oSNG9nRHsyTdi2miBfti4egT+CHPGu0QTXXUasISsfwhex6SS4qeVFe8mt8/clnTlyJD9N7EDgABDduSYQv87g==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 7576406df..2ccb7632c 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.30", + "@github/copilot": "^1.0.32-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 574f9878b..7281be70f 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.30", + "@github/copilot": "^1.0.32-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c5b84a6d4..a3d50d5ff 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -1019,6 +1019,26 @@ export class CopilotClient { const result = await this.connection.sendRequest("models.list", {}); const response = result as { models: ModelInfo[] }; models = response.models; + + // Normalize model capabilities — some models (e.g. embedding models) + // may omit 'supports' or 'limits' in their capabilities. + for (const model of models) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const m = model as any; + if (!m.capabilities) { + m.capabilities = { + supports: {}, + limits: { max_context_window_tokens: 0 }, + }; + } else { + if (!m.capabilities.supports) m.capabilities.supports = {}; + if (!m.capabilities.limits) { + m.capabilities.limits = { max_context_window_tokens: 0 }; + } else if (m.capabilities.limits.max_context_window_tokens === undefined) { + m.capabilities.limits.max_context_window_tokens = 0; + } + } + } } // Update cache before releasing lock (copy to prevent external mutation) diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index ff60d2534..dedfa8068 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,6 +5,60 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; +/** + * MCP server configuration (local/stdio or remote/http) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfig". + */ +export type McpServerConfig = + | { + /** + * Tools to include. Defaults to all tools if not specified. + */ + tools?: string[]; + type?: "local" | "stdio"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + command: string; + args: string[]; + cwd?: string; + env?: { + [k: string]: string; + }; + } + | { + /** + * Tools to include. Defaults to all tools if not specified. + */ + tools?: string[]; + /** + * Remote transport type. Defaults to "http" when omitted. + */ + type?: "http" | "sse"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + url: string; + headers?: { + [k: string]: string; + }; + oauthClientId?: string; + oauthPublicClient?: boolean; + }; + +export type FilterMapping = + | { + [k: string]: "none" | "markdown" | "hidden_characters"; + } + | ("none" | "markdown" | "hidden_characters"); /** * The agent mode. Valid values: "interactive", "plan", "autopilot". * @@ -12,11 +66,16 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; * via the `definition` "SessionMode". */ export type SessionMode = "interactive" | "plan" | "autopilot"; + +export type UIElicitationFieldValue = string | number | boolean | string[]; /** * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponseAction". */ export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; -export type UIElicitationFieldValue = string | number | boolean | string[]; + export type PermissionDecision = | { /** @@ -66,22 +125,523 @@ export type PermissionDecision = } | { /** - * Denied by a permission request hook registered by an extension or plugin + * Denied by a permission request hook registered by an extension or plugin + */ + kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ + message?: string; + /** + * Whether to interrupt the current agent turn + */ + interrupt?: boolean; + }; +/** + * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionLogLevel". + */ +export type SessionLogLevel = "info" | "warning" | "error"; +/** + * MCP server configuration (local/stdio or remote/http) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_McpServerConfig". + */ +export type $Defs_McpServerConfig = + | { + /** + * Tools to include. Defaults to all tools if not specified. + */ + tools?: string[]; + type?: "local" | "stdio"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + command: string; + args: string[]; + cwd?: string; + env?: { + [k: string]: string; + }; + } + | { + /** + * Tools to include. Defaults to all tools if not specified. + */ + tools?: string[]; + /** + * Remote transport type. Defaults to "http" when omitted. + */ + type?: "http" | "sse"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + url: string; + headers?: { + [k: string]: string; + }; + oauthClientId?: string; + oauthPublicClient?: boolean; + }; + +export type $Defs_FilterMapping = + | { + [k: string]: "none" | "markdown" | "hidden_characters"; + } + | ("none" | "markdown" | "hidden_characters"); +/** + * The agent mode. Valid values: "interactive", "plan", "autopilot". + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_SessionMode". + */ +export type $Defs_SessionMode = "interactive" | "plan" | "autopilot"; +/** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_UIElicitationResponseAction". + */ +export type $Defs_UIElicitationResponseAction = "accept" | "decline" | "cancel"; + +export type $Defs_UIElicitationFieldValue = string | number | boolean | string[]; + +export type $Defs_PermissionDecision = + | { + /** + * The permission request was approved + */ + kind: "approved"; + } + | { + /** + * Denied because approval rules explicitly blocked it + */ + kind: "denied-by-rules"; + /** + * Rules that denied the request + */ + rules: unknown[]; + } + | { + /** + * Denied because no approval rule matched and user confirmation was unavailable + */ + kind: "denied-no-approval-rule-and-could-not-request-from-user"; + } + | { + /** + * Denied by the user during an interactive prompt + */ + kind: "denied-interactively-by-user"; + /** + * Optional feedback from the user explaining the denial + */ + feedback?: string; + } + | { + /** + * Denied by the organization's content exclusion policy + */ + kind: "denied-by-content-exclusion-policy"; + /** + * File path that triggered the exclusion + */ + path: string; + /** + * Human-readable explanation of why the path was excluded + */ + message: string; + } + | { + /** + * Denied by a permission request hook registered by an extension or plugin + */ + kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ + message?: string; + /** + * Whether to interrupt the current agent turn + */ + interrupt?: boolean; + }; +/** + * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_SessionLogLevel". + */ +export type $Defs_SessionLogLevel = "info" | "warning" | "error"; + +/** + * Model capabilities and limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilities". + */ +export interface ModelCapabilities { + /** + * Feature flags indicating what the model supports + */ + supports?: { + /** + * Whether this model supports vision/image input + */ + vision?: boolean; + /** + * Whether this model supports reasoning effort configuration + */ + reasoningEffort?: boolean; + }; + /** + * Token limits for prompts, outputs, and context window + */ + limits?: { + /** + * Maximum number of prompt/input tokens + */ + max_prompt_tokens?: number; + /** + * Maximum number of output/completion tokens + */ + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesLimitsVision; + }; +} +/** + * Vision-specific limits + */ +export interface ModelCapabilitiesLimitsVision { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; +} +/** + * Vision-specific limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesLimitsVision". + */ +export interface ModelCapabilitiesLimitsVision1 { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; +} + +export interface DiscoveredMcpServer { + /** + * Server name (config key) + */ + name: string; + /** + * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + */ + type?: "stdio" | "http" | "sse" | "memory"; + /** + * Configuration source + */ + source: "user" | "workspace" | "plugin" | "builtin"; + /** + * Whether the server is enabled (not in the disabled list) + */ + enabled: boolean; +} + +export interface ServerSkillList { + /** + * All discovered skills across all sources + */ + skills: ServerSkill[]; +} + +export interface ServerSkill { + /** + * Unique identifier for the skill + */ + name: string; + /** + * Description of what the skill does + */ + description: string; + /** + * Source location type (e.g., project, personal-copilot, plugin, builtin) + */ + source: string; + /** + * Whether the skill can be invoked by the user as a slash command + */ + userInvocable: boolean; + /** + * Whether the skill is currently enabled (based on global config) + */ + enabled: boolean; + /** + * Absolute path to the skill file + */ + path?: string; + /** + * The project path this skill belongs to (only for project/inherited skills) + */ + projectPath?: string; +} + +export interface CurrentModel { + /** + * Currently active model identifier + */ + modelId?: string; +} +/** + * Override individual model capabilities resolved by the runtime + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverride". + */ +export interface ModelCapabilitiesOverride { + /** + * Feature flags indicating what the model supports + */ + supports?: { + vision?: boolean; + reasoningEffort?: boolean; + }; + /** + * Token limits for prompts, outputs, and context window + */ + limits?: { + max_prompt_tokens?: number; + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: { + /** + * MIME types the model accepts */ - kind: "denied-by-permission-request-hook"; + supported_media_types?: string[]; /** - * Optional message from the hook explaining the denial + * Maximum number of images per prompt */ - message?: string; + max_prompt_images?: number; /** - * Whether to interrupt the current agent turn + * Maximum image size in bytes */ - interrupt?: boolean; + max_prompt_image_size?: number; }; + }; +} + +export interface AgentInfo { + /** + * Unique identifier of the custom agent + */ + name: string; + /** + * Human-readable display name + */ + displayName: string; + /** + * Description of the agent's purpose + */ + description: string; +} + +/** @experimental */ +export interface McpServerList { + /** + * Configured MCP servers + */ + servers: { + /** + * Server name (config key) + */ + name: string; + /** + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ + status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; + /** + * Configuration source: user, workspace, plugin, or builtin + */ + source?: "user" | "workspace" | "plugin" | "builtin"; + /** + * Error message if the server failed to connect + */ + error?: string; + }[]; +} + +export interface ToolCallResult { + /** + * Text result to send back to the LLM + */ + textResultForLlm: string; + /** + * Type of the tool result + */ + resultType?: string; + /** + * Error message if the tool call failed + */ + error?: string; + /** + * Telemetry data from tool execution + */ + toolTelemetry?: { + [k: string]: unknown; + }; +} + +export interface HandleToolCallResult { + /** + * Whether the tool call result was handled successfully + */ + success: boolean; +} + +export interface UIElicitationStringEnumField { + type: "string"; + description?: string; + enum: string[]; + enumNames?: string[]; + default?: string; +} + +export interface UIElicitationStringOneOfField { + type: "string"; + description?: string; + oneOf: { + const: string; + }[]; + default?: string; +} + +export interface UIElicitationArrayEnumField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + type: "string"; + enum: string[]; + }; + default?: string[]; +} + +export interface UIElicitationArrayAnyOfField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + anyOf: { + const: string; + }[]; + }; + default?: string[]; +} /** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + * The elicitation response (accept with form values, decline, or cancel) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponse". */ -export type SessionLogLevel = "info" | "warning" | "error"; +export interface UIElicitationResponse { + /** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + */ + action: "accept" | "decline" | "cancel"; + content?: UIElicitationResponseContent; +} +/** + * The form values submitted by the user (present when action is 'accept') + */ +export interface UIElicitationResponseContent { + [k: string]: UIElicitationFieldValue; +} +/** + * The form values submitted by the user (present when action is 'accept') + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponseContent". + */ +export interface UIElicitationResponseContent1 { + [k: string]: UIElicitationFieldValue; +} + +export interface UIHandlePendingElicitationRequest { + /** + * The unique request ID from the elicitation.requested event + */ + requestId: string; + result: UIElicitationResponse1; +} +/** + * The elicitation response (accept with form values, decline, or cancel) + */ +export interface UIElicitationResponse1 { + /** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + */ + action: "accept" | "decline" | "cancel"; + content?: UIElicitationResponseContent; +} + +export interface UIElicitationResult { + /** + * Whether the response was accepted. False if the request was already resolved by another client. + */ + success: boolean; +} + +export interface PermissionDecisionRequest { + /** + * Request ID of the pending permission request + */ + requestId: string; + result: PermissionDecision; +} + +export interface PermissionRequestResult { + /** + * Whether the permission request was handled successfully + */ + success: boolean; +} export interface PingResult { /** @@ -118,7 +678,7 @@ export interface ModelList { * Display name */ name: string; - capabilities: ModelCapabilities; + capabilities: ModelCapabilities1; /** * Policy state (if applicable) */ @@ -154,7 +714,7 @@ export interface ModelList { /** * Model capabilities and limits */ -export interface ModelCapabilities { +export interface ModelCapabilities1 { /** * Feature flags indicating what the model supports */ @@ -184,23 +744,7 @@ export interface ModelCapabilities { * Maximum total context window size in tokens */ max_context_window_tokens?: number; - /** - * Vision-specific limits - */ - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; - }; + vision?: ModelCapabilitiesLimitsVision; }; } @@ -291,11 +835,7 @@ export interface McpConfigList { tools?: string[]; type?: "local" | "stdio"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -317,11 +857,7 @@ export interface McpConfigList { */ type?: "http" | "sse"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -352,11 +888,7 @@ export interface McpConfigAddRequest { tools?: string[]; type?: "local" | "stdio"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -378,11 +910,7 @@ export interface McpConfigAddRequest { */ type?: "http" | "sse"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -412,11 +940,7 @@ export interface McpConfigUpdateRequest { tools?: string[]; type?: "local" | "stdio"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -438,11 +962,7 @@ export interface McpConfigUpdateRequest { */ type?: "http" | "sse"; isDefaultServer?: boolean; - filterMapping?: - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); + filterMapping?: FilterMapping; /** * Timeout in milliseconds for tool calls to this server. */ @@ -465,78 +985,23 @@ export interface McpConfigRemoveRequest { export interface McpDiscoverResult { /** - * MCP servers discovered from all sources - */ - servers: DiscoveredMcpServer[]; -} -export interface DiscoveredMcpServer { - /** - * Server name (config key) - */ - name: string; - /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) - */ - type?: "stdio" | "http" | "sse" | "memory"; - /** - * Configuration source - */ - source: "user" | "workspace" | "plugin" | "builtin"; - /** - * Whether the server is enabled (not in the disabled list) - */ - enabled: boolean; -} - -export interface McpDiscoverRequest { - /** - * Working directory used as context for discovery (e.g., plugin resolution) - */ - workingDirectory?: string; -} - -export interface SkillsConfigSetDisabledSkillsRequest { - /** - * List of skill names to disable - */ - disabledSkills: string[]; -} - -export interface ServerSkillList { - /** - * All discovered skills across all sources - */ - skills: ServerSkill[]; -} -export interface ServerSkill { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled (based on global config) + * MCP servers discovered from all sources */ - enabled: boolean; + servers: DiscoveredMcpServer[]; +} + +export interface McpDiscoverRequest { /** - * Absolute path to the skill file + * Working directory used as context for discovery (e.g., plugin resolution) */ - path?: string; + workingDirectory?: string; +} + +export interface SkillsConfigSetDisabledSkillsRequest { /** - * The project path this skill belongs to (only for project/inherited skills) + * List of skill names to disable */ - projectPath?: string; + disabledSkills: string[]; } export interface SkillsDiscoverRequest { @@ -592,13 +1057,6 @@ export interface SessionsForkRequest { toEventId?: string; } -export interface CurrentModel { - /** - * Currently active model identifier - */ - modelId?: string; -} - export interface ModelSwitchToResult { /** * Currently active model identifier after the switch @@ -615,12 +1073,12 @@ export interface ModelSwitchToRequest { * Reasoning effort level to use for the model */ reasoningEffort?: string; - modelCapabilities?: ModelCapabilitiesOverride; + modelCapabilities?: ModelCapabilitiesOverride1; } /** * Override individual model capabilities resolved by the runtime */ -export interface ModelCapabilitiesOverride { +export interface ModelCapabilitiesOverride1 { /** * Feature flags indicating what the model supports */ @@ -656,7 +1114,10 @@ export interface ModelCapabilitiesOverride { } export interface ModeSetRequest { - mode: SessionMode; + /** + * The agent mode. Valid values: "interactive", "plan", "autopilot". + */ + mode: "interactive" | "plan" | "autopilot"; } export interface NameGetResult { @@ -752,6 +1213,46 @@ export interface WorkspacesCreateFileRequest { content: string; } +export interface InstructionsGetSourcesResult { + /** + * Instruction sources for the session + */ + sources: { + /** + * Unique identifier for this source (used for toggling) + */ + id: string; + /** + * Human-readable label + */ + label: string; + /** + * File path relative to repo or absolute for home + */ + sourcePath: string; + /** + * Raw content of the instruction file + */ + content: string; + /** + * Category of instruction source — used for merge logic + */ + type: "home" | "repo" | "model" | "vscode" | "nested-agents" | "child-instructions"; + /** + * Where this source lives — used for UI grouping + */ + location: "user" | "repository" | "working-directory"; + /** + * Glob pattern from frontmatter — when set, this instruction applies only to matching files + */ + applyTo?: string; + /** + * Short description (body after frontmatter) for use in instruction tables + */ + description?: string; + }[]; +} + /** @experimental */ export interface FleetStartResult { /** @@ -773,20 +1274,7 @@ export interface AgentList { /** * Available custom agents */ - agents: { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; - }[]; + agents: AgentInfo[]; } /** @experimental */ @@ -794,41 +1282,29 @@ export interface AgentGetCurrentResult { /** * Currently selected custom agent, or null if using the default agent */ - agent: { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; - } | null; + agent?: AgentInfo | null; } /** @experimental */ export interface AgentSelectResult { + agent: AgentInfo1; +} +/** + * The newly selected custom agent + */ +export interface AgentInfo1 { /** - * The newly selected custom agent + * Unique identifier of the custom agent */ - agent: { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; - }; + name: string; + /** + * Human-readable display name + */ + displayName: string; + /** + * Description of the agent's purpose + */ + description: string; } /** @experimental */ @@ -844,20 +1320,7 @@ export interface AgentReloadResult { /** * Reloaded custom agents */ - agents: { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; - }[]; + agents: AgentInfo[]; } /** @experimental */ @@ -909,31 +1372,6 @@ export interface SkillsDisableRequest { name: string; } -/** @experimental */ -export interface McpServerList { - /** - * Configured MCP servers - */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: "user" | "workspace" | "plugin" | "builtin"; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; -} - /** @experimental */ export interface McpEnableRequest { /** @@ -1020,13 +1458,6 @@ export interface ExtensionsDisableRequest { id: string; } -export interface HandleToolCallResult { - /** - * Whether the tool call result was handled successfully - */ - success: boolean; -} - export interface ToolsHandlePendingToolCallRequest { /** * Request ID of the pending tool call @@ -1041,26 +1472,6 @@ export interface ToolsHandlePendingToolCallRequest { */ error?: string; } -export interface ToolCallResult { - /** - * Text result to send back to the LLM - */ - textResultForLlm: string; - /** - * Type of the tool result - */ - resultType?: string; - /** - * Error message if the tool call failed - */ - error?: string; - /** - * Telemetry data from tool execution - */ - toolTelemetry?: { - [k: string]: unknown; - }; -} export interface CommandsHandlePendingCommandResult { /** @@ -1079,22 +1490,6 @@ export interface CommandsHandlePendingCommandRequest { */ error?: string; } -/** - * The elicitation response (accept with form values, decline, or cancel) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponse". - */ -export interface UIElicitationResponse { - action: UIElicitationResponseAction; - content?: UIElicitationResponseContent; -} -/** - * The form values submitted by the user (present when action is 'accept') - */ -export interface UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; -} export interface UIElicitationRequest { /** @@ -1116,102 +1511,34 @@ export interface UIElicitationRequest { [k: string]: | UIElicitationStringEnumField | UIElicitationStringOneOfField - | UIElicitationArrayEnumField - | UIElicitationArrayAnyOfField - | { - type: "boolean"; - description?: string; - default?: boolean; - } - | { - type: "string"; - description?: string; - minLength?: number; - maxLength?: number; - format?: "email" | "uri" | "date" | "date-time"; - default?: string; - } - | { - type: "number" | "integer"; - description?: string; - minimum?: number; - maximum?: number; - default?: number; - }; - }; - /** - * List of required field names - */ - required?: string[]; - }; -} -export interface UIElicitationStringEnumField { - type: "string"; - description?: string; - enum: string[]; - enumNames?: string[]; - default?: string; -} -export interface UIElicitationStringOneOfField { - type: "string"; - description?: string; - oneOf: { - const: string; - }[]; - default?: string; -} -export interface UIElicitationArrayEnumField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; -} -export interface UIElicitationArrayAnyOfField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - }[]; - }; - default?: string[]; -} - -export interface UIElicitationResult { - /** - * Whether the response was accepted. False if the request was already resolved by another client. - */ - success: boolean; -} - -export interface UIHandlePendingElicitationRequest { - /** - * The unique request ID from the elicitation.requested event - */ - requestId: string; - result: UIElicitationResponse; -} - -export interface PermissionRequestResult { - /** - * Whether the permission request was handled successfully - */ - success: boolean; -} - -export interface PermissionDecisionRequest { - /** - * Request ID of the pending permission request - */ - requestId: string; - result: PermissionDecision; + | UIElicitationArrayEnumField + | UIElicitationArrayAnyOfField + | { + type: "boolean"; + description?: string; + default?: boolean; + } + | { + type: "string"; + description?: string; + minLength?: number; + maxLength?: number; + format?: "email" | "uri" | "date" | "date-time"; + default?: string; + } + | { + type: "number" | "integer"; + description?: string; + minimum?: number; + maximum?: number; + default?: number; + }; + }; + /** + * List of required field names + */ + required?: string[]; + }; } export interface LogResult { @@ -1226,7 +1553,10 @@ export interface LogRequest { * Human-readable message */ message: string; - level?: SessionLogLevel; + /** + * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + */ + level?: "info" | "warning" | "error"; /** * When true, the message is transient and not persisted to the session event log on disk */ @@ -1466,176 +1796,533 @@ export interface SessionFsWriteFileRequest { /** * Optional POSIX-style mode for newly created files */ - mode?: number; + mode?: number; +} + +export interface SessionFsAppendFileRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; + /** + * Content to append + */ + content: string; + /** + * Optional POSIX-style mode for newly created files + */ + mode?: number; +} + +export interface SessionFsExistsResult { + /** + * Whether the path exists + */ + exists: boolean; +} + +export interface SessionFsExistsRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; +} + +export interface SessionFsStatResult { + /** + * Whether the path is a file + */ + isFile: boolean; + /** + * Whether the path is a directory + */ + isDirectory: boolean; + /** + * File size in bytes + */ + size: number; + /** + * ISO 8601 timestamp of last modification + */ + mtime: string; + /** + * ISO 8601 timestamp of creation + */ + birthtime: string; +} + +export interface SessionFsStatRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; +} + +export interface SessionFsMkdirRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; + /** + * Create parent directories as needed + */ + recursive?: boolean; + /** + * Optional POSIX-style mode for newly created directories + */ + mode?: number; +} + +export interface SessionFsReaddirResult { + /** + * Entry names in the directory + */ + entries: string[]; +} + +export interface SessionFsReaddirRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; +} + +export interface SessionFsReaddirWithTypesResult { + /** + * Directory entries with type information + */ + entries: { + /** + * Entry name + */ + name: string; + /** + * Entry type + */ + type: "file" | "directory"; + }[]; +} + +export interface SessionFsReaddirWithTypesRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; +} + +export interface SessionFsRmRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; + /** + * Remove directories and their contents recursively + */ + recursive?: boolean; + /** + * Ignore errors if the path does not exist + */ + force?: boolean; +} + +export interface SessionFsRenameRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Source path using SessionFs conventions + */ + src: string; + /** + * Destination path using SessionFs conventions + */ + dest: string; +} +/** + * Model capabilities and limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_ModelCapabilities". + */ +export interface $Defs_ModelCapabilities { + /** + * Feature flags indicating what the model supports + */ + supports?: { + /** + * Whether this model supports vision/image input + */ + vision?: boolean; + /** + * Whether this model supports reasoning effort configuration + */ + reasoningEffort?: boolean; + }; + /** + * Token limits for prompts, outputs, and context window + */ + limits?: { + /** + * Maximum number of prompt/input tokens + */ + max_prompt_tokens?: number; + /** + * Maximum number of output/completion tokens + */ + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesLimitsVision2; + }; +} +/** + * Vision-specific limits + */ +export interface ModelCapabilitiesLimitsVision2 { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; +} +/** + * Vision-specific limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_ModelCapabilitiesLimitsVision". + */ +export interface $Defs_ModelCapabilitiesLimitsVision { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; } -export interface SessionFsAppendFileRequest { +export interface $Defs_DiscoveredMcpServer { /** - * Target session identifier + * Server name (config key) */ - sessionId: string; + name: string; /** - * Path using SessionFs conventions + * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) */ - path: string; + type?: "stdio" | "http" | "sse" | "memory"; /** - * Content to append + * Configuration source */ - content: string; + source: "user" | "workspace" | "plugin" | "builtin"; /** - * Optional POSIX-style mode for newly created files + * Whether the server is enabled (not in the disabled list) */ - mode?: number; + enabled: boolean; } -export interface SessionFsExistsResult { +export interface $Defs_ServerSkillList { /** - * Whether the path exists + * All discovered skills across all sources */ - exists: boolean; + skills: ServerSkill[]; } -export interface SessionFsExistsRequest { +export interface $Defs_ServerSkill { /** - * Target session identifier + * Unique identifier for the skill */ - sessionId: string; + name: string; /** - * Path using SessionFs conventions + * Description of what the skill does */ - path: string; -} - -export interface SessionFsStatResult { + description: string; /** - * Whether the path is a file + * Source location type (e.g., project, personal-copilot, plugin, builtin) */ - isFile: boolean; + source: string; /** - * Whether the path is a directory + * Whether the skill can be invoked by the user as a slash command */ - isDirectory: boolean; + userInvocable: boolean; /** - * File size in bytes + * Whether the skill is currently enabled (based on global config) */ - size: number; + enabled: boolean; /** - * ISO 8601 timestamp of last modification + * Absolute path to the skill file */ - mtime: string; + path?: string; /** - * ISO 8601 timestamp of creation + * The project path this skill belongs to (only for project/inherited skills) */ - birthtime: string; + projectPath?: string; } -export interface SessionFsStatRequest { +export interface $Defs_CurrentModel { /** - * Target session identifier - */ - sessionId: string; - /** - * Path using SessionFs conventions + * Currently active model identifier */ - path: string; + modelId?: string; } - -export interface SessionFsMkdirRequest { - /** - * Target session identifier - */ - sessionId: string; - /** - * Path using SessionFs conventions - */ - path: string; +/** + * Override individual model capabilities resolved by the runtime + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_ModelCapabilitiesOverride". + */ +export interface $Defs_ModelCapabilitiesOverride { /** - * Create parent directories as needed + * Feature flags indicating what the model supports */ - recursive?: boolean; + supports?: { + vision?: boolean; + reasoningEffort?: boolean; + }; /** - * Optional POSIX-style mode for newly created directories + * Token limits for prompts, outputs, and context window */ - mode?: number; + limits?: { + max_prompt_tokens?: number; + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: { + /** + * MIME types the model accepts + */ + supported_media_types?: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images?: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size?: number; + }; + }; } -export interface SessionFsReaddirResult { +export interface $Defs_AgentInfo { /** - * Entry names in the directory + * Unique identifier of the custom agent */ - entries: string[]; -} - -export interface SessionFsReaddirRequest { + name: string; /** - * Target session identifier + * Human-readable display name */ - sessionId: string; + displayName: string; /** - * Path using SessionFs conventions + * Description of the agent's purpose */ - path: string; + description: string; } -export interface SessionFsReaddirWithTypesResult { +export interface $Defs_McpServerList { /** - * Directory entries with type information + * Configured MCP servers */ - entries: { + servers: { /** - * Entry name + * Server name (config key) */ name: string; /** - * Entry type + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured */ - type: "file" | "directory"; + status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; + /** + * Configuration source: user, workspace, plugin, or builtin + */ + source?: "user" | "workspace" | "plugin" | "builtin"; + /** + * Error message if the server failed to connect + */ + error?: string; }[]; } -export interface SessionFsReaddirWithTypesRequest { +export interface $Defs_ToolCallResult { /** - * Target session identifier + * Text result to send back to the LLM */ - sessionId: string; + textResultForLlm: string; /** - * Path using SessionFs conventions + * Type of the tool result */ - path: string; + resultType?: string; + /** + * Error message if the tool call failed + */ + error?: string; + /** + * Telemetry data from tool execution + */ + toolTelemetry?: { + [k: string]: unknown; + }; } -export interface SessionFsRmRequest { +export interface $Defs_HandleToolCallResult { /** - * Target session identifier + * Whether the tool call result was handled successfully */ - sessionId: string; + success: boolean; +} + +export interface $Defs_UIElicitationStringEnumField { + type: "string"; + description?: string; + enum: string[]; + enumNames?: string[]; + default?: string; +} + +export interface $Defs_UIElicitationStringOneOfField { + type: "string"; + description?: string; + oneOf: { + const: string; + }[]; + default?: string; +} + +export interface $Defs_UIElicitationArrayEnumField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + type: "string"; + enum: string[]; + }; + default?: string[]; +} + +export interface $Defs_UIElicitationArrayAnyOfField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: { + anyOf: { + const: string; + }[]; + }; + default?: string[]; +} +/** + * The elicitation response (accept with form values, decline, or cancel) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_UIElicitationResponse". + */ +export interface $Defs_UIElicitationResponse { /** - * Path using SessionFs conventions + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) */ - path: string; + action: "accept" | "decline" | "cancel"; + content?: UIElicitationResponseContent2; +} +/** + * The form values submitted by the user (present when action is 'accept') + */ +export interface UIElicitationResponseContent2 { + [k: string]: UIElicitationFieldValue; +} +/** + * The form values submitted by the user (present when action is 'accept') + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_UIElicitationResponseContent". + */ +export interface $Defs_UIElicitationResponseContent { + [k: string]: UIElicitationFieldValue; +} + +export interface $Defs_UIHandlePendingElicitationRequest { /** - * Remove directories and their contents recursively + * The unique request ID from the elicitation.requested event */ - recursive?: boolean; + requestId: string; + result: UIElicitationResponse2; +} +/** + * The elicitation response (accept with form values, decline, or cancel) + */ +export interface UIElicitationResponse2 { /** - * Ignore errors if the path does not exist + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) */ - force?: boolean; + action: "accept" | "decline" | "cancel"; + content?: UIElicitationResponseContent; } -export interface SessionFsRenameRequest { +export interface $Defs_UIElicitationResult { /** - * Target session identifier + * Whether the response was accepted. False if the request was already resolved by another client. */ - sessionId: string; + success: boolean; +} + +export interface $Defs_PermissionDecisionRequest { /** - * Source path using SessionFs conventions + * Request ID of the pending permission request */ - src: string; + requestId: string; + result: PermissionDecision; +} + +export interface $Defs_PermissionRequestResult { /** - * Destination path using SessionFs conventions + * Whether the permission request was handled successfully */ - dest: string; + success: boolean; } /** Create typed server-scoped RPC methods (no session required). */ @@ -1728,6 +2415,10 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin createFile: async (params: Omit): Promise => connection.sendRequest("session.workspaces.createFile", { sessionId, ...params }), }, + instructions: { + getSources: async (): Promise => + connection.sendRequest("session.instructions.getSources", { sessionId }), + }, /** @experimental */ fleet: { start: async (params: Omit): Promise => diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 65deaf2b3..d2de8d250 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -21,6 +21,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.start"; /** * Session initialization metadata including context and configuration @@ -54,39 +58,7 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - /** - * Working directory and git context at session start - */ - context?: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + context?: WorkingDirectoryContext; /** * Whether the session was already in use by another client at start time */ @@ -114,6 +86,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.resume"; /** * Session resume metadata including current context and event count @@ -135,39 +111,7 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - /** - * Updated working directory and git context at resume time - */ - context?: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + context?: WorkingDirectoryContext1; /** * Whether the session was already in use by another client at resume time */ @@ -195,6 +139,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.remote_steerable_changed"; /** * Notifies Mission Control that the session's remote steering capability has changed @@ -223,6 +171,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.error"; /** * Error details for timeline display including message and optional diagnostic information @@ -268,6 +220,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.idle"; /** * Payload indicating the session is idle with no background agents in flight @@ -293,6 +249,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.title_changed"; /** * Session title change payload containing the new display title @@ -316,6 +276,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.info"; /** * Informational message for timeline display with categorization @@ -352,6 +316,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.warning"; /** * Warning message for timeline display with categorization @@ -388,6 +356,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.model_change"; /** * Model change details including previous and new model identifiers @@ -428,6 +400,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mode_changed"; /** * Agent mode change details including previous and new modes @@ -460,6 +436,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.plan_changed"; /** * Plan file operation details indicating what changed @@ -488,6 +468,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.workspace_file_changed"; /** * Workspace file change details including path and operation type @@ -520,6 +504,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.handoff"; /** * Session handoff metadata including source, context, and repository information @@ -585,6 +573,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.truncation"; /** * Conversation truncation statistics including token counts and removed content metrics @@ -638,6 +630,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.snapshot_rewind"; /** * Session rewind details including target event and count of removed events @@ -670,6 +666,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.shutdown"; /** * Session termination metrics including usage statistics, code changes, and shutdown reason @@ -796,40 +796,12 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - type: "session.context_changed"; /** - * Updated working directory and git context after the change + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - data: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + agentId?: string; + type: "session.context_changed"; + data: WorkingDirectoryContext2; } | { /** @@ -845,6 +817,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.usage_info"; /** * Current context window usage statistics including token and message counts @@ -897,6 +873,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.compaction_start"; /** * Context window breakdown at the start of LLM-powered conversation compaction @@ -933,6 +913,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.compaction_complete"; /** * Conversation compaction results including success status, metrics, and optional error details @@ -1030,6 +1014,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.task_complete"; /** * Task completion notification with summary from the agent @@ -1062,6 +1050,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user.message"; data: { /** @@ -1207,6 +1199,14 @@ export type SessionEvent = displayName?: string; } )[]; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit + */ + nativeDocumentPathFallbackPaths?: string[]; /** * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) */ @@ -1235,6 +1235,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "pending_messages.modified"; /** * Empty payload; the event signals that the pending message queue has changed @@ -1258,6 +1262,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.turn_start"; /** * Turn initialization metadata including identifier and interaction tracking @@ -1287,6 +1295,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.intent"; /** * Agent intent description for current activity or plan @@ -1315,6 +1327,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.reasoning"; /** * Assistant reasoning content for timeline display with complete thinking text @@ -1344,6 +1360,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.reasoning_delta"; /** * Streaming reasoning delta for incremental extended thinking updates @@ -1373,6 +1393,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.streaming_delta"; /** * Streaming response progress with cumulative byte count @@ -1401,6 +1425,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.message"; /** * Assistant response containing text content, optional tool requests, and interaction metadata @@ -1478,6 +1506,7 @@ export type SessionEvent = */ requestId?: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1497,6 +1526,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.message_delta"; /** * Streaming assistant message delta for incremental response updates @@ -1511,6 +1544,7 @@ export type SessionEvent = */ deltaContent: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1533,6 +1567,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.turn_end"; /** * Turn completion metadata including the turn identifier @@ -1558,6 +1596,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.usage"; /** * LLM API call usage metrics including tokens, costs, quotas, and billing information @@ -1616,6 +1658,7 @@ export type SessionEvent = */ providerCallId?: string; /** + * @deprecated * Parent tool call ID when this usage originates from a sub-agent */ parentToolCallId?: string; @@ -1711,6 +1754,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "abort"; /** * Turn abort information including the reason for termination @@ -1739,6 +1786,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.user_requested"; /** * User-initiated tool invocation request with tool name and arguments @@ -1777,6 +1828,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_start"; /** * Tool execution startup details including MCP server information when applicable @@ -1805,6 +1860,7 @@ export type SessionEvent = */ mcpToolName?: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1824,6 +1880,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_partial_result"; /** * Streaming tool execution output for incremental result display @@ -1853,6 +1913,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_progress"; /** * Tool execution progress notification with status message @@ -1885,6 +1949,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_complete"; /** * Tool execution completion results including success status, detailed output, and error information @@ -2061,6 +2129,7 @@ export type SessionEvent = [k: string]: unknown; }; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -2083,6 +2152,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "skill.invoked"; /** * Skill invocation details including content, allowed tools, and plugin metadata @@ -2135,6 +2208,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.started"; /** * Sub-agent startup details including parent tool call and agent information @@ -2175,6 +2252,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.completed"; /** * Sub-agent completion details for successful execution @@ -2227,6 +2308,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.failed"; /** * Sub-agent failure details including error message and agent information @@ -2283,6 +2368,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.selected"; /** * Custom agent selection details including name and available tools @@ -2319,6 +2408,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.deselected"; /** * Empty payload; the event signals that the custom agent was deselected, returning to the default agent @@ -2342,6 +2435,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "hook.start"; /** * Hook invocation start details including type and input data @@ -2380,6 +2477,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "hook.end"; /** * Hook invocation completion details including output, success status, and error information @@ -2435,13 +2536,17 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "system.message"; /** - * System or developer message content with role and optional template metadata + * System/developer instruction content with role and optional template metadata */ data: { /** - * The system or developer prompt text + * The system or developer prompt text sent as model input */ content: string; /** @@ -2486,6 +2591,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "system.notification"; /** * System-generated notification for runtime events like background task completion @@ -2579,6 +2688,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "permission.requested"; /** * Permission request notification requiring client approval with request details @@ -2848,6 +2961,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "permission.completed"; /** * Permission request completion notification signaling UI dismissal @@ -2888,6 +3005,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user_input.requested"; /** * User input request notification with question and optional predefined choices @@ -2929,6 +3050,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user_input.completed"; /** * User input request completion with the user's response @@ -2962,6 +3087,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "elicitation.requested"; /** * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) @@ -3027,6 +3156,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "elicitation.completed"; /** * Elicitation request completion with the user's response @@ -3062,6 +3195,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "sampling.requested"; /** * Sampling request from an MCP server; contains the server name and a requestId for correlation @@ -3096,6 +3233,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "sampling.completed"; /** * Sampling request completion notification signaling UI dismissal @@ -3121,6 +3262,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "mcp.oauth_required"; /** * OAuth authentication request for an MCP server @@ -3167,6 +3312,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "mcp.oauth_completed"; /** * MCP OAuth request completion notification @@ -3192,6 +3341,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "external_tool.requested"; /** * External tool invocation request for client-side tool execution @@ -3243,6 +3396,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "external_tool.completed"; /** * External tool completion notification signaling UI dismissal @@ -3268,6 +3425,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.queued"; /** * Queued slash command dispatch request for client execution @@ -3297,6 +3458,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.execute"; /** * Registered command dispatch request routed to the owning client @@ -3334,6 +3499,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.completed"; /** * Queued command completion notification signaling UI dismissal @@ -3359,6 +3528,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "commands.changed"; /** * SDK command registration change notification @@ -3387,6 +3560,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "capabilities.changed"; /** * Session capability change notification @@ -3417,6 +3594,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "exit_plan_mode.requested"; /** * Plan approval request with plan content and available user actions @@ -3458,6 +3639,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "exit_plan_mode.completed"; /** * Plan mode exit completion with the user's approval decision and optional feedback @@ -3499,6 +3684,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.tools_updated"; data: { model: string; @@ -3518,6 +3707,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.background_tasks_changed"; data: {}; } @@ -3535,6 +3728,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.skills_loaded"; data: { /** @@ -3582,6 +3779,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.custom_agents_updated"; data: { /** @@ -3645,6 +3846,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mcp_servers_loaded"; data: { /** @@ -3684,6 +3889,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mcp_server_status_changed"; data: { /** @@ -3710,6 +3919,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.extensions_loaded"; data: { /** @@ -3736,6 +3949,105 @@ export type SessionEvent = }; }; +/** + * Working directory and git context at session start + */ +export interface WorkingDirectoryContext { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; +} +/** + * Updated working directory and git context at resume time + */ +export interface WorkingDirectoryContext1 { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; +} +/** + * Updated working directory and git context after the change + */ +export interface WorkingDirectoryContext2 { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; +} export interface EmbeddedTextResourceContents { /** * URI identifying the resource diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index c99182b17..1aa658823 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -21,14 +21,18 @@ EnumT = TypeVar("EnumT", bound=Enum) -def from_str(x: Any) -> str: - assert isinstance(x, str) - return x - def from_int(x: Any) -> int: assert isinstance(x, int) and not isinstance(x, bool) return x +def from_list(f: Callable[[Any], T], x: Any) -> list[T]: + assert isinstance(x, list) + return [f(y) for y in x] + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + def from_none(x: Any) -> Any: assert x is None return x @@ -41,18 +45,6 @@ def from_union(fs, x): pass assert False -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) - -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) - return x - -def from_list(f: Callable[[Any], T], x: Any) -> list[T]: - assert isinstance(x, list) - return [f(y) for y in x] - def to_class(c: type[T], x: Any) -> dict: assert isinstance(x, c) return cast(Any, x).to_dict() @@ -65,76 +57,23 @@ def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: assert isinstance(x, dict) return { k: f(v) for (k, v) in x.items() } -def from_datetime(x: Any) -> datetime: - return dateutil.parser.parse(x) - def to_enum(c: type[EnumT], x: Any) -> EnumT: assert isinstance(x, c) return x.value -@dataclass -class PingResult: - message: str - """Echoed message (or default greeting)""" - - protocol_version: int - """Server protocol version number""" - - timestamp: int - """Server timestamp in milliseconds""" - - @staticmethod - def from_dict(obj: Any) -> 'PingResult': - assert isinstance(obj, dict) - message = from_str(obj.get("message")) - protocol_version = from_int(obj.get("protocolVersion")) - timestamp = from_int(obj.get("timestamp")) - return PingResult(message, protocol_version, timestamp) - - def to_dict(self) -> dict: - result: dict = {} - result["message"] = from_str(self.message) - result["protocolVersion"] = from_int(self.protocol_version) - result["timestamp"] = from_int(self.timestamp) - return result - -@dataclass -class PingRequest: - message: str | None = None - """Optional message to echo back""" - - @staticmethod - def from_dict(obj: Any) -> 'PingRequest': - assert isinstance(obj, dict) - message = from_union([from_str, from_none], obj.get("message")) - return PingRequest(message) - - def to_dict(self) -> dict: - result: dict = {} - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - return result - -@dataclass -class ModelBilling: - """Billing information""" - - multiplier: float - """Billing cost multiplier relative to the base rate""" +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) - @staticmethod - def from_dict(obj: Any) -> 'ModelBilling': - assert isinstance(obj, dict) - multiplier = from_float(obj.get("multiplier")) - return ModelBilling(multiplier) +def to_float(x: Any) -> float: + assert isinstance(x, (int, float)) + return x - def to_dict(self) -> dict: - result: dict = {} - result["multiplier"] = to_float(self.multiplier) - return result +def from_datetime(x: Any) -> datetime: + return dateutil.parser.parse(x) @dataclass -class ModelCapabilitiesLimitsVision: +class PurpleModelCapabilitiesLimitsVision: """Vision-specific limits""" max_prompt_image_size: int @@ -147,12 +86,12 @@ class ModelCapabilitiesLimitsVision: """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': + def from_dict(obj: Any) -> 'PurpleModelCapabilitiesLimitsVision': assert isinstance(obj, dict) max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) max_prompt_images = from_int(obj.get("max_prompt_images")) supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + return PurpleModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} @@ -161,43 +100,6 @@ def to_dict(self) -> dict: result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result -@dataclass -class ModelCapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" - - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" - - vision: ModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) - return result - @dataclass class ModelCapabilitiesSupports: """Feature flags indicating what the model supports""" @@ -224,716 +126,690 @@ def to_dict(self) -> dict: return result @dataclass -class ModelCapabilities: - """Model capabilities and limits""" +class ModelCapabilitiesLimitsVision: + """Vision-specific limits""" - limits: ModelCapabilitiesLimits | None = None - """Token limits for prompts, outputs, and context window""" + max_prompt_image_size: int + """Maximum image size in bytes""" - supports: ModelCapabilitiesSupports | None = None - """Feature flags indicating what the model supports""" + max_prompt_images: int + """Maximum number of images per prompt""" + + supported_media_types: list[str] + """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilities': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': assert isinstance(obj, dict) - limits = from_union([ModelCapabilitiesLimits.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesSupports.from_dict, from_none], obj.get("supports")) - return ModelCapabilities(limits, supports) + max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) + max_prompt_images = from_int(obj.get("max_prompt_images")) + supported_media_types = from_list(from_str, obj.get("supported_media_types")) + return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimits, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) + result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) + result["max_prompt_images"] = from_int(self.max_prompt_images) + result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result -@dataclass -class ModelPolicy: - """Policy state (if applicable)""" +class FilterMappingString(Enum): + HIDDEN_CHARACTERS = "hidden_characters" + MARKDOWN = "markdown" + NONE = "none" - state: str - """Current policy state for this model""" +class MCPServerConfigType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" - terms: str - """Usage terms or conditions for this model""" + HTTP = "http" + LOCAL = "local" + SSE = "sse" + STDIO = "stdio" - @staticmethod - def from_dict(obj: Any) -> 'ModelPolicy': - assert isinstance(obj, dict) - state = from_str(obj.get("state")) - terms = from_str(obj.get("terms")) - return ModelPolicy(state, terms) +class MCPServerSource(Enum): + """Configuration source - def to_dict(self) -> dict: - result: dict = {} - result["state"] = from_str(self.state) - result["terms"] = from_str(self.terms) - return result + Configuration source: user, workspace, plugin, or builtin + """ + BUILTIN = "builtin" + PLUGIN = "plugin" + USER = "user" + WORKSPACE = "workspace" + +class DiscoveredMCPServerType(Enum): + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + + HTTP = "http" + MEMORY = "memory" + SSE = "sse" + STDIO = "stdio" @dataclass -class Model: - capabilities: ModelCapabilities - """Model capabilities and limits""" +class SkillElement: + description: str + """Description of what the skill does""" - id: str - """Model identifier (e.g., "claude-sonnet-4.5")""" + enabled: bool + """Whether the skill is currently enabled (based on global config)""" name: str - """Display name""" + """Unique identifier for the skill""" - billing: ModelBilling | None = None - """Billing information""" + source: str + """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - default_reasoning_effort: str | None = None - """Default reasoning effort level (only present if model supports reasoning effort)""" + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" - policy: ModelPolicy | None = None - """Policy state (if applicable)""" + path: str | None = None + """Absolute path to the skill file""" - supported_reasoning_efforts: list[str] | None = None - """Supported reasoning effort levels (only present if model supports reasoning effort)""" + project_path: str | None = None + """The project path this skill belongs to (only for project/inherited skills)""" @staticmethod - def from_dict(obj: Any) -> 'Model': + def from_dict(obj: Any) -> 'SkillElement': assert isinstance(obj, dict) - capabilities = ModelCapabilities.from_dict(obj.get("capabilities")) - id = from_str(obj.get("id")) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) - default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) - policy = from_union([ModelPolicy.from_dict, from_none], obj.get("policy")) - supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) - return Model(capabilities, id, name, billing, default_reasoning_effort, policy, supported_reasoning_efforts) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return SkillElement(description, enabled, name, source, user_invocable, path, project_path) def to_dict(self) -> dict: result: dict = {} - result["capabilities"] = to_class(ModelCapabilities, self.capabilities) - result["id"] = from_str(self.id) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) result["name"] = from_str(self.name) - if self.billing is not None: - result["billing"] = from_union([lambda x: to_class(ModelBilling, x), from_none], self.billing) - if self.default_reasoning_effort is not None: - result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) - if self.policy is not None: - result["policy"] = from_union([lambda x: to_class(ModelPolicy, x), from_none], self.policy) - if self.supported_reasoning_efforts is not None: - result["supportedReasoningEfforts"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_reasoning_efforts) - return result - -@dataclass -class ModelList: - models: list[Model] - """List of available models with full metadata""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelList': - assert isinstance(obj, dict) - models = from_list(Model.from_dict, obj.get("models")) - return ModelList(models) - - def to_dict(self) -> dict: - result: dict = {} - result["models"] = from_list(lambda x: to_class(Model, x), self.models) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) return result @dataclass -class Tool: +class ServerSkill: description: str - """Description of what the tool does""" + """Description of what the skill does""" + + enabled: bool + """Whether the skill is currently enabled (based on global config)""" name: str - """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" + """Unique identifier for the skill""" - instructions: str | None = None - """Optional instructions for how to use this tool effectively""" + source: str + """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - namespaced_name: str | None = None - """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP - tools) - """ - parameters: dict[str, Any] | None = None - """JSON Schema for the tool's input parameters""" + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" + + path: str | None = None + """Absolute path to the skill file""" + + project_path: str | None = None + """The project path this skill belongs to (only for project/inherited skills)""" @staticmethod - def from_dict(obj: Any) -> 'Tool': + def from_dict(obj: Any) -> 'ServerSkill': assert isinstance(obj, dict) description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - instructions = from_union([from_str, from_none], obj.get("instructions")) - namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) - parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) - return Tool(description, name, instructions, namespaced_name, parameters) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) def to_dict(self) -> dict: result: dict = {} result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) result["name"] = from_str(self.name) - if self.instructions is not None: - result["instructions"] = from_union([from_str, from_none], self.instructions) - if self.namespaced_name is not None: - result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) - if self.parameters is not None: - result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) return result @dataclass -class ToolList: - tools: list[Tool] - """List of available built-in tools with metadata""" +class CurrentModel: + model_id: str | None = None + """Currently active model identifier""" @staticmethod - def from_dict(obj: Any) -> 'ToolList': + def from_dict(obj: Any) -> 'CurrentModel': assert isinstance(obj, dict) - tools = from_list(Tool.from_dict, obj.get("tools")) - return ToolList(tools) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return CurrentModel(model_id) def to_dict(self) -> dict: result: dict = {} - result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) return result @dataclass -class ToolsListRequest: - model: str | None = None - """Optional model ID — when provided, the returned tool list reflects model-specific - overrides - """ +class PurpleModelCapabilitiesOverrideLimitsVision: + max_prompt_image_size: int | None = None + """Maximum image size in bytes""" + + max_prompt_images: int | None = None + """Maximum number of images per prompt""" + + supported_media_types: list[str] | None = None + """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'ToolsListRequest': + def from_dict(obj: Any) -> 'PurpleModelCapabilitiesOverrideLimitsVision': assert isinstance(obj, dict) - model = from_union([from_str, from_none], obj.get("model")) - return ToolsListRequest(model) + max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) + max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) + supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) + return PurpleModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + if self.max_prompt_image_size is not None: + result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) + if self.max_prompt_images is not None: + result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) + if self.supported_media_types is not None: + result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) return result @dataclass -class AccountQuotaSnapshot: - entitlement_requests: int - """Number of requests included in the entitlement""" - - overage: int - """Number of overage requests made this period""" - - overage_allowed_with_exhausted_quota: bool - """Whether pay-per-request usage is allowed when quota is exhausted""" - - remaining_percentage: float - """Percentage of entitlement remaining""" - - used_requests: int - """Number of requests used so far this period""" +class ModelCapabilitiesOverrideSupports: + """Feature flags indicating what the model supports""" - reset_date: datetime | None = None - """Date when the quota resets (ISO 8601)""" + reasoning_effort: bool | None = None + vision: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'AccountQuotaSnapshot': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': assert isinstance(obj, dict) - entitlement_requests = from_int(obj.get("entitlementRequests")) - overage = from_int(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - used_requests = from_int(obj.get("usedRequests")) - reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) + reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) + vision = from_union([from_bool, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = from_int(self.entitlement_requests) - result["overage"] = from_int(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usedRequests"] = from_int(self.used_requests) - if self.reset_date is not None: - result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) + if self.vision is not None: + result["vision"] = from_union([from_bool, from_none], self.vision) return result @dataclass -class AccountGetQuotaResult: - quota_snapshots: dict[str, AccountQuotaSnapshot] - """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" +class AgentInfo: + description: str + """Description of the agent's purpose""" + + display_name: str + """Human-readable display name""" + + name: str + """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'AccountGetQuotaResult': + def from_dict(obj: Any) -> 'AgentInfo': assert isinstance(obj, dict) - quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) - return AccountGetQuotaResult(quota_snapshots) + description = from_str(obj.get("description")) + display_name = from_str(obj.get("displayName")) + name = from_str(obj.get("name")) + return AgentInfo(description, display_name, name) def to_dict(self) -> dict: result: dict = {} - result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) + result["description"] = from_str(self.description) + result["displayName"] = from_str(self.display_name) + result["name"] = from_str(self.name) return result -class MCPConfigFilterMappingString(Enum): - HIDDEN_CHARACTERS = "hidden_characters" - MARKDOWN = "markdown" - NONE = "none" - -class MCPConfigType(Enum): - """Remote transport type. Defaults to "http" when omitted.""" +class MCPServerStatus(Enum): + """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - HTTP = "http" - LOCAL = "local" - SSE = "sse" - STDIO = "stdio" + CONNECTED = "connected" + DISABLED = "disabled" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + NOT_CONFIGURED = "not_configured" + PENDING = "pending" @dataclass -class MCPConfigServer: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" +class ToolCallResult: + text_result_for_llm: str + """Text result to send back to the LLM""" - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + error: str | None = None + """Error message if the tool call failed""" - type: MCPConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" + result_type: str | None = None + """Type of the tool result""" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None + tool_telemetry: dict[str, Any] | None = None + """Telemetry data from tool execution""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigServer': + def from_dict(obj: Any) -> 'ToolCallResult': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigServer(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + text_result_for_llm = from_str(obj.get("textResultForLlm")) + error = from_union([from_str, from_none], obj.get("error")) + result_type = from_union([from_str, from_none], obj.get("resultType")) + tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) + return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["textResultForLlm"] = from_str(self.text_result_for_llm) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result_type is not None: + result["resultType"] = from_union([from_str, from_none], self.result_type) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result @dataclass -class MCPConfigList: - servers: dict[str, MCPConfigServer] - """All MCP servers from user config, keyed by name""" +class HandleToolCallResult: + success: bool + """Whether the tool call result was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigList': + def from_dict(obj: Any) -> 'HandleToolCallResult': assert isinstance(obj, dict) - servers = from_dict(MCPConfigServer.from_dict, obj.get("servers")) - return MCPConfigList(servers) + success = from_bool(obj.get("success")) + return HandleToolCallResult(success) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_dict(lambda x: to_class(MCPConfigServer, x), self.servers) + result["success"] = from_bool(self.success) return result +class UIElicitationStringEnumFieldType(Enum): + STRING = "string" + @dataclass -class MCPConfigAddConfig: - """MCP server configuration (local/stdio or remote/http)""" +class UIElicitationStringOneOfFieldOneOf: + const: str + title: str - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': + assert isinstance(obj, dict) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationStringOneOfFieldOneOf(const, title) - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + def to_dict(self) -> dict: + result: dict = {} + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) + return result - type: MCPConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" +class UIElicitationArrayEnumFieldType(Enum): + ARRAY = "array" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None +@dataclass +class PurpleUIElicitationArrayAnyOfFieldItemsAnyOf: + const: str + title: str @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddConfig': + def from_dict(obj: Any) -> 'PurpleUIElicitationArrayAnyOfFieldItemsAnyOf': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigAddConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return PurpleUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) return result -@dataclass -class MCPConfigAddRequest: - config: MCPConfigAddConfig - """MCP server configuration (local/stdio or remote/http)""" +class UIElicitationResponseAction(Enum): + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" - name: str - """Unique name for the MCP server""" + ACCEPT = "accept" + CANCEL = "cancel" + DECLINE = "decline" + +@dataclass +class UIElicitationResult: + success: bool + """Whether the response was accepted. False if the request was already resolved by another + client. + """ @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddRequest': + def from_dict(obj: Any) -> 'UIElicitationResult': assert isinstance(obj, dict) - config = MCPConfigAddConfig.from_dict(obj.get("config")) - name = from_str(obj.get("name")) - return MCPConfigAddRequest(config, name) + success = from_bool(obj.get("success")) + return UIElicitationResult(success) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigAddConfig, self.config) - result["name"] = from_str(self.name) + result["success"] = from_bool(self.success) return result +class Kind(Enum): + APPROVED = "approved" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + DENIED_BY_RULES = "denied-by-rules" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + @dataclass -class MCPConfigUpdateConfig: - """MCP server configuration (local/stdio or remote/http)""" +class PermissionRequestResult: + success: bool + """Whether the permission request was handled successfully""" - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, MCPConfigFilterMappingString] | MCPConfigFilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionRequestResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return PermissionRequestResult(success) - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result - type: MCPConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" +@dataclass +class PingResult: + message: str + """Echoed message (or default greeting)""" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None + protocol_version: int + """Server protocol version number""" + + timestamp: int + """Server timestamp in milliseconds""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateConfig': + def from_dict(obj: Any) -> 'PingResult': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(MCPConfigFilterMappingString, x), MCPConfigFilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigUpdateConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + message = from_str(obj.get("message")) + protocol_version = from_int(obj.get("protocolVersion")) + timestamp = from_int(obj.get("timestamp")) + return PingResult(message, protocol_version, timestamp) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(MCPConfigFilterMappingString, x), x), lambda x: to_enum(MCPConfigFilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["message"] = from_str(self.message) + result["protocolVersion"] = from_int(self.protocol_version) + result["timestamp"] = from_int(self.timestamp) return result @dataclass -class MCPConfigUpdateRequest: - config: MCPConfigUpdateConfig - """MCP server configuration (local/stdio or remote/http)""" - - name: str - """Name of the MCP server to update""" +class PingRequest: + message: str | None = None + """Optional message to echo back""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': + def from_dict(obj: Any) -> 'PingRequest': assert isinstance(obj, dict) - config = MCPConfigUpdateConfig.from_dict(obj.get("config")) - name = from_str(obj.get("name")) - return MCPConfigUpdateRequest(config, name) + message = from_union([from_str, from_none], obj.get("message")) + return PingRequest(message) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigUpdateConfig, self.config) - result["name"] = from_str(self.name) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result @dataclass -class MCPConfigRemoveRequest: - name: str - """Name of the MCP server to remove""" +class ModelBilling: + """Billing information""" + + multiplier: float + """Billing cost multiplier relative to the base rate""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': + def from_dict(obj: Any) -> 'ModelBilling': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return MCPConfigRemoveRequest(name) + multiplier = from_float(obj.get("multiplier")) + return ModelBilling(multiplier) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["multiplier"] = to_float(self.multiplier) return result -class MCPServerSource(Enum): - """Configuration source +@dataclass +class FluffyModelCapabilitiesLimitsVision: + """Vision-specific limits""" - Configuration source: user, workspace, plugin, or builtin - """ - BUILTIN = "builtin" - PLUGIN = "plugin" - USER = "user" - WORKSPACE = "workspace" + max_prompt_image_size: int + """Maximum image size in bytes""" -class DiscoveredMCPServerType(Enum): - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + max_prompt_images: int + """Maximum number of images per prompt""" - HTTP = "http" - MEMORY = "memory" - SSE = "sse" - STDIO = "stdio" + supported_media_types: list[str] + """MIME types the model accepts""" -@dataclass -class DiscoveredMCPServer: - enabled: bool - """Whether the server is enabled (not in the disabled list)""" + @staticmethod + def from_dict(obj: Any) -> 'FluffyModelCapabilitiesLimitsVision': + assert isinstance(obj, dict) + max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) + max_prompt_images = from_int(obj.get("max_prompt_images")) + supported_media_types = from_list(from_str, obj.get("supported_media_types")) + return FluffyModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) - name: str - """Server name (config key)""" + def to_dict(self) -> dict: + result: dict = {} + result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) + result["max_prompt_images"] = from_int(self.max_prompt_images) + result["supported_media_types"] = from_list(from_str, self.supported_media_types) + return result - source: MCPServerSource - """Configuration source""" +@dataclass +class CapabilitiesSupports: + """Feature flags indicating what the model supports""" - type: DiscoveredMCPServerType | None = None - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + reasoning_effort: bool | None = None + """Whether this model supports reasoning effort configuration""" + + vision: bool | None = None + """Whether this model supports vision/image input""" @staticmethod - def from_dict(obj: Any) -> 'DiscoveredMCPServer': + def from_dict(obj: Any) -> 'CapabilitiesSupports': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = MCPServerSource(obj.get("source")) - type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) - return DiscoveredMCPServer(enabled, name, source, type) + reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) + vision = from_union([from_bool, from_none], obj.get("vision")) + return CapabilitiesSupports(reasoning_effort, vision) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = to_enum(MCPServerSource, self.source) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) + if self.vision is not None: + result["vision"] = from_union([from_bool, from_none], self.vision) return result @dataclass -class MCPDiscoverResult: - servers: list[DiscoveredMCPServer] - """MCP servers discovered from all sources""" +class ModelPolicy: + """Policy state (if applicable)""" + + state: str + """Current policy state for this model""" + + terms: str + """Usage terms or conditions for this model""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverResult': + def from_dict(obj: Any) -> 'ModelPolicy': assert isinstance(obj, dict) - servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) - return MCPDiscoverResult(servers) + state = from_str(obj.get("state")) + terms = from_str(obj.get("terms")) + return ModelPolicy(state, terms) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) + result["state"] = from_str(self.state) + result["terms"] = from_str(self.terms) return result @dataclass -class MCPDiscoverRequest: - working_directory: str | None = None - """Working directory used as context for discovery (e.g., plugin resolution)""" +class Tool: + description: str + """Description of what the tool does""" + + name: str + """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" + + instructions: str | None = None + """Optional instructions for how to use this tool effectively""" + + namespaced_name: str | None = None + """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP + tools) + """ + parameters: dict[str, Any] | None = None + """JSON Schema for the tool's input parameters""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverRequest': + def from_dict(obj: Any) -> 'Tool': assert isinstance(obj, dict) - working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return MCPDiscoverRequest(working_directory) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + instructions = from_union([from_str, from_none], obj.get("instructions")) + namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) + parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) + return Tool(description, name, instructions, namespaced_name, parameters) def to_dict(self) -> dict: result: dict = {} - if self.working_directory is not None: - result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.instructions is not None: + result["instructions"] = from_union([from_str, from_none], self.instructions) + if self.namespaced_name is not None: + result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) + if self.parameters is not None: + result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result @dataclass -class SkillsConfigSetDisabledSkillsRequest: - disabled_skills: list[str] - """List of skill names to disable""" +class ToolsListRequest: + model: str | None = None + """Optional model ID — when provided, the returned tool list reflects model-specific + overrides + """ @staticmethod - def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': + def from_dict(obj: Any) -> 'ToolsListRequest': assert isinstance(obj, dict) - disabled_skills = from_list(from_str, obj.get("disabledSkills")) - return SkillsConfigSetDisabledSkillsRequest(disabled_skills) + model = from_union([from_str, from_none], obj.get("model")) + return ToolsListRequest(model) def to_dict(self) -> dict: result: dict = {} - result["disabledSkills"] = from_list(from_str, self.disabled_skills) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) return result @dataclass -class ServerSkill: - description: str - """Description of what the skill does""" +class AccountQuotaSnapshot: + entitlement_requests: int + """Number of requests included in the entitlement""" - enabled: bool - """Whether the skill is currently enabled (based on global config)""" + overage: int + """Number of overage requests made this period""" - name: str - """Unique identifier for the skill""" + overage_allowed_with_exhausted_quota: bool + """Whether pay-per-request usage is allowed when quota is exhausted""" - source: str - """Source location type (e.g., project, personal-copilot, plugin, builtin)""" + remaining_percentage: float + """Percentage of entitlement remaining""" - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" + used_requests: int + """Number of requests used so far this period""" - path: str | None = None - """Absolute path to the skill file""" + reset_date: datetime | None = None + """Date when the quota resets (ISO 8601)""" - project_path: str | None = None - """The project path this skill belongs to (only for project/inherited skills)""" + @staticmethod + def from_dict(obj: Any) -> 'AccountQuotaSnapshot': + assert isinstance(obj, dict) + entitlement_requests = from_int(obj.get("entitlementRequests")) + overage = from_int(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + used_requests = from_int(obj.get("usedRequests")) + reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) + return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) + + def to_dict(self) -> dict: + result: dict = {} + result["entitlementRequests"] = from_int(self.entitlement_requests) + result["overage"] = from_int(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + result["usedRequests"] = from_int(self.used_requests) + if self.reset_date is not None: + result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + return result + +@dataclass +class MCPConfigRemoveRequest: + name: str + """Name of the MCP server to remove""" @staticmethod - def from_dict(obj: Any) -> 'ServerSkill': + def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) + return MCPConfigRemoveRequest(name) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) return result @dataclass -class ServerSkillList: - skills: list[ServerSkill] - """All discovered skills across all sources""" +class MCPDiscoverRequest: + working_directory: str | None = None + """Working directory used as context for discovery (e.g., plugin resolution)""" @staticmethod - def from_dict(obj: Any) -> 'ServerSkillList': + def from_dict(obj: Any) -> 'MCPDiscoverRequest': assert isinstance(obj, dict) - skills = from_list(ServerSkill.from_dict, obj.get("skills")) - return ServerSkillList(skills) + working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) + return MCPDiscoverRequest(working_directory) + + def to_dict(self) -> dict: + result: dict = {} + if self.working_directory is not None: + result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) + return result + +@dataclass +class SkillsConfigSetDisabledSkillsRequest: + disabled_skills: list[str] + """List of skill names to disable""" + + @staticmethod + def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': + assert isinstance(obj, dict) + disabled_skills = from_list(from_str, obj.get("disabledSkills")) + return SkillsConfigSetDisabledSkillsRequest(disabled_skills) def to_dict(self) -> dict: result: dict = {} - result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) + result["disabledSkills"] = from_list(from_str, self.disabled_skills) return result @dataclass @@ -981,32 +857,6 @@ class SessionFSSetProviderConventions(Enum): POSIX = "posix" WINDOWS = "windows" -@dataclass -class SessionFSSetProviderRequest: - conventions: SessionFSSetProviderConventions - """Path conventions used by this filesystem""" - - initial_cwd: str - """Initial working directory for sessions""" - - session_state_path: str - """Path within each session's SessionFs where the runtime stores files for that session""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': - assert isinstance(obj, dict) - conventions = SessionFSSetProviderConventions(obj.get("conventions")) - initial_cwd = from_str(obj.get("initialCwd")) - session_state_path = from_str(obj.get("sessionStatePath")) - return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) - - def to_dict(self) -> dict: - result: dict = {} - result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) - result["initialCwd"] = from_str(self.initial_cwd) - result["sessionStatePath"] = from_str(self.session_state_path) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SessionsForkResult: @@ -1050,24 +900,7 @@ def to_dict(self) -> dict: return result @dataclass -class CurrentModel: - model_id: str | None = None - """Currently active model identifier""" - - @staticmethod - def from_dict(obj: Any) -> 'CurrentModel': - assert isinstance(obj, dict) - model_id = from_union([from_str, from_none], obj.get("modelId")) - return CurrentModel(model_id) - - def to_dict(self) -> dict: - result: dict = {} - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) - return result - -@dataclass -class ModelSwitchToResult: +class ModelSwitchToResult: model_id: str | None = None """Currently active model identifier after the switch""" @@ -1084,7 +917,7 @@ def to_dict(self) -> dict: return result @dataclass -class ModelCapabilitiesOverrideLimitsVision: +class FluffyModelCapabilitiesOverrideLimitsVision: max_prompt_image_size: int | None = None """Maximum image size in bytes""" @@ -1095,12 +928,12 @@ class ModelCapabilitiesOverrideLimitsVision: """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimitsVision': + def from_dict(obj: Any) -> 'FluffyModelCapabilitiesOverrideLimitsVision': assert isinstance(obj, dict) max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) - return ModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + return FluffyModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} @@ -1112,113 +945,6 @@ def to_dict(self) -> dict: result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) return result -@dataclass -class ModelCapabilitiesOverrideLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: ModelCapabilitiesOverrideLimitsVision | None = None - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([ModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) - return result - -@dataclass -class ModelCapabilitiesOverrideSupports: - """Feature flags indicating what the model supports""" - - reasoning_effort: bool | None = None - vision: bool | None = None - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': - assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) - return result - -@dataclass -class ModelCapabilitiesOverride: - """Override individual model capabilities resolved by the runtime""" - - limits: ModelCapabilitiesOverrideLimits | None = None - """Token limits for prompts, outputs, and context window""" - - supports: ModelCapabilitiesOverrideSupports | None = None - """Feature flags indicating what the model supports""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverride': - assert isinstance(obj, dict) - limits = from_union([ModelCapabilitiesOverrideLimits.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) - return ModelCapabilitiesOverride(limits, supports) - - def to_dict(self) -> dict: - result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimits, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) - return result - -@dataclass -class ModelSwitchToRequest: - model_id: str - """Model identifier to switch to""" - - model_capabilities: ModelCapabilitiesOverride | None = None - """Override individual model capabilities resolved by the runtime""" - - reasoning_effort: str | None = None - """Reasoning effort level to use for the model""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelSwitchToRequest': - assert isinstance(obj, dict) - model_id = from_str(obj.get("modelId")) - model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) - reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) - return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort) - - def to_dict(self) -> dict: - result: dict = {} - result["modelId"] = from_str(self.model_id) - if self.model_capabilities is not None: - result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesOverride, x), from_none], self.model_capabilities) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) - return result - class SessionMode(Enum): """The agent mode. Valid values: "interactive", "plan", "autopilot".""" @@ -1226,22 +952,6 @@ class SessionMode(Enum): INTERACTIVE = "interactive" PLAN = "plan" -@dataclass -class ModeSetRequest: - mode: SessionMode - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" - - @staticmethod - def from_dict(obj: Any) -> 'ModeSetRequest': - assert isinstance(obj, dict) - mode = SessionMode(obj.get("mode")) - return ModeSetRequest(mode) - - def to_dict(self) -> dict: - result: dict = {} - result["mode"] = to_enum(SessionMode, self.mode) - return result - @dataclass class NameGetResult: name: str | None = None @@ -1325,101 +1035,6 @@ class SessionSyncLevel(Enum): REPO_AND_USER = "repo_and_user" USER = "user" -@dataclass -class Workspace: - id: UUID - branch: str | None = None - chronicle_sync_dismissed: bool | None = None - created_at: datetime | None = None - cwd: str | None = None - git_root: str | None = None - host_type: HostType | None = None - mc_last_event_id: str | None = None - mc_session_id: str | None = None - mc_task_id: str | None = None - name: str | None = None - pr_create_sync_dismissed: bool | None = None - repository: str | None = None - session_sync_level: SessionSyncLevel | None = None - summary: str | None = None - summary_count: int | None = None - updated_at: datetime | None = None - - @staticmethod - def from_dict(obj: Any) -> 'Workspace': - assert isinstance(obj, dict) - id = UUID(obj.get("id")) - branch = from_union([from_str, from_none], obj.get("branch")) - chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) - created_at = from_union([from_datetime, from_none], obj.get("created_at")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - git_root = from_union([from_str, from_none], obj.get("git_root")) - host_type = from_union([HostType, from_none], obj.get("host_type")) - mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) - mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) - mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) - name = from_union([from_str, from_none], obj.get("name")) - pr_create_sync_dismissed = from_union([from_bool, from_none], obj.get("pr_create_sync_dismissed")) - repository = from_union([from_str, from_none], obj.get("repository")) - session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) - summary = from_union([from_str, from_none], obj.get("summary")) - summary_count = from_union([from_int, from_none], obj.get("summary_count")) - updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, pr_create_sync_dismissed, repository, session_sync_level, summary, summary_count, updated_at) - - def to_dict(self) -> dict: - result: dict = {} - result["id"] = str(self.id) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.chronicle_sync_dismissed is not None: - result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) - if self.created_at is not None: - result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.git_root is not None: - result["git_root"] = from_union([from_str, from_none], self.git_root) - if self.host_type is not None: - result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) - if self.mc_last_event_id is not None: - result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) - if self.mc_session_id is not None: - result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) - if self.mc_task_id is not None: - result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.pr_create_sync_dismissed is not None: - result["pr_create_sync_dismissed"] = from_union([from_bool, from_none], self.pr_create_sync_dismissed) - if self.repository is not None: - result["repository"] = from_union([from_str, from_none], self.repository) - if self.session_sync_level is not None: - result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) - if self.summary_count is not None: - result["summary_count"] = from_union([from_int, from_none], self.summary_count) - if self.updated_at is not None: - result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) - return result - -@dataclass -class WorkspacesGetWorkspaceResult: - workspace: Workspace | None = None - """Current workspace metadata, or null if not available""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': - assert isinstance(obj, dict) - workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) - return WorkspacesGetWorkspaceResult(workspace) - - def to_dict(self) -> dict: - result: dict = {} - result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) - return result - @dataclass class WorkspacesListFilesResult: files: list[str] @@ -1489,6 +1104,23 @@ def to_dict(self) -> dict: result["path"] = from_str(self.path) return result +class InstructionsSourcesLocation(Enum): + """Where this source lives — used for UI grouping""" + + REPOSITORY = "repository" + USER = "user" + WORKING_DIRECTORY = "working-directory" + +class InstructionsSourcesType(Enum): + """Category of instruction source — used for merge logic""" + + CHILD_INSTRUCTIONS = "child-instructions" + HOME = "home" + MODEL = "model" + NESTED_AGENTS = "nested-agents" + REPO = "repo" + VSCODE = "vscode" + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class FleetStartResult: @@ -1525,7 +1157,7 @@ def to_dict(self) -> dict: return result @dataclass -class Agent: +class AgentListAgent: description: str """Description of the agent's purpose""" @@ -1536,12 +1168,12 @@ class Agent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'Agent': + def from_dict(obj: Any) -> 'AgentListAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return Agent(description, display_name, name) + return AgentListAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1550,25 +1182,10 @@ 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 AgentList: - agents: list[Agent] - """Available custom agents""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentList': - assert isinstance(obj, dict) - agents = from_list(Agent.from_dict, obj.get("agents")) - return AgentList(agents) - - def to_dict(self) -> dict: - result: dict = {} - result["agents"] = from_list(lambda x: to_class(Agent, x), self.agents) - return result +class AgentSelectResultAgent: + """The newly selected custom agent""" -@dataclass -class AgentGetCurrentResultAgent: description: str """Description of the agent's purpose""" @@ -1579,12 +1196,12 @@ class AgentGetCurrentResultAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'AgentGetCurrentResultAgent': + def from_dict(obj: Any) -> 'AgentSelectResultAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return AgentGetCurrentResultAgent(description, display_name, name) + return AgentSelectResultAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1595,74 +1212,12 @@ def to_dict(self) -> dict: # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentGetCurrentResult: - agent: AgentGetCurrentResultAgent | None = None - """Currently selected custom agent, or null if using the default agent""" +class AgentSelectRequest: + name: str + """Name of the custom agent to select""" @staticmethod - def from_dict(obj: Any) -> 'AgentGetCurrentResult': - assert isinstance(obj, dict) - agent = from_union([AgentGetCurrentResultAgent.from_dict, from_none], obj.get("agent")) - return AgentGetCurrentResult(agent) - - def to_dict(self) -> dict: - result: dict = {} - result["agent"] = from_union([lambda x: to_class(AgentGetCurrentResultAgent, x), from_none], self.agent) - return result - -@dataclass -class AgentSelectAgent: - """The newly selected custom agent""" - - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" - - name: str - """Unique identifier of the custom agent""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentSelectAgent': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentSelectAgent(description, display_name, name) - - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentSelectResult: - agent: AgentSelectAgent - """The newly selected custom agent""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentSelectResult': - assert isinstance(obj, dict) - agent = AgentSelectAgent.from_dict(obj.get("agent")) - return AgentSelectResult(agent) - - def to_dict(self) -> dict: - result: dict = {} - result["agent"] = to_class(AgentSelectAgent, self.agent) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentSelectRequest: - name: str - """Name of the custom agent to select""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentSelectRequest': + def from_dict(obj: Any) -> 'AgentSelectRequest': assert isinstance(obj, dict) name = from_str(obj.get("name")) return AgentSelectRequest(name) @@ -1673,7 +1228,7 @@ def to_dict(self) -> dict: return result @dataclass -class AgentReloadAgent: +class AgentReloadResultAgent: description: str """Description of the agent's purpose""" @@ -1684,12 +1239,12 @@ class AgentReloadAgent: """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'AgentReloadAgent': + def from_dict(obj: Any) -> 'AgentReloadResultAgent': assert isinstance(obj, dict) description = from_str(obj.get("description")) display_name = from_str(obj.get("displayName")) name = from_str(obj.get("name")) - return AgentReloadAgent(description, display_name, name) + return AgentReloadResultAgent(description, display_name, name) def to_dict(self) -> dict: result: dict = {} @@ -1698,23 +1253,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentReloadResult: - agents: list[AgentReloadAgent] - """Reloaded custom agents""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResult': - assert isinstance(obj, dict) - agents = from_list(AgentReloadAgent.from_dict, obj.get("agents")) - return AgentReloadResult(agents) - - def to_dict(self) -> dict: - result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentReloadAgent, x), self.agents) - return result - @dataclass class Skill: description: str @@ -1757,23 +1295,6 @@ def to_dict(self) -> dict: result["path"] = from_union([from_str, from_none], self.path) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SkillList: - skills: list[Skill] - """Available skills""" - - @staticmethod - def from_dict(obj: Any) -> 'SkillList': - assert isinstance(obj, dict) - skills = from_list(Skill.from_dict, obj.get("skills")) - return SkillList(skills) - - def to_dict(self) -> dict: - result: dict = {} - result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillsEnableRequest: @@ -1808,65 +1329,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result -class MCPServerStatus(Enum): - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - CONNECTED = "connected" - DISABLED = "disabled" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - NOT_CONFIGURED = "not_configured" - PENDING = "pending" - -@dataclass -class MCPServer: - name: str - """Server name (config key)""" - - status: MCPServerStatus - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - error: str | None = None - """Error message if the server failed to connect""" - - source: MCPServerSource | None = None - """Configuration source: user, workspace, plugin, or builtin""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServer': - assert isinstance(obj, dict) - name = from_str(obj.get("name")) - status = MCPServerStatus(obj.get("status")) - error = from_union([from_str, from_none], obj.get("error")) - source = from_union([MCPServerSource, from_none], obj.get("source")) - return MCPServer(name, status, error, source) - - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_str(self.name) - result["status"] = to_enum(MCPServerStatus, self.status) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.source is not None: - result["source"] = from_union([lambda x: to_enum(MCPServerSource, x), from_none], self.source) - return result - -@dataclass -class MCPServerList: - servers: list[MCPServer] - """Configured MCP servers""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerList': - assert isinstance(obj, dict) - servers = from_list(MCPServer.from_dict, obj.get("servers")) - return MCPServerList(servers) - - def to_dict(self) -> dict: - result: dict = {} - result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) - return result - @dataclass class MCPEnableRequest: server_name: str @@ -1931,23 +1393,6 @@ def to_dict(self) -> dict: result["version"] = from_union([from_str, from_none], self.version) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class PluginList: - plugins: list[Plugin] - """Installed plugins""" - - @staticmethod - def from_dict(obj: Any) -> 'PluginList': - assert isinstance(obj, dict) - plugins = from_list(Plugin.from_dict, obj.get("plugins")) - return PluginList(plugins) - - def to_dict(self) -> dict: - result: dict = {} - result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) - return result - class ExtensionSource(Enum): """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" @@ -1962,60 +1407,6 @@ class ExtensionStatus(Enum): RUNNING = "running" STARTING = "starting" -@dataclass -class Extension: - id: str - """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" - - name: str - """Extension name (directory name)""" - - source: ExtensionSource - """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" - - status: ExtensionStatus - """Current status: running, disabled, failed, or starting""" - - pid: int | None = None - """Process ID if the extension is running""" - - @staticmethod - def from_dict(obj: Any) -> 'Extension': - assert isinstance(obj, dict) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = ExtensionSource(obj.get("source")) - status = ExtensionStatus(obj.get("status")) - pid = from_union([from_int, from_none], obj.get("pid")) - return Extension(id, name, source, status, pid) - - def to_dict(self) -> dict: - result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionSource, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) - if self.pid is not None: - result["pid"] = from_union([from_int, from_none], self.pid) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ExtensionList: - extensions: list[Extension] - """Discovered extensions and their current status""" - - @staticmethod - def from_dict(obj: Any) -> 'ExtensionList': - assert isinstance(obj, dict) - extensions = from_list(Extension.from_dict, obj.get("extensions")) - return ExtensionList(extensions) - - def to_dict(self) -> dict: - result: dict = {} - result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class ExtensionsEnableRequest: @@ -2051,15 +1442,15 @@ def to_dict(self) -> dict: return result @dataclass -class HandleToolCallResult: +class CommandsHandlePendingCommandResult: success: bool - """Whether the tool call result was handled successfully""" + """Whether the command was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'HandleToolCallResult': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return HandleToolCallResult(success) + return CommandsHandlePendingCommandResult(success) def to_dict(self) -> dict: result: dict = {} @@ -2067,847 +1458,2314 @@ def to_dict(self) -> dict: return result @dataclass -class ToolCallResult: - text_result_for_llm: str - """Text result to send back to the LLM""" +class CommandsHandlePendingCommandRequest: + request_id: str + """Request ID from the command invocation event""" error: str | None = None - """Error message if the tool call failed""" - - result_type: str | None = None - """Type of the tool result""" - - tool_telemetry: dict[str, Any] | None = None - """Telemetry data from tool execution""" + """Error message if the command handler failed""" @staticmethod - def from_dict(obj: Any) -> 'ToolCallResult': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': assert isinstance(obj, dict) - text_result_for_llm = from_str(obj.get("textResultForLlm")) + request_id = from_str(obj.get("requestId")) error = from_union([from_str, from_none], obj.get("error")) - result_type = from_union([from_str, from_none], obj.get("resultType")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) + return CommandsHandlePendingCommandRequest(request_id, error) def to_dict(self) -> dict: result: dict = {} - result["textResultForLlm"] = from_str(self.text_result_for_llm) + result["requestId"] = from_str(self.request_id) if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) - if self.result_type is not None: - result["resultType"] = from_union([from_str, from_none], self.result_type) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result +class UIElicitationSchemaPropertyStringFormat(Enum): + DATE = "date" + DATE_TIME = "date-time" + EMAIL = "email" + URI = "uri" + @dataclass -class ToolsHandlePendingToolCallRequest: - request_id: str - """Request ID of the pending tool call""" - - error: str | None = None - """Error message if the tool call failed""" - - result: ToolCallResult | str | None = None - """Tool call result (string or expanded result object)""" +class FluffyUIElicitationArrayAnyOfFieldItemsAnyOf: + const: str + title: str @staticmethod - def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + def from_dict(obj: Any) -> 'FluffyUIElicitationArrayAnyOfFieldItemsAnyOf': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) - return ToolsHandlePendingToolCallRequest(request_id, error, result) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return FluffyUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) return result @dataclass -class CommandsHandlePendingCommandResult: - success: bool - """Whether the command was handled successfully""" +class UIElicitationSchemaPropertyOneOf: + const: str + title: str @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyOneOf': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return CommandsHandlePendingCommandResult(success) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationSchemaPropertyOneOf(const, title) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) return result -@dataclass -class CommandsHandlePendingCommandRequest: - request_id: str - """Request ID from the command invocation event""" +class UIElicitationSchemaPropertyNumberType(Enum): + ARRAY = "array" + BOOLEAN = "boolean" + INTEGER = "integer" + NUMBER = "number" + STRING = "string" - error: str | None = None - """Error message if the command handler failed""" +class RequestedSchemaType(Enum): + OBJECT = "object" + +@dataclass +class LogResult: + event_id: UUID + """The unique identifier of the emitted session event""" @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': + def from_dict(obj: Any) -> 'LogResult': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - return CommandsHandlePendingCommandRequest(request_id, error) + event_id = UUID(obj.get("eventId")) + return LogResult(event_id) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) + result["eventId"] = str(self.event_id) return result -class UIElicitationResponseAction(Enum): - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" +class SessionLogLevel(Enum): + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". + """ + ERROR = "error" + INFO = "info" + WARNING = "warning" - ACCEPT = "accept" - CANCEL = "cancel" - DECLINE = "decline" +@dataclass +class ShellExecResult: + process_id: str + """Unique identifier for tracking streamed output""" + + @staticmethod + def from_dict(obj: Any) -> 'ShellExecResult': + assert isinstance(obj, dict) + process_id = from_str(obj.get("processId")) + return ShellExecResult(process_id) + + def to_dict(self) -> dict: + result: dict = {} + result["processId"] = from_str(self.process_id) + return result @dataclass -class UIElicitationResponse: - """The elicitation response (accept with form values, decline, or cancel)""" +class ShellExecRequest: + command: str + """Shell command to execute""" - action: UIElicitationResponseAction - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + cwd: str | None = None + """Working directory (defaults to session working directory)""" - content: dict[str, float | bool | list[str] | str] | None = None - """The form values submitted by the user (present when action is 'accept')""" + timeout: int | None = None + """Timeout in milliseconds (default: 30000)""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResponse': + def from_dict(obj: Any) -> 'ShellExecRequest': assert isinstance(obj, dict) - action = UIElicitationResponseAction(obj.get("action")) - content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return UIElicitationResponse(action, content) + command = from_str(obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + return ShellExecRequest(command, cwd, timeout) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(UIElicitationResponseAction, self.action) - if self.content is not None: - result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) + result["command"] = from_str(self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) return result -class UIElicitationSchemaPropertyStringFormat(Enum): - DATE = "date" - DATE_TIME = "date-time" - EMAIL = "email" - URI = "uri" - @dataclass -class UIElicitationArrayAnyOfFieldItemsAnyOf: - const: str - title: str +class ShellKillResult: + killed: bool + """Whether the signal was sent successfully""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItemsAnyOf': + def from_dict(obj: Any) -> 'ShellKillResult': assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationArrayAnyOfFieldItemsAnyOf(const, title) + killed = from_bool(obj.get("killed")) + return ShellKillResult(killed) def to_dict(self) -> dict: result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) + result["killed"] = from_bool(self.killed) return result -class ItemsType(Enum): - STRING = "string" +class ShellKillSignal(Enum): + """Signal to send (default: SIGTERM)""" + + SIGINT = "SIGINT" + SIGKILL = "SIGKILL" + SIGTERM = "SIGTERM" @dataclass -class UIElicitationArrayFieldItems: - enum: list[str] | None = None - type: ItemsType | None = None - any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] | None = None +class HistoryCompactContextWindow: + """Post-compaction context window usage breakdown""" + + current_tokens: int + """Current total tokens in the context window (system + conversation + tool definitions)""" + + messages_length: int + """Current number of messages in the conversation""" + + token_limit: int + """Maximum token count for the model's context window""" + + conversation_tokens: int | None = None + """Token count from non-system messages (user, assistant, tool)""" + + system_tokens: int | None = None + """Token count from system message(s)""" + + tool_definitions_tokens: int | None = None + """Token count from tool definitions""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': + def from_dict(obj: Any) -> 'HistoryCompactContextWindow': assert isinstance(obj, dict) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - type = from_union([ItemsType, from_none], obj.get("type")) - any_of = from_union([lambda x: from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) - return UIElicitationArrayFieldItems(enum, type, any_of) + current_tokens = from_int(obj.get("currentTokens")) + messages_length = from_int(obj.get("messagesLength")) + token_limit = from_int(obj.get("tokenLimit")) + conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) + return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) def to_dict(self) -> dict: result: dict = {} - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(ItemsType, x), from_none], self.type) - if self.any_of is not None: - result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) + result["currentTokens"] = from_int(self.current_tokens) + result["messagesLength"] = from_int(self.messages_length) + result["tokenLimit"] = from_int(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIElicitationStringOneOfFieldOneOf: - const: str - title: str +class HistoryTruncateResult: + events_removed: int + """Number of events that were removed""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': + def from_dict(obj: Any) -> 'HistoryTruncateResult': assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationStringOneOfFieldOneOf(const, title) + events_removed = from_int(obj.get("eventsRemoved")) + return HistoryTruncateResult(events_removed) def to_dict(self) -> dict: result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) + result["eventsRemoved"] = from_int(self.events_removed) return result -class UIElicitationSchemaPropertyNumberType(Enum): - ARRAY = "array" - BOOLEAN = "boolean" - INTEGER = "integer" - NUMBER = "number" - STRING = "string" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIElicitationSchemaProperty: - type: UIElicitationSchemaPropertyNumberType - default: float | bool | list[str] | str | None = None - description: str | None = None - enum: list[str] | None = None - enum_names: list[str] | None = None - title: str | None = None - one_of: list[UIElicitationStringOneOfFieldOneOf] | None = None - items: UIElicitationArrayFieldItems | None = None - max_items: float | None = None - min_items: float | None = None - format: UIElicitationSchemaPropertyStringFormat | None = None - max_length: float | None = None - min_length: float | None = None - maximum: float | None = None - minimum: float | None = None +class HistoryTruncateRequest: + event_id: str + """Event ID to truncate to. This event and all events after it are removed from the session.""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': + def from_dict(obj: Any) -> 'HistoryTruncateRequest': assert isinstance(obj, dict) - type = UIElicitationSchemaPropertyNumberType(obj.get("type")) - default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) - title = from_union([from_str, from_none], obj.get("title")) - one_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("oneOf")) - items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) - max_items = from_union([from_float, from_none], obj.get("maxItems")) - min_items = from_union([from_float, from_none], obj.get("minItems")) - format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) - max_length = from_union([from_float, from_none], obj.get("maxLength")) - min_length = from_union([from_float, from_none], obj.get("minLength")) - maximum = from_union([from_float, from_none], obj.get("maximum")) - minimum = from_union([from_float, from_none], obj.get("minimum")) - return UIElicitationSchemaProperty(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) + event_id = from_str(obj.get("eventId")) + return HistoryTruncateRequest(event_id) + + def to_dict(self) -> dict: + result: dict = {} + result["eventId"] = from_str(self.event_id) + return result + +@dataclass +class UsageMetricsCodeChanges: + """Aggregated code change metrics""" + + files_modified_count: int + """Number of distinct files modified""" + + lines_added: int + """Total lines of code added""" + + lines_removed: int + """Total lines of code removed""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': + assert isinstance(obj, dict) + files_modified_count = from_int(obj.get("filesModifiedCount")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + return UsageMetricsCodeChanges(files_modified_count, lines_added, lines_removed) + + def to_dict(self) -> dict: + result: dict = {} + result["filesModifiedCount"] = from_int(self.files_modified_count) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) + return result + +@dataclass +class UsageMetricsModelMetricRequests: + """Request count and cost metrics for this model""" + + cost: float + """User-initiated premium request cost (with multiplier applied)""" + + count: int + """Number of API requests made with this model""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsModelMetricRequests': + assert isinstance(obj, dict) + cost = from_float(obj.get("cost")) + count = from_int(obj.get("count")) + return UsageMetricsModelMetricRequests(cost, count) + + def to_dict(self) -> dict: + result: dict = {} + result["cost"] = to_float(self.cost) + result["count"] = from_int(self.count) + return result + +@dataclass +class UsageMetricsModelMetricUsage: + """Token usage metrics for this model""" + + cache_read_tokens: int + """Total tokens read from prompt cache""" + + cache_write_tokens: int + """Total tokens written to prompt cache""" + + input_tokens: int + """Total input tokens consumed""" + + output_tokens: int + """Total output tokens produced""" + + reasoning_tokens: int | None = None + """Total output tokens used for reasoning""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsModelMetricUsage': + assert isinstance(obj, dict) + cache_read_tokens = from_int(obj.get("cacheReadTokens")) + cache_write_tokens = from_int(obj.get("cacheWriteTokens")) + input_tokens = from_int(obj.get("inputTokens")) + output_tokens = from_int(obj.get("outputTokens")) + reasoning_tokens = from_union([from_int, from_none], obj.get("reasoningTokens")) + return UsageMetricsModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) + + def to_dict(self) -> dict: + result: dict = {} + result["cacheReadTokens"] = from_int(self.cache_read_tokens) + result["cacheWriteTokens"] = from_int(self.cache_write_tokens) + result["inputTokens"] = from_int(self.input_tokens) + result["outputTokens"] = from_int(self.output_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) + return result + +@dataclass +class SessionFSReadFileResult: + content: str + """File content as UTF-8 string""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReadFileResult': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + return SessionFSReadFileResult(content) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + return result + +@dataclass +class SessionFSReadFileRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReadFileRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReadFileRequest(path, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + return result + +@dataclass +class SessionFSWriteFileRequest: + content: str + """Content to write""" + + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + mode: int | None = None + """Optional POSIX-style mode for newly created files""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSWriteFileRequest(content, path, session_id, mode) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) + return result + +@dataclass +class SessionFSAppendFileRequest: + content: str + """Content to append""" + + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + mode: int | None = None + """Optional POSIX-style mode for newly created files""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSAppendFileRequest(content, path, session_id, mode) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) + return result + +@dataclass +class SessionFSExistsResult: + exists: bool + """Whether the path exists""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSExistsResult': + assert isinstance(obj, dict) + exists = from_bool(obj.get("exists")) + return SessionFSExistsResult(exists) + + def to_dict(self) -> dict: + result: dict = {} + result["exists"] = from_bool(self.exists) + return result + +@dataclass +class SessionFSExistsRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSExistsRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSExistsRequest(path, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + return result + +@dataclass +class SessionFSStatResult: + birthtime: datetime + """ISO 8601 timestamp of creation""" + + is_directory: bool + """Whether the path is a directory""" + + is_file: bool + """Whether the path is a file""" + + mtime: datetime + """ISO 8601 timestamp of last modification""" + + size: int + """File size in bytes""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSStatResult': + assert isinstance(obj, dict) + birthtime = from_datetime(obj.get("birthtime")) + is_directory = from_bool(obj.get("isDirectory")) + is_file = from_bool(obj.get("isFile")) + mtime = from_datetime(obj.get("mtime")) + size = from_int(obj.get("size")) + return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size) + + def to_dict(self) -> dict: + result: dict = {} + result["birthtime"] = self.birthtime.isoformat() + result["isDirectory"] = from_bool(self.is_directory) + result["isFile"] = from_bool(self.is_file) + result["mtime"] = self.mtime.isoformat() + result["size"] = from_int(self.size) + return result + +@dataclass +class SessionFSStatRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSStatRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSStatRequest(path, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + return result + +@dataclass +class SessionFSMkdirRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + mode: int | None = None + """Optional POSIX-style mode for newly created directories""" + + recursive: bool | None = None + """Create parent directories as needed""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSMkdirRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSMkdirRequest(path, session_id, mode, recursive) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) + return result + +@dataclass +class SessionFSReaddirResult: + entries: list[str] + """Entry names in the directory""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirResult': + assert isinstance(obj, dict) + entries = from_list(from_str, obj.get("entries")) + return SessionFSReaddirResult(entries) + + def to_dict(self) -> dict: + result: dict = {} + result["entries"] = from_list(from_str, self.entries) + return result + +@dataclass +class SessionFSReaddirRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirRequest(path, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + return result + +class SessionFSReaddirWithTypesEntryType(Enum): + """Entry type""" + + DIRECTORY = "directory" + FILE = "file" + +@dataclass +class SessionFSReaddirWithTypesRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirWithTypesRequest(path, session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + return result + +@dataclass +class SessionFSRmRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + force: bool | None = None + """Ignore errors if the path does not exist""" + + recursive: bool | None = None + """Remove directories and their contents recursively""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSRmRequest': + assert isinstance(obj, dict) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + force = from_union([from_bool, from_none], obj.get("force")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSRmRequest(path, session_id, force, recursive) + + def to_dict(self) -> dict: + result: dict = {} + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.force is not None: + result["force"] = from_union([from_bool, from_none], self.force) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) + return result + +@dataclass +class SessionFSRenameRequest: + dest: str + """Destination path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + src: str + """Source path using SessionFs conventions""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSRenameRequest': + assert isinstance(obj, dict) + dest = from_str(obj.get("dest")) + session_id = from_str(obj.get("sessionId")) + src = from_str(obj.get("src")) + return SessionFSRenameRequest(dest, session_id, src) + + def to_dict(self) -> dict: + result: dict = {} + result["dest"] = from_str(self.dest) + result["sessionId"] = from_str(self.session_id) + result["src"] = from_str(self.src) + return result + +@dataclass +class ModelCapabilitiesLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + """Maximum number of output/completion tokens""" + + max_prompt_tokens: int | None = None + """Maximum number of prompt/input tokens""" + + vision: PurpleModelCapabilitiesLimitsVision | None = None + """Vision-specific limits""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': + assert isinstance(obj, dict) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([PurpleModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesLimitsVision, x), from_none], self.vision) + return result + +@dataclass +class MCPServerConfig: + """MCP server configuration (local/stdio or remote/http)""" + + args: list[str] | None = None + command: str | None = None + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + headers: dict[str, str] | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfig': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + command = from_union([from_str, from_none], obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +@dataclass +class MCPServerConfigValue: + """MCP server configuration (local/stdio or remote/http)""" + + args: list[str] | None = None + command: str | None = None + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + headers: dict[str, str] | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigValue': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + command = from_union([from_str, from_none], obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPServerConfigValue(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +@dataclass +class MCPConfigAddRequestMCPServerConfig: + """MCP server configuration (local/stdio or remote/http)""" + + args: list[str] | None = None + command: str | None = None + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + headers: dict[str, str] | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigAddRequestMCPServerConfig': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + command = from_union([from_str, from_none], obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPConfigAddRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +@dataclass +class MCPConfigUpdateRequestMCPServerConfig: + """MCP server configuration (local/stdio or remote/http)""" + + args: list[str] | None = None + command: str | None = None + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + headers: dict[str, str] | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + url: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigUpdateRequestMCPServerConfig': + assert isinstance(obj, dict) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + command = from_union([from_str, from_none], obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPConfigUpdateRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + + def to_dict(self) -> dict: + result: dict = {} + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) + return result + +@dataclass +class DiscoveredMCPServer: + enabled: bool + """Whether the server is enabled (not in the disabled list)""" + + name: str + """Server name (config key)""" + + source: MCPServerSource + """Configuration source""" + + type: DiscoveredMCPServerType | None = None + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + + @staticmethod + def from_dict(obj: Any) -> 'DiscoveredMCPServer': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = MCPServerSource(obj.get("source")) + type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) + return DiscoveredMCPServer(enabled, name, source, type) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = to_enum(MCPServerSource, self.source) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) + return result + +@dataclass +class ServerElement: + enabled: bool + """Whether the server is enabled (not in the disabled list)""" + + name: str + """Server name (config key)""" + + source: MCPServerSource + """Configuration source""" + + type: DiscoveredMCPServerType | None = None + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + + @staticmethod + def from_dict(obj: Any) -> 'ServerElement': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = MCPServerSource(obj.get("source")) + type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) + return ServerElement(enabled, name, source, type) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = to_enum(MCPServerSource, self.source) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) + return result + +@dataclass +class ServerSkillList: + skills: list[SkillElement] + """All discovered skills across all sources""" + + @staticmethod + def from_dict(obj: Any) -> 'ServerSkillList': + assert isinstance(obj, dict) + skills = from_list(SkillElement.from_dict, obj.get("skills")) + return ServerSkillList(skills) + + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(SkillElement, x), self.skills) + return result + +@dataclass +class ModelCapabilitiesOverrideLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None + vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': + assert isinstance(obj, dict) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) + return result + +@dataclass +class MCPServer: + name: str + """Server name (config key)""" + + status: MCPServerStatus + """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" + + error: str | None = None + """Error message if the server failed to connect""" + + source: MCPServerSource | None = None + """Configuration source: user, workspace, plugin, or builtin""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServer': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + status = MCPServerStatus(obj.get("status")) + error = from_union([from_str, from_none], obj.get("error")) + source = from_union([MCPServerSource, from_none], obj.get("source")) + return MCPServer(name, status, error, source) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["status"] = to_enum(MCPServerStatus, self.status) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(MCPServerSource, x), from_none], self.source) + return result + +@dataclass +class UIElicitationStringEnumField: + enum: list[str] + type: UIElicitationStringEnumFieldType + default: str | None = None + description: str | None = None + enum_names: list[str] | None = None + title: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringEnumField': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationStringEnumFieldType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringEnumField(enum, type, default, description, enum_names, title) + + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.enum_names is not None: + result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationArrayEnumFieldItems: + enum: list[str] + type: UIElicitationStringEnumFieldType + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayEnumFieldItems': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationStringEnumFieldType(obj.get("type")) + return UIElicitationArrayEnumFieldItems(enum, type) + + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) + return result + +@dataclass +class UIElicitationStringOneOfField: + one_of: list[UIElicitationStringOneOfFieldOneOf] + type: UIElicitationStringEnumFieldType + default: str | None = None + description: str | None = None + title: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringOneOfField': + assert isinstance(obj, dict) + one_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("oneOf")) + type = UIElicitationStringEnumFieldType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringOneOfField(one_of, type, default, description, title) + + def to_dict(self) -> dict: + result: dict = {} + result["oneOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.one_of) + result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationArrayAnyOfFieldItems: + any_of: list[PurpleUIElicitationArrayAnyOfFieldItemsAnyOf] + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': + assert isinstance(obj, dict) + any_of = from_list(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, obj.get("anyOf")) + return UIElicitationArrayAnyOfFieldItems(any_of) + + def to_dict(self) -> dict: + result: dict = {} + result["anyOf"] = from_list(lambda x: to_class(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf, x), self.any_of) + return result + +@dataclass +class UIElicitationResponse: + """The elicitation response (accept with form values, decline, or cancel)""" + + action: UIElicitationResponseAction + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + + content: dict[str, float | bool | list[str] | str] | None = None + """The form values submitted by the user (present when action is 'accept')""" + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationResponse': + assert isinstance(obj, dict) + action = UIElicitationResponseAction(obj.get("action")) + content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) + return UIElicitationResponse(action, content) + + def to_dict(self) -> dict: + result: dict = {} + result["action"] = to_enum(UIElicitationResponseAction, self.action) + if self.content is not None: + result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) + return result + +@dataclass +class PermissionDecision: + kind: Kind + """The permission request was approved + + Denied because approval rules explicitly blocked it + + Denied because no approval rule matched and user confirmation was unavailable + + Denied by the user during an interactive prompt + + Denied by the organization's content exclusion policy + + Denied by a permission request hook registered by an extension or plugin + """ + rules: list[Any] | None = None + """Rules that denied the request""" + + feedback: str | None = None + """Optional feedback from the user explaining the denial""" + + message: str | None = None + """Human-readable explanation of why the path was excluded + + Optional message from the hook explaining the denial + """ + path: str | None = None + """File path that triggered the exclusion""" + + interrupt: bool | None = None + """Whether to interrupt the current agent turn""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecision': + assert isinstance(obj, dict) + kind = Kind(obj.get("kind")) + rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) + feedback = from_union([from_str, from_none], obj.get("feedback")) + message = from_union([from_str, from_none], obj.get("message")) + path = from_union([from_str, from_none], obj.get("path")) + interrupt = from_union([from_bool, from_none], obj.get("interrupt")) + return PermissionDecision(kind, rules, feedback, message, path, interrupt) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(Kind, self.kind) + if self.rules is not None: + result["rules"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.rules) + if self.feedback is not None: + result["feedback"] = from_union([from_str, from_none], self.feedback) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.interrupt is not None: + result["interrupt"] = from_union([from_bool, from_none], self.interrupt) + return result + +@dataclass +class CapabilitiesLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + """Maximum number of output/completion tokens""" + + max_prompt_tokens: int | None = None + """Maximum number of prompt/input tokens""" + + vision: FluffyModelCapabilitiesLimitsVision | None = None + """Vision-specific limits""" + + @staticmethod + def from_dict(obj: Any) -> 'CapabilitiesLimits': + assert isinstance(obj, dict) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([FluffyModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + return CapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesLimitsVision, x), from_none], self.vision) + return result + +@dataclass +class ToolList: + tools: list[Tool] + """List of available built-in tools with metadata""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolList': + assert isinstance(obj, dict) + tools = from_list(Tool.from_dict, obj.get("tools")) + return ToolList(tools) + + def to_dict(self) -> dict: + result: dict = {} + result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) + return result + +@dataclass +class ToolsHandlePendingToolCallRequest: + request_id: str + """Request ID of the pending tool call""" + + error: str | None = None + """Error message if the tool call failed""" + + result: ToolCallResult | str | None = None + """Tool call result (string or expanded result object)""" + + @staticmethod + def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) + return ToolsHandlePendingToolCallRequest(request_id, error, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result is not None: + result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + return result + +@dataclass +class AccountGetQuotaResult: + quota_snapshots: dict[str, AccountQuotaSnapshot] + """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" + + @staticmethod + def from_dict(obj: Any) -> 'AccountGetQuotaResult': + assert isinstance(obj, dict) + quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) + return AccountGetQuotaResult(quota_snapshots) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) - if self.default is not None: - result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) + result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) + return result + +@dataclass +class SessionFSSetProviderRequest: + conventions: SessionFSSetProviderConventions + """Path conventions used by this filesystem""" + + initial_cwd: str + """Initial working directory for sessions""" + + session_state_path: str + """Path within each session's SessionFs where the runtime stores files for that session""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': + assert isinstance(obj, dict) + conventions = SessionFSSetProviderConventions(obj.get("conventions")) + initial_cwd = from_str(obj.get("initialCwd")) + session_state_path = from_str(obj.get("sessionStatePath")) + return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) + + def to_dict(self) -> dict: + result: dict = {} + result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) + result["initialCwd"] = from_str(self.initial_cwd) + result["sessionStatePath"] = from_str(self.session_state_path) + return result + +@dataclass +class ModelCapabilitiesLimitsClass: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None + vision: FluffyModelCapabilitiesOverrideLimitsVision | None = None + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsClass': + assert isinstance(obj, dict) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([FluffyModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesLimitsClass(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) + return result + +@dataclass +class ModeSetRequest: + mode: SessionMode + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" + + @staticmethod + def from_dict(obj: Any) -> 'ModeSetRequest': + assert isinstance(obj, dict) + mode = SessionMode(obj.get("mode")) + return ModeSetRequest(mode) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(SessionMode, self.mode) + return result + +@dataclass +class Workspace: + id: UUID + branch: str | None = None + chronicle_sync_dismissed: bool | None = None + created_at: datetime | None = None + cwd: str | None = None + git_root: str | None = None + host_type: HostType | None = None + mc_last_event_id: str | None = None + mc_session_id: str | None = None + mc_task_id: str | None = None + name: str | None = None + pr_create_sync_dismissed: bool | None = None + repository: str | None = None + session_sync_level: SessionSyncLevel | None = None + summary: str | None = None + summary_count: int | None = None + updated_at: datetime | None = None + + @staticmethod + def from_dict(obj: Any) -> 'Workspace': + assert isinstance(obj, dict) + id = UUID(obj.get("id")) + branch = from_union([from_str, from_none], obj.get("branch")) + chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) + created_at = from_union([from_datetime, from_none], obj.get("created_at")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + git_root = from_union([from_str, from_none], obj.get("git_root")) + host_type = from_union([HostType, from_none], obj.get("host_type")) + mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) + mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) + mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) + name = from_union([from_str, from_none], obj.get("name")) + pr_create_sync_dismissed = from_union([from_bool, from_none], obj.get("pr_create_sync_dismissed")) + repository = from_union([from_str, from_none], obj.get("repository")) + session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) + summary = from_union([from_str, from_none], obj.get("summary")) + summary_count = from_union([from_int, from_none], obj.get("summary_count")) + updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, pr_create_sync_dismissed, repository, session_sync_level, summary, summary_count, updated_at) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = str(self.id) + if self.branch is not None: + result["branch"] = from_union([from_str, from_none], self.branch) + if self.chronicle_sync_dismissed is not None: + result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) + if self.created_at is not None: + result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.git_root is not None: + result["git_root"] = from_union([from_str, from_none], self.git_root) + if self.host_type is not None: + result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + if self.mc_last_event_id is not None: + result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) + if self.mc_session_id is not None: + result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) + if self.mc_task_id is not None: + result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.pr_create_sync_dismissed is not None: + result["pr_create_sync_dismissed"] = from_union([from_bool, from_none], self.pr_create_sync_dismissed) + if self.repository is not None: + result["repository"] = from_union([from_str, from_none], self.repository) + if self.session_sync_level is not None: + result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) + if self.summary is not None: + result["summary"] = from_union([from_str, from_none], self.summary) + if self.summary_count is not None: + result["summary_count"] = from_union([from_int, from_none], self.summary_count) + if self.updated_at is not None: + result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + return result + +@dataclass +class InstructionsSources: + content: str + """Raw content of the instruction file""" + + id: str + """Unique identifier for this source (used for toggling)""" + + label: str + """Human-readable label""" + + location: InstructionsSourcesLocation + """Where this source lives — used for UI grouping""" + + source_path: str + """File path relative to repo or absolute for home""" + + type: InstructionsSourcesType + """Category of instruction source — used for merge logic""" + + apply_to: str | None = None + """Glob pattern from frontmatter — when set, this instruction applies only to matching files""" + + description: str | None = None + """Short description (body after frontmatter) for use in instruction tables""" + + @staticmethod + def from_dict(obj: Any) -> 'InstructionsSources': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + id = from_str(obj.get("id")) + label = from_str(obj.get("label")) + location = InstructionsSourcesLocation(obj.get("location")) + source_path = from_str(obj.get("sourcePath")) + type = InstructionsSourcesType(obj.get("type")) + apply_to = from_union([from_str, from_none], obj.get("applyTo")) + description = from_union([from_str, from_none], obj.get("description")) + return InstructionsSources(content, id, label, location, source_path, type, apply_to, description) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + result["id"] = from_str(self.id) + result["label"] = from_str(self.label) + result["location"] = to_enum(InstructionsSourcesLocation, self.location) + result["sourcePath"] = from_str(self.source_path) + result["type"] = to_enum(InstructionsSourcesType, self.type) + if self.apply_to is not None: + result["applyTo"] = from_union([from_str, from_none], self.apply_to) if self.description is not None: result["description"] = from_union([from_str, from_none], self.description) - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.enum_names is not None: - result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.one_of is not None: - result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.one_of) - if self.items is not None: - result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) - if self.max_items is not None: - result["maxItems"] = from_union([to_float, from_none], self.max_items) - if self.min_items is not None: - result["minItems"] = from_union([to_float, from_none], self.min_items) - if self.format is not None: - result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) - if self.max_length is not None: - result["maxLength"] = from_union([to_float, from_none], self.max_length) - if self.min_length is not None: - result["minLength"] = from_union([to_float, from_none], self.min_length) - if self.maximum is not None: - result["maximum"] = from_union([to_float, from_none], self.maximum) - if self.minimum is not None: - result["minimum"] = from_union([to_float, from_none], self.minimum) return result -class RequestedSchemaType(Enum): - OBJECT = "object" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentList: + agents: list[AgentListAgent] + """Available custom agents""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentList': + assert isinstance(obj, dict) + agents = from_list(AgentListAgent.from_dict, obj.get("agents")) + return AgentList(agents) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(AgentListAgent, x), self.agents) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentSelectResult: + agent: AgentSelectResultAgent + """The newly selected custom agent""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentSelectResult': + assert isinstance(obj, dict) + agent = AgentSelectResultAgent.from_dict(obj.get("agent")) + return AgentSelectResult(agent) + + def to_dict(self) -> dict: + result: dict = {} + result["agent"] = to_class(AgentSelectResultAgent, self.agent) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentGetCurrentResult: + agent: AgentReloadResultAgent | None = None + """Currently selected custom agent, or null if using the default agent""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentGetCurrentResult': + assert isinstance(obj, dict) + agent = from_union([AgentReloadResultAgent.from_dict, from_none], obj.get("agent")) + return AgentGetCurrentResult(agent) + + def to_dict(self) -> dict: + result: dict = {} + if self.agent is not None: + result["agent"] = from_union([lambda x: to_class(AgentReloadResultAgent, x), from_none], self.agent) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentReloadResult: + agents: list[AgentReloadResultAgent] + """Reloaded custom agents""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentReloadResult': + assert isinstance(obj, dict) + agents = from_list(AgentReloadResultAgent.from_dict, obj.get("agents")) + return AgentReloadResult(agents) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(AgentReloadResultAgent, x), self.agents) + return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIElicitationSchema: - """JSON Schema describing the form fields to present to the user""" +class SkillList: + skills: list[Skill] + """Available skills""" - properties: dict[str, UIElicitationSchemaProperty] - """Form field definitions, keyed by field name""" + @staticmethod + def from_dict(obj: Any) -> 'SkillList': + assert isinstance(obj, dict) + skills = from_list(Skill.from_dict, obj.get("skills")) + return SkillList(skills) - type: RequestedSchemaType - """Schema type indicator (always 'object')""" + def to_dict(self) -> dict: + result: dict = {} + result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills) + return result - required: list[str] | None = None - """List of required field names""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class PluginList: + plugins: list[Plugin] + """Installed plugins""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationSchema': + def from_dict(obj: Any) -> 'PluginList': assert isinstance(obj, dict) - properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) - type = RequestedSchemaType(obj.get("type")) - required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) - return UIElicitationSchema(properties, type, required) + plugins = from_list(Plugin.from_dict, obj.get("plugins")) + return PluginList(plugins) def to_dict(self) -> dict: result: dict = {} - result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) - result["type"] = to_enum(RequestedSchemaType, self.type) - if self.required is not None: - result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) + result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) return result @dataclass -class UIElicitationRequest: - message: str - """Message describing what information is needed from the user""" +class Extension: + id: str + """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" - requested_schema: UIElicitationSchema - """JSON Schema describing the form fields to present to the user""" + name: str + """Extension name (directory name)""" + + source: ExtensionSource + """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" + + status: ExtensionStatus + """Current status: running, disabled, failed, or starting""" + + pid: int | None = None + """Process ID if the extension is running""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationRequest': + def from_dict(obj: Any) -> 'Extension': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema")) - return UIElicitationRequest(message, requested_schema) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = ExtensionSource(obj.get("source")) + status = ExtensionStatus(obj.get("status")) + pid = from_union([from_int, from_none], obj.get("pid")) + return Extension(id, name, source, status, pid) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionSource, self.source) + result["status"] = to_enum(ExtensionStatus, self.status) + if self.pid is not None: + result["pid"] = from_union([from_int, from_none], self.pid) return result @dataclass -class UIElicitationResult: - success: bool - """Whether the response was accepted. False if the request was already resolved by another - client. - """ +class UIElicitationArrayFieldItems: + enum: list[str] | None = None + type: UIElicitationStringEnumFieldType | None = None + any_of: list[FluffyUIElicitationArrayAnyOfFieldItemsAnyOf] | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResult': + def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return UIElicitationResult(success) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + type = from_union([UIElicitationStringEnumFieldType, from_none], obj.get("type")) + any_of = from_union([lambda x: from_list(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) + return UIElicitationArrayFieldItems(enum, type, any_of) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(UIElicitationStringEnumFieldType, x), from_none], self.type) + if self.any_of is not None: + result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) return result @dataclass -class UIHandlePendingElicitationRequest: - request_id: str - """The unique request ID from the elicitation.requested event""" +class LogRequest: + message: str + """Human-readable message""" - result: UIElicitationResponse - """The elicitation response (accept with form values, decline, or cancel)""" + ephemeral: bool | None = None + """When true, the message is transient and not persisted to the session event log on disk""" + + level: SessionLogLevel | None = None + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". + """ + url: str | None = None + """Optional URL the user can open in their browser for more details""" @staticmethod - def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': + def from_dict(obj: Any) -> 'LogRequest': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = UIElicitationResponse.from_dict(obj.get("result")) - return UIHandlePendingElicitationRequest(request_id, result) + message = from_str(obj.get("message")) + ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) + level = from_union([SessionLogLevel, from_none], obj.get("level")) + url = from_union([from_str, from_none], obj.get("url")) + return LogRequest(message, ephemeral, level, url) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(UIElicitationResponse, self.result) + result["message"] = from_str(self.message) + if self.ephemeral is not None: + result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) + if self.level is not None: + result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) return result @dataclass -class PermissionRequestResult: - success: bool - """Whether the permission request was handled successfully""" +class ShellKillRequest: + process_id: str + """Process identifier returned by shell.exec""" + + signal: ShellKillSignal | None = None + """Signal to send (default: SIGTERM)""" @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestResult': + def from_dict(obj: Any) -> 'ShellKillRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return PermissionRequestResult(success) + process_id = from_str(obj.get("processId")) + signal = from_union([ShellKillSignal, from_none], obj.get("signal")) + return ShellKillRequest(process_id, signal) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["processId"] = from_str(self.process_id) + if self.signal is not None: + result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) return result -class Kind(Enum): - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PermissionDecision: - kind: Kind - """The permission request was approved - - Denied because approval rules explicitly blocked it - - Denied because no approval rule matched and user confirmation was unavailable +class HistoryCompactResult: + messages_removed: int + """Number of messages removed during compaction""" - Denied by the user during an interactive prompt + success: bool + """Whether compaction completed successfully""" - Denied by the organization's content exclusion policy + tokens_removed: int + """Number of tokens freed by compaction""" - Denied by a permission request hook registered by an extension or plugin - """ - rules: list[Any] | None = None - """Rules that denied the request""" + context_window: HistoryCompactContextWindow | None = None + """Post-compaction context window usage breakdown""" - feedback: str | None = None - """Optional feedback from the user explaining the denial""" + @staticmethod + def from_dict(obj: Any) -> 'HistoryCompactResult': + assert isinstance(obj, dict) + messages_removed = from_int(obj.get("messagesRemoved")) + success = from_bool(obj.get("success")) + tokens_removed = from_int(obj.get("tokensRemoved")) + context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) + return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) - message: str | None = None - """Human-readable explanation of why the path was excluded + def to_dict(self) -> dict: + result: dict = {} + result["messagesRemoved"] = from_int(self.messages_removed) + result["success"] = from_bool(self.success) + result["tokensRemoved"] = from_int(self.tokens_removed) + if self.context_window is not None: + result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) + return result - Optional message from the hook explaining the denial - """ - path: str | None = None - """File path that triggered the exclusion""" +@dataclass +class UsageMetricsModelMetric: + requests: UsageMetricsModelMetricRequests + """Request count and cost metrics for this model""" - interrupt: bool | None = None - """Whether to interrupt the current agent turn""" + usage: UsageMetricsModelMetricUsage + """Token usage metrics for this model""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecision': + def from_dict(obj: Any) -> 'UsageMetricsModelMetric': assert isinstance(obj, dict) - kind = Kind(obj.get("kind")) - rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) - feedback = from_union([from_str, from_none], obj.get("feedback")) - message = from_union([from_str, from_none], obj.get("message")) - path = from_union([from_str, from_none], obj.get("path")) - interrupt = from_union([from_bool, from_none], obj.get("interrupt")) - return PermissionDecision(kind, rules, feedback, message, path, interrupt) + requests = UsageMetricsModelMetricRequests.from_dict(obj.get("requests")) + usage = UsageMetricsModelMetricUsage.from_dict(obj.get("usage")) + return UsageMetricsModelMetric(requests, usage) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(Kind, self.kind) - if self.rules is not None: - result["rules"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.rules) - if self.feedback is not None: - result["feedback"] = from_union([from_str, from_none], self.feedback) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.interrupt is not None: - result["interrupt"] = from_union([from_bool, from_none], self.interrupt) + result["requests"] = to_class(UsageMetricsModelMetricRequests, self.requests) + result["usage"] = to_class(UsageMetricsModelMetricUsage, self.usage) return result @dataclass -class PermissionDecisionRequest: - request_id: str - """Request ID of the pending permission request""" +class SessionFSReaddirWithTypesEntry: + name: str + """Entry name""" - result: PermissionDecision + type: SessionFSReaddirWithTypesEntryType + """Entry type""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionRequest': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = PermissionDecision.from_dict(obj.get("result")) - return PermissionDecisionRequest(request_id, result) + name = from_str(obj.get("name")) + type = SessionFSReaddirWithTypesEntryType(obj.get("type")) + return SessionFSReaddirWithTypesEntry(name, type) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionDecision, self.result) + result["name"] = from_str(self.name) + result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) return result @dataclass -class LogResult: - event_id: UUID - """The unique identifier of the emitted session event""" +class MCPConfigList: + servers: dict[str, MCPServerConfigValue] + """All MCP servers from user config, keyed by name""" @staticmethod - def from_dict(obj: Any) -> 'LogResult': + def from_dict(obj: Any) -> 'MCPConfigList': assert isinstance(obj, dict) - event_id = UUID(obj.get("eventId")) - return LogResult(event_id) + servers = from_dict(MCPServerConfigValue.from_dict, obj.get("servers")) + return MCPConfigList(servers) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = str(self.event_id) + result["servers"] = from_dict(lambda x: to_class(MCPServerConfigValue, x), self.servers) return result -class SessionLogLevel(Enum): - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - ERROR = "error" - INFO = "info" - WARNING = "warning" - @dataclass -class LogRequest: - message: str - """Human-readable message""" - - ephemeral: bool | None = None - """When true, the message is transient and not persisted to the session event log on disk""" +class MCPConfigAddRequest: + config: MCPConfigAddRequestMCPServerConfig + """MCP server configuration (local/stdio or remote/http)""" - level: SessionLogLevel | None = None - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - url: str | None = None - """Optional URL the user can open in their browser for more details""" + name: str + """Unique name for the MCP server""" @staticmethod - def from_dict(obj: Any) -> 'LogRequest': + def from_dict(obj: Any) -> 'MCPConfigAddRequest': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - level = from_union([SessionLogLevel, from_none], obj.get("level")) - url = from_union([from_str, from_none], obj.get("url")) - return LogRequest(message, ephemeral, level, url) + config = MCPConfigAddRequestMCPServerConfig.from_dict(obj.get("config")) + name = from_str(obj.get("name")) + return MCPConfigAddRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.ephemeral is not None: - result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) - if self.level is not None: - result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["config"] = to_class(MCPConfigAddRequestMCPServerConfig, self.config) + result["name"] = from_str(self.name) return result @dataclass -class ShellExecResult: - process_id: str - """Unique identifier for tracking streamed output""" +class MCPConfigUpdateRequest: + config: MCPConfigUpdateRequestMCPServerConfig + """MCP server configuration (local/stdio or remote/http)""" + + name: str + """Name of the MCP server to update""" @staticmethod - def from_dict(obj: Any) -> 'ShellExecResult': + def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - return ShellExecResult(process_id) + config = MCPConfigUpdateRequestMCPServerConfig.from_dict(obj.get("config")) + name = from_str(obj.get("name")) + return MCPConfigUpdateRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) + result["config"] = to_class(MCPConfigUpdateRequestMCPServerConfig, self.config) + result["name"] = from_str(self.name) return result @dataclass -class ShellExecRequest: - command: str - """Shell command to execute""" - - cwd: str | None = None - """Working directory (defaults to session working directory)""" - - timeout: int | None = None - """Timeout in milliseconds (default: 30000)""" +class MCPDiscoverResult: + servers: list[ServerElement] + """MCP servers discovered from all sources""" @staticmethod - def from_dict(obj: Any) -> 'ShellExecRequest': + def from_dict(obj: Any) -> 'MCPDiscoverResult': assert isinstance(obj, dict) - command = from_str(obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - return ShellExecRequest(command, cwd, timeout) + servers = from_list(ServerElement.from_dict, obj.get("servers")) + return MCPDiscoverResult(servers) def to_dict(self) -> dict: result: dict = {} - result["command"] = from_str(self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) + result["servers"] = from_list(lambda x: to_class(ServerElement, x), self.servers) return result @dataclass -class ShellKillResult: - killed: bool - """Whether the signal was sent successfully""" +class ModelCapabilitiesOverride: + """Override individual model capabilities resolved by the runtime""" + + limits: ModelCapabilitiesOverrideLimits | None = None + """Token limits for prompts, outputs, and context window""" + + supports: ModelCapabilitiesOverrideSupports | None = None + """Feature flags indicating what the model supports""" @staticmethod - def from_dict(obj: Any) -> 'ShellKillResult': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverride': assert isinstance(obj, dict) - killed = from_bool(obj.get("killed")) - return ShellKillResult(killed) + limits = from_union([ModelCapabilitiesOverrideLimits.from_dict, from_none], obj.get("limits")) + supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) + return ModelCapabilitiesOverride(limits, supports) def to_dict(self) -> dict: result: dict = {} - result["killed"] = from_bool(self.killed) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimits, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) return result -class ShellKillSignal(Enum): - """Signal to send (default: SIGTERM)""" - - SIGINT = "SIGINT" - SIGKILL = "SIGKILL" - SIGTERM = "SIGTERM" - @dataclass -class ShellKillRequest: - process_id: str - """Process identifier returned by shell.exec""" - - signal: ShellKillSignal | None = None - """Signal to send (default: SIGTERM)""" +class MCPServerList: + servers: list[MCPServer] + """Configured MCP servers""" @staticmethod - def from_dict(obj: Any) -> 'ShellKillRequest': + def from_dict(obj: Any) -> 'MCPServerList': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - signal = from_union([ShellKillSignal, from_none], obj.get("signal")) - return ShellKillRequest(process_id, signal) + servers = from_list(MCPServer.from_dict, obj.get("servers")) + return MCPServerList(servers) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) - if self.signal is not None: - result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) + result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) return result @dataclass -class HistoryCompactContextWindow: - """Post-compaction context window usage breakdown""" - - current_tokens: int - """Current total tokens in the context window (system + conversation + tool definitions)""" - - messages_length: int - """Current number of messages in the conversation""" - - token_limit: int - """Maximum token count for the model's context window""" - - conversation_tokens: int | None = None - """Token count from non-system messages (user, assistant, tool)""" - - system_tokens: int | None = None - """Token count from system message(s)""" - - tool_definitions_tokens: int | None = None - """Token count from tool definitions""" +class UIElicitationArrayEnumField: + items: UIElicitationArrayEnumFieldItems + type: UIElicitationArrayEnumFieldType + default: list[str] | None = None + description: str | None = None + max_items: float | None = None + min_items: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactContextWindow': + def from_dict(obj: Any) -> 'UIElicitationArrayEnumField': assert isinstance(obj, dict) - current_tokens = from_int(obj.get("currentTokens")) - messages_length = from_int(obj.get("messagesLength")) - token_limit = from_int(obj.get("tokenLimit")) - conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) - system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) - return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayEnumFieldType(obj.get("type")) + default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationArrayEnumField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["currentTokens"] = from_int(self.current_tokens) - result["messagesLength"] = from_int(self.messages_length) - result["tokenLimit"] = from_int(self.token_limit) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) + result["items"] = to_class(UIElicitationArrayEnumFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) + if self.default is not None: + result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactResult: - messages_removed: int - """Number of messages removed during compaction""" - - success: bool - """Whether compaction completed successfully""" - - tokens_removed: int - """Number of tokens freed by compaction""" - - context_window: HistoryCompactContextWindow | None = None - """Post-compaction context window usage breakdown""" +class UIElicitationArrayAnyOfField: + items: UIElicitationArrayAnyOfFieldItems + type: UIElicitationArrayEnumFieldType + default: list[str] | None = None + description: str | None = None + max_items: float | None = None + min_items: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactResult': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfField': assert isinstance(obj, dict) - messages_removed = from_int(obj.get("messagesRemoved")) - success = from_bool(obj.get("success")) - tokens_removed = from_int(obj.get("tokensRemoved")) - context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) - return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) + items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayEnumFieldType(obj.get("type")) + default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationArrayAnyOfField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["messagesRemoved"] = from_int(self.messages_removed) - result["success"] = from_bool(self.success) - result["tokensRemoved"] = from_int(self.tokens_removed) - if self.context_window is not None: - result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) + result["items"] = to_class(UIElicitationArrayAnyOfFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) + if self.default is not None: + result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateResult: - events_removed: int - """Number of events that were removed""" +class UIHandlePendingElicitationRequest: + request_id: str + """The unique request ID from the elicitation.requested event""" + + result: UIElicitationResponse + """The elicitation response (accept with form values, decline, or cancel)""" @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateResult': + def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': assert isinstance(obj, dict) - events_removed = from_int(obj.get("eventsRemoved")) - return HistoryTruncateResult(events_removed) + request_id = from_str(obj.get("requestId")) + result = UIElicitationResponse.from_dict(obj.get("result")) + return UIHandlePendingElicitationRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} - result["eventsRemoved"] = from_int(self.events_removed) + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(UIElicitationResponse, self.result) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateRequest: - event_id: str - """Event ID to truncate to. This event and all events after it are removed from the session.""" +class PermissionDecisionRequest: + request_id: str + """Request ID of the pending permission request""" + + result: PermissionDecision @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateRequest': + def from_dict(obj: Any) -> 'PermissionDecisionRequest': assert isinstance(obj, dict) - event_id = from_str(obj.get("eventId")) - return HistoryTruncateRequest(event_id) + request_id = from_str(obj.get("requestId")) + result = PermissionDecision.from_dict(obj.get("result")) + return PermissionDecisionRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = from_str(self.event_id) + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionDecision, self.result) return result @dataclass -class UsageMetricsCodeChanges: - """Aggregated code change metrics""" - - files_modified_count: int - """Number of distinct files modified""" +class ModelCapabilitiesClass: + """Override individual model capabilities resolved by the runtime""" - lines_added: int - """Total lines of code added""" + limits: ModelCapabilitiesLimitsClass | None = None + """Token limits for prompts, outputs, and context window""" - lines_removed: int - """Total lines of code removed""" + supports: ModelCapabilitiesOverrideSupports | None = None + """Feature flags indicating what the model supports""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': + def from_dict(obj: Any) -> 'ModelCapabilitiesClass': assert isinstance(obj, dict) - files_modified_count = from_int(obj.get("filesModifiedCount")) - lines_added = from_int(obj.get("linesAdded")) - lines_removed = from_int(obj.get("linesRemoved")) - return UsageMetricsCodeChanges(files_modified_count, lines_added, lines_removed) + limits = from_union([ModelCapabilitiesLimitsClass.from_dict, from_none], obj.get("limits")) + supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) + return ModelCapabilitiesClass(limits, supports) def to_dict(self) -> dict: result: dict = {} - result["filesModifiedCount"] = from_int(self.files_modified_count) - result["linesAdded"] = from_int(self.lines_added) - result["linesRemoved"] = from_int(self.lines_removed) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsClass, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) return result @dataclass -class UsageMetricsModelMetricRequests: - """Request count and cost metrics for this model""" - - cost: float - """User-initiated premium request cost (with multiplier applied)""" - - count: int - """Number of API requests made with this model""" +class WorkspacesGetWorkspaceResult: + workspace: Workspace | None = None + """Current workspace metadata, or null if not available""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsModelMetricRequests': + def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': assert isinstance(obj, dict) - cost = from_float(obj.get("cost")) - count = from_int(obj.get("count")) - return UsageMetricsModelMetricRequests(cost, count) + workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) + return WorkspacesGetWorkspaceResult(workspace) def to_dict(self) -> dict: result: dict = {} - result["cost"] = to_float(self.cost) - result["count"] = from_int(self.count) + result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result @dataclass -class UsageMetricsModelMetricUsage: - """Token usage metrics for this model""" - - cache_read_tokens: int - """Total tokens read from prompt cache""" +class InstructionsGetSourcesResult: + sources: list[InstructionsSources] + """Instruction sources for the session""" - cache_write_tokens: int - """Total tokens written to prompt cache""" - - input_tokens: int - """Total input tokens consumed""" + @staticmethod + def from_dict(obj: Any) -> 'InstructionsGetSourcesResult': + assert isinstance(obj, dict) + sources = from_list(InstructionsSources.from_dict, obj.get("sources")) + return InstructionsGetSourcesResult(sources) - output_tokens: int - """Total output tokens produced""" + def to_dict(self) -> dict: + result: dict = {} + result["sources"] = from_list(lambda x: to_class(InstructionsSources, x), self.sources) + return result - reasoning_tokens: int | None = None - """Total output tokens used for reasoning""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionList: + extensions: list[Extension] + """Discovered extensions and their current status""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsModelMetricUsage': + def from_dict(obj: Any) -> 'ExtensionList': assert isinstance(obj, dict) - cache_read_tokens = from_int(obj.get("cacheReadTokens")) - cache_write_tokens = from_int(obj.get("cacheWriteTokens")) - input_tokens = from_int(obj.get("inputTokens")) - output_tokens = from_int(obj.get("outputTokens")) - reasoning_tokens = from_union([from_int, from_none], obj.get("reasoningTokens")) - return UsageMetricsModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) + extensions = from_list(Extension.from_dict, obj.get("extensions")) + return ExtensionList(extensions) def to_dict(self) -> dict: result: dict = {} - result["cacheReadTokens"] = from_int(self.cache_read_tokens) - result["cacheWriteTokens"] = from_int(self.cache_write_tokens) - result["inputTokens"] = from_int(self.input_tokens) - result["outputTokens"] = from_int(self.output_tokens) - if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) + result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result @dataclass -class UsageMetricsModelMetric: - requests: UsageMetricsModelMetricRequests - """Request count and cost metrics for this model""" - - usage: UsageMetricsModelMetricUsage - """Token usage metrics for this model""" +class UIElicitationSchemaProperty: + type: UIElicitationSchemaPropertyNumberType + default: float | bool | list[str] | str | None = None + description: str | None = None + enum: list[str] | None = None + enum_names: list[str] | None = None + title: str | None = None + one_of: list[UIElicitationSchemaPropertyOneOf] | None = None + items: UIElicitationArrayFieldItems | None = None + max_items: float | None = None + min_items: float | None = None + format: UIElicitationSchemaPropertyStringFormat | None = None + max_length: float | None = None + min_length: float | None = None + maximum: float | None = None + minimum: float | None = None @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsModelMetric': + def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': assert isinstance(obj, dict) - requests = UsageMetricsModelMetricRequests.from_dict(obj.get("requests")) - usage = UsageMetricsModelMetricUsage.from_dict(obj.get("usage")) - return UsageMetricsModelMetric(requests, usage) + type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) + title = from_union([from_str, from_none], obj.get("title")) + one_of = from_union([lambda x: from_list(UIElicitationSchemaPropertyOneOf.from_dict, x), from_none], obj.get("oneOf")) + items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) + max_length = from_union([from_float, from_none], obj.get("maxLength")) + min_length = from_union([from_float, from_none], obj.get("minLength")) + maximum = from_union([from_float, from_none], obj.get("maximum")) + minimum = from_union([from_float, from_none], obj.get("minimum")) + return UIElicitationSchemaProperty(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) def to_dict(self) -> dict: result: dict = {} - result["requests"] = to_class(UsageMetricsModelMetricRequests, self.requests) - result["usage"] = to_class(UsageMetricsModelMetricUsage, self.usage) + result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + if self.default is not None: + result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.enum_names is not None: + result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + if self.one_of is not None: + result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationSchemaPropertyOneOf, x), x), from_none], self.one_of) + if self.items is not None: + result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.format is not None: + result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) + if self.max_length is not None: + result["maxLength"] = from_union([to_float, from_none], self.max_length) + if self.min_length is not None: + result["minLength"] = from_union([to_float, from_none], self.min_length) + if self.maximum is not None: + result["maximum"] = from_union([to_float, from_none], self.maximum) + if self.minimum is not None: + result["minimum"] = from_union([to_float, from_none], self.minimum) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -2944,418 +3802,408 @@ class UsageGetMetricsResult: @staticmethod def from_dict(obj: Any) -> 'UsageGetMetricsResult': assert isinstance(obj, dict) - code_changes = UsageMetricsCodeChanges.from_dict(obj.get("codeChanges")) - last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) - last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) - model_metrics = from_dict(UsageMetricsModelMetric.from_dict, obj.get("modelMetrics")) - session_start_time = from_int(obj.get("sessionStartTime")) - total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) - total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) - total_user_requests = from_int(obj.get("totalUserRequests")) - current_model = from_union([from_str, from_none], obj.get("currentModel")) - return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) + code_changes = UsageMetricsCodeChanges.from_dict(obj.get("codeChanges")) + last_call_input_tokens = from_int(obj.get("lastCallInputTokens")) + last_call_output_tokens = from_int(obj.get("lastCallOutputTokens")) + model_metrics = from_dict(UsageMetricsModelMetric.from_dict, obj.get("modelMetrics")) + session_start_time = from_int(obj.get("sessionStartTime")) + total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) + total_user_requests = from_int(obj.get("totalUserRequests")) + current_model = from_union([from_str, from_none], obj.get("currentModel")) + return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) + + def to_dict(self) -> dict: + result: dict = {} + result["codeChanges"] = to_class(UsageMetricsCodeChanges, self.code_changes) + result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) + result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) + result["modelMetrics"] = from_dict(lambda x: to_class(UsageMetricsModelMetric, x), self.model_metrics) + result["sessionStartTime"] = from_int(self.session_start_time) + result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) + result["totalUserRequests"] = from_int(self.total_user_requests) + if self.current_model is not None: + result["currentModel"] = from_union([from_str, from_none], self.current_model) + return result + +@dataclass +class SessionFSReaddirWithTypesResult: + entries: list[SessionFSReaddirWithTypesEntry] + """Directory entries with type information""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': + assert isinstance(obj, dict) + entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) + return SessionFSReaddirWithTypesResult(entries) + + def to_dict(self) -> dict: + result: dict = {} + result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + return result + +@dataclass +class UIElicitationSchema: + """JSON Schema describing the form fields to present to the user""" + + properties: dict[str, UIElicitationSchemaProperty] + """Form field definitions, keyed by field name""" + + type: RequestedSchemaType + """Schema type indicator (always 'object')""" + + required: list[str] | None = None + """List of required field names""" + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationSchema': + assert isinstance(obj, dict) + properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) + type = RequestedSchemaType(obj.get("type")) + required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) + return UIElicitationSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} - result["codeChanges"] = to_class(UsageMetricsCodeChanges, self.code_changes) - result["lastCallInputTokens"] = from_int(self.last_call_input_tokens) - result["lastCallOutputTokens"] = from_int(self.last_call_output_tokens) - result["modelMetrics"] = from_dict(lambda x: to_class(UsageMetricsModelMetric, x), self.model_metrics) - result["sessionStartTime"] = from_int(self.session_start_time) - result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) - result["totalPremiumRequestCost"] = to_float(self.total_premium_request_cost) - result["totalUserRequests"] = from_int(self.total_user_requests) - if self.current_model is not None: - result["currentModel"] = from_union([from_str, from_none], self.current_model) + result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) + result["type"] = to_enum(RequestedSchemaType, self.type) + if self.required is not None: + result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result @dataclass -class SessionFSReadFileResult: - content: str - """File content as UTF-8 string""" +class UIElicitationRequest: + message: str + """Message describing what information is needed from the user""" + + requested_schema: UIElicitationSchema + """JSON Schema describing the form fields to present to the user""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileResult': + def from_dict(obj: Any) -> 'UIElicitationRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - return SessionFSReadFileResult(content) + message = from_str(obj.get("message")) + requested_schema = UIElicitationSchema.from_dict(obj.get("requestedSchema")) + return UIElicitationRequest(message, requested_schema) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) + result["message"] = from_str(self.message) + result["requestedSchema"] = to_class(UIElicitationSchema, self.requested_schema) return result @dataclass -class SessionFSReadFileRequest: - path: str - """Path using SessionFs conventions""" +class ModelCapabilities: + """Model capabilities and limits""" - session_id: str - """Target session identifier""" + limits: ModelCapabilitiesLimits | None = None + """Token limits for prompts, outputs, and context window""" + + supports: ModelCapabilitiesSupports | None = None + """Feature flags indicating what the model supports""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileRequest': + def from_dict(obj: Any) -> 'ModelCapabilities': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReadFileRequest(path, session_id) + limits = from_union([ModelCapabilitiesLimits.from_dict, from_none], obj.get("limits")) + supports = from_union([ModelCapabilitiesSupports.from_dict, from_none], obj.get("supports")) + return ModelCapabilities(limits, supports) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimits, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) return result @dataclass -class SessionFSWriteFileRequest: - content: str - """Content to write""" - - path: str - """Path using SessionFs conventions""" +class CapabilitiesClass: + """Model capabilities and limits""" - session_id: str - """Target session identifier""" + limits: CapabilitiesLimits | None = None + """Token limits for prompts, outputs, and context window""" - mode: int | None = None - """Optional POSIX-style mode for newly created files""" + supports: CapabilitiesSupports | None = None + """Feature flags indicating what the model supports""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': + def from_dict(obj: Any) -> 'CapabilitiesClass': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSWriteFileRequest(content, path, session_id, mode) + limits = from_union([CapabilitiesLimits.from_dict, from_none], obj.get("limits")) + supports = from_union([CapabilitiesSupports.from_dict, from_none], obj.get("supports")) + return CapabilitiesClass(limits, supports) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(CapabilitiesLimits, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(CapabilitiesSupports, x), from_none], self.supports) return result @dataclass -class SessionFSAppendFileRequest: - content: str - """Content to append""" +class Model: + capabilities: CapabilitiesClass + """Model capabilities and limits""" - path: str - """Path using SessionFs conventions""" + id: str + """Model identifier (e.g., "claude-sonnet-4.5")""" - session_id: str - """Target session identifier""" + name: str + """Display name""" - mode: int | None = None - """Optional POSIX-style mode for newly created files""" + billing: ModelBilling | None = None + """Billing information""" + + default_reasoning_effort: str | None = None + """Default reasoning effort level (only present if model supports reasoning effort)""" + + policy: ModelPolicy | None = None + """Policy state (if applicable)""" + + supported_reasoning_efforts: list[str] | None = None + """Supported reasoning effort levels (only present if model supports reasoning effort)""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': + def from_dict(obj: Any) -> 'Model': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSAppendFileRequest(content, path, session_id, mode) + capabilities = CapabilitiesClass.from_dict(obj.get("capabilities")) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) + default_reasoning_effort = from_union([from_str, from_none], obj.get("defaultReasoningEffort")) + policy = from_union([ModelPolicy.from_dict, from_none], obj.get("policy")) + supported_reasoning_efforts = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supportedReasoningEfforts")) + return Model(capabilities, id, name, billing, default_reasoning_effort, policy, supported_reasoning_efforts) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) + result["capabilities"] = to_class(CapabilitiesClass, self.capabilities) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + if self.billing is not None: + result["billing"] = from_union([lambda x: to_class(ModelBilling, x), from_none], self.billing) + if self.default_reasoning_effort is not None: + result["defaultReasoningEffort"] = from_union([from_str, from_none], self.default_reasoning_effort) + if self.policy is not None: + result["policy"] = from_union([lambda x: to_class(ModelPolicy, x), from_none], self.policy) + if self.supported_reasoning_efforts is not None: + result["supportedReasoningEfforts"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_reasoning_efforts) return result @dataclass -class SessionFSExistsResult: - exists: bool - """Whether the path exists""" +class ModelList: + models: list[Model] + """List of available models with full metadata""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsResult': + def from_dict(obj: Any) -> 'ModelList': assert isinstance(obj, dict) - exists = from_bool(obj.get("exists")) - return SessionFSExistsResult(exists) + models = from_list(Model.from_dict, obj.get("models")) + return ModelList(models) def to_dict(self) -> dict: result: dict = {} - result["exists"] = from_bool(self.exists) + result["models"] = from_list(lambda x: to_class(Model, x), self.models) return result @dataclass -class SessionFSExistsRequest: - path: str - """Path using SessionFs conventions""" +class ModelSwitchToRequest: + model_id: str + """Model identifier to switch to""" - session_id: str - """Target session identifier""" + model_capabilities: ModelCapabilitiesClass | None = None + """Override individual model capabilities resolved by the runtime""" + + reasoning_effort: str | None = None + """Reasoning effort level to use for the model""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsRequest': + def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSExistsRequest(path, session_id) + model_id = from_str(obj.get("modelId")) + model_capabilities = from_union([ModelCapabilitiesClass.from_dict, from_none], obj.get("modelCapabilities")) + reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) + return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["modelId"] = from_str(self.model_id) + if self.model_capabilities is not None: + result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesClass, x), from_none], self.model_capabilities) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) return result -@dataclass -class SessionFSStatResult: - birthtime: datetime - """ISO 8601 timestamp of creation""" +def model_capabilities_from_dict(s: Any) -> ModelCapabilities: + return ModelCapabilities.from_dict(s) - is_directory: bool - """Whether the path is a directory""" +def model_capabilities_to_dict(x: ModelCapabilities) -> Any: + return to_class(ModelCapabilities, x) - is_file: bool - """Whether the path is a file""" +def model_capabilities_limits_vision_from_dict(s: Any) -> ModelCapabilitiesLimitsVision: + return ModelCapabilitiesLimitsVision.from_dict(s) - mtime: datetime - """ISO 8601 timestamp of last modification""" +def model_capabilities_limits_vision_to_dict(x: ModelCapabilitiesLimitsVision) -> Any: + return to_class(ModelCapabilitiesLimitsVision, x) - size: int - """File size in bytes""" +def mcp_server_config_from_dict(s: Any) -> MCPServerConfig: + return MCPServerConfig.from_dict(s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatResult': - assert isinstance(obj, dict) - birthtime = from_datetime(obj.get("birthtime")) - is_directory = from_bool(obj.get("isDirectory")) - is_file = from_bool(obj.get("isFile")) - mtime = from_datetime(obj.get("mtime")) - size = from_int(obj.get("size")) - return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size) +def mcp_server_config_to_dict(x: MCPServerConfig) -> Any: + return to_class(MCPServerConfig, x) - def to_dict(self) -> dict: - result: dict = {} - result["birthtime"] = self.birthtime.isoformat() - result["isDirectory"] = from_bool(self.is_directory) - result["isFile"] = from_bool(self.is_file) - result["mtime"] = self.mtime.isoformat() - result["size"] = from_int(self.size) - return result +def filter_mapping_from_dict(s: Any) -> dict[str, FilterMappingString] | FilterMappingString: + return from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], s) -@dataclass -class SessionFSStatRequest: - path: str - """Path using SessionFs conventions""" +def filter_mapping_to_dict(x: dict[str, FilterMappingString] | FilterMappingString) -> Any: + return from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x)], x) - session_id: str - """Target session identifier""" +def discovered_mcp_server_from_dict(s: Any) -> DiscoveredMCPServer: + return DiscoveredMCPServer.from_dict(s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSStatRequest(path, session_id) +def discovered_mcp_server_to_dict(x: DiscoveredMCPServer) -> Any: + return to_class(DiscoveredMCPServer, x) - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - return result +def server_skill_list_from_dict(s: Any) -> ServerSkillList: + return ServerSkillList.from_dict(s) + +def server_skill_list_to_dict(x: ServerSkillList) -> Any: + return to_class(ServerSkillList, x) + +def server_skill_from_dict(s: Any) -> ServerSkill: + return ServerSkill.from_dict(s) + +def server_skill_to_dict(x: ServerSkill) -> Any: + return to_class(ServerSkill, x) + +def current_model_from_dict(s: Any) -> CurrentModel: + return CurrentModel.from_dict(s) + +def current_model_to_dict(x: CurrentModel) -> Any: + return to_class(CurrentModel, x) + +def model_capabilities_override_from_dict(s: Any) -> ModelCapabilitiesOverride: + return ModelCapabilitiesOverride.from_dict(s) + +def model_capabilities_override_to_dict(x: ModelCapabilitiesOverride) -> Any: + return to_class(ModelCapabilitiesOverride, x) + +def session_mode_from_dict(s: Any) -> SessionMode: + return SessionMode(s) -@dataclass -class SessionFSMkdirRequest: - path: str - """Path using SessionFs conventions""" +def session_mode_to_dict(x: SessionMode) -> Any: + return to_enum(SessionMode, x) - session_id: str - """Target session identifier""" +def agent_info_from_dict(s: Any) -> AgentInfo: + return AgentInfo.from_dict(s) - mode: int | None = None - """Optional POSIX-style mode for newly created directories""" +def agent_info_to_dict(x: AgentInfo) -> Any: + return to_class(AgentInfo, x) - recursive: bool | None = None - """Create parent directories as needed""" +def mcp_server_list_from_dict(s: Any) -> MCPServerList: + return MCPServerList.from_dict(s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSMkdirRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSMkdirRequest(path, session_id, mode, recursive) +def mcp_server_list_to_dict(x: MCPServerList) -> Any: + return to_class(MCPServerList, x) - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) - return result +def tool_call_result_from_dict(s: Any) -> ToolCallResult: + return ToolCallResult.from_dict(s) -@dataclass -class SessionFSReaddirResult: - entries: list[str] - """Entry names in the directory""" +def tool_call_result_to_dict(x: ToolCallResult) -> Any: + return to_class(ToolCallResult, x) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirResult': - assert isinstance(obj, dict) - entries = from_list(from_str, obj.get("entries")) - return SessionFSReaddirResult(entries) +def handle_tool_call_result_from_dict(s: Any) -> HandleToolCallResult: + return HandleToolCallResult.from_dict(s) - def to_dict(self) -> dict: - result: dict = {} - result["entries"] = from_list(from_str, self.entries) - return result +def handle_tool_call_result_to_dict(x: HandleToolCallResult) -> Any: + return to_class(HandleToolCallResult, x) -@dataclass -class SessionFSReaddirRequest: - path: str - """Path using SessionFs conventions""" +def ui_elicitation_string_enum_field_from_dict(s: Any) -> UIElicitationStringEnumField: + return UIElicitationStringEnumField.from_dict(s) - session_id: str - """Target session identifier""" +def ui_elicitation_string_enum_field_to_dict(x: UIElicitationStringEnumField) -> Any: + return to_class(UIElicitationStringEnumField, x) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirRequest(path, session_id) +def ui_elicitation_string_one_of_field_from_dict(s: Any) -> UIElicitationStringOneOfField: + return UIElicitationStringOneOfField.from_dict(s) - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - return result +def ui_elicitation_string_one_of_field_to_dict(x: UIElicitationStringOneOfField) -> Any: + return to_class(UIElicitationStringOneOfField, x) -class SessionFSReaddirWithTypesEntryType(Enum): - """Entry type""" +def ui_elicitation_array_enum_field_from_dict(s: Any) -> UIElicitationArrayEnumField: + return UIElicitationArrayEnumField.from_dict(s) - DIRECTORY = "directory" - FILE = "file" +def ui_elicitation_array_enum_field_to_dict(x: UIElicitationArrayEnumField) -> Any: + return to_class(UIElicitationArrayEnumField, x) -@dataclass -class SessionFSReaddirWithTypesEntry: - name: str - """Entry name""" +def ui_elicitation_array_any_of_field_from_dict(s: Any) -> UIElicitationArrayAnyOfField: + return UIElicitationArrayAnyOfField.from_dict(s) - type: SessionFSReaddirWithTypesEntryType - """Entry type""" +def ui_elicitation_array_any_of_field_to_dict(x: UIElicitationArrayAnyOfField) -> Any: + return to_class(UIElicitationArrayAnyOfField, x) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': - assert isinstance(obj, dict) - name = from_str(obj.get("name")) - type = SessionFSReaddirWithTypesEntryType(obj.get("type")) - return SessionFSReaddirWithTypesEntry(name, type) +def ui_elicitation_response_from_dict(s: Any) -> UIElicitationResponse: + return UIElicitationResponse.from_dict(s) - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_str(self.name) - result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) - return result +def ui_elicitation_response_to_dict(x: UIElicitationResponse) -> Any: + return to_class(UIElicitationResponse, x) -@dataclass -class SessionFSReaddirWithTypesResult: - entries: list[SessionFSReaddirWithTypesEntry] - """Directory entries with type information""" +def ui_elicitation_response_action_from_dict(s: Any) -> UIElicitationResponseAction: + return UIElicitationResponseAction(s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': - assert isinstance(obj, dict) - entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) - return SessionFSReaddirWithTypesResult(entries) +def ui_elicitation_response_action_to_dict(x: UIElicitationResponseAction) -> Any: + return to_enum(UIElicitationResponseAction, x) - def to_dict(self) -> dict: - result: dict = {} - result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) - return result +def ui_elicitation_response_content_from_dict(s: Any) -> dict[str, float | bool | list[str] | str]: + return from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), s) -@dataclass -class SessionFSReaddirWithTypesRequest: - path: str - """Path using SessionFs conventions""" +def ui_elicitation_response_content_to_dict(x: dict[str, float | bool | list[str] | str]) -> Any: + return from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x) - session_id: str - """Target session identifier""" +def ui_elicitation_field_value_from_dict(s: Any) -> float | bool | list[str] | str: + return from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirWithTypesRequest(path, session_id) +def ui_elicitation_field_value_to_dict(x: float | bool | list[str] | str) -> Any: + return from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x) - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - return result +def ui_handle_pending_elicitation_request_from_dict(s: Any) -> UIHandlePendingElicitationRequest: + return UIHandlePendingElicitationRequest.from_dict(s) -@dataclass -class SessionFSRmRequest: - path: str - """Path using SessionFs conventions""" +def ui_handle_pending_elicitation_request_to_dict(x: UIHandlePendingElicitationRequest) -> Any: + return to_class(UIHandlePendingElicitationRequest, x) - session_id: str - """Target session identifier""" +def ui_elicitation_result_from_dict(s: Any) -> UIElicitationResult: + return UIElicitationResult.from_dict(s) - force: bool | None = None - """Ignore errors if the path does not exist""" +def ui_elicitation_result_to_dict(x: UIElicitationResult) -> Any: + return to_class(UIElicitationResult, x) - recursive: bool | None = None - """Remove directories and their contents recursively""" +def permission_decision_request_from_dict(s: Any) -> PermissionDecisionRequest: + return PermissionDecisionRequest.from_dict(s) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSRmRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - force = from_union([from_bool, from_none], obj.get("force")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSRmRequest(path, session_id, force, recursive) +def permission_decision_request_to_dict(x: PermissionDecisionRequest) -> Any: + return to_class(PermissionDecisionRequest, x) - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.force is not None: - result["force"] = from_union([from_bool, from_none], self.force) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) - return result +def permission_decision_from_dict(s: Any) -> PermissionDecision: + return PermissionDecision.from_dict(s) -@dataclass -class SessionFSRenameRequest: - dest: str - """Destination path using SessionFs conventions""" +def permission_decision_to_dict(x: PermissionDecision) -> Any: + return to_class(PermissionDecision, x) - session_id: str - """Target session identifier""" +def permission_request_result_from_dict(s: Any) -> PermissionRequestResult: + return PermissionRequestResult.from_dict(s) - src: str - """Source path using SessionFs conventions""" +def permission_request_result_to_dict(x: PermissionRequestResult) -> Any: + return to_class(PermissionRequestResult, x) - @staticmethod - def from_dict(obj: Any) -> 'SessionFSRenameRequest': - assert isinstance(obj, dict) - dest = from_str(obj.get("dest")) - session_id = from_str(obj.get("sessionId")) - src = from_str(obj.get("src")) - return SessionFSRenameRequest(dest, session_id, src) +def session_log_level_from_dict(s: Any) -> SessionLogLevel: + return SessionLogLevel(s) - def to_dict(self) -> dict: - result: dict = {} - result["dest"] = from_str(self.dest) - result["sessionId"] = from_str(self.session_id) - result["src"] = from_str(self.src) - return result +def session_log_level_to_dict(x: SessionLogLevel) -> Any: + return to_enum(SessionLogLevel, x) def ping_result_from_dict(s: Any) -> PingResult: return PingResult.from_dict(s) @@ -3435,12 +4283,6 @@ def skills_config_set_disabled_skills_request_from_dict(s: Any) -> SkillsConfigS def skills_config_set_disabled_skills_request_to_dict(x: SkillsConfigSetDisabledSkillsRequest) -> Any: return to_class(SkillsConfigSetDisabledSkillsRequest, x) -def server_skill_list_from_dict(s: Any) -> ServerSkillList: - return ServerSkillList.from_dict(s) - -def server_skill_list_to_dict(x: ServerSkillList) -> Any: - return to_class(ServerSkillList, x) - def skills_discover_request_from_dict(s: Any) -> SkillsDiscoverRequest: return SkillsDiscoverRequest.from_dict(s) @@ -3471,12 +4313,6 @@ def sessions_fork_request_from_dict(s: Any) -> SessionsForkRequest: def sessions_fork_request_to_dict(x: SessionsForkRequest) -> Any: return to_class(SessionsForkRequest, x) -def current_model_from_dict(s: Any) -> CurrentModel: - return CurrentModel.from_dict(s) - -def current_model_to_dict(x: CurrentModel) -> Any: - return to_class(CurrentModel, x) - def model_switch_to_result_from_dict(s: Any) -> ModelSwitchToResult: return ModelSwitchToResult.from_dict(s) @@ -3489,12 +4325,6 @@ def model_switch_to_request_from_dict(s: Any) -> ModelSwitchToRequest: def model_switch_to_request_to_dict(x: ModelSwitchToRequest) -> Any: return to_class(ModelSwitchToRequest, x) -def session_mode_from_dict(s: Any) -> SessionMode: - return SessionMode(s) - -def session_mode_to_dict(x: SessionMode) -> Any: - return to_enum(SessionMode, x) - def mode_set_request_from_dict(s: Any) -> ModeSetRequest: return ModeSetRequest.from_dict(s) @@ -3555,6 +4385,12 @@ def workspaces_create_file_request_from_dict(s: Any) -> WorkspacesCreateFileRequ def workspaces_create_file_request_to_dict(x: WorkspacesCreateFileRequest) -> Any: return to_class(WorkspacesCreateFileRequest, x) +def instructions_get_sources_result_from_dict(s: Any) -> InstructionsGetSourcesResult: + return InstructionsGetSourcesResult.from_dict(s) + +def instructions_get_sources_result_to_dict(x: InstructionsGetSourcesResult) -> Any: + return to_class(InstructionsGetSourcesResult, x) + def fleet_start_result_from_dict(s: Any) -> FleetStartResult: return FleetStartResult.from_dict(s) @@ -3615,12 +4451,6 @@ def skills_disable_request_from_dict(s: Any) -> SkillsDisableRequest: def skills_disable_request_to_dict(x: SkillsDisableRequest) -> Any: return to_class(SkillsDisableRequest, x) -def mcp_server_list_from_dict(s: Any) -> MCPServerList: - return MCPServerList.from_dict(s) - -def mcp_server_list_to_dict(x: MCPServerList) -> Any: - return to_class(MCPServerList, x) - def mcp_enable_request_from_dict(s: Any) -> MCPEnableRequest: return MCPEnableRequest.from_dict(s) @@ -3657,12 +4487,6 @@ def extensions_disable_request_from_dict(s: Any) -> ExtensionsDisableRequest: def extensions_disable_request_to_dict(x: ExtensionsDisableRequest) -> Any: return to_class(ExtensionsDisableRequest, x) -def handle_tool_call_result_from_dict(s: Any) -> HandleToolCallResult: - return HandleToolCallResult.from_dict(s) - -def handle_tool_call_result_to_dict(x: HandleToolCallResult) -> Any: - return to_class(HandleToolCallResult, x) - def tools_handle_pending_tool_call_request_from_dict(s: Any) -> ToolsHandlePendingToolCallRequest: return ToolsHandlePendingToolCallRequest.from_dict(s) @@ -3681,42 +4505,12 @@ def commands_handle_pending_command_request_from_dict(s: Any) -> CommandsHandleP def commands_handle_pending_command_request_to_dict(x: CommandsHandlePendingCommandRequest) -> Any: return to_class(CommandsHandlePendingCommandRequest, x) -def ui_elicitation_response_from_dict(s: Any) -> UIElicitationResponse: - return UIElicitationResponse.from_dict(s) - -def ui_elicitation_response_to_dict(x: UIElicitationResponse) -> Any: - return to_class(UIElicitationResponse, x) - def ui_elicitation_request_from_dict(s: Any) -> UIElicitationRequest: return UIElicitationRequest.from_dict(s) def ui_elicitation_request_to_dict(x: UIElicitationRequest) -> Any: return to_class(UIElicitationRequest, x) -def ui_elicitation_result_from_dict(s: Any) -> UIElicitationResult: - return UIElicitationResult.from_dict(s) - -def ui_elicitation_result_to_dict(x: UIElicitationResult) -> Any: - return to_class(UIElicitationResult, x) - -def ui_handle_pending_elicitation_request_from_dict(s: Any) -> UIHandlePendingElicitationRequest: - return UIHandlePendingElicitationRequest.from_dict(s) - -def ui_handle_pending_elicitation_request_to_dict(x: UIHandlePendingElicitationRequest) -> Any: - return to_class(UIHandlePendingElicitationRequest, x) - -def permission_request_result_from_dict(s: Any) -> PermissionRequestResult: - return PermissionRequestResult.from_dict(s) - -def permission_request_result_to_dict(x: PermissionRequestResult) -> Any: - return to_class(PermissionRequestResult, x) - -def permission_decision_request_from_dict(s: Any) -> PermissionDecisionRequest: - return PermissionDecisionRequest.from_dict(s) - -def permission_decision_request_to_dict(x: PermissionDecisionRequest) -> Any: - return to_class(PermissionDecisionRequest, x) - def log_result_from_dict(s: Any) -> LogResult: return LogResult.from_dict(s) @@ -4087,6 +4881,15 @@ async def create_file(self, params: WorkspacesCreateFileRequest, *, timeout: flo await self._client.request("session.workspaces.createFile", params_dict, **_timeout_kwargs(timeout)) +class InstructionsApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get_sources(self, *, timeout: float | None = None) -> InstructionsGetSourcesResult: + return InstructionsGetSourcesResult.from_dict(await self._client.request("session.instructions.getSources", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class FleetApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -4302,6 +5105,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.name = NameApi(client, session_id) self.plan = PlanApi(client, session_id) self.workspaces = WorkspacesApi(client, session_id) + self.instructions = InstructionsApi(client, session_id) self.fleet = FleetApi(client, session_id) self.agent = AgentApi(client, session_id) self.skills = SkillsApi(client, session_id) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 784b0bb52..7cbff3039 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -252,27 +252,27 @@ def to_dict(self) -> dict: @dataclass -class SessionStartDataContext: +class WorkingDirectoryContext: "Working directory and git context at session start" cwd: str git_root: str | None = None repository: str | None = None - host_type: SessionStartDataContextHostType | None = None + host_type: WorkingDirectoryContextHostType | None = None branch: str | None = None head_commit: str | None = None base_commit: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionStartDataContext": + def from_dict(obj: Any) -> "WorkingDirectoryContext": assert isinstance(obj, dict) cwd = from_str(obj.get("cwd")) git_root = from_union([from_none, from_str], obj.get("gitRoot")) repository = from_union([from_none, from_str], obj.get("repository")) - host_type = from_union([from_none, lambda x: parse_enum(SessionStartDataContextHostType, x)], obj.get("hostType")) + host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) branch = from_union([from_none, from_str], obj.get("branch")) head_commit = from_union([from_none, from_str], obj.get("headCommit")) base_commit = from_union([from_none, from_str], obj.get("baseCommit")) - return SessionStartDataContext( + return WorkingDirectoryContext( cwd=cwd, git_root=git_root, repository=repository, @@ -290,7 +290,7 @@ def to_dict(self) -> dict: if self.repository is not None: result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: - result["hostType"] = from_union([from_none, lambda x: to_enum(SessionStartDataContextHostType, x)], self.host_type) + result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) if self.branch is not None: result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: @@ -310,7 +310,7 @@ class SessionStartData: start_time: datetime selected_model: str | None = None reasoning_effort: str | None = None - context: SessionStartDataContext | None = None + context: WorkingDirectoryContext | None = None already_in_use: bool | None = None remote_steerable: bool | None = None @@ -324,7 +324,7 @@ def from_dict(obj: Any) -> "SessionStartData": start_time = from_datetime(obj.get("startTime")) selected_model = from_union([from_none, from_str], obj.get("selectedModel")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - context = from_union([from_none, SessionStartDataContext.from_dict], obj.get("context")) + context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) return SessionStartData( @@ -352,7 +352,7 @@ def to_dict(self) -> dict: if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.context is not None: - result["context"] = from_union([from_none, lambda x: to_class(SessionStartDataContext, x)], self.context) + result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) if self.remote_steerable is not None: @@ -360,55 +360,6 @@ def to_dict(self) -> dict: return result -@dataclass -class SessionResumeDataContext: - "Updated working directory and git context at resume time" - cwd: str - git_root: str | None = None - repository: str | None = None - host_type: SessionResumeDataContextHostType | None = None - branch: str | None = None - head_commit: str | None = None - base_commit: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "SessionResumeDataContext": - assert isinstance(obj, dict) - cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, from_str], obj.get("gitRoot")) - repository = from_union([from_none, from_str], obj.get("repository")) - host_type = from_union([from_none, lambda x: parse_enum(SessionResumeDataContextHostType, x)], obj.get("hostType")) - branch = from_union([from_none, from_str], obj.get("branch")) - head_commit = from_union([from_none, from_str], obj.get("headCommit")) - base_commit = from_union([from_none, from_str], obj.get("baseCommit")) - return SessionResumeDataContext( - cwd=cwd, - git_root=git_root, - repository=repository, - host_type=host_type, - branch=branch, - head_commit=head_commit, - base_commit=base_commit, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["cwd"] = from_str(self.cwd) - if self.git_root is not None: - result["gitRoot"] = from_union([from_none, from_str], self.git_root) - if self.repository is not None: - result["repository"] = from_union([from_none, from_str], self.repository) - if self.host_type is not None: - result["hostType"] = from_union([from_none, lambda x: to_enum(SessionResumeDataContextHostType, x)], self.host_type) - if self.branch is not None: - result["branch"] = from_union([from_none, from_str], self.branch) - if self.head_commit is not None: - result["headCommit"] = from_union([from_none, from_str], self.head_commit) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, from_str], self.base_commit) - return result - - @dataclass class SessionResumeData: "Session resume metadata including current context and event count" @@ -416,7 +367,7 @@ class SessionResumeData: event_count: float selected_model: str | None = None reasoning_effort: str | None = None - context: SessionResumeDataContext | None = None + context: WorkingDirectoryContext | None = None already_in_use: bool | None = None remote_steerable: bool | None = None @@ -427,7 +378,7 @@ def from_dict(obj: Any) -> "SessionResumeData": event_count = from_float(obj.get("eventCount")) selected_model = from_union([from_none, from_str], obj.get("selectedModel")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - context = from_union([from_none, SessionResumeDataContext.from_dict], obj.get("context")) + context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) return SessionResumeData( @@ -449,7 +400,7 @@ def to_dict(self) -> dict: if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.context is not None: - result["context"] = from_union([from_none, lambda x: to_class(SessionResumeDataContext, x)], self.context) + result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) if self.already_in_use is not None: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) if self.remote_steerable is not None: @@ -1038,7 +989,7 @@ def to_dict(self) -> dict: @dataclass class SessionContextChangedData: - "Updated working directory and git context after the change" + "Working directory and git context at session start" cwd: str git_root: str | None = None repository: str | None = None @@ -1484,6 +1435,8 @@ class UserMessageData: content: str transformed_content: str | None = None attachments: list[UserMessageAttachment] | None = None + supported_native_document_mime_types: list[str] | None = None + native_document_path_fallback_paths: list[str] | None = None source: str | None = None agent_mode: UserMessageAgentMode | None = None interaction_id: str | None = None @@ -1494,6 +1447,8 @@ def from_dict(obj: Any) -> "UserMessageData": content = from_str(obj.get("content")) transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments")) + supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) + native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths")) source = from_union([from_none, from_str], obj.get("source")) agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) @@ -1501,6 +1456,8 @@ def from_dict(obj: Any) -> "UserMessageData": content=content, transformed_content=transformed_content, attachments=attachments, + supported_native_document_mime_types=supported_native_document_mime_types, + native_document_path_fallback_paths=native_document_path_fallback_paths, source=source, agent_mode=agent_mode, interaction_id=interaction_id, @@ -1513,6 +1470,10 @@ def to_dict(self) -> dict: result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) if self.attachments is not None: result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments) + if self.supported_native_document_mime_types is not None: + result["supportedNativeDocumentMimeTypes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.supported_native_document_mime_types) + 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.source is not None: result["source"] = from_union([from_none, from_str], self.source) if self.agent_mode is not None: @@ -1703,6 +1664,7 @@ class AssistantMessageData: output_tokens: float | None = None interaction_id: str | None = None request_id: str | None = None + # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @staticmethod @@ -1763,6 +1725,7 @@ class AssistantMessageDeltaData: "Streaming assistant message delta for incremental response updates" message_id: str delta_content: str + # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @staticmethod @@ -1922,6 +1885,7 @@ class AssistantUsageData: initiator: str | None = None api_call_id: str | None = None provider_call_id: str | None = None + # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None copilot_usage: AssistantUsageCopilotUsage | None = None @@ -2060,6 +2024,7 @@ class ToolExecutionStartData: arguments: Any = None mcp_server_name: str | None = None mcp_tool_name: str | None = None + # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @staticmethod @@ -2318,6 +2283,7 @@ class ToolExecutionCompleteData: result: ToolExecutionCompleteDataResult | None = None error: ToolExecutionCompleteDataError | None = None tool_telemetry: dict[str, Any] | None = None + # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @staticmethod @@ -2696,7 +2662,7 @@ def to_dict(self) -> dict: @dataclass class SystemMessageData: - "System or developer message content with role and optional template metadata" + "System/developer instruction content with role and optional template metadata" content: str role: SystemMessageDataRole name: str | None = None @@ -3939,13 +3905,7 @@ def to_dict(self) -> dict: return result -class SessionStartDataContextHostType(Enum): - "Hosting platform type of the repository (github or ado)" - GITHUB = "github" - ADO = "ado" - - -class SessionResumeDataContextHostType(Enum): +class WorkingDirectoryContextHostType(Enum): "Hosting platform type of the repository (github or ado)" GITHUB = "github" ADO = "ado" diff --git a/python/copilot/session.py b/python/copilot/session.py index 9552f75b6..43a1c4c5a 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -43,7 +43,7 @@ UIElicitationSchemaPropertyNumberType, UIHandlePendingElicitationRequest, ) -from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride +from .generated.rpc import ModelCapabilitiesClass as _RpcModelCapabilitiesOverride from .generated.session_events import ( AssistantMessageData, CapabilitiesChangedData, diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index f8bcfad1c..d9a4b0f96 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -13,6 +13,7 @@ import { promisify } from "util"; import type { JSONSchema7 } from "json-schema"; import { cloneSchemaForCodegen, + fixNullableRequiredRefsInApiSchema, getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, @@ -326,7 +327,7 @@ function getOrCreateEnum(parentClassName: string, propName: string, values: stri const lines: string[] = []; lines.push(...xmlDocEnumComment(description, "")); - if (deprecated) lines.push(`[Obsolete]`); + if (deprecated) lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); lines.push(`[JsonConverter(typeof(JsonStringEnumConverter<${enumName}>))]`, `public enum ${enumName}`, `{`); for (const value of values) { lines.push(` /// The ${escapeXml(value)} variant.`); @@ -461,7 +462,7 @@ function generateDerivedClass( const required = new Set(schema.required || []); lines.push(...xmlDocCommentWithFallback(schema.description, `The ${escapeXml(discriminatorValue)} variant of .`, "")); - if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`); + if (isSchemaDeprecated(schema)) lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); lines.push(`public partial class ${className} : ${baseClassName}`); lines.push(`{`); lines.push(` /// `); @@ -480,7 +481,7 @@ function generateDerivedClass( lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " ")); lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " ")); - if (isSchemaDeprecated(propSchema as JSONSchema7)) lines.push(` [Obsolete]`); + if (isSchemaDeprecated(propSchema as JSONSchema7)) lines.push(` [Obsolete("This member is deprecated and will be removed in a future version.")]`); if (isDurationProperty(propSchema as JSONSchema7)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); @@ -504,7 +505,7 @@ function generateNestedClass( const required = new Set(schema.required || []); const lines: string[] = []; lines.push(...xmlDocCommentWithFallback(schema.description, `Nested data type for ${className}.`, "")); - if (isSchemaDeprecated(schema)) lines.push(`[Obsolete]`); + if (isSchemaDeprecated(schema)) lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); lines.push(`public partial class ${className}`, `{`); for (const [propName, propSchema] of Object.entries(schema.properties || {})) { @@ -516,7 +517,7 @@ function generateNestedClass( lines.push(...xmlDocPropertyComment(prop.description, propName, " ")); lines.push(...emitDataAnnotations(prop, " ")); - if (isSchemaDeprecated(prop)) lines.push(` [Obsolete]`); + if (isSchemaDeprecated(prop)) lines.push(` [Obsolete("This member is deprecated and will be removed in a future version.")]`); if (isDurationProperty(prop)) lines.push(` [JsonConverter(typeof(MillisecondsTimeSpanConverter))]`); if (!isReq) lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); lines.push(` [JsonPropertyName("${propName}")]`); @@ -615,7 +616,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map.`, "")); } if (isSchemaDeprecated(variant.dataSchema)) { - lines.push(`[Obsolete]`); + lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); } lines.push(`public partial class ${variant.dataClassName}`, `{`); @@ -627,7 +628,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map, cl lines.push(`[Experimental(Diagnostics.Experimental)]`); } if (groupDeprecated) { - lines.push(`[Obsolete]`); + lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); } lines.push(`public sealed class ${className}`); lines.push(`{`); @@ -1104,7 +1105,7 @@ function emitServerInstanceMethod( lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`); } if (method.deprecated && !groupDeprecated) { - lines.push(`${indent}[Obsolete]`); + lines.push(`${indent}[Obsolete("This member is deprecated and will be removed in a future version.")]`); } const sigParams: string[] = []; @@ -1208,7 +1209,7 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas lines.push(`${indent}[Experimental(Diagnostics.Experimental)]`); } if (method.deprecated && !groupDeprecated) { - lines.push(`${indent}[Obsolete]`); + lines.push(`${indent}[Obsolete("This member is deprecated and will be removed in a future version.")]`); } const sigParams: string[] = []; const bodyAssignments = [`SessionId = _sessionId`]; @@ -1238,7 +1239,7 @@ function emitSessionApiClass(className: string, node: Record, c const groupExperimental = isNodeFullyExperimental(node); const groupDeprecated = isNodeFullyDeprecated(node); const experimentalAttr = groupExperimental ? `[Experimental(Diagnostics.Experimental)]\n` : ""; - const deprecatedAttr = groupDeprecated ? `[Obsolete]\n` : ""; + const deprecatedAttr = groupDeprecated ? `[Obsolete("This member is deprecated and will be removed in a future version.")]\n` : ""; const subGroups = Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)); const lines = [`/// Provides session-scoped ${displayName} APIs.`, `${experimentalAttr}${deprecatedAttr}public sealed class ${className}`, `{`, ` private readonly JsonRpc _rpc;`, ` private readonly string _sessionId;`, ""]; @@ -1328,7 +1329,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(`[Experimental(Diagnostics.Experimental)]`); } if (groupDeprecated) { - lines.push(`[Obsolete]`); + lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); } lines.push(`public interface ${interfaceName}`); lines.push(`{`); @@ -1342,7 +1343,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, lines.push(` [Experimental(Diagnostics.Experimental)]`); } if (method.deprecated && !groupDeprecated) { - lines.push(` [Obsolete]`); + lines.push(` [Obsolete("This member is deprecated and will be removed in a future version.")]`); } if (hasParams) { lines.push(` ${taskType} ${clientHandlerMethodName(method.rpcMethod)}(${paramsTypeName(method)} request, CancellationToken cancellationToken = default);`); @@ -1481,7 +1482,7 @@ internal static class Diagnostics export async function generateRpc(schemaPath?: string): Promise { console.log("C#: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); + const schema = fixNullableRequiredRefsInApiSchema(cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema)); const code = generateRpcCode(schema); const outPath = await writeGeneratedFile("dotnet/src/Generated/Rpc.cs", code); console.log(` ✓ ${outPath}`); diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index fa21aa703..8f9d40321 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -13,6 +13,7 @@ import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from " import { promisify } from "util"; import { cloneSchemaForCodegen, + fixNullableRequiredRefsInApiSchema, getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, @@ -968,7 +969,7 @@ async function generateRpc(schemaPath?: string): Promise { console.log("Go: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); + const schema = fixNullableRequiredRefsInApiSchema(cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema)); const allMethods = [ ...collectRpcMethods(schema.server || {}), diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index bb1f56e0d..175c5175b 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -12,6 +12,7 @@ import type { JSONSchema7 } from "json-schema"; import { fileURLToPath } from "url"; import { cloneSchemaForCodegen, + fixNullableRequiredRefsInApiSchema, getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, @@ -184,6 +185,157 @@ function collapsePlaceholderPythonDataclasses(code: string): string { return code.replace(/\n{3,}/g, "\n\n"); } +/** + * Reorder Python class/enum definitions so forward references are resolved. + * Quicktype may emit classes in an order where a class references another + * that hasn't been defined yet, causing NameError at import time. + * This performs a topological sort of type definitions while preserving + * the relative position of non-class blocks (functions, standalone code). + */ +function reorderPythonForwardRefs(code: string): string { + // Split code into top-level blocks. Each block starts at an unindented + // line that begins a class, decorated class, enum, or function definition. + const lines = code.split("\n"); + + interface Block { + name: string; + code: string; + isType: boolean; // true for class/enum definitions + } + + const blocks: Block[] = []; + let currentLines: string[] = []; + let currentName: string | null = null; + let isType = false; + + function flushBlock() { + if (currentLines.length === 0) return; + const blockCode = currentLines.join("\n"); + blocks.push({ + name: currentName ?? `__anon_${blocks.length}`, + code: blockCode, + isType, + }); + currentLines = []; + currentName = null; + isType = false; + } + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const isTopLevel = line.length > 0 && line[0] !== " " && line[0] !== "\t"; + + if (isTopLevel) { + const classMatch = line.match(/^class\s+(\w+)/); + const defMatch = line.match(/^def\s+(\w+)/); + const decoratorMatch = line === "@dataclass"; + const commentMatch = line.startsWith("# "); + + if (classMatch) { + // If previous block was just a decorator waiting for a class, merge + if (currentLines.length > 0 && currentName === null && isType) { + // This is the class line following @dataclass + currentName = classMatch[1]; + currentLines.push(line); + continue; + } + flushBlock(); + currentLines = [line]; + currentName = classMatch[1]; + isType = true; + } else if (decoratorMatch) { + flushBlock(); + currentLines = [line]; + isType = true; + } else if (defMatch) { + flushBlock(); + currentLines = [line]; + currentName = defMatch[1]; + isType = false; + } else if (commentMatch && currentLines.length === 0) { + // Standalone comment — attach to next block + currentLines = [line]; + } else { + currentLines.push(line); + } + } else { + currentLines.push(line); + } + } + flushBlock(); + + if (blocks.length === 0) return code; + + // Collect all type names (classes and enums) + const typeNames = new Set(blocks.filter((b) => b.isType).map((b) => b.name)); + if (typeNames.size === 0) return code; + + // Build dependency graph: for each type block, find references to other type names + const deps = new Map>(); + for (const block of blocks) { + if (!block.isType) continue; + const blockDeps = new Set(); + for (const tn of typeNames) { + if (tn === block.name) continue; + if (new RegExp(`\\b${tn}\\b`).test(block.code)) { + blockDeps.add(tn); + } + } + deps.set(block.name, blockDeps); + } + + // Kahn's algorithm for topological sort + const inDegree = new Map(); + for (const tn of typeNames) inDegree.set(tn, deps.get(tn)?.size ?? 0); + + const dependents = new Map(); + for (const tn of typeNames) dependents.set(tn, []); + for (const [name, d] of deps) { + for (const dep of d) { + dependents.get(dep)!.push(name); + } + } + + const queue: string[] = []; + for (const [tn, deg] of inDegree) { + if (deg === 0) queue.push(tn); + } + + const sorted: string[] = []; + while (queue.length > 0) { + const node = queue.shift()!; + sorted.push(node); + for (const dep of dependents.get(node) ?? []) { + const newDeg = inDegree.get(dep)! - 1; + inDegree.set(dep, newDeg); + if (newDeg === 0) queue.push(dep); + } + } + + // If there are cycles, keep remaining nodes in original order + for (const block of blocks) { + if (block.isType && !sorted.includes(block.name)) { + sorted.push(block.name); + } + } + + // Rebuild: place type blocks in sorted order at the positions + // where type blocks originally appeared + const typeBlockMap = new Map(blocks.filter((b) => b.isType).map((b) => [b.name, b])); + let sortIdx = 0; + const result: string[] = []; + for (const block of blocks) { + if (block.isType) { + result.push(typeBlockMap.get(sorted[sortIdx])!.code); + sortIdx++; + } else { + result.push(block.code); + } + } + + return result.join("\n"); +} + function normalizePythonDataclassBlock(block: string, name: string): string { return block .replace(/^@dataclass\r?\nclass\s+\w+:/, "@dataclass\nclass:") @@ -1395,7 +1547,7 @@ async function generateRpc(schemaPath?: string): Promise { const { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } = await import("quicktype-core"); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); + const schema = fixNullableRequiredRefsInApiSchema(cloneSchemaForCodegen(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema)); const allMethods = [ ...collectRpcMethods(schema.server || {}), @@ -1482,6 +1634,10 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = modernizePython(typesCode); typesCode = collapsePlaceholderPythonDataclasses(typesCode); + // Reorder class/enum definitions to resolve forward references. + // Quicktype may emit classes before their dependencies are defined. + typesCode = reorderPythonForwardRefs(typesCode); + // Strip quicktype's import block and preamble — we provide our own unified header. // The preamble ends just before the first helper function (e.g. "def from_str") // or class definition. diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 8cc3e4078..1aba7384c 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -11,6 +11,7 @@ import type { JSONSchema7 } from "json-schema"; import { compile } from "json-schema-to-typescript"; import { getApiSchemaPath, + fixNullableRequiredRefsInApiSchema, getRpcSchemaTypeName, getSessionEventsSchemaPath, normalizeSchemaTitles, @@ -320,7 +321,7 @@ async function generateRpc(schemaPath?: string): Promise { console.log("TypeScript: generating RPC types..."); const resolvedPath = schemaPath ?? (await getApiSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema; + const schema = fixNullableRequiredRefsInApiSchema(JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as ApiSchema); const lines: string[] = []; lines.push(`/** diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index d6083adec..bc144bf75 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -128,6 +128,78 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { return processed; } +/** + * Normalize schema defects where a required property with a `$ref` to an object type + * has a description explicitly mentioning "null" as a valid value. + * + * In JSON Schema, `required` only means the key must be present — it doesn't prevent + * the value from being null. Some schemas mark properties as required but describe them + * as nullable (e.g., "Currently selected agent, or null if using the default"). + * + * This function converts such properties from: + * `{ "$ref": "#/definitions/Foo", "description": "...null..." }` + * to: + * `{ "anyOf": [{ "$ref": "#/definitions/Foo" }, { "type": "null" }], "description": "...null..." }` + * + * This makes all downstream codegen (Go, C#, Python/quicktype, TypeScript) correctly + * emit nullable/optional types without per-language heuristics. + */ +export function normalizeNullableRequiredRefs(schema: JSONSchema7): JSONSchema7 { + if (typeof schema !== "object" || schema === null) return schema; + + const processed = { ...schema }; + + if (processed.properties && processed.required) { + const requiredSet = new Set(processed.required); + const newProps: Record = {}; + const newRequired = [...processed.required]; + + for (const [key, value] of Object.entries(processed.properties)) { + if (typeof value !== "object" || value === null) { + newProps[key] = value; + continue; + } + const prop = value as JSONSchema7; + if ( + requiredSet.has(key) && + prop.$ref && + typeof prop.description === "string" && + /\bnull\b/i.test(prop.description) + ) { + // Convert to anyOf: [$ref, null] and remove from required + const { $ref, ...rest } = prop; + newProps[key] = { + ...rest, + anyOf: [{ $ref }, { type: "null" as const }], + }; + const idx = newRequired.indexOf(key); + if (idx !== -1) newRequired.splice(idx, 1); + } else { + newProps[key] = normalizeNullableRequiredRefs(prop); + } + } + + processed.properties = newProps; + processed.required = newRequired; + } + + // Recurse into nested schemas + if (processed.items) { + if (typeof processed.items === "object" && !Array.isArray(processed.items)) { + processed.items = normalizeNullableRequiredRefs(processed.items as JSONSchema7); + } + } + for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { + if (processed[combiner]) { + processed[combiner] = processed[combiner]!.map((item) => + typeof item === "object" ? normalizeNullableRequiredRefs(item as JSONSchema7) : item + ) as JSONSchema7Definition[]; + } + } + + return processed; +} + // ── File output ───────────────────────────────────────────────────────────── export async function writeGeneratedFile(relativePath: string, content: string): Promise { @@ -452,6 +524,52 @@ export function normalizeApiSchema(schema: ApiSchema): ApiSchema { }; } +/** + * Apply `normalizeNullableRequiredRefs` to every JSON Schema reachable from the API schema + * (method params, results, and shared definitions). Call after `cloneSchemaForCodegen` to + * fix schema defects before any per-language codegen runs. + */ +export function fixNullableRequiredRefsInApiSchema(schema: ApiSchema): ApiSchema { + function walkApiNode(node: Record | undefined): Record | undefined { + if (!node) return undefined; + const result: Record = {}; + for (const [key, value] of Object.entries(node)) { + if (isRpcMethod(value)) { + const method = value as RpcMethod; + result[key] = { + ...method, + params: method.params ? normalizeNullableRequiredRefs(method.params) : method.params, + result: method.result ? normalizeNullableRequiredRefs(method.result) : method.result, + }; + } else if (typeof value === "object" && value !== null) { + result[key] = walkApiNode(value as Record); + } else { + result[key] = value; + } + } + return result; + } + + function normalizeDefs(defs: Record | undefined): Record | undefined { + if (!defs) return undefined; + return Object.fromEntries( + Object.entries(defs).map(([key, value]) => [ + key, + typeof value === "object" && value !== null ? normalizeNullableRequiredRefs(value as JSONSchema7) : value, + ]) + ); + } + + return { + ...schema, + definitions: normalizeDefs(schema.definitions), + $defs: normalizeDefs(schema.$defs), + server: walkApiNode(schema.server), + session: walkApiNode(schema.session), + clientSession: walkApiNode(schema.clientSession), + }; +} + /** Returns true when every leaf RPC method inside `node` is marked experimental. */ export function isNodeFullyExperimental(node: Record): boolean { const methods: RpcMethod[] = []; diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 2c82d7b87..dad61dfd3 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.30", + "@github/copilot": "^1.0.32-1", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.30.tgz", - "integrity": "sha512-JYZNMM6hteAE6tIMbHobRjpAaXzvqeeglXgGlDCr26rRq3K6h5ul2GN27qzhMBaWyujUQN402KLKdrhDPqcL7A==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32-1.tgz", + "integrity": "sha512-uJgZWkd+gYS6t8NeWgZd+KDlQ41RFvAydOPdJqMDdB8aBwJYKQA75AVQzJyIne/CaMmv2Cy24X+IeRsMXvg+YA==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.30", - "@github/copilot-darwin-x64": "1.0.30", - "@github/copilot-linux-arm64": "1.0.30", - "@github/copilot-linux-x64": "1.0.30", - "@github/copilot-win32-arm64": "1.0.30", - "@github/copilot-win32-x64": "1.0.30" + "@github/copilot-darwin-arm64": "1.0.32-1", + "@github/copilot-darwin-x64": "1.0.32-1", + "@github/copilot-linux-arm64": "1.0.32-1", + "@github/copilot-linux-x64": "1.0.32-1", + "@github/copilot-win32-arm64": "1.0.32-1", + "@github/copilot-win32-x64": "1.0.32-1" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.30.tgz", - "integrity": "sha512-qhLMhAY7nskG6yabbsWSqErxPWcZLX1ixJBdQX3RLqgw5dyNvZRNzG2evUnABo5bqgndztsFXjE3u4XtfX0WkA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32-1.tgz", + "integrity": "sha512-MGz9kKJYqrfZ94DOVsKy8c0sTFn1Gax60hM3TjMt6K+Tt7n8vGhrpBn+KjFYOb+6+r7fp3E7fc6tTtwjgaURVw==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.30.tgz", - "integrity": "sha512-nsjGRt1jLBzCaVd6eb3ok75zqePr8eU8GSTqu1KVf5KUrnvvfIlsvESkEAE8l+lkR14f7SGQLfMJ2EEbcJMGcg==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32-1.tgz", + "integrity": "sha512-HSLJXMVk2yf6Xb6NhNxEYvD57hBGdWs5zQ7EOHrFYO+qA5/iD4JVGgQNg7sS88+qsTR5PtEcxwbtQPid1KZJnQ==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.30.tgz", - "integrity": "sha512-7wOrOKm9MHnglyzzGeZnXSkfRi4sXB2Db7rK/CgUenxS+dwwIuXhT4rgkH/DIOiDbGCxYjigICxln28Jvbs+cA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32-1.tgz", + "integrity": "sha512-XBiX4947+ygPugwsZrrVOwftIWWASoknq1FzehIpj7BqPxjwTpzDXPDJNleHf+6a1cGm8cUutDn/wslHjJEW9A==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.30.tgz", - "integrity": "sha512-OSJtP7mV9vnDzGFjBkI3sgbNOcxsRcq7vXrT4PNrjJw4Mc71aaW55hc5F1j2fElfGWIb+Jubm3AB8nb6AoufnA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32-1.tgz", + "integrity": "sha512-iJkcWKSoaDY5GKtOZtoZV5YhuOqvVSdENashNKjXzkIoFN0mqonIhsbAv3OB2Kr34ZwoQF3CfNoOCNBs2tg8pg==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.30.tgz", - "integrity": "sha512-5nCz/+9VWJdNvW2uRYeMmnRdQq/gpuSlmYMvRv8fIsFF8KH0mdJndJn8xN6GeJtx0fKJrLzgKqJHWdgb5MtLgA==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32-1.tgz", + "integrity": "sha512-U/lfmWAqOIxucqotmsOsJtOjfAhNIYAFeqxyaKo+V35YkurXZGTNjB2YxqUlmKm/7fuOgAACHKvrK+tWs+Mlvg==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.30", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.30.tgz", - "integrity": "sha512-tJvgCsWLJVQvHLvFyQZ0P5MQ7YGX51/bl9kbXDUFCGATtPpELul3NyHWwEYGjRv+VDPvhFxjbf+V7Bf/VzYZ7w==", + "version": "1.0.32-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32-1.tgz", + "integrity": "sha512-oSNG9nRHsyTdi2miBfti4egT+CHPGu0QTXXUasISsfwhex6SS4qeVFe8mt8/clnTlyJD9N7EDgABDduSYQv87g==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 94fe9d8c5..37bb8031a 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.30", + "@github/copilot": "^1.0.32-1", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From fd0495cfac9372d1f38a80038ecdbb3a8fa121e5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 21:27:40 -0400 Subject: [PATCH 29/34] Update @github/copilot to 1.0.32 (#1107) - Updated nodejs and test harness dependencies - Re-ran code generators - Formatted generated code Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- nodejs/package-lock.json | 56 ++++++++++++++++---------------- nodejs/package.json | 2 +- nodejs/samples/package-lock.json | 2 +- test/harness/package-lock.json | 56 ++++++++++++++++---------------- test/harness/package.json | 2 +- 5 files changed, 59 insertions(+), 59 deletions(-) diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 9ccf85c04..4725ac205 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.32-1", + "@github/copilot": "^1.0.32", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32-1.tgz", - "integrity": "sha512-uJgZWkd+gYS6t8NeWgZd+KDlQ41RFvAydOPdJqMDdB8aBwJYKQA75AVQzJyIne/CaMmv2Cy24X+IeRsMXvg+YA==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32.tgz", + "integrity": "sha512-ydEYAztJQa1sLQw+WPmnkkt3Sf/k2Smn/7szzYvt1feUOdNIak1gHpQhKcgPr2w252gjVLRWjOiynoeLVW0Fbw==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.32-1", - "@github/copilot-darwin-x64": "1.0.32-1", - "@github/copilot-linux-arm64": "1.0.32-1", - "@github/copilot-linux-x64": "1.0.32-1", - "@github/copilot-win32-arm64": "1.0.32-1", - "@github/copilot-win32-x64": "1.0.32-1" + "@github/copilot-darwin-arm64": "1.0.32", + "@github/copilot-darwin-x64": "1.0.32", + "@github/copilot-linux-arm64": "1.0.32", + "@github/copilot-linux-x64": "1.0.32", + "@github/copilot-win32-arm64": "1.0.32", + "@github/copilot-win32-x64": "1.0.32" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32-1.tgz", - "integrity": "sha512-MGz9kKJYqrfZ94DOVsKy8c0sTFn1Gax60hM3TjMt6K+Tt7n8vGhrpBn+KjFYOb+6+r7fp3E7fc6tTtwjgaURVw==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32.tgz", + "integrity": "sha512-RtGHpnrbP1eVtpzitLqC0jkBlo63PJiByv6W/NTtLw4ZAllumb5kMk8JaTtydKl9DCOHA0wfXbG5/JkGXuQ81g==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32-1.tgz", - "integrity": "sha512-HSLJXMVk2yf6Xb6NhNxEYvD57hBGdWs5zQ7EOHrFYO+qA5/iD4JVGgQNg7sS88+qsTR5PtEcxwbtQPid1KZJnQ==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32.tgz", + "integrity": "sha512-eyF6uy8gcZ4m/0UdM9UoykMDotZ8hZPJ1xIg0iHy4wrNtkYOaAspAoVpOkm50ODOQAHJ5PVV+9LuT6IoeL+wHQ==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32-1.tgz", - "integrity": "sha512-XBiX4947+ygPugwsZrrVOwftIWWASoknq1FzehIpj7BqPxjwTpzDXPDJNleHf+6a1cGm8cUutDn/wslHjJEW9A==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32.tgz", + "integrity": "sha512-acRAu5ehFPnw3hQSIxcmi7wzv8PAYd+nqdxZXizOi++en3QWgez7VEXiKLe9Ukf50iiGReg19yvWV4iDOGC0HQ==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32-1.tgz", - "integrity": "sha512-iJkcWKSoaDY5GKtOZtoZV5YhuOqvVSdENashNKjXzkIoFN0mqonIhsbAv3OB2Kr34ZwoQF3CfNoOCNBs2tg8pg==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32.tgz", + "integrity": "sha512-lw86YDwkTKwmeVpfnPErDe9DhemrOHN+l92xOU9wQSH5/d+HguXwRb3e4cQjlxsGLS+/fWRGtwf+u2fbQ37avw==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32-1.tgz", - "integrity": "sha512-U/lfmWAqOIxucqotmsOsJtOjfAhNIYAFeqxyaKo+V35YkurXZGTNjB2YxqUlmKm/7fuOgAACHKvrK+tWs+Mlvg==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32.tgz", + "integrity": "sha512-+eZpuzgBbLHMIzltH541wfbbMy0HEdG91ISzRae3qPCssf3Ad85sat6k7FWTRBSZBFrN7z4yMQm5gROqDJYGSA==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32-1.tgz", - "integrity": "sha512-oSNG9nRHsyTdi2miBfti4egT+CHPGu0QTXXUasISsfwhex6SS4qeVFe8mt8/clnTlyJD9N7EDgABDduSYQv87g==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32.tgz", + "integrity": "sha512-R6SW1dsEVmPMhrN/WRTetS4gVxcuYcxi2zfDPOfcjW3W0iD0Vwpt3MlqwBaU2UL36j+rnTnmiOA+g82FIBCYVg==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 2ccb7632c..220e76aef 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.32-1", + "@github/copilot": "^1.0.32", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 7281be70f..37dda6dc4 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.32-1", + "@github/copilot": "^1.0.32", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index dad61dfd3..51538fe1d 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.32-1", + "@github/copilot": "^1.0.32", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32-1.tgz", - "integrity": "sha512-uJgZWkd+gYS6t8NeWgZd+KDlQ41RFvAydOPdJqMDdB8aBwJYKQA75AVQzJyIne/CaMmv2Cy24X+IeRsMXvg+YA==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32.tgz", + "integrity": "sha512-ydEYAztJQa1sLQw+WPmnkkt3Sf/k2Smn/7szzYvt1feUOdNIak1gHpQhKcgPr2w252gjVLRWjOiynoeLVW0Fbw==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.32-1", - "@github/copilot-darwin-x64": "1.0.32-1", - "@github/copilot-linux-arm64": "1.0.32-1", - "@github/copilot-linux-x64": "1.0.32-1", - "@github/copilot-win32-arm64": "1.0.32-1", - "@github/copilot-win32-x64": "1.0.32-1" + "@github/copilot-darwin-arm64": "1.0.32", + "@github/copilot-darwin-x64": "1.0.32", + "@github/copilot-linux-arm64": "1.0.32", + "@github/copilot-linux-x64": "1.0.32", + "@github/copilot-win32-arm64": "1.0.32", + "@github/copilot-win32-x64": "1.0.32" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32-1.tgz", - "integrity": "sha512-MGz9kKJYqrfZ94DOVsKy8c0sTFn1Gax60hM3TjMt6K+Tt7n8vGhrpBn+KjFYOb+6+r7fp3E7fc6tTtwjgaURVw==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32.tgz", + "integrity": "sha512-RtGHpnrbP1eVtpzitLqC0jkBlo63PJiByv6W/NTtLw4ZAllumb5kMk8JaTtydKl9DCOHA0wfXbG5/JkGXuQ81g==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32-1.tgz", - "integrity": "sha512-HSLJXMVk2yf6Xb6NhNxEYvD57hBGdWs5zQ7EOHrFYO+qA5/iD4JVGgQNg7sS88+qsTR5PtEcxwbtQPid1KZJnQ==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32.tgz", + "integrity": "sha512-eyF6uy8gcZ4m/0UdM9UoykMDotZ8hZPJ1xIg0iHy4wrNtkYOaAspAoVpOkm50ODOQAHJ5PVV+9LuT6IoeL+wHQ==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32-1.tgz", - "integrity": "sha512-XBiX4947+ygPugwsZrrVOwftIWWASoknq1FzehIpj7BqPxjwTpzDXPDJNleHf+6a1cGm8cUutDn/wslHjJEW9A==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32.tgz", + "integrity": "sha512-acRAu5ehFPnw3hQSIxcmi7wzv8PAYd+nqdxZXizOi++en3QWgez7VEXiKLe9Ukf50iiGReg19yvWV4iDOGC0HQ==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32-1.tgz", - "integrity": "sha512-iJkcWKSoaDY5GKtOZtoZV5YhuOqvVSdENashNKjXzkIoFN0mqonIhsbAv3OB2Kr34ZwoQF3CfNoOCNBs2tg8pg==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32.tgz", + "integrity": "sha512-lw86YDwkTKwmeVpfnPErDe9DhemrOHN+l92xOU9wQSH5/d+HguXwRb3e4cQjlxsGLS+/fWRGtwf+u2fbQ37avw==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32-1.tgz", - "integrity": "sha512-U/lfmWAqOIxucqotmsOsJtOjfAhNIYAFeqxyaKo+V35YkurXZGTNjB2YxqUlmKm/7fuOgAACHKvrK+tWs+Mlvg==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32.tgz", + "integrity": "sha512-+eZpuzgBbLHMIzltH541wfbbMy0HEdG91ISzRae3qPCssf3Ad85sat6k7FWTRBSZBFrN7z4yMQm5gROqDJYGSA==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.32-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32-1.tgz", - "integrity": "sha512-oSNG9nRHsyTdi2miBfti4egT+CHPGu0QTXXUasISsfwhex6SS4qeVFe8mt8/clnTlyJD9N7EDgABDduSYQv87g==", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32.tgz", + "integrity": "sha512-R6SW1dsEVmPMhrN/WRTetS4gVxcuYcxi2zfDPOfcjW3W0iD0Vwpt3MlqwBaU2UL36j+rnTnmiOA+g82FIBCYVg==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 37bb8031a..af521775b 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.32-1", + "@github/copilot": "^1.0.32", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", From 922959f4a7b83509c3620d4881733c6c5677f00c Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 17 Apr 2026 21:46:42 -0400 Subject: [PATCH 30/34] Expose IncludeSubAgentStreamingEvents in all four SDKs (#1108) * Expose IncludeSubAgentStreamingEvents in all four SDKs Add the includeSubAgentStreamingEvents property to session config types and wire payloads across Node/TS, Python, Go, and .NET. The property controls whether streaming delta events from sub-agents (e.g., assistant.message_delta, assistant.reasoning_delta, assistant.streaming_delta with agentId set) are forwarded to the connection. When false, only non-streaming sub-agent events and subagent.* lifecycle events are forwarded. The SDK defaults the property to true when not specified, so existing consumers automatically receive sub-agent streaming events without any code changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix gofmt alignment in types.go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add unit tests for IncludeSubAgentStreamingEvents across all SDKs - .NET: Clone test assertions for SessionConfig and ResumeSessionConfig - Node.js: Wire payload tests for session.create and session.resume - Python: Payload capture tests for create_session and resume_session - Go: JSON marshal tests for createSessionRequest and resumeSessionRequest Each language tests both the default (true) and explicit false paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Client.cs | 4 ++ dotnet/src/Types.cs | 24 +++++++ dotnet/test/CloneTests.cs | 26 ++++++++ go/client.go | 10 +++ go/client_test.go | 74 +++++++++++++++++++++ go/types.go | 130 +++++++++++++++++++++---------------- nodejs/src/client.ts | 2 + nodejs/src/types.ts | 12 ++++ nodejs/test/client.test.ts | 68 +++++++++++++++++++ python/copilot/client.py | 26 ++++++++ python/copilot/session.py | 14 ++++ python/test_client.py | 104 +++++++++++++++++++++++++++++ 12 files changed, 437 insertions(+), 57 deletions(-) diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 0124008f4..668d090f5 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -497,6 +497,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance hasHooks ? true : null, config.WorkingDirectory, config.Streaming is true ? true : null, + config.IncludeSubAgentStreamingEvents, config.McpServers, "direct", config.CustomAgents, @@ -622,6 +623,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.EnableConfigDiscovery, config.DisableResume is true ? true : null, config.Streaming is true ? true : null, + config.IncludeSubAgentStreamingEvents, config.McpServers, "direct", config.CustomAgents, @@ -1636,6 +1638,7 @@ internal record CreateSessionRequest( bool? Hooks, string? WorkingDirectory, bool? Streaming, + bool? IncludeSubAgentStreamingEvents, IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, @@ -1691,6 +1694,7 @@ internal record ResumeSessionRequest( bool? EnableConfigDiscovery, bool? DisableResume, bool? Streaming, + bool? IncludeSubAgentStreamingEvents, IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 1fd8afa39..fd42d0c27 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1732,6 +1732,7 @@ protected SessionConfig(SessionConfig? other) SessionId = other.SessionId; SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; Streaming = other.Streaming; + IncludeSubAgentStreamingEvents = other.IncludeSubAgentStreamingEvents; SystemMessage = other.SystemMessage; Tools = other.Tools is not null ? [.. other.Tools] : null; WorkingDirectory = other.WorkingDirectory; @@ -1848,6 +1849,17 @@ protected SessionConfig(SessionConfig? other) /// public bool Streaming { get; set; } + /// + /// Include sub-agent streaming events in the event stream. When true, streaming + /// delta events from sub-agents (e.g., assistant.message_delta, + /// assistant.reasoning_delta, assistant.streaming_delta with + /// agentId set) are forwarded to this connection. When false, only + /// non-streaming sub-agent events and subagent.* lifecycle events are + /// forwarded; streaming deltas from sub-agents are suppressed. + /// Default: true. + /// + public bool IncludeSubAgentStreamingEvents { get; set; } = true; + /// /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). @@ -1961,6 +1973,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) CreateSessionFsHandler = other.CreateSessionFsHandler; SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; Streaming = other.Streaming; + IncludeSubAgentStreamingEvents = other.IncludeSubAgentStreamingEvents; SystemMessage = other.SystemMessage; Tools = other.Tools is not null ? [.. other.Tools] : null; WorkingDirectory = other.WorkingDirectory; @@ -2082,6 +2095,17 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// public bool Streaming { get; set; } + /// + /// Include sub-agent streaming events in the event stream. When true, streaming + /// delta events from sub-agents (e.g., assistant.message_delta, + /// assistant.reasoning_delta, assistant.streaming_delta with + /// agentId set) are forwarded to this connection. When false, only + /// non-streaming sub-agent events and subagent.* lifecycle events are + /// forwarded; streaming deltas from sub-agents are suppressed. + /// Default: true. + /// + public bool IncludeSubAgentStreamingEvents { get; set; } = true; + /// /// MCP server configurations for the session. /// Keys are server names, values are server configurations ( or ). diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index 39c42fb25..cc36162ff 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -86,6 +86,7 @@ public void SessionConfig_Clone_CopiesAllProperties() ExcludedTools = ["tool3"], WorkingDirectory = "/workspace", Streaming = true, + IncludeSubAgentStreamingEvents = false, McpServers = new Dictionary { ["server1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "agent1" }], Agent = "agent1", @@ -104,6 +105,7 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.ExcludedTools, clone.ExcludedTools); Assert.Equal(original.WorkingDirectory, clone.WorkingDirectory); Assert.Equal(original.Streaming, clone.Streaming); + Assert.Equal(original.IncludeSubAgentStreamingEvents, clone.IncludeSubAgentStreamingEvents); Assert.Equal(original.McpServers.Count, clone.McpServers!.Count); Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count); Assert.Equal(original.Agent, clone.Agent); @@ -243,6 +245,7 @@ public void Clone_WithNullCollections_ReturnsNullCollections() Assert.Null(clone.SkillDirectories); Assert.Null(clone.DisabledSkills); Assert.Null(clone.Tools); + Assert.True(clone.IncludeSubAgentStreamingEvents); } [Fact] @@ -272,4 +275,27 @@ public void ResumeSessionConfig_Clone_CopiesAgentProperty() Assert.Equal("test-agent", clone.Agent); } + + [Fact] + public void ResumeSessionConfig_Clone_CopiesIncludeSubAgentStreamingEvents() + { + var original = new ResumeSessionConfig + { + IncludeSubAgentStreamingEvents = false, + }; + + var clone = original.Clone(); + + Assert.False(clone.IncludeSubAgentStreamingEvents); + } + + [Fact] + public void ResumeSessionConfig_Clone_PreservesIncludeSubAgentStreamingEventsDefault() + { + var original = new ResumeSessionConfig(); + + var clone = original.Clone(); + + Assert.True(clone.IncludeSubAgentStreamingEvents); + } } diff --git a/go/client.go b/go/client.go index db8438041..37e572dc8 100644 --- a/go/client.go +++ b/go/client.go @@ -611,6 +611,11 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses if config.Streaming { req.Streaming = Bool(true) } + if config.IncludeSubAgentStreamingEvents != nil { + req.IncludeSubAgentStreamingEvents = config.IncludeSubAgentStreamingEvents + } else { + req.IncludeSubAgentStreamingEvents = Bool(true) + } if config.OnUserInputRequest != nil { req.RequestUserInput = Bool(true) } @@ -744,6 +749,11 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, if config.Streaming { req.Streaming = Bool(true) } + if config.IncludeSubAgentStreamingEvents != nil { + req.IncludeSubAgentStreamingEvents = config.IncludeSubAgentStreamingEvents + } else { + req.IncludeSubAgentStreamingEvents = Bool(true) + } if config.OnUserInputRequest != nil { req.RequestUserInput = Bool(true) } diff --git a/go/client_test.go b/go/client_test.go index 091c31726..8840e8269 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -861,6 +861,80 @@ func TestResumeSessionRequest_RequestElicitation(t *testing.T) { }) } +func TestCreateSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) { + t.Run("defaults to true when nil", func(t *testing.T) { + req := createSessionRequest{ + IncludeSubAgentStreamingEvents: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["includeSubAgentStreamingEvents"] != true { + t.Errorf("Expected includeSubAgentStreamingEvents to be true, got %v", m["includeSubAgentStreamingEvents"]) + } + }) + + t.Run("preserves explicit false", func(t *testing.T) { + req := createSessionRequest{ + IncludeSubAgentStreamingEvents: Bool(false), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["includeSubAgentStreamingEvents"] != false { + t.Errorf("Expected includeSubAgentStreamingEvents to be false, got %v", m["includeSubAgentStreamingEvents"]) + } + }) +} + +func TestResumeSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) { + t.Run("defaults to true when nil", func(t *testing.T) { + req := resumeSessionRequest{ + SessionID: "s1", + IncludeSubAgentStreamingEvents: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["includeSubAgentStreamingEvents"] != true { + t.Errorf("Expected includeSubAgentStreamingEvents to be true, got %v", m["includeSubAgentStreamingEvents"]) + } + }) + + t.Run("preserves explicit false", func(t *testing.T) { + req := resumeSessionRequest{ + SessionID: "s1", + IncludeSubAgentStreamingEvents: Bool(false), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["includeSubAgentStreamingEvents"] != false { + t.Errorf("Expected includeSubAgentStreamingEvents to be false, got %v", m["includeSubAgentStreamingEvents"]) + } + }) +} + func TestCreateSessionResponse_Capabilities(t *testing.T) { t.Run("reads capabilities from session.create response", func(t *testing.T) { responseJSON := `{"sessionId":"s1","workspacePath":"/tmp","capabilities":{"ui":{"elicitation":true}}}` diff --git a/go/types.go b/go/types.go index 15c62cec0..aa4fafc94 100644 --- a/go/types.go +++ b/go/types.go @@ -527,6 +527,13 @@ type SessionConfig struct { // When true, assistant.message_delta and assistant.reasoning_delta events // with deltaContent are sent as the response is generated. Streaming bool + // IncludeSubAgentStreamingEvents includes sub-agent streaming events in the + // event stream. When true, streaming delta events from sub-agents (e.g., + // assistant.message_delta, assistant.reasoning_delta, assistant.streaming_delta + // with agentId set) are forwarded to this connection. When false, only + // non-streaming sub-agent events and subagent.* lifecycle events are forwarded; + // streaming deltas from sub-agents are suppressed. When nil, defaults to true. + IncludeSubAgentStreamingEvents *bool // Provider configures a custom model provider (BYOK) Provider *ProviderConfig // ModelCapabilities overrides individual model capabilities resolved by the runtime. @@ -740,6 +747,13 @@ type ResumeSessionConfig struct { // When true, assistant.message_delta and assistant.reasoning_delta events // with deltaContent are sent as the response is generated. Streaming bool + // IncludeSubAgentStreamingEvents includes sub-agent streaming events in the + // event stream. When true, streaming delta events from sub-agents (e.g., + // assistant.message_delta, assistant.reasoning_delta, assistant.streaming_delta + // with agentId set) are forwarded to this connection. When false, only + // non-streaming sub-agent events and subagent.* lifecycle events are forwarded; + // streaming deltas from sub-agents are suppressed. When nil, defaults to true. + IncludeSubAgentStreamingEvents *bool // MCPServers configures MCP servers for the session MCPServers map[string]MCPServerConfig // CustomAgents configures custom agents for the session @@ -937,34 +951,35 @@ type SessionLifecycleHandler func(event SessionLifecycleEvent) // createSessionRequest is the request for session.create type createSessionRequest struct { - Model string `json:"model,omitempty"` - SessionID string `json:"sessionId,omitempty"` - ClientName string `json:"clientName,omitempty"` - ReasoningEffort string `json:"reasoningEffort,omitempty"` - Tools []Tool `json:"tools,omitempty"` - SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` - AvailableTools []string `json:"availableTools"` - ExcludedTools []string `json:"excludedTools,omitempty"` - Provider *ProviderConfig `json:"provider,omitempty"` - ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` - RequestPermission *bool `json:"requestPermission,omitempty"` - RequestUserInput *bool `json:"requestUserInput,omitempty"` - Hooks *bool `json:"hooks,omitempty"` - WorkingDirectory string `json:"workingDirectory,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` - EnvValueMode string `json:"envValueMode,omitempty"` - CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` - Agent string `json:"agent,omitempty"` - ConfigDir string `json:"configDir,omitempty"` - EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` - DisabledSkills []string `json:"disabledSkills,omitempty"` - InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` - Commands []wireCommand `json:"commands,omitempty"` - RequestElicitation *bool `json:"requestElicitation,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` + Model string `json:"model,omitempty"` + SessionID string `json:"sessionId,omitempty"` + ClientName string `json:"clientName,omitempty"` + ReasoningEffort string `json:"reasoningEffort,omitempty"` + Tools []Tool `json:"tools,omitempty"` + SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` + AvailableTools []string `json:"availableTools"` + ExcludedTools []string `json:"excludedTools,omitempty"` + Provider *ProviderConfig `json:"provider,omitempty"` + ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + RequestPermission *bool `json:"requestPermission,omitempty"` + RequestUserInput *bool `json:"requestUserInput,omitempty"` + Hooks *bool `json:"hooks,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` + EnvValueMode string `json:"envValueMode,omitempty"` + CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + Agent string `json:"agent,omitempty"` + ConfigDir string `json:"configDir,omitempty"` + EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitempty"` + DisabledSkills []string `json:"disabledSkills,omitempty"` + InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` + Commands []wireCommand `json:"commands,omitempty"` + RequestElicitation *bool `json:"requestElicitation,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` } // wireCommand is the wire representation of a command (name + description only, no handler). @@ -982,35 +997,36 @@ type createSessionResponse struct { // resumeSessionRequest is the request for session.resume type resumeSessionRequest struct { - SessionID string `json:"sessionId"` - ClientName string `json:"clientName,omitempty"` - Model string `json:"model,omitempty"` - ReasoningEffort string `json:"reasoningEffort,omitempty"` - Tools []Tool `json:"tools,omitempty"` - SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` - AvailableTools []string `json:"availableTools"` - ExcludedTools []string `json:"excludedTools,omitempty"` - Provider *ProviderConfig `json:"provider,omitempty"` - ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` - RequestPermission *bool `json:"requestPermission,omitempty"` - RequestUserInput *bool `json:"requestUserInput,omitempty"` - Hooks *bool `json:"hooks,omitempty"` - WorkingDirectory string `json:"workingDirectory,omitempty"` - ConfigDir string `json:"configDir,omitempty"` - EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` - DisableResume *bool `json:"disableResume,omitempty"` - Streaming *bool `json:"streaming,omitempty"` - MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` - EnvValueMode string `json:"envValueMode,omitempty"` - CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` - Agent string `json:"agent,omitempty"` - SkillDirectories []string `json:"skillDirectories,omitempty"` - DisabledSkills []string `json:"disabledSkills,omitempty"` - InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` - Commands []wireCommand `json:"commands,omitempty"` - RequestElicitation *bool `json:"requestElicitation,omitempty"` - Traceparent string `json:"traceparent,omitempty"` - Tracestate string `json:"tracestate,omitempty"` + SessionID string `json:"sessionId"` + ClientName string `json:"clientName,omitempty"` + Model string `json:"model,omitempty"` + ReasoningEffort string `json:"reasoningEffort,omitempty"` + Tools []Tool `json:"tools,omitempty"` + SystemMessage *SystemMessageConfig `json:"systemMessage,omitempty"` + AvailableTools []string `json:"availableTools"` + ExcludedTools []string `json:"excludedTools,omitempty"` + Provider *ProviderConfig `json:"provider,omitempty"` + ModelCapabilities *rpc.ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + RequestPermission *bool `json:"requestPermission,omitempty"` + RequestUserInput *bool `json:"requestUserInput,omitempty"` + Hooks *bool `json:"hooks,omitempty"` + WorkingDirectory string `json:"workingDirectory,omitempty"` + ConfigDir string `json:"configDir,omitempty"` + EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` + DisableResume *bool `json:"disableResume,omitempty"` + Streaming *bool `json:"streaming,omitempty"` + IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` + MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` + EnvValueMode string `json:"envValueMode,omitempty"` + CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + Agent string `json:"agent,omitempty"` + SkillDirectories []string `json:"skillDirectories,omitempty"` + DisabledSkills []string `json:"disabledSkills,omitempty"` + InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` + Commands []wireCommand `json:"commands,omitempty"` + RequestElicitation *bool `json:"requestElicitation,omitempty"` + Traceparent string `json:"traceparent,omitempty"` + Tracestate string `json:"tracestate,omitempty"` } // resumeSessionResponse is the response from session.resume diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index a3d50d5ff..c8c137c3d 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -748,6 +748,7 @@ export class CopilotClient { hooks: !!(config.hooks && Object.values(config.hooks).some(Boolean)), workingDirectory: config.workingDirectory, streaming: config.streaming, + includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, @@ -888,6 +889,7 @@ export class CopilotClient { configDir: config.configDir, enableConfigDiscovery: config.enableConfigDiscovery, streaming: config.streaming, + includeSubAgentStreamingEvents: config.includeSubAgentStreamingEvents ?? true, mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 0c901f989..9b2df4193 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1270,6 +1270,17 @@ export interface SessionConfig { */ streaming?: boolean; + /** + * Include sub-agent streaming events in the event stream. When true, streaming + * delta events from sub-agents (e.g., `assistant.message_delta`, + * `assistant.reasoning_delta`, `assistant.streaming_delta` with `agentId` set) + * are forwarded to this connection. When false, only non-streaming sub-agent + * events and `subagent.*` lifecycle events are forwarded; streaming deltas from + * sub-agents are suppressed. + * @default true + */ + includeSubAgentStreamingEvents?: boolean; + /** * MCP server configurations for the session. * Keys are server names, values are server configurations. @@ -1338,6 +1349,7 @@ export type ResumeSessionConfig = Pick< | "provider" | "modelCapabilities" | "streaming" + | "includeSubAgentStreamingEvents" | "reasoningEffort" | "onPermissionRequest" | "onUserInputRequest" diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 870ccb1ed..1c0eceb65 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -98,6 +98,74 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("defaults includeSubAgentStreamingEvents to true in session.create when not specified", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ onPermissionRequest: approveAll }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(payload.includeSubAgentStreamingEvents).toBe(true); + }); + + it("forwards explicit false for includeSubAgentStreamingEvents in session.create", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ + onPermissionRequest: approveAll, + includeSubAgentStreamingEvents: false, + }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.create")![1] as any; + expect(payload.includeSubAgentStreamingEvents).toBe(false); + }); + + it("defaults includeSubAgentStreamingEvents to true in session.resume when not specified", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any; + expect(payload.includeSubAgentStreamingEvents).toBe(true); + spy.mockRestore(); + }); + + it("forwards explicit false for includeSubAgentStreamingEvents in session.resume", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + includeSubAgentStreamingEvents: false, + }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any; + expect(payload.includeSubAgentStreamingEvents).toBe(false); + spy.mockRestore(); + }); + it("forwards provider headers in session.create request", async () => { const client = new CopilotClient(); await client.start(); diff --git a/python/copilot/client.py b/python/copilot/client.py index 5d62db301..4c1186f23 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -1196,6 +1196,7 @@ async def create_session( provider: ProviderConfig | None = None, model_capabilities: ModelCapabilitiesOverride | None = None, streaming: bool | None = None, + include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, agent: str | None = None, @@ -1233,6 +1234,11 @@ async def create_session( provider: Provider configuration for Azure or custom endpoints. model_capabilities: Override individual model capabilities resolved by the runtime. streaming: Whether to enable streaming responses. + include_sub_agent_streaming_events: Whether to include sub-agent streaming + delta events (e.g., ``assistant.message_delta``, + ``assistant.reasoning_delta``, ``assistant.streaming_delta`` with + ``agentId`` set). When False, only non-streaming sub-agent events and + ``subagent.*`` lifecycle events are forwarded. Defaults to True. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. agent: Agent to use for the session. @@ -1341,6 +1347,13 @@ async def create_session( if streaming is not None: payload["streaming"] = streaming + # Include sub-agent streaming events (defaults to True) + payload["includeSubAgentStreamingEvents"] = ( + include_sub_agent_streaming_events + if include_sub_agent_streaming_events is not None + else True + ) + # Add provider configuration if provided if provider: payload["provider"] = self._convert_provider_to_wire_format(provider) @@ -1461,6 +1474,7 @@ async def resume_session( provider: ProviderConfig | None = None, model_capabilities: ModelCapabilitiesOverride | None = None, streaming: bool | None = None, + include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, agent: str | None = None, @@ -1498,6 +1512,11 @@ async def resume_session( provider: Provider configuration for Azure or custom endpoints. model_capabilities: Override individual model capabilities resolved by the runtime. streaming: Whether to enable streaming responses. + include_sub_agent_streaming_events: Whether to include sub-agent streaming + delta events (e.g., ``assistant.message_delta``, + ``assistant.reasoning_delta``, ``assistant.streaming_delta`` with + ``agentId`` set). When False, only non-streaming sub-agent events and + ``subagent.*`` lifecycle events are forwarded. Defaults to True. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. agent: Agent to use for the session. @@ -1584,6 +1603,13 @@ async def resume_session( if streaming is not None: payload["streaming"] = streaming + # Include sub-agent streaming events (defaults to True) + payload["includeSubAgentStreamingEvents"] = ( + include_sub_agent_streaming_events + if include_sub_agent_streaming_events is not None + else True + ) + # Always enable permission request callback payload["requestPermission"] = True diff --git a/python/copilot/session.py b/python/copilot/session.py index 43a1c4c5a..ac771923a 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -859,6 +859,13 @@ class SessionConfig(TypedDict, total=False): # When True, assistant.message_delta and assistant.reasoning_delta events # with delta_content are sent as the response is generated streaming: bool + # Include sub-agent streaming events in the event stream. When True, streaming + # delta events from sub-agents (e.g., assistant.message_delta, + # assistant.reasoning_delta, assistant.streaming_delta with agentId set) are + # forwarded to this connection. When False, only non-streaming sub-agent events + # and subagent.* lifecycle events are forwarded; streaming deltas from sub-agents + # are suppressed. Defaults to True. + include_sub_agent_streaming_events: bool # MCP server configurations for the session mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session @@ -920,6 +927,13 @@ class ResumeSessionConfig(TypedDict, total=False): config_dir: str # Enable streaming of assistant message chunks streaming: bool + # Include sub-agent streaming events in the event stream. When True, streaming + # delta events from sub-agents (e.g., assistant.message_delta, + # assistant.reasoning_delta, assistant.streaming_delta with agentId set) are + # forwarded to this connection. When False, only non-streaming sub-agent events + # and subagent.* lifecycle events are forwarded; streaming deltas from sub-agents + # are suppressed. Defaults to True. + include_sub_agent_streaming_events: bool # MCP server configurations for the session mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session diff --git a/python/test_client.py b/python/test_client.py index 0896b54e2..eb132cd0d 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -594,6 +594,110 @@ async def mock_request(method, params): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_create_session_defaults_include_sub_agent_streaming_events_to_true(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + return await original_request(method, params) + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + assert captured["session.create"]["includeSubAgentStreamingEvents"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_create_session_preserves_explicit_false_include_sub_agent_streaming_events( + self, + ): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + return await original_request(method, params) + + client._client.request = mock_request + await client.create_session( + on_permission_request=PermissionHandler.approve_all, + include_sub_agent_streaming_events=False, + ) + assert captured["session.create"]["includeSubAgentStreamingEvents"] is False + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_defaults_include_sub_agent_streaming_events_to_true(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert captured["session.resume"]["includeSubAgentStreamingEvents"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_preserves_explicit_false_include_sub_agent_streaming_events( + self, + ): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + include_sub_agent_streaming_events=False, + ) + assert captured["session.resume"]["includeSubAgentStreamingEvents"] is False + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_set_model_sends_correct_rpc(self): client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) From b1b0df5cf85a199ab03fe3f32f4d2998486cc8dd Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Tue, 21 Apr 2026 06:23:57 -0700 Subject: [PATCH 31/34] feat: add per-agent tool visibility via defaultAgent.excludedTools (#1098) * feat: add per-agent tool visibility via defaultAgent.excludedTools Add a new DefaultAgentConfig type and defaultAgent property to SessionConfig across all four SDKs (Node.js, Python, Go, .NET). This allows tools to be hidden from the default agent while remaining available to custom sub-agents, enabling the orchestrator pattern where the default agent delegates heavy-context work to specialized sub-agents. The default agent is the built-in agent that handles turns when no custom agent is selected. Tools listed in defaultAgent.excludedTools are excluded from the default agent but remain available to sub-agents that reference them in their tools array. Changes: - Node.js: DefaultAgentConfig interface, defaultAgent on SessionConfig/ ResumeSessionConfig, RPC pass-through, 2 unit tests - Python: DefaultAgentConfig TypedDict, default_agent parameter on create_session()/resume_session(), wire format conversion - Go: DefaultAgentConfig struct, DefaultAgent on config and request structs - .NET: DefaultAgentConfig class, DefaultAgent property on configs, copy constructors, and RPC request records - Docs: Agent-Exclusive Tools section in custom-agents.md - Test scenario: custom-agents scenario updated with defaultAgent usage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address CI validation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback - Fix defineTool signature in TS scenario and docs (name, config) - Remove unused System.Text.Json import from C# scenario Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address remaining CI validation failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/custom-agents.md | 148 ++++++++++++++++++ dotnet/src/Client.cs | 4 + dotnet/src/Types.cs | 31 ++++ dotnet/test/CloneTests.cs | 3 + dotnet/test/SessionTests.cs | 29 ++++ go/client.go | 2 + go/internal/e2e/session_test.go | 51 ++++++ go/types.go | 16 ++ nodejs/src/client.ts | 2 + nodejs/src/index.ts | 1 + nodejs/src/types.ts | 24 +++ nodejs/test/client.test.ts | 39 +++++ nodejs/test/e2e/mcp_and_agents.test.ts | 73 ++++++++- python/copilot/client.py | 32 ++++ python/copilot/session.py | 18 +++ python/e2e/test_session.py | 28 ++++ test/scenarios/tools/custom-agents/README.md | 18 ++- .../tools/custom-agents/csharp/Program.cs | 16 +- test/scenarios/tools/custom-agents/go/main.go | 17 +- .../tools/custom-agents/python/main.py | 38 +++-- .../custom-agents/typescript/src/index.ts | 17 +- ...agent_configuration_on_session_resume.yaml | 14 ++ ...ide_excluded_tools_from_default_agent.yaml | 10 ++ ...ssion_with_defaultagent_excludedtools.yaml | 10 ++ 24 files changed, 617 insertions(+), 24 deletions(-) create mode 100644 test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml create mode 100644 test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml create mode 100644 test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index f3c508922..0d27fe873 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -759,6 +759,154 @@ const session = await client.createSession({ > **Note:** When `tools` is `null` or omitted, the agent inherits access to all tools configured on the session. Use explicit tool lists to enforce the principle of least privilege. +## Agent-Exclusive Tools + +Use the `defaultAgent` property on the session configuration to hide specific tools from the default agent (the built-in agent that handles turns when no custom agent is selected). This forces the main agent to delegate to sub-agents when those tools' capabilities are needed, keeping the main agent's context clean. + +This is useful when: +- Certain tools generate large amounts of context that would overwhelm the main agent +- You want the main agent to act as an orchestrator, delegating heavy work to specialized sub-agents +- You need strict separation between orchestration and execution + +
+Node.js / TypeScript + +```typescript +import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk"; +import { z } from "zod"; + +const heavyContextTool = defineTool("analyze-codebase", { + description: "Performs deep analysis of the codebase, generating extensive context", + parameters: z.object({ query: z.string() }), + handler: async ({ query }) => { + // ... expensive analysis that returns lots of data + return { analysis: "..." }; + }, +}); + +const session = await client.createSession({ + tools: [heavyContextTool], + defaultAgent: { + excludedTools: ["analyze-codebase"], + }, + customAgents: [ + { + name: "researcher", + description: "Deep codebase analysis agent with access to heavy-context tools", + tools: ["analyze-codebase"], + prompt: "You perform thorough codebase analysis using the analyze-codebase tool.", + }, + ], +}); +``` + +
+ +
+Python + +```python +from copilot import CopilotClient +from copilot.tools import Tool + +heavy_tool = Tool( + name="analyze-codebase", + description="Performs deep analysis of the codebase", + handler=analyze_handler, + parameters={"type": "object", "properties": {"query": {"type": "string"}}}, +) + +session = await client.create_session( + tools=[heavy_tool], + default_agent={"excluded_tools": ["analyze-codebase"]}, + custom_agents=[ + { + "name": "researcher", + "description": "Deep codebase analysis agent", + "tools": ["analyze-codebase"], + "prompt": "You perform thorough codebase analysis.", + }, + ], + on_permission_request=approve_all, +) +``` + +
+ +
+Go + + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + Tools: []copilot.Tool{heavyTool}, + DefaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"analyze-codebase"}, + }, + CustomAgents: []copilot.CustomAgentConfig{ + { + Name: "researcher", + Description: "Deep codebase analysis agent", + Tools: []string{"analyze-codebase"}, + Prompt: "You perform thorough codebase analysis.", + }, + }, +}) +``` + +
+ +
+C# / .NET + + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Tools = [analyzeCodebaseTool], + DefaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["analyze-codebase"], + }, + CustomAgents = + [ + new CustomAgentConfig + { + Name = "researcher", + Description = "Deep codebase analysis agent", + Tools = ["analyze-codebase"], + Prompt = "You perform thorough codebase analysis.", + }, + ], +}); +``` + +
+ +### How It Works + +Tools listed in `defaultAgent.excludedTools`: + +1. **Are registered** — their handlers are available for execution +2. **Are hidden** from the main agent's tool list — the LLM won't see or call them directly +3. **Remain available** to any custom sub-agent that includes them in its `tools` array + +### Interaction with Other Tool Filters + +`defaultAgent.excludedTools` is orthogonal to the session-level `availableTools` and `excludedTools`: + +| Filter | Scope | Effect | +|--------|-------|--------| +| `availableTools` | Session-wide | Allowlist — only these tools exist for anyone | +| `excludedTools` | Session-wide | Blocklist — these tools are blocked for everyone | +| `defaultAgent.excludedTools` | Main agent only | These tools are hidden from the main agent but available to sub-agents | + +Precedence: +1. Session-level `availableTools`/`excludedTools` are applied first (globally) +2. `defaultAgent.excludedTools` is applied on top, further restricting the main agent only + +> **Note:** If a tool is in both `excludedTools` (session-level) and `defaultAgent.excludedTools`, the session-level exclusion takes precedence — the tool is unavailable to everyone. + ## Attaching MCP Servers to Agents Each custom agent can have its own MCP (Model Context Protocol) servers, giving it access to specialized data sources: diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 668d090f5..3941abbec 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -501,6 +501,7 @@ public async Task CreateSessionAsync(SessionConfig config, Cance config.McpServers, "direct", config.CustomAgents, + config.DefaultAgent, config.Agent, config.ConfigDir, config.EnableConfigDiscovery, @@ -627,6 +628,7 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes config.McpServers, "direct", config.CustomAgents, + config.DefaultAgent, config.Agent, config.SkillDirectories, config.DisabledSkills, @@ -1642,6 +1644,7 @@ internal record CreateSessionRequest( IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, + DefaultAgentConfig? DefaultAgent, string? Agent, string? ConfigDir, bool? EnableConfigDiscovery, @@ -1698,6 +1701,7 @@ internal record ResumeSessionRequest( IDictionary? McpServers, string? EnvValueMode, IList? CustomAgents, + DefaultAgentConfig? DefaultAgent, string? Agent, IList? SkillDirectories, IList? DisabledSkills, diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index fd42d0c27..131362055 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1656,6 +1656,21 @@ public class CustomAgentConfig public IList? Skills { get; set; } } +/// +/// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). +/// Use to hide specific tools from the default agent +/// while keeping them available to custom sub-agents. +/// +public class DefaultAgentConfig +{ + /// + /// List of tool names to exclude from the default agent. + /// These tools remain available to custom sub-agents that reference them + /// in their list. + /// + public IList? ExcludedTools { get; set; } +} + /// /// Configuration for infinite sessions with automatic context compaction and workspace persistence. /// When enabled, sessions automatically manage context window limits through background compaction @@ -1709,6 +1724,7 @@ protected SessionConfig(SessionConfig? other) Commands = other.Commands is not null ? [.. other.Commands] : null; ConfigDir = other.ConfigDir; CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null; + DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; EnableConfigDiscovery = other.EnableConfigDiscovery; @@ -1871,6 +1887,13 @@ protected SessionConfig(SessionConfig? other) /// public IList? CustomAgents { get; set; } + /// + /// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). + /// Use to hide specific tools from the default agent + /// while keeping them available to custom sub-agents. + /// + public DefaultAgentConfig? DefaultAgent { get; set; } + /// /// Name of the custom agent to activate when the session starts. /// Must match the of one of the agents in . @@ -1950,6 +1973,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Commands = other.Commands is not null ? [.. other.Commands] : null; ConfigDir = other.ConfigDir; CustomAgents = other.CustomAgents is not null ? [.. other.CustomAgents] : null; + DefaultAgent = other.DefaultAgent; Agent = other.Agent; DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; DisableResume = other.DisableResume; @@ -2117,6 +2141,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// public IList? CustomAgents { get; set; } + /// + /// Configuration for the default agent (the built-in agent that handles turns when no custom agent is selected). + /// Use to hide specific tools from the default agent + /// while keeping them available to custom sub-agents. + /// + public DefaultAgentConfig? DefaultAgent { get; set; } + /// /// Name of the custom agent to activate when the session starts. /// Must match the of one of the agents in . diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index cc36162ff..5c326dcc4 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -90,6 +90,7 @@ public void SessionConfig_Clone_CopiesAllProperties() McpServers = new Dictionary { ["server1"] = new McpStdioServerConfig { Command = "echo" } }, CustomAgents = [new CustomAgentConfig { Name = "agent1" }], Agent = "agent1", + DefaultAgent = new DefaultAgentConfig { ExcludedTools = ["hidden-tool"] }, SkillDirectories = ["/skills"], DisabledSkills = ["skill1"], }; @@ -109,6 +110,7 @@ public void SessionConfig_Clone_CopiesAllProperties() Assert.Equal(original.McpServers.Count, clone.McpServers!.Count); Assert.Equal(original.CustomAgents.Count, clone.CustomAgents!.Count); Assert.Equal(original.Agent, clone.Agent); + Assert.Equal(original.DefaultAgent!.ExcludedTools, clone.DefaultAgent!.ExcludedTools); Assert.Equal(original.SkillDirectories, clone.SkillDirectories); Assert.Equal(original.DisabledSkills, clone.DisabledSkills); } @@ -245,6 +247,7 @@ public void Clone_WithNullCollections_ReturnsNullCollections() Assert.Null(clone.SkillDirectories); Assert.Null(clone.DisabledSkills); Assert.Null(clone.Tools); + Assert.Null(clone.DefaultAgent); Assert.True(clone.IncludeSubAgentStreamingEvents); } diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 59c11a84f..241698516 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -162,6 +162,35 @@ public async Task Should_Create_A_Session_With_ExcludedTools() Assert.Contains("grep", toolNames); } + [Fact] + public async Task Should_Create_A_Session_With_DefaultAgent_ExcludedTools() + { + var session = await CreateSessionAsync(new SessionConfig + { + Tools = + [ + AIFunctionFactory.Create( + (string input) => "SECRET", + "secret_tool", + "A secret tool hidden from the default agent"), + ], + DefaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["secret_tool"], + }, + }); + + await session.SendAsync(new MessageOptions { Prompt = "What is 1+1?" }); + await TestHelper.GetFinalAssistantMessageAsync(session); + + // The real assertion: verify the runtime excluded the tool from the CAPI request + var traffic = await Ctx.GetExchangesAsync(); + Assert.NotEmpty(traffic); + + var toolNames = GetToolNames(traffic[0]); + Assert.DoesNotContain("secret_tool", toolNames); + } + [Fact] public async Task Should_Create_Session_With_Custom_Tool() { diff --git a/go/client.go b/go/client.go index 37e572dc8..74e4839be 100644 --- a/go/client.go +++ b/go/client.go @@ -592,6 +592,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.MCPServers = config.MCPServers req.EnvValueMode = "direct" req.CustomAgents = config.CustomAgents + req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills @@ -776,6 +777,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.MCPServers = config.MCPServers req.EnvValueMode = "direct" req.CustomAgents = config.CustomAgents + req.DefaultAgent = config.DefaultAgent req.Agent = config.Agent req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index 1fed130d3..96ab7a908 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -313,6 +313,57 @@ func TestSession(t *testing.T) { } }) + t.Run("should create a session with defaultAgent excludedTools", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + Tools: []copilot.Tool{ + { + Name: "secret_tool", + Description: "A secret tool hidden from the default agent", + Parameters: map[string]any{ + "type": "object", + "properties": map[string]any{"input": map[string]any{"type": "string"}}, + }, + Handler: func(invocation copilot.ToolInvocation) (copilot.ToolResult, error) { + return copilot.ToolResult{TextResultForLLM: "SECRET", ResultType: "success"}, nil + }, + }, + }, + DefaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"secret_tool"}, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + _, err = session.Send(t.Context(), copilot.MessageOptions{Prompt: "What is 1+1?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + _, err = testharness.GetFinalAssistantMessage(t.Context(), session) + if err != nil { + t.Fatalf("Failed to get assistant message: %v", err) + } + + // The real assertion: verify the runtime excluded the tool from the CAPI request + traffic, err := ctx.GetExchanges() + if err != nil { + t.Fatalf("Failed to get exchanges: %v", err) + } + if len(traffic) == 0 { + t.Fatal("Expected at least one exchange") + } + + toolNames := getToolNames(traffic[0]) + if contains(toolNames, "secret_tool") { + t.Errorf("Expected 'secret_tool' to be excluded from default agent, got %v", toolNames) + } + }) + t.Run("should create session with custom tool", func(t *testing.T) { ctx.ConfigureForTest(t) diff --git a/go/types.go b/go/types.go index aa4fafc94..e9f78e276 100644 --- a/go/types.go +++ b/go/types.go @@ -454,6 +454,15 @@ type CustomAgentConfig struct { Skills []string `json:"skills,omitempty"` } +// DefaultAgentConfig configures the default agent (the built-in agent that handles turns when no custom agent is selected). +// Use ExcludedTools to hide specific tools from the default agent while keeping +// them available to custom sub-agents. +type DefaultAgentConfig struct { + // ExcludedTools is a list of tool names to exclude from the default agent. + // These tools remain available to custom sub-agents that reference them in their Tools list. + ExcludedTools []string `json:"excludedTools,omitempty"` +} + // InfiniteSessionConfig configures infinite sessions with automatic context compaction // and workspace persistence. When enabled, sessions automatically manage context window // limits through background compaction and persist state to a workspace directory. @@ -543,6 +552,9 @@ type SessionConfig struct { MCPServers map[string]MCPServerConfig // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig + // DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected). + // Use ExcludedTools to hide tools from the default agent while keeping them available to sub-agents. + DefaultAgent *DefaultAgentConfig // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string @@ -758,6 +770,8 @@ type ResumeSessionConfig struct { MCPServers map[string]MCPServerConfig // CustomAgents configures custom agents for the session CustomAgents []CustomAgentConfig + // DefaultAgent configures the default agent (the built-in agent that handles turns when no custom agent is selected). + DefaultAgent *DefaultAgentConfig // Agent is the name of the custom agent to activate when the session starts. // Must match the Name of one of the agents in CustomAgents. Agent string @@ -970,6 +984,7 @@ type createSessionRequest struct { MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` Agent string `json:"agent,omitempty"` ConfigDir string `json:"configDir,omitempty"` EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` @@ -1019,6 +1034,7 @@ type resumeSessionRequest struct { MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` EnvValueMode string `json:"envValueMode,omitempty"` CustomAgents []CustomAgentConfig `json:"customAgents,omitempty"` + DefaultAgent *DefaultAgentConfig `json:"defaultAgent,omitempty"` Agent string `json:"agent,omitempty"` SkillDirectories []string `json:"skillDirectories,omitempty"` DisabledSkills []string `json:"disabledSkills,omitempty"` diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c8c137c3d..f4aa1e44f 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -752,6 +752,7 @@ export class CopilotClient { mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, + defaultAgent: config.defaultAgent, agent: config.agent, configDir: config.configDir, enableConfigDiscovery: config.enableConfigDiscovery, @@ -893,6 +894,7 @@ export class CopilotClient { mcpServers: config.mcpServers, envValueMode: "direct", customAgents: config.customAgents, + defaultAgent: config.defaultAgent, agent: config.agent, skillDirectories: config.skillDirectories, disabledSkills: config.disabledSkills, diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index e2942998a..503d0942d 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -38,6 +38,7 @@ export type { MCPStdioServerConfig, MCPHTTPServerConfig, MCPServerConfig, + DefaultAgentConfig, MessageOptions, ModelBilling, ModelCapabilities, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 9b2df4193..a8c644341 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1114,6 +1114,21 @@ export interface CustomAgentConfig { skills?: string[]; } +/** + * Configuration for the default agent (the built-in agent that handles + * turns when no custom agent is selected). + * Use this to control tool visibility for the default agent independently of custom sub-agents. + */ +export interface DefaultAgentConfig { + /** + * List of tool names to exclude from the default agent. + * These tools remain available to custom sub-agents that reference them in their `tools` array. + * Use this to register tools that should only be accessed via delegation to sub-agents, + * keeping the default agent's context clean. + */ + excludedTools?: string[]; +} + /** * Configuration for infinite sessions with automatic context compaction and workspace persistence. * When enabled, sessions automatically manage context window limits through background compaction @@ -1292,6 +1307,14 @@ export interface SessionConfig { */ customAgents?: CustomAgentConfig[]; + /** + * Configuration for the default agent (the built-in agent that handles + * turns when no custom agent is selected). + * Use `excludedTools` to hide specific tools from the default agent while keeping + * them available to custom sub-agents. + */ + defaultAgent?: DefaultAgentConfig; + /** * Name of the custom agent to activate when the session starts. * Must match the `name` of one of the agents in `customAgents`. @@ -1360,6 +1383,7 @@ export type ResumeSessionConfig = Pick< | "enableConfigDiscovery" | "mcpServers" | "customAgents" + | "defaultAgent" | "agent" | "skillDirectories" | "disabledSkills" diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 1c0eceb65..4ea74b576 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -227,6 +227,45 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("forwards defaultAgent in session.create request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.createSession({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.create", + expect.objectContaining({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + }) + ); + }); + + it("forwards defaultAgent in session.resume request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi.spyOn((client as any).connection!, "sendRequest"); + await client.resumeSession(session.sessionId, { + defaultAgent: { excludedTools: ["heavy-tool"] }, + onPermissionRequest: approveAll, + }); + + expect(spy).toHaveBeenCalledWith( + "session.resume", + expect.objectContaining({ + defaultAgent: { excludedTools: ["heavy-tool"] }, + }) + ); + }); + it("does not request permissions on session.resume when using the default joinSession handler", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/e2e/mcp_and_agents.test.ts b/nodejs/test/e2e/mcp_and_agents.test.ts index 59e6d498b..aa580cdee 100644 --- a/nodejs/test/e2e/mcp_and_agents.test.ts +++ b/nodejs/test/e2e/mcp_and_agents.test.ts @@ -5,8 +5,9 @@ import { dirname, resolve } from "path"; import { fileURLToPath } from "url"; import { describe, expect, it } from "vitest"; +import { z } from "zod"; import type { CustomAgentConfig, MCPStdioServerConfig, MCPServerConfig } from "../../src/index.js"; -import { approveAll } from "../../src/index.js"; +import { approveAll, defineTool } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; const __filename = fileURLToPath(import.meta.url); @@ -14,7 +15,7 @@ const __dirname = dirname(__filename); const TEST_MCP_SERVER = resolve(__dirname, "../../../test/harness/test-mcp-server.mjs"); describe("MCP Servers and Custom Agents", async () => { - const { copilotClient: client } = await createSdkTestContext(); + const { copilotClient: client, openAiEndpoint } = await createSdkTestContext(); describe("MCP Servers", () => { it("should accept MCP server configuration on session create", async () => { @@ -296,4 +297,72 @@ describe("MCP Servers and Custom Agents", async () => { await session.disconnect(); }); }); + + describe("Default Agent Tool Exclusion", () => { + it("should hide excluded tools from default agent", async () => { + const secretTool = defineTool("secret_tool", { + description: "A secret tool hidden from the default agent", + parameters: z.object({ + input: z.string().describe("Input to process"), + }), + handler: ({ input }) => `SECRET:${input}`, + }); + + const session = await client.createSession({ + onPermissionRequest: approveAll, + tools: [secretTool], + defaultAgent: { + excludedTools: ["secret_tool"], + }, + }); + + // Ask about the tool — the default agent should not see it + const message = await session.sendAndWait({ + prompt: "Do you have access to a tool called secret_tool? Answer yes or no.", + }); + + // Sanity-check the replayed response (not the actual exclusion assertion) + expect(message?.data.content?.toLowerCase()).toContain("no"); + + // The real assertion: verify the runtime excluded the tool from the CAPI request + const exchanges = await openAiEndpoint.getExchanges(); + const toolNames = exchanges.flatMap((e) => + (e.request.tools ?? []).map((t) => ("function" in t ? t.function.name : "")) + ); + expect(toolNames).not.toContain("secret_tool"); + + await session.disconnect(); + }); + + it("should accept defaultAgent configuration on session resume", async () => { + const session1 = await client.createSession({ onPermissionRequest: approveAll }); + const sessionId = session1.sessionId; + await session1.sendAndWait({ prompt: "What is 3+3?" }); + + const secretTool = defineTool("secret_tool", { + description: "A secret tool hidden from the default agent", + parameters: z.object({ + input: z.string().describe("Input to process"), + }), + handler: ({ input }) => `SECRET:${input}`, + }); + + const session2 = await client.resumeSession(sessionId, { + onPermissionRequest: approveAll, + tools: [secretTool], + defaultAgent: { + excludedTools: ["secret_tool"], + }, + }); + + expect(session2.sessionId).toBe(sessionId); + + const message = await session2.sendAndWait({ + prompt: "What is 4+4?", + }); + expect(message?.data.content).toContain("8"); + + await session2.disconnect(); + }); + }); }); diff --git a/python/copilot/client.py b/python/copilot/client.py index 4c1186f23..09d970f4b 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -47,6 +47,7 @@ CopilotSession, CreateSessionFsHandler, CustomAgentConfig, + DefaultAgentConfig, ElicitationHandler, InfiniteSessionConfig, MCPServerConfig, @@ -1199,6 +1200,7 @@ async def create_session( include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, + default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_dir: str | None = None, enable_config_discovery: bool | None = None, @@ -1241,6 +1243,8 @@ async def create_session( ``subagent.*`` lifecycle events are forwarded. Defaults to True. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. + default_agent: Configuration for the default agent, + including tool visibility controls. agent: Agent to use for the session. config_dir: Override for the configuration directory. enable_config_discovery: When True, automatically discovers MCP server @@ -1373,6 +1377,10 @@ async def create_session( self._convert_custom_agent_to_wire_format(agent) for agent in custom_agents ] + # Add default agent configuration if provided + if default_agent: + payload["defaultAgent"] = self._convert_default_agent_to_wire_format(default_agent) + # Add agent selection if provided if agent: payload["agent"] = agent @@ -1477,6 +1485,7 @@ async def resume_session( include_sub_agent_streaming_events: bool | None = None, mcp_servers: dict[str, MCPServerConfig] | None = None, custom_agents: list[CustomAgentConfig] | None = None, + default_agent: DefaultAgentConfig | dict[str, Any] | None = None, agent: str | None = None, config_dir: str | None = None, enable_config_discovery: bool | None = None, @@ -1519,6 +1528,8 @@ async def resume_session( ``subagent.*`` lifecycle events are forwarded. Defaults to True. mcp_servers: MCP server configurations. custom_agents: Custom agent configurations. + default_agent: Configuration for the default agent, + including tool visibility controls. agent: Agent to use for the session. config_dir: Override for the configuration directory. enable_config_discovery: When True, automatically discovers MCP server @@ -1645,6 +1656,10 @@ async def resume_session( self._convert_custom_agent_to_wire_format(a) for a in custom_agents ] + # Add default agent configuration if provided + if default_agent: + payload["defaultAgent"] = self._convert_default_agent_to_wire_format(default_agent) + if agent: payload["agent"] = agent if skill_directories: @@ -2188,6 +2203,23 @@ def _convert_custom_agent_to_wire_format( wire_agent["skills"] = agent["skills"] return wire_agent + def _convert_default_agent_to_wire_format( + self, config: DefaultAgentConfig | dict[str, Any] + ) -> dict[str, Any]: + """ + Convert default agent config from snake_case to camelCase wire format. + + Args: + config: The default agent configuration in snake_case format. + + Returns: + The default agent configuration in camelCase wire format. + """ + wire: dict[str, Any] = {} + if "excluded_tools" in config: + wire["excludedTools"] = config["excluded_tools"] + return wire + async def _start_cli_server(self) -> None: """ Start the CLI server process. diff --git a/python/copilot/session.py b/python/copilot/session.py index ac771923a..148b1aa63 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -781,6 +781,18 @@ class CustomAgentConfig(TypedDict, total=False): skills: NotRequired[list[str]] +class DefaultAgentConfig(TypedDict, total=False): + """Configuration for the default agent. + + The default agent is the built-in agent that handles turns + when no custom agent is selected. + """ + + # List of tool names to exclude from the default agent. + # These tools remain available to custom sub-agents that reference them. + excluded_tools: list[str] + + class InfiniteSessionConfig(TypedDict, total=False): """ Configuration for infinite sessions with automatic context compaction @@ -870,6 +882,10 @@ class SessionConfig(TypedDict, total=False): mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session custom_agents: list[CustomAgentConfig] + # Configuration for the default agent. + # Use excluded_tools to hide tools from the default agent + # while keeping them available to sub-agents. + default_agent: DefaultAgentConfig # Name of the custom agent to activate when the session starts. # Must match the name of one of the agents in custom_agents. agent: str @@ -938,6 +954,8 @@ class ResumeSessionConfig(TypedDict, total=False): mcp_servers: dict[str, MCPServerConfig] # Custom agent configurations for the session custom_agents: list[CustomAgentConfig] + # Configuration for the default agent. + default_agent: DefaultAgentConfig # Name of the custom agent to activate when the session starts. # Must match the name of one of the agents in custom_agents. agent: str diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index 621062e4e..9e8440b9d 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -146,6 +146,34 @@ async def test_should_create_a_session_with_excludedTools(self, ctx: E2ETestCont assert "grep" in tool_names assert "view" not in tool_names + async def test_should_create_a_session_with_defaultAgent_excludedTools( + self, ctx: E2ETestContext + ): + secret_tool = Tool( + name="secret_tool", + description="A secret tool hidden from the default agent", + handler=lambda args: "SECRET", + parameters={ + "type": "object", + "properties": {"input": {"type": "string"}}, + }, + ) + + session = await ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + tools=[secret_tool], + default_agent={"excluded_tools": ["secret_tool"]}, + ) + + await session.send("What is 1+1?") + await get_final_assistant_message(session) + + # The real assertion: verify the runtime excluded the tool from the CAPI request + traffic = await ctx.get_exchanges() + tools = traffic[0]["request"]["tools"] + tool_names = [t["function"]["name"] for t in tools] + assert "secret_tool" not in tool_names + # TODO: This test shows there's a race condition inside client.ts. If createSession # is called concurrently and autoStart is on, it may start multiple child processes. # This needs to be fixed. Right now it manifests as being unable to delete the temp diff --git a/test/scenarios/tools/custom-agents/README.md b/test/scenarios/tools/custom-agents/README.md index 41bb78c9e..391345454 100644 --- a/test/scenarios/tools/custom-agents/README.md +++ b/test/scenarios/tools/custom-agents/README.md @@ -1,26 +1,30 @@ # Config Sample: Custom Agents -Demonstrates configuring the Copilot SDK with **custom agent definitions** that restrict which tools an agent can use. This validates: +Demonstrates configuring the Copilot SDK with **custom agent definitions** that restrict which tools an agent can use, and **agent-exclusive tools** that are hidden from the main agent. This validates: 1. **Agent definition** — The `customAgents` session config accepts agent definitions with name, description, tool lists, and custom prompts. 2. **Tool scoping** — Each custom agent can be restricted to a subset of available tools (e.g. read-only tools like `grep`, `glob`, `view`). -3. **Agent awareness** — The model recognizes and can describe the configured custom agents. +3. **Agent-exclusive tools** — The `defaultAgent.excludedTools` option hides tools from the main agent while keeping them available to sub-agents. +4. **Agent awareness** — The model recognizes and can describe the configured custom agents. ## What Each Sample Does -1. Creates a session with a `customAgents` array containing a "researcher" agent -2. The researcher agent is scoped to read-only tools: `grep`, `glob`, `view` -3. Sends: _"What custom agents are available? Describe the researcher agent and its capabilities."_ -4. Prints the response — which should describe the researcher agent and its tool restrictions +1. Creates a session with a custom `analyze-codebase` tool and a `customAgents` array containing a "researcher" agent +2. Uses `defaultAgent.excludedTools` to hide `analyze-codebase` from the main agent +3. The researcher agent is scoped to read-only tools plus `analyze-codebase`: `grep`, `glob`, `view`, `analyze-codebase` +4. Sends: _"What custom agents are available? Describe the researcher agent and its capabilities."_ +5. Prints the response — which should describe the researcher agent and its tool restrictions ## Configuration | Option | Value | Effect | |--------|-------|--------| +| `tools` | `[analyze-codebase]` | Registers custom tool at session level | +| `defaultAgent.excludedTools` | `["analyze-codebase"]` | Hides tool from main agent | | `customAgents[0].name` | `"researcher"` | Internal identifier for the agent | | `customAgents[0].displayName` | `"Research Agent"` | Human-readable name | | `customAgents[0].description` | Custom text | Describes agent purpose | -| `customAgents[0].tools` | `["grep", "glob", "view"]` | Restricts agent to read-only tools | +| `customAgents[0].tools` | `["grep", "glob", "view", "analyze-codebase"]` | Restricts agent to read-only tools + analysis | | `customAgents[0].prompt` | Custom text | Sets agent behavior instructions | ## Run diff --git a/test/scenarios/tools/custom-agents/csharp/Program.cs b/test/scenarios/tools/custom-agents/csharp/Program.cs index c5c6525f1..d3c068ade 100644 --- a/test/scenarios/tools/custom-agents/csharp/Program.cs +++ b/test/scenarios/tools/custom-agents/csharp/Program.cs @@ -1,4 +1,5 @@ using GitHub.Copilot.SDK; +using Microsoft.Extensions.AI; var cliPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); @@ -12,9 +13,22 @@ try { + var analyzeCodebase = AIFunctionFactory.Create( + (string query) => $"Analysis result for: {query}", + new AIFunctionFactoryOptions + { + Name = "analyze-codebase", + Description = "Performs deep analysis of the codebase", + }); + await using var session = await client.CreateSessionAsync(new SessionConfig { Model = "claude-haiku-4.5", + Tools = [analyzeCodebase], + DefaultAgent = new DefaultAgentConfig + { + ExcludedTools = ["analyze-codebase"], + }, CustomAgents = [ new CustomAgentConfig @@ -22,7 +36,7 @@ Name = "researcher", DisplayName = "Research Agent", Description = "A research agent that can only read and search files, not modify them", - Tools = ["grep", "glob", "view"], + Tools = ["grep", "glob", "view", "analyze-codebase"], Prompt = "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, ], diff --git a/test/scenarios/tools/custom-agents/go/main.go b/test/scenarios/tools/custom-agents/go/main.go index d1769ff2b..1e6ada739 100644 --- a/test/scenarios/tools/custom-agents/go/main.go +++ b/test/scenarios/tools/custom-agents/go/main.go @@ -20,14 +20,29 @@ func main() { } defer client.Stop() + type AnalyzeParams struct { + Query string `json:"query" jsonschema:"the analysis query"` + } + + analyzeCodebase := copilot.DefineTool("analyze-codebase", + "Performs deep analysis of the codebase", + func(params AnalyzeParams, inv copilot.ToolInvocation) (string, error) { + return fmt.Sprintf("Analysis result for: %s", params.Query), nil + }, + ) + session, err := client.CreateSession(ctx, &copilot.SessionConfig{ Model: "claude-haiku-4.5", + Tools: []copilot.Tool{analyzeCodebase}, + DefaultAgent: &copilot.DefaultAgentConfig{ + ExcludedTools: []string{"analyze-codebase"}, + }, CustomAgents: []copilot.CustomAgentConfig{ { Name: "researcher", DisplayName: "Research Agent", Description: "A research agent that can only read and search files, not modify them", - Tools: []string{"grep", "glob", "view"}, + Tools: []string{"grep", "glob", "view", "analyze-codebase"}, Prompt: "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, }, diff --git a/test/scenarios/tools/custom-agents/python/main.py b/test/scenarios/tools/custom-agents/python/main.py index d4c45950f..bf6e3978c 100644 --- a/test/scenarios/tools/custom-agents/python/main.py +++ b/test/scenarios/tools/custom-agents/python/main.py @@ -2,6 +2,11 @@ import os from copilot import CopilotClient from copilot.client import SubprocessConfig +from copilot.tools import Tool + + +async def analyze_handler(args): + return f"Analysis result for: {args.get('query', '')}" async def main(): @@ -12,18 +17,29 @@ async def main(): try: session = await client.create_session( - { - "model": "claude-haiku-4.5", - "custom_agents": [ - { - "name": "researcher", - "display_name": "Research Agent", - "description": "A research agent that can only read and search files, not modify them", - "tools": ["grep", "glob", "view"], - "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", + model="claude-haiku-4.5", + tools=[ + Tool( + name="analyze-codebase", + description="Performs deep analysis of the codebase", + handler=analyze_handler, + parameters={ + "type": "object", + "properties": {"query": {"type": "string"}}, }, - ], - } + ), + ], + default_agent={"excluded_tools": ["analyze-codebase"]}, + custom_agents=[ + { + "name": "researcher", + "display_name": "Research Agent", + "description": "A research agent that can only read and search files, not modify them", + "tools": ["grep", "glob", "view", "analyze-codebase"], + "prompt": "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", + }, + ], + on_permission_request=lambda _: {"action": "allow"}, ) response = await session.send_and_wait( diff --git a/test/scenarios/tools/custom-agents/typescript/src/index.ts b/test/scenarios/tools/custom-agents/typescript/src/index.ts index f6e163256..ffb0bd827 100644 --- a/test/scenarios/tools/custom-agents/typescript/src/index.ts +++ b/test/scenarios/tools/custom-agents/typescript/src/index.ts @@ -1,4 +1,13 @@ -import { CopilotClient } from "@github/copilot-sdk"; +import { CopilotClient, defineTool } from "@github/copilot-sdk"; +import { z } from "zod"; + +const analyzeCodebase = defineTool("analyze-codebase", { + description: "Performs deep analysis of the codebase, generating extensive context", + parameters: z.object({ query: z.string().describe("The analysis query") }), + handler: async ({ query }) => { + return `Analysis result for: ${query}`; + }, +}); async function main() { const client = new CopilotClient({ @@ -9,12 +18,16 @@ async function main() { try { const session = await client.createSession({ model: "claude-haiku-4.5", + tools: [analyzeCodebase], + defaultAgent: { + excludedTools: ["analyze-codebase"], + }, customAgents: [ { name: "researcher", displayName: "Research Agent", description: "A research agent that can only read and search files, not modify them", - tools: ["grep", "glob", "view"], + tools: ["grep", "glob", "view", "analyze-codebase"], prompt: "You are a research assistant. You can search and read files but cannot modify anything. When asked about your capabilities, list the tools you have access to.", }, ], diff --git a/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml new file mode 100644 index 000000000..65fe6664e --- /dev/null +++ b/test/snapshots/mcp_and_agents/should_accept_defaultagent_configuration_on_session_resume.yaml @@ -0,0 +1,14 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 3+3? + - role: assistant + content: 3 + 3 = 6 + - role: user + content: What is 4+4? + - role: assistant + content: 4 + 4 = 8 diff --git a/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml new file mode 100644 index 000000000..f5506bb18 --- /dev/null +++ b/test/snapshots/mcp_and_agents/should_hide_excluded_tools_from_default_agent.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Do you have access to a tool called secret_tool? Answer yes or no. + - role: assistant + content: No, I don't have access to a tool called secret_tool. diff --git a/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml new file mode 100644 index 000000000..250402101 --- /dev/null +++ b/test/snapshots/session/should_create_a_session_with_defaultagent_excludedtools.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 1+1? + - role: assistant + content: 1 + 1 = 2 From a3e273c9df5ab89c559262edb2de02fe42114cad Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 21 Apr 2026 15:57:24 +0100 Subject: [PATCH 32/34] SessionFs structured error contract and codegen changes --- dotnet/src/Client.cs | 4 +- dotnet/src/Generated/Rpc.cs | 1194 +-- dotnet/src/Generated/SessionEvents.cs | 1676 ++-- dotnet/src/Session.cs | 6 +- dotnet/src/SessionFsProvider.cs | 216 + dotnet/src/Types.cs | 4 +- dotnet/test/CompactionTests.cs | 4 +- dotnet/test/Harness/E2ETestBase.cs | 21 + dotnet/test/Harness/E2ETestContext.cs | 5 + dotnet/test/SessionEventSerializationTests.cs | 25 +- dotnet/test/SessionFsTests.cs | 154 +- go/client.go | 4 +- go/generated_session_events.go | 2553 ++--- go/internal/e2e/compaction_test.go | 1 + go/internal/e2e/session_fs_test.go | 214 +- go/rpc/generated_rpc.go | 2149 +++-- go/session.go | 48 +- go/session_fs_provider.go | 174 + go/types.go | 6 +- nodejs/docs/agent-author.md | 36 +- nodejs/docs/examples.md | 66 +- nodejs/package-lock.json | 7284 +++++++-------- nodejs/package.json | 172 +- nodejs/samples/package-lock.json | 1216 +-- nodejs/samples/package.json | 24 +- nodejs/src/client.ts | 9 +- nodejs/src/generated/rpc.ts | 2784 +++--- nodejs/src/generated/session-events.ts | 8275 +++++++++-------- nodejs/src/index.ts | 4 +- nodejs/src/sessionFsProvider.ts | 159 + nodejs/src/types.ts | 11 +- nodejs/test/e2e/session_fs.test.ts | 99 +- python/copilot/__init__.py | 10 +- python/copilot/_jsonrpc.py | 13 +- python/copilot/client.py | 9 +- python/copilot/generated/rpc.py | 5386 +++++------ python/copilot/generated/session_events.py | 5283 +++++------ python/copilot/session.py | 24 +- python/copilot/session_fs_provider.py | 223 + python/e2e/test_compaction.py | 7 +- python/e2e/test_session_fs.py | 179 +- scripts/codegen/csharp.ts | 125 +- scripts/codegen/go.ts | 113 +- scripts/codegen/python.ts | 207 +- scripts/codegen/typescript.ts | 122 +- scripts/codegen/utils.ts | 276 +- .../should_search_for_patterns_in_files.yaml | 6 +- .../should_persist_plan_md_via_sessionfs.yaml | 10 + ...rite_workspace_metadata_via_sessionfs.yaml | 10 + 49 files changed, 20719 insertions(+), 19881 deletions(-) create mode 100644 dotnet/src/SessionFsProvider.cs create mode 100644 go/session_fs_provider.go create mode 100644 nodejs/src/sessionFsProvider.ts create mode 100644 python/copilot/session_fs_provider.py create mode 100644 test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml create mode 100644 test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 3941abbec..3a161a391 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using StreamJsonRpc; +using StreamJsonRpc.Protocol; using System.Collections.Concurrent; using System.Data; using System.Diagnostics; @@ -1106,7 +1107,7 @@ await Rpc.SessionFs.SetProviderAsync( cancellationToken); } - private void ConfigureSessionFsHandlers(CopilotSession session, Func? createSessionFsHandler) + private void ConfigureSessionFsHandlers(CopilotSession session, Func? createSessionFsHandler) { if (_options.SessionFs is null) { @@ -1840,6 +1841,7 @@ private static LogLevel MapLevel(TraceEventType eventType) AllowOutOfOrderMetadataProperties = true, NumberHandling = JsonNumberHandling.AllowReadingFromString, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(CommonErrorData))] [JsonSerializable(typeof(CreateSessionRequest))] [JsonSerializable(typeof(CreateSessionResponse))] [JsonSerializable(typeof(CustomAgentConfig))] diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 0c9880de6..8de5b2fd4 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -30,13 +30,13 @@ public sealed class PingResult [JsonPropertyName("message")] public string Message { get; set; } = string.Empty; - /// Server timestamp in milliseconds. - [JsonPropertyName("timestamp")] - public long Timestamp { get; set; } - /// Server protocol version number. [JsonPropertyName("protocolVersion")] public long ProtocolVersion { get; set; } + + /// Server timestamp in milliseconds. + [JsonPropertyName("timestamp")] + public long Timestamp { get; set; } } /// RPC data type for Ping operations. @@ -47,69 +47,77 @@ internal sealed class PingRequest public string? Message { get; set; } } -/// Feature flags indicating what the model supports. -public sealed class ModelCapabilitiesSupports +/// Billing information. +public sealed class ModelBilling { - /// Whether this model supports vision/image input. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } - - /// Whether this model supports reasoning effort configuration. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } + /// Billing cost multiplier relative to the base rate. + [JsonPropertyName("multiplier")] + public double Multiplier { get; set; } } /// Vision-specific limits. public sealed class ModelCapabilitiesLimitsVision { - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList SupportedMediaTypes { get => field ??= []; set; } + /// Maximum image size in bytes. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("max_prompt_image_size")] + public long MaxPromptImageSize { get; set; } /// Maximum number of images per prompt. [Range((double)1, (double)long.MaxValue)] [JsonPropertyName("max_prompt_images")] public long MaxPromptImages { get; set; } - /// Maximum image size in bytes. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_prompt_image_size")] - public long MaxPromptImageSize { get; set; } + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList SupportedMediaTypes { get => field ??= []; set; } } /// Token limits for prompts, outputs, and context window. public sealed class ModelCapabilitiesLimits { - /// Maximum number of prompt/input tokens. + /// Maximum total context window size in tokens. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_prompt_tokens")] - public long? MaxPromptTokens { get; set; } + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { get; set; } /// Maximum number of output/completion tokens. [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_output_tokens")] public long? MaxOutputTokens { get; set; } - /// Maximum total context window size in tokens. + /// Maximum number of prompt/input tokens. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; set; } + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } /// Vision-specific limits. [JsonPropertyName("vision")] public ModelCapabilitiesLimitsVision? Vision { get; set; } } +/// Feature flags indicating what the model supports. +public sealed class ModelCapabilitiesSupports +{ + /// Whether this model supports reasoning effort configuration. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } + + /// Whether this model supports vision/image input. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } +} + /// Model capabilities and limits. public sealed class ModelCapabilities { - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesSupports? Supports { get; set; } - /// Token limits for prompts, outputs, and context window. [JsonPropertyName("limits")] public ModelCapabilitiesLimits? Limits { get; set; } + + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesSupports? Supports { get; set; } } /// Policy state (if applicable). @@ -124,17 +132,21 @@ public sealed class ModelPolicy public string Terms { get; set; } = string.Empty; } -/// Billing information. -public sealed class ModelBilling -{ - /// Billing cost multiplier relative to the base rate. - [JsonPropertyName("multiplier")] - public double Multiplier { get; set; } -} - /// RPC data type for Model operations. public sealed class Model { + /// Billing information. + [JsonPropertyName("billing")] + public ModelBilling? Billing { get; set; } + + /// Model capabilities and limits. + [JsonPropertyName("capabilities")] + public ModelCapabilities Capabilities { get => field ??= new(); set; } + + /// Default reasoning effort level (only present if model supports reasoning effort). + [JsonPropertyName("defaultReasoningEffort")] + public string? DefaultReasoningEffort { get; set; } + /// Model identifier (e.g., "claude-sonnet-4.5"). [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; @@ -143,25 +155,13 @@ public sealed class Model [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Model capabilities and limits. - [JsonPropertyName("capabilities")] - public ModelCapabilities Capabilities { get => field ??= new(); set; } - /// Policy state (if applicable). [JsonPropertyName("policy")] public ModelPolicy? Policy { get; set; } - /// Billing information. - [JsonPropertyName("billing")] - public ModelBilling? Billing { get; set; } - /// Supported reasoning effort levels (only present if model supports reasoning effort). [JsonPropertyName("supportedReasoningEfforts")] public IList? SupportedReasoningEfforts { get; set; } - - /// Default reasoning effort level (only present if model supports reasoning effort). - [JsonPropertyName("defaultReasoningEffort")] - public string? DefaultReasoningEffort { get; set; } } /// RPC data type for ModelList operations. @@ -175,6 +175,14 @@ public sealed class ModelList /// RPC data type for Tool operations. public sealed class Tool { + /// Description of what the tool does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Optional instructions for how to use this tool effectively. + [JsonPropertyName("instructions")] + public string? Instructions { get; set; } + /// Tool identifier (e.g., "bash", "grep", "str_replace_editor"). [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -183,17 +191,9 @@ public sealed class Tool [JsonPropertyName("namespacedName")] public string? NamespacedName { get; set; } - /// Description of what the tool does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; - /// JSON Schema for the tool's input parameters. [JsonPropertyName("parameters")] public IDictionary? Parameters { get; set; } - - /// Optional instructions for how to use this tool effectively. - [JsonPropertyName("instructions")] - public string? Instructions { get; set; } } /// RPC data type for ToolList operations. @@ -219,27 +219,35 @@ public sealed class AccountQuotaSnapshot [JsonPropertyName("entitlementRequests")] public long EntitlementRequests { get; set; } - /// Number of requests used so far this period. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("usedRequests")] - public long UsedRequests { get; set; } - - /// Percentage of entitlement remaining. - [JsonPropertyName("remainingPercentage")] - public double RemainingPercentage { get; set; } + /// Whether the user has an unlimited usage entitlement. + [JsonPropertyName("isUnlimitedEntitlement")] + public bool IsUnlimitedEntitlement { get; set; } /// Number of overage requests made this period. - [Range((double)0, (double)long.MaxValue)] + [Range(0, double.MaxValue)] [JsonPropertyName("overage")] - public long Overage { get; set; } + public double Overage { get; set; } - /// Whether pay-per-request usage is allowed when quota is exhausted. + /// Whether overage is allowed when quota is exhausted. [JsonPropertyName("overageAllowedWithExhaustedQuota")] public bool OverageAllowedWithExhaustedQuota { get; set; } - /// Date when the quota resets (ISO 8601). + /// Percentage of entitlement remaining. + [JsonPropertyName("remainingPercentage")] + public double RemainingPercentage { get; set; } + + /// Date when the quota resets (ISO 8601 string). [JsonPropertyName("resetDate")] - public DateTimeOffset? ResetDate { get; set; } + public string? ResetDate { get; set; } + + /// Whether usage is still permitted after quota exhaustion. + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + public bool UsageAllowedWithExhaustedQuota { get; set; } + + /// Number of requests used so far this period. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("usedRequests")] + public long UsedRequests { get; set; } } /// RPC data type for AccountGetQuota operations. @@ -253,22 +261,22 @@ public sealed class AccountGetQuotaResult /// RPC data type for DiscoveredMcpServer operations. public sealed class DiscoveredMcpServer { + /// Whether the server is enabled (not in the disabled list). + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + /// Server name (config key). [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). - [JsonPropertyName("type")] - public DiscoveredMcpServerType? Type { get; set; } - /// Configuration source. [JsonPropertyName("source")] public DiscoveredMcpServerSource Source { get; set; } - /// Whether the server is enabled (not in the disabled list). - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } + /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). + [JsonPropertyName("type")] + public DiscoveredMcpServerType? Type { get; set; } } /// RPC data type for McpDiscover operations. @@ -298,27 +306,27 @@ public sealed class McpConfigList /// RPC data type for McpConfigAdd operations. internal sealed class McpConfigAddRequest { + /// MCP server configuration (local/stdio or remote/http). + [JsonPropertyName("config")] + public object Config { get; set; } = null!; + /// Unique name for the MCP server. [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - /// MCP server configuration (local/stdio or remote/http). - [JsonPropertyName("config")] - public object Config { get; set; } = null!; } /// RPC data type for McpConfigUpdate operations. internal sealed class McpConfigUpdateRequest { + /// MCP server configuration (local/stdio or remote/http). + [JsonPropertyName("config")] + public object Config { get; set; } = null!; + /// Name of the MCP server to update. [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - - /// MCP server configuration (local/stdio or remote/http). - [JsonPropertyName("config")] - public object Config { get; set; } = null!; } /// RPC data type for McpConfigRemove operations. @@ -333,26 +341,18 @@ internal sealed class McpConfigRemoveRequest /// RPC data type for ServerSkill operations. public sealed class ServerSkill { - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - /// Description of what the skill does. [JsonPropertyName("description")] public string Description { get; set; } = string.Empty; - /// Source location type (e.g., project, personal-copilot, plugin, builtin). - [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; - - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public bool UserInvocable { get; set; } - /// Whether the skill is currently enabled (based on global config). [JsonPropertyName("enabled")] public bool Enabled { get; set; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + /// Absolute path to the skill file. [JsonPropertyName("path")] public string? Path { get; set; } @@ -360,6 +360,14 @@ public sealed class ServerSkill /// The project path this skill belongs to (only for project/inherited skills). [JsonPropertyName("projectPath")] public string? ProjectPath { get; set; } + + /// Source location type (e.g., project, personal-copilot, plugin, builtin). + [JsonPropertyName("source")] + public string Source { get; set; } = string.Empty; + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public bool UserInvocable { get; set; } } /// RPC data type for ServerSkillList operations. @@ -401,6 +409,10 @@ public sealed class SessionFsSetProviderResult /// RPC data type for SessionFsSetProvider operations. internal sealed class SessionFsSetProviderRequest { + /// Path conventions used by this filesystem. + [JsonPropertyName("conventions")] + public SessionFsSetProviderConventions Conventions { get; set; } + /// Initial working directory for sessions. [JsonPropertyName("initialCwd")] public string InitialCwd { get; set; } = string.Empty; @@ -408,10 +420,6 @@ internal sealed class SessionFsSetProviderRequest /// Path within each session's SessionFs where the runtime stores files for that session. [JsonPropertyName("sessionStatePath")] public string SessionStatePath { get; set; } = string.Empty; - - /// Path conventions used by this filesystem. - [JsonPropertyName("conventions")] - public SessionFsSetProviderConventions Conventions { get; set; } } /// RPC data type for SessionsFork operations. @@ -447,21 +455,21 @@ public sealed class LogResult /// RPC data type for Log operations. internal sealed class LogRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// Human-readable message. - [JsonPropertyName("message")] - public string Message { get; set; } = string.Empty; + /// When true, the message is transient and not persisted to the session event log on disk. + [JsonPropertyName("ephemeral")] + public bool? Ephemeral { get; set; } /// Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". [JsonPropertyName("level")] public SessionLogLevel? Level { get; set; } - /// When true, the message is transient and not persisted to the session event log on disk. - [JsonPropertyName("ephemeral")] - public bool? Ephemeral { get; set; } + /// Human-readable message. + [JsonPropertyName("message")] + public string Message { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; /// Optional URL the user can open in their browser for more details. [Url] @@ -494,77 +502,77 @@ public sealed class ModelSwitchToResult public string? ModelId { get; set; } } -/// Feature flags indicating what the model supports. -public sealed class ModelCapabilitiesOverrideSupports -{ - /// Gets or sets the vision value. - [JsonPropertyName("vision")] - public bool? Vision { get; set; } - - /// Gets or sets the reasoningEffort value. - [JsonPropertyName("reasoningEffort")] - public bool? ReasoningEffort { get; set; } -} - /// RPC data type for ModelCapabilitiesOverrideLimitsVision operations. public sealed class ModelCapabilitiesOverrideLimitsVision { - /// MIME types the model accepts. - [JsonPropertyName("supported_media_types")] - public IList? SupportedMediaTypes { get; set; } + /// Maximum image size in bytes. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("max_prompt_image_size")] + public long? MaxPromptImageSize { get; set; } /// Maximum number of images per prompt. [Range((double)1, (double)long.MaxValue)] [JsonPropertyName("max_prompt_images")] public long? MaxPromptImages { get; set; } - /// Maximum image size in bytes. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_prompt_image_size")] - public long? MaxPromptImageSize { get; set; } + /// MIME types the model accepts. + [JsonPropertyName("supported_media_types")] + public IList? SupportedMediaTypes { get; set; } } /// Token limits for prompts, outputs, and context window. public sealed class ModelCapabilitiesOverrideLimits { - /// Gets or sets the max_prompt_tokens value. + /// Maximum total context window size in tokens. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_prompt_tokens")] - public long? MaxPromptTokens { get; set; } + [JsonPropertyName("max_context_window_tokens")] + public long? MaxContextWindowTokens { get; set; } /// Gets or sets the max_output_tokens value. [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("max_output_tokens")] public long? MaxOutputTokens { get; set; } - /// Maximum total context window size in tokens. + /// Gets or sets the max_prompt_tokens value. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("max_context_window_tokens")] - public long? MaxContextWindowTokens { get; set; } + [JsonPropertyName("max_prompt_tokens")] + public long? MaxPromptTokens { get; set; } /// Gets or sets the vision value. [JsonPropertyName("vision")] public ModelCapabilitiesOverrideLimitsVision? Vision { get; set; } } +/// Feature flags indicating what the model supports. +public sealed class ModelCapabilitiesOverrideSupports +{ + /// Gets or sets the reasoningEffort value. + [JsonPropertyName("reasoningEffort")] + public bool? ReasoningEffort { get; set; } + + /// Gets or sets the vision value. + [JsonPropertyName("vision")] + public bool? Vision { get; set; } +} + /// Override individual model capabilities resolved by the runtime. public sealed class ModelCapabilitiesOverride { - /// Feature flags indicating what the model supports. - [JsonPropertyName("supports")] - public ModelCapabilitiesOverrideSupports? Supports { get; set; } - /// Token limits for prompts, outputs, and context window. [JsonPropertyName("limits")] public ModelCapabilitiesOverrideLimits? Limits { get; set; } + + /// Feature flags indicating what the model supports. + [JsonPropertyName("supports")] + public ModelCapabilitiesOverrideSupports? Supports { get; set; } } /// RPC data type for ModelSwitchTo operations. internal sealed class ModelSwitchToRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Override individual model capabilities resolved by the runtime. + [JsonPropertyName("modelCapabilities")] + public ModelCapabilitiesOverride? ModelCapabilities { get; set; } /// Model identifier to switch to. [JsonPropertyName("modelId")] @@ -574,9 +582,9 @@ internal sealed class ModelSwitchToRequest [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } - /// Override individual model capabilities resolved by the runtime. - [JsonPropertyName("modelCapabilities")] - public ModelCapabilitiesOverride? ModelCapabilities { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionModeGet operations. @@ -590,13 +598,13 @@ internal sealed class SessionModeGetRequest /// RPC data type for ModeSet operations. internal sealed class ModeSetRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// The agent mode. Valid values: "interactive", "plan", "autopilot". [JsonPropertyName("mode")] public SessionMode Mode { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for NameGet operations. @@ -618,29 +626,29 @@ internal sealed class SessionNameGetRequest /// RPC data type for NameSet operations. internal sealed class NameSetRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// New session name (1–100 characters, trimmed of leading/trailing whitespace). [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] [MinLength(1)] [MaxLength(100)] [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 PlanRead operations. public sealed class PlanReadResult { - /// Whether the plan file exists in the workspace. - [JsonPropertyName("exists")] - public bool Exists { get; set; } - /// The content of the plan file, or null if it does not exist. [JsonPropertyName("content")] public string? Content { get; set; } + /// Whether the plan file exists in the workspace. + [JsonPropertyName("exists")] + public bool Exists { get; set; } + /// Absolute file path of the plan file, or null if workspace is not enabled. [JsonPropertyName("path")] public string? Path { get; set; } @@ -657,13 +665,13 @@ internal sealed class SessionPlanReadRequest /// RPC data type for PlanUpdate operations. internal sealed class PlanUpdateRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// The new content for the plan file. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionPlanDelete operations. @@ -677,9 +685,17 @@ internal sealed class SessionPlanDeleteRequest /// RPC data type for WorkspacesGetWorkspaceResultWorkspace operations. public sealed class WorkspacesGetWorkspaceResultWorkspace { - /// Gets or sets the id value. - [JsonPropertyName("id")] - public Guid Id { get; set; } + /// Gets or sets the branch value. + [JsonPropertyName("branch")] + public string? Branch { get; set; } + + /// Gets or sets the chronicle_sync_dismissed value. + [JsonPropertyName("chronicle_sync_dismissed")] + public bool? ChronicleSyncDismissed { get; set; } + + /// Gets or sets the created_at value. + [JsonPropertyName("created_at")] + public DateTimeOffset? CreatedAt { get; set; } /// Gets or sets the cwd value. [JsonPropertyName("cwd")] @@ -689,62 +705,54 @@ public sealed class WorkspacesGetWorkspaceResultWorkspace [JsonPropertyName("git_root")] public string? GitRoot { get; set; } - /// Gets or sets the repository value. - [JsonPropertyName("repository")] - public string? Repository { get; set; } - /// Gets or sets the host_type value. [JsonPropertyName("host_type")] public WorkspacesGetWorkspaceResultWorkspaceHostType? HostType { get; set; } - /// Gets or sets the branch value. - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Gets or sets the summary value. - [JsonPropertyName("summary")] - public string? Summary { get; set; } - - /// Gets or sets the name value. - [JsonPropertyName("name")] - public string? Name { get; set; } - - /// Gets or sets the summary_count value. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("summary_count")] - public long? SummaryCount { get; set; } + /// Gets or sets the id value. + [JsonPropertyName("id")] + public Guid Id { get; set; } - /// Gets or sets the created_at value. - [JsonPropertyName("created_at")] - public DateTimeOffset? CreatedAt { get; set; } + /// Gets or sets the mc_last_event_id value. + [JsonPropertyName("mc_last_event_id")] + public string? McLastEventId { get; set; } - /// Gets or sets the updated_at value. - [JsonPropertyName("updated_at")] - public DateTimeOffset? UpdatedAt { get; set; } + /// Gets or sets the mc_session_id value. + [JsonPropertyName("mc_session_id")] + public string? McSessionId { get; set; } /// Gets or sets the mc_task_id value. [JsonPropertyName("mc_task_id")] public string? McTaskId { get; set; } - /// Gets or sets the mc_session_id value. - [JsonPropertyName("mc_session_id")] - public string? McSessionId { get; set; } + /// Gets or sets the name value. + [JsonPropertyName("name")] + public string? Name { get; set; } - /// Gets or sets the mc_last_event_id value. - [JsonPropertyName("mc_last_event_id")] - public string? McLastEventId { get; set; } + /// Gets or sets the remote_steerable value. + [JsonPropertyName("remote_steerable")] + public bool? RemoteSteerable { get; set; } + + /// Gets or sets the repository value. + [JsonPropertyName("repository")] + public string? Repository { get; set; } /// Gets or sets the session_sync_level value. [JsonPropertyName("session_sync_level")] public WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel? SessionSyncLevel { get; set; } - /// Gets or sets the pr_create_sync_dismissed value. - [JsonPropertyName("pr_create_sync_dismissed")] - public bool? PrCreateSyncDismissed { get; set; } + /// Gets or sets the summary value. + [JsonPropertyName("summary")] + public string? Summary { get; set; } - /// Gets or sets the chronicle_sync_dismissed value. - [JsonPropertyName("chronicle_sync_dismissed")] - public bool? ChronicleSyncDismissed { get; set; } + /// Gets or sets the summary_count value. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("summary_count")] + public long? SummaryCount { get; set; } + + /// Gets or sets the updated_at value. + [JsonPropertyName("updated_at")] + public DateTimeOffset? UpdatedAt { get; set; } } /// RPC data type for WorkspacesGetWorkspace operations. @@ -790,34 +798,46 @@ public sealed class WorkspacesReadFileResult /// RPC data type for WorkspacesReadFile operations. internal sealed class WorkspacesReadFileRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Relative path within the workspace files directory. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for WorkspacesCreateFile operations. internal sealed class WorkspacesCreateFileRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// File content to write as a UTF-8 string. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; /// Relative path within the workspace files directory. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; - /// File content to write as a UTF-8 string. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for InstructionsSources operations. public sealed class InstructionsSources { + /// Glob pattern from frontmatter — when set, this instruction applies only to matching files. + [JsonPropertyName("applyTo")] + public string? ApplyTo { get; set; } + + /// Raw content of the instruction file. + [JsonPropertyName("content")] + public string Content { get; set; } = string.Empty; + + /// Short description (body after frontmatter) for use in instruction tables. + [JsonPropertyName("description")] + public string? Description { get; set; } + /// Unique identifier for this source (used for toggling). [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; @@ -826,29 +846,17 @@ public sealed class InstructionsSources [JsonPropertyName("label")] public string Label { get; set; } = string.Empty; + /// Where this source lives — used for UI grouping. + [JsonPropertyName("location")] + public InstructionsSourcesLocation Location { get; set; } + /// File path relative to repo or absolute for home. [JsonPropertyName("sourcePath")] public string SourcePath { get; set; } = string.Empty; - /// Raw content of the instruction file. - [JsonPropertyName("content")] - public string Content { get; set; } = string.Empty; - /// Category of instruction source — used for merge logic. [JsonPropertyName("type")] public InstructionsSourcesType Type { get; set; } - - /// Where this source lives — used for UI grouping. - [JsonPropertyName("location")] - public InstructionsSourcesLocation Location { get; set; } - - /// Glob pattern from frontmatter — when set, this instruction applies only to matching files. - [JsonPropertyName("applyTo")] - public string? ApplyTo { get; set; } - - /// Short description (body after frontmatter) for use in instruction tables. - [JsonPropertyName("description")] - public string? Description { get; set; } } /// RPC data type for InstructionsGetSources operations. @@ -880,29 +888,29 @@ public sealed class FleetStartResult [Experimental(Diagnostics.Experimental)] internal sealed class FleetStartRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Optional user prompt to combine with fleet instructions. [JsonPropertyName("prompt")] public string? Prompt { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for AgentInfo operations. public sealed class AgentInfo { - /// Unique identifier of the custom agent. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Description of the agent's purpose. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; /// Human-readable display name. [JsonPropertyName("displayName")] public string DisplayName { get; set; } = string.Empty; - /// Description of the agent's purpose. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Unique identifier of the custom agent. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; } /// RPC data type for AgentList operations. @@ -954,13 +962,13 @@ public sealed class AgentSelectResult [Experimental(Diagnostics.Experimental)] internal sealed class AgentSelectRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Name of the custom agent to select. [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 SessionAgentDeselect operations. @@ -993,13 +1001,21 @@ internal sealed class SessionAgentReloadRequest /// RPC data type for Skill operations. public sealed class Skill { + /// Description of what the skill does. + [JsonPropertyName("description")] + public string Description { get; set; } = string.Empty; + + /// Whether the skill is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + /// Unique identifier for the skill. [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Description of what the skill does. - [JsonPropertyName("description")] - public string Description { get; set; } = string.Empty; + /// Absolute path to the skill file. + [JsonPropertyName("path")] + public string? Path { get; set; } /// Source location type (e.g., project, personal, plugin). [JsonPropertyName("source")] @@ -1008,14 +1024,6 @@ public sealed class Skill /// Whether the skill can be invoked by the user as a slash command. [JsonPropertyName("userInvocable")] public bool UserInvocable { get; set; } - - /// Whether the skill is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } - - /// Absolute path to the skill file. - [JsonPropertyName("path")] - public string? Path { get; set; } } /// RPC data type for SkillList operations. @@ -1040,26 +1048,26 @@ internal sealed class SessionSkillsListRequest [Experimental(Diagnostics.Experimental)] internal sealed class SkillsEnableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Name of the skill to enable. [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 SkillsDisable operations. [Experimental(Diagnostics.Experimental)] internal sealed class SkillsDisableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Name of the skill to disable. [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 SessionSkillsReload operations. @@ -1074,22 +1082,22 @@ internal sealed class SessionSkillsReloadRequest /// RPC data type for McpServer operations. public sealed class McpServer { + /// Error message if the server failed to connect. + [JsonPropertyName("error")] + public string? Error { get; set; } + /// Server name (config key). [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. - [JsonPropertyName("status")] - public McpServerStatus Status { get; set; } - /// Configuration source: user, workspace, plugin, or builtin. [JsonPropertyName("source")] public McpServerSource? Source { get; set; } - /// Error message if the server failed to connect. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + [JsonPropertyName("status")] + public McpServerStatus Status { get; set; } } /// RPC data type for McpServerList operations. @@ -1114,28 +1122,28 @@ internal sealed class SessionMcpListRequest [Experimental(Diagnostics.Experimental)] internal sealed class McpEnableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Name of the MCP server to enable. [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for McpDisable operations. [Experimental(Diagnostics.Experimental)] internal sealed class McpDisableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Name of the MCP server to disable. [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionMcpReload operations. @@ -1150,21 +1158,21 @@ internal sealed class SessionMcpReloadRequest /// RPC data type for Plugin operations. public sealed class Plugin { - /// Plugin name. - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; + /// Whether the plugin is currently enabled. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } /// Marketplace the plugin came from. [JsonPropertyName("marketplace")] public string Marketplace { get; set; } = string.Empty; + /// Plugin name. + [JsonPropertyName("name")] + public string Name { get; set; } = string.Empty; + /// Installed version. [JsonPropertyName("version")] public string? Version { get; set; } - - /// Whether the plugin is currently enabled. - [JsonPropertyName("enabled")] - public bool Enabled { get; set; } } /// RPC data type for PluginList operations. @@ -1196,6 +1204,10 @@ public sealed class Extension [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; + /// Process ID if the extension is running. + [JsonPropertyName("pid")] + public long? Pid { get; set; } + /// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/). [JsonPropertyName("source")] public ExtensionSource Source { get; set; } @@ -1203,10 +1215,6 @@ public sealed class Extension /// Current status: running, disabled, failed, or starting. [JsonPropertyName("status")] public ExtensionStatus Status { get; set; } - - /// Process ID if the extension is running. - [JsonPropertyName("pid")] - public long? Pid { get; set; } } /// RPC data type for ExtensionList operations. @@ -1231,26 +1239,26 @@ internal sealed class SessionExtensionsListRequest [Experimental(Diagnostics.Experimental)] internal sealed class ExtensionsEnableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Source-qualified extension ID to enable. [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for ExtensionsDisable operations. [Experimental(Diagnostics.Experimental)] internal sealed class ExtensionsDisableRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Source-qualified extension ID to disable. [JsonPropertyName("id")] public string Id { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionExtensionsReload operations. @@ -1273,9 +1281,9 @@ public sealed class HandleToolCallResult /// RPC data type for ToolsHandlePendingToolCall operations. internal sealed class ToolsHandlePendingToolCallRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Error message if the tool call failed. + [JsonPropertyName("error")] + public string? Error { get; set; } /// Request ID of the pending tool call. [JsonPropertyName("requestId")] @@ -1285,9 +1293,9 @@ internal sealed class ToolsHandlePendingToolCallRequest [JsonPropertyName("result")] public object? Result { get; set; } - /// Error message if the tool call failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for CommandsHandlePendingCommand operations. @@ -1301,17 +1309,17 @@ public sealed class CommandsHandlePendingCommandResult /// RPC data type for CommandsHandlePendingCommand operations. internal sealed class CommandsHandlePendingCommandRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Error message if the command handler failed. + [JsonPropertyName("error")] + public string? Error { get; set; } /// Request ID from the command invocation event. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; - /// Error message if the command handler failed. - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// The elicitation response (accept with form values, decline, or cancel). @@ -1329,10 +1337,6 @@ public sealed class UIElicitationResponse /// JSON Schema describing the form fields to present to the user. public sealed class UIElicitationSchema { - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; - /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] public IDictionary Properties { get => field ??= new Dictionary(); set; } @@ -1340,15 +1344,15 @@ public sealed class UIElicitationSchema /// List of required field names. [JsonPropertyName("required")] public IList? Required { get; set; } + + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public string Type { get; set; } = string.Empty; } /// RPC data type for UIElicitation operations. internal sealed class UIElicitationRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Message describing what information is needed from the user. [JsonPropertyName("message")] public string Message { get; set; } = string.Empty; @@ -1356,6 +1360,10 @@ internal sealed class UIElicitationRequest /// JSON Schema describing the form fields to present to the user. [JsonPropertyName("requestedSchema")] public UIElicitationSchema RequestedSchema { get => field ??= new(); set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for UIElicitation operations. @@ -1369,10 +1377,6 @@ public sealed class UIElicitationResult /// RPC data type for UIHandlePendingElicitation operations. internal sealed class UIHandlePendingElicitationRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// The unique request ID from the elicitation.requested event. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; @@ -1380,6 +1384,10 @@ internal sealed class UIHandlePendingElicitationRequest /// The elicitation response (accept with form values, decline, or cancel). [JsonPropertyName("result")] public UIElicitationResponse Result { get => field ??= new(); set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for PermissionRequest operations. @@ -1390,20 +1398,113 @@ public sealed class PermissionRequestResult public bool Success { get; set; } } +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproved), "approved")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByRules), "denied-by-rules")] +[JsonDerivedType(typeof(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedInteractivelyByUser), "denied-interactively-by-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +public partial class PermissionDecision +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The approved variant of . +public partial class PermissionDecisionApproved : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved"; +} + +/// The denied-by-rules variant of . +public partial class PermissionDecisionDeniedByRules : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; + + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required object[] Rules { get; set; } +} + +/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; +} + +/// The denied-interactively-by-user variant of . +public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-interactively-by-user"; + + /// Optional feedback from the user explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } +} + +/// The denied-by-content-exclusion-policy variant of . +public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-content-exclusion-policy"; + + /// Human-readable explanation of why the path was excluded. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// File path that triggered the exclusion. + [JsonPropertyName("path")] + public required string Path { get; set; } +} + +/// The denied-by-permission-request-hook variant of . +public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; + + /// Whether to interrupt the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interrupt")] + public bool? Interrupt { get; set; } + + /// Optional message from the hook explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } +} + /// RPC data type for PermissionDecision operations. internal sealed class PermissionDecisionRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Request ID of the pending permission request. [JsonPropertyName("requestId")] public string RequestId { get; set; } = string.Empty; /// Gets or sets the result value. [JsonPropertyName("result")] - public object Result { get; set; } = null!; + public PermissionDecision Result { get => field ??= new(); set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for ShellExec operations. @@ -1417,10 +1518,6 @@ public sealed class ShellExecResult /// RPC data type for ShellExec operations. internal sealed class ShellExecRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Shell command to execute. [JsonPropertyName("command")] public string Command { get; set; } = string.Empty; @@ -1429,6 +1526,10 @@ internal sealed class ShellExecRequest [JsonPropertyName("cwd")] public string? Cwd { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + /// Timeout in milliseconds (default: 30000). [Range((double)0, (double)long.MaxValue)] [JsonConverter(typeof(MillisecondsTimeSpanConverter))] @@ -1447,14 +1548,14 @@ public sealed class ShellKillResult /// RPC data type for ShellKill operations. internal sealed class ShellKillRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Process identifier returned by shell.exec. [JsonPropertyName("processId")] public string ProcessId { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; + /// Signal to send (default: SIGTERM). [JsonPropertyName("signal")] public ShellKillSignal? Signal { get; set; } @@ -1463,10 +1564,10 @@ internal sealed class ShellKillRequest /// Post-compaction context window usage breakdown. public sealed class HistoryCompactContextWindow { - /// Maximum token count for the model's context window. + /// Token count from non-system messages (user, assistant, tool). [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("tokenLimit")] - public long TokenLimit { get; set; } + [JsonPropertyName("conversationTokens")] + public long? ConversationTokens { get; set; } /// Current total tokens in the context window (system + conversation + tool definitions). [Range((double)0, (double)long.MaxValue)] @@ -1483,10 +1584,10 @@ public sealed class HistoryCompactContextWindow [JsonPropertyName("systemTokens")] public long? SystemTokens { get; set; } - /// Token count from non-system messages (user, assistant, tool). + /// Maximum token count for the model's context window. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("conversationTokens")] - public long? ConversationTokens { get; set; } + [JsonPropertyName("tokenLimit")] + public long TokenLimit { get; set; } /// Token count from tool definitions. [Range((double)0, (double)long.MaxValue)] @@ -1498,6 +1599,15 @@ public sealed class HistoryCompactContextWindow [Experimental(Diagnostics.Experimental)] public sealed class HistoryCompactResult { + /// Post-compaction context window usage breakdown. + [JsonPropertyName("contextWindow")] + public HistoryCompactContextWindow? ContextWindow { get; set; } + + /// Number of messages removed during compaction. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("messagesRemoved")] + public long MessagesRemoved { get; set; } + /// Whether compaction completed successfully. [JsonPropertyName("success")] public bool Success { get; set; } @@ -1506,15 +1616,6 @@ public sealed class HistoryCompactResult [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("tokensRemoved")] public long TokensRemoved { get; set; } - - /// Number of messages removed during compaction. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("messagesRemoved")] - public long MessagesRemoved { get; set; } - - /// Post-compaction context window usage breakdown. - [JsonPropertyName("contextWindow")] - public HistoryCompactContextWindow? ContextWindow { get; set; } } /// RPC data type for SessionHistoryCompact operations. @@ -1540,18 +1641,22 @@ public sealed class HistoryTruncateResult [Experimental(Diagnostics.Experimental)] internal sealed class HistoryTruncateRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Event ID to truncate to. This event and all events after it are removed from the session. [JsonPropertyName("eventId")] public string EventId { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// Aggregated code change metrics. public sealed class UsageMetricsCodeChanges { + /// Number of distinct files modified. + [JsonPropertyName("filesModifiedCount")] + public long FilesModifiedCount { get; set; } + /// Total lines of code added. [JsonPropertyName("linesAdded")] public long LinesAdded { get; set; } @@ -1559,37 +1664,23 @@ public sealed class UsageMetricsCodeChanges /// Total lines of code removed. [JsonPropertyName("linesRemoved")] public long LinesRemoved { get; set; } - - /// Number of distinct files modified. - [JsonPropertyName("filesModifiedCount")] - public long FilesModifiedCount { get; set; } } /// Request count and cost metrics for this model. public sealed class UsageMetricsModelMetricRequests { - /// Number of API requests made with this model. - [JsonPropertyName("count")] - public long Count { get; set; } - /// User-initiated premium request cost (with multiplier applied). [JsonPropertyName("cost")] public double Cost { get; set; } + + /// Number of API requests made with this model. + [JsonPropertyName("count")] + public long Count { get; set; } } /// Token usage metrics for this model. public sealed class UsageMetricsModelMetricUsage { - /// Total input tokens consumed. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("inputTokens")] - public long InputTokens { get; set; } - - /// Total output tokens produced. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("outputTokens")] - public long OutputTokens { get; set; } - /// Total tokens read from prompt cache. [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("cacheReadTokens")] @@ -1600,6 +1691,16 @@ public sealed class UsageMetricsModelMetricUsage [JsonPropertyName("cacheWriteTokens")] public long CacheWriteTokens { get; set; } + /// Total input tokens consumed. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("inputTokens")] + public long InputTokens { get; set; } + + /// Total output tokens produced. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("outputTokens")] + public long OutputTokens { get; set; } + /// Total output tokens used for reasoning. [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("reasoningTokens")] @@ -1622,46 +1723,46 @@ public sealed class UsageMetricsModelMetric [Experimental(Diagnostics.Experimental)] public sealed class UsageGetMetricsResult { - /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). - [JsonPropertyName("totalPremiumRequestCost")] - public double TotalPremiumRequestCost { get; set; } - - /// Raw count of user-initiated API requests. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("totalUserRequests")] - public long TotalUserRequests { get; set; } - - /// Total time spent in model API calls (milliseconds). - [Range(0, double.MaxValue)] - [JsonConverter(typeof(MillisecondsTimeSpanConverter))] - [JsonPropertyName("totalApiDurationMs")] - public TimeSpan TotalApiDurationMs { get; set; } - - /// Session start timestamp (epoch milliseconds). - [JsonPropertyName("sessionStartTime")] - public long SessionStartTime { get; set; } - /// Aggregated code change metrics. [JsonPropertyName("codeChanges")] public UsageMetricsCodeChanges CodeChanges { get => field ??= new(); set; } + /// Currently active model identifier. + [JsonPropertyName("currentModel")] + public string? CurrentModel { get; set; } + + /// Input tokens from the most recent main-agent API call. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("lastCallInputTokens")] + public long LastCallInputTokens { get; set; } + + /// Output tokens from the most recent main-agent API call. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("lastCallOutputTokens")] + public long LastCallOutputTokens { get; set; } + /// Per-model token and request metrics, keyed by model identifier. [JsonPropertyName("modelMetrics")] public IDictionary ModelMetrics { get => field ??= new Dictionary(); set; } - /// Currently active model identifier. - [JsonPropertyName("currentModel")] - public string? CurrentModel { get; set; } + /// Session start timestamp (epoch milliseconds). + [JsonPropertyName("sessionStartTime")] + public long SessionStartTime { get; set; } - /// Input tokens from the most recent main-agent API call. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("lastCallInputTokens")] - public long LastCallInputTokens { get; set; } + /// Total time spent in model API calls (milliseconds). + [Range(0, double.MaxValue)] + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] + [JsonPropertyName("totalApiDurationMs")] + public TimeSpan TotalApiDurationMs { get; set; } - /// Output tokens from the most recent main-agent API call. + /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). + [JsonPropertyName("totalPremiumRequestCost")] + public double TotalPremiumRequestCost { get; set; } + + /// Raw count of user-initiated API requests. [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("lastCallOutputTokens")] - public long LastCallOutputTokens { get; set; } + [JsonPropertyName("totalUserRequests")] + public long TotalUserRequests { get; set; } } /// RPC data type for SessionUsageGetMetrics operations. @@ -1673,37 +1774,45 @@ internal sealed class SessionUsageGetMetricsRequest public string SessionId { get; set; } = string.Empty; } +/// Describes a filesystem error. +public sealed class SessionFsError +{ + /// Error classification. + [JsonPropertyName("code")] + public SessionFsErrorCode Code { get; set; } + + /// Free-form detail about the error, for logging/diagnostics. + [JsonPropertyName("message")] + public string? Message { get; set; } +} + /// RPC data type for SessionFsReadFile operations. public sealed class SessionFsReadFileResult { /// File content as UTF-8 string. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReadFile operations. public sealed class SessionFsReadFileRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; -} -/// RPC data type for SessionFsWriteFile operations. -public sealed class SessionFsWriteFileRequest -{ /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; +} - /// Path using SessionFs conventions. - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - +/// RPC data type for SessionFsWriteFile operations. +public sealed class SessionFsWriteFileRequest +{ /// Content to write. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; @@ -1712,19 +1821,19 @@ public sealed class SessionFsWriteFileRequest [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("mode")] public long? Mode { get; set; } -} - -/// RPC data type for SessionFsAppendFile operations. -public sealed class SessionFsAppendFileRequest -{ - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for SessionFsAppendFile operations. +public sealed class SessionFsAppendFileRequest +{ /// Content to append. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; @@ -1733,6 +1842,14 @@ public sealed class SessionFsAppendFileRequest [Range((double)0, (double)long.MaxValue)] [JsonPropertyName("mode")] public long? Mode { get; set; } + + /// Path using SessionFs conventions. + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsExists operations. @@ -1746,58 +1863,63 @@ public sealed class SessionFsExistsResult /// RPC data type for SessionFsExists operations. public sealed class SessionFsExistsRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsStat operations. public sealed class SessionFsStatResult { - /// Whether the path is a file. - [JsonPropertyName("isFile")] - public bool IsFile { get; set; } + /// ISO 8601 timestamp of creation. + [JsonPropertyName("birthtime")] + public DateTimeOffset Birthtime { get; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } /// Whether the path is a directory. [JsonPropertyName("isDirectory")] public bool IsDirectory { get; set; } - /// File size in bytes. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("size")] - public long Size { get; set; } + /// Whether the path is a file. + [JsonPropertyName("isFile")] + public bool IsFile { get; set; } /// ISO 8601 timestamp of last modification. [JsonPropertyName("mtime")] public DateTimeOffset Mtime { get; set; } - /// ISO 8601 timestamp of creation. - [JsonPropertyName("birthtime")] - public DateTimeOffset Birthtime { get; set; } + /// File size in bytes. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("size")] + public long Size { get; set; } } /// RPC data type for SessionFsStat operations. public sealed class SessionFsStatRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsMkdir operations. public sealed class SessionFsMkdirRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Optional POSIX-style mode for newly created directories. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("mode")] + public long? Mode { get; set; } /// Path using SessionFs conventions. [JsonPropertyName("path")] @@ -1807,10 +1929,9 @@ public sealed class SessionFsMkdirRequest [JsonPropertyName("recursive")] public bool? Recursive { get; set; } - /// Optional POSIX-style mode for newly created directories. - [Range((double)0, (double)long.MaxValue)] - [JsonPropertyName("mode")] - public long? Mode { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsReaddir operations. @@ -1819,18 +1940,22 @@ public sealed class SessionFsReaddirResult /// Entry names in the directory. [JsonPropertyName("entries")] public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReaddir operations. public sealed class SessionFsReaddirRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsReaddirWithTypesEntry operations. @@ -1851,26 +1976,30 @@ public sealed class SessionFsReaddirWithTypesResult /// Directory entries with type information. [JsonPropertyName("entries")] public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. public sealed class SessionFsReaddirWithTypesRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - /// Path using SessionFs conventions. [JsonPropertyName("path")] public string Path { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsRm operations. public sealed class SessionFsRmRequest { - /// Target session identifier. - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; + /// Ignore errors if the path does not exist. + [JsonPropertyName("force")] + public bool? Force { get; set; } /// Path using SessionFs conventions. [JsonPropertyName("path")] @@ -1880,14 +2009,18 @@ public sealed class SessionFsRmRequest [JsonPropertyName("recursive")] public bool? Recursive { get; set; } - /// Ignore errors if the path does not exist. - [JsonPropertyName("force")] - public bool? Force { get; set; } + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; } /// RPC data type for SessionFsRename operations. public sealed class SessionFsRenameRequest { + /// Destination path using SessionFs conventions. + [JsonPropertyName("dest")] + public string Dest { get; set; } = string.Empty; + /// Target session identifier. [JsonPropertyName("sessionId")] public string SessionId { get; set; } = string.Empty; @@ -1895,12 +2028,27 @@ public sealed class SessionFsRenameRequest /// Source path using SessionFs conventions. [JsonPropertyName("src")] public string Src { get; set; } = string.Empty; +} - /// Destination path using SessionFs conventions. - [JsonPropertyName("dest")] - public string Dest { get; set; } = string.Empty; +/// Configuration source. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum DiscoveredMcpServerSource +{ + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The workspace variant. + [JsonStringEnumMemberName("workspace")] + Workspace, + /// The plugin variant. + [JsonStringEnumMemberName("plugin")] + Plugin, + /// The builtin variant. + [JsonStringEnumMemberName("builtin")] + Builtin, } + /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). [JsonConverter(typeof(JsonStringEnumConverter))] public enum DiscoveredMcpServerType @@ -1920,25 +2068,6 @@ public enum DiscoveredMcpServerType } -/// Configuration source. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum DiscoveredMcpServerSource -{ - /// The user variant. - [JsonStringEnumMemberName("user")] - User, - /// The workspace variant. - [JsonStringEnumMemberName("workspace")] - Workspace, - /// The plugin variant. - [JsonStringEnumMemberName("plugin")] - Plugin, - /// The builtin variant. - [JsonStringEnumMemberName("builtin")] - Builtin, -} - - /// Path conventions used by this filesystem. [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionFsSetProviderConventions @@ -2013,6 +2142,22 @@ public enum WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel } +/// Where this source lives — used for UI grouping. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum InstructionsSourcesLocation +{ + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The repository variant. + [JsonStringEnumMemberName("repository")] + Repository, + /// The working-directory variant. + [JsonStringEnumMemberName("working-directory")] + WorkingDirectory, +} + + /// Category of instruction source — used for merge logic. [JsonConverter(typeof(JsonStringEnumConverter))] public enum InstructionsSourcesType @@ -2038,19 +2183,22 @@ public enum InstructionsSourcesType } -/// Where this source lives — used for UI grouping. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum InstructionsSourcesLocation +/// Configuration source: user, workspace, plugin, or builtin. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum McpServerSource { /// The user variant. [JsonStringEnumMemberName("user")] User, - /// The repository variant. - [JsonStringEnumMemberName("repository")] - Repository, - /// The working-directory variant. - [JsonStringEnumMemberName("working-directory")] - WorkingDirectory, + /// The workspace variant. + [JsonStringEnumMemberName("workspace")] + Workspace, + /// The plugin variant. + [JsonStringEnumMemberName("plugin")] + Plugin, + /// The builtin variant. + [JsonStringEnumMemberName("builtin")] + Builtin, } @@ -2079,25 +2227,6 @@ public enum McpServerStatus } -/// Configuration source: user, workspace, plugin, or builtin. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum McpServerSource -{ - /// The user variant. - [JsonStringEnumMemberName("user")] - User, - /// The workspace variant. - [JsonStringEnumMemberName("workspace")] - Workspace, - /// The plugin variant. - [JsonStringEnumMemberName("plugin")] - Plugin, - /// The builtin variant. - [JsonStringEnumMemberName("builtin")] - Builtin, -} - - /// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/). [JsonConverter(typeof(JsonStringEnumConverter))] public enum ExtensionSource @@ -2162,6 +2291,19 @@ public enum ShellKillSignal } +/// Error classification. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionFsErrorCode +{ + /// The ENOENT variant. + [JsonStringEnumMemberName("ENOENT")] + ENOENT, + /// The UNKNOWN variant. + [JsonStringEnumMemberName("UNKNOWN")] + UNKNOWN, +} + + /// Entry type. [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionFsReaddirWithTypesEntryType @@ -2979,7 +3121,7 @@ internal PermissionsApi(JsonRpc rpc, string sessionId) } /// Calls "session.permissions.handlePendingPermissionRequest". - public async Task HandlePendingPermissionRequestAsync(string requestId, object result, CancellationToken cancellationToken = default) + public async Task HandlePendingPermissionRequestAsync(string requestId, PermissionDecision result, CancellationToken cancellationToken = default) { var request = new PermissionDecisionRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); @@ -3068,23 +3210,23 @@ public interface ISessionFsHandler /// Handles "sessionFs.readFile". Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.writeFile". - Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); + Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.appendFile". - Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); + Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.exists". Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.stat". Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.mkdir". - Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); + Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdir". Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdirWithTypes". Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rm". - Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); + Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rename". - Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); + Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); } /// Provides all client session API handler groups for a session. @@ -3114,21 +3256,21 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsWriteFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.WriteFileAsync(request, cancellationToken); + return await handler.WriteFileAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsWriteFileMethod.Method, registerSessionFsWriteFileMethod.Target!, new JsonRpcMethodAttribute("sessionFs.writeFile") { UseSingleObjectParameterDeserialization = true }); - var registerSessionFsAppendFileMethod = (Func)(async (request, cancellationToken) => + var registerSessionFsAppendFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.AppendFileAsync(request, cancellationToken); + return await handler.AppendFileAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsAppendFileMethod.Method, registerSessionFsAppendFileMethod.Target!, new JsonRpcMethodAttribute("sessionFs.appendFile") { @@ -3154,11 +3296,11 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsMkdirMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.MkdirAsync(request, cancellationToken); + return await handler.MkdirAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsMkdirMethod.Method, registerSessionFsMkdirMethod.Target!, new JsonRpcMethodAttribute("sessionFs.mkdir") { @@ -3184,21 +3326,21 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsRmMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.RmAsync(request, cancellationToken); + return await handler.RmAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsRmMethod.Method, registerSessionFsRmMethod.Target!, new JsonRpcMethodAttribute("sessionFs.rm") { UseSingleObjectParameterDeserialization = true }); - var registerSessionFsRenameMethod = (Func)(async (request, cancellationToken) => + var registerSessionFsRenameMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.RenameAsync(request, cancellationToken); + return await handler.RenameAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsRenameMethod.Method, registerSessionFsRenameMethod.Target!, new JsonRpcMethodAttribute("sessionFs.rename") { @@ -3265,6 +3407,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncSession initialization metadata including context and configuration. public partial class SessionStartData { - /// Unique identifier for the session. - [JsonPropertyName("sessionId")] - public required string SessionId { get; set; } - - /// Schema version number for the session event format. - [JsonPropertyName("version")] - public required double Version { get; set; } + /// Whether the session was already in use by another client at start time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("alreadyInUse")] + public bool? AlreadyInUse { get; set; } - /// Identifier of the software producing the events (e.g., "copilot-agent"). - [JsonPropertyName("producer")] - public required string Producer { get; set; } + /// Working directory and git context at session start. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("context")] + public WorkingDirectoryContext? Context { get; set; } /// Version string of the Copilot application. [JsonPropertyName("copilotVersion")] public required string CopilotVersion { get; set; } - /// ISO 8601 timestamp when the session was created. - [JsonPropertyName("startTime")] - public required DateTimeOffset StartTime { get; set; } - - /// Model selected at session creation time, if any. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } + /// Identifier of the software producing the events (e.g., "copilot-agent"). + [JsonPropertyName("producer")] + public required string Producer { get; set; } /// Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } - /// Working directory and git context at session start. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("context")] - public WorkingDirectoryContext? Context { get; set; } - - /// Whether the session was already in use by another client at start time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("alreadyInUse")] - public bool? AlreadyInUse { get; set; } - /// Whether this session supports remote steering via Mission Control. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("remoteSteerable")] public bool? RemoteSteerable { get; set; } + + /// Model selected at session creation time, if any. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } + + /// Unique identifier for the session. + [JsonPropertyName("sessionId")] + public required string SessionId { get; set; } + + /// ISO 8601 timestamp when the session was created. + [JsonPropertyName("startTime")] + public required DateTimeOffset StartTime { get; set; } + + /// Schema version number for the session event format. + [JsonPropertyName("version")] + public required double Version { get; set; } } /// Session resume metadata including current context and event count. public partial class SessionResumeData { - /// ISO 8601 timestamp when the session was resumed. - [JsonPropertyName("resumeTime")] - public required DateTimeOffset ResumeTime { get; set; } + /// Whether the session was already in use by another client at resume time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("alreadyInUse")] + public bool? AlreadyInUse { get; set; } + + /// Updated working directory and git context at resume time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("context")] + public WorkingDirectoryContext? Context { get; set; } /// Total number of persisted events in the session at the time of resume. [JsonPropertyName("eventCount")] public required double EventCount { get; set; } - /// Model currently selected at resume time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("selectedModel")] - public string? SelectedModel { get; set; } - /// Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } - /// Updated working directory and git context at resume time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("context")] - public WorkingDirectoryContext? Context { get; set; } - - /// Whether the session was already in use by another client at resume time. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("alreadyInUse")] - public bool? AlreadyInUse { get; set; } - /// Whether this session supports remote steering via Mission Control. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("remoteSteerable")] public bool? RemoteSteerable { get; set; } + + /// ISO 8601 timestamp when the session was resumed. + [JsonPropertyName("resumeTime")] + public required DateTimeOffset ResumeTime { get; set; } + + /// Model currently selected at resume time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("selectedModel")] + public string? SelectedModel { get; set; } } /// Notifies Mission Control that the session's remote steering capability has changed. @@ -1193,6 +1193,11 @@ public partial class SessionErrorData [JsonPropertyName("message")] public required string Message { get; set; } + /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("providerCallId")] + public string? ProviderCallId { get; set; } + /// Error stack trace, when available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("stack")] @@ -1203,11 +1208,6 @@ public partial class SessionErrorData [JsonPropertyName("statusCode")] public long? StatusCode { get; set; } - /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("providerCallId")] - public string? ProviderCallId { get; set; } - /// Optional URL associated with this error that the user can open in a browser. [Url] [StringSyntax(StringSyntaxAttribute.Uri)] @@ -1255,10 +1255,6 @@ public partial class SessionInfoData /// Warning message for timeline display with categorization. public partial class SessionWarningData { - /// Category of warning (e.g., "subscription", "policy", "mcp"). - [JsonPropertyName("warningType")] - public required string WarningType { get; set; } - /// Human-readable warning message for display in the timeline. [JsonPropertyName("message")] public required string Message { get; set; } @@ -1269,20 +1265,24 @@ public partial class SessionWarningData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] public string? Url { get; set; } + + /// Category of warning (e.g., "subscription", "policy", "mcp"). + [JsonPropertyName("warningType")] + public required string WarningType { get; set; } } /// Model change details including previous and new model identifiers. public partial class SessionModelChangeData { + /// Newly selected model identifier. + [JsonPropertyName("newModel")] + public required string NewModel { get; set; } + /// Model that was previously selected, if any. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("previousModel")] public string? PreviousModel { get; set; } - /// Newly selected model identifier. - [JsonPropertyName("newModel")] - public required string NewModel { get; set; } - /// Reasoning effort level before the model change, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("previousReasoningEffort")] @@ -1297,13 +1297,13 @@ public partial class SessionModelChangeData /// Agent mode change details including previous and new modes. public partial class SessionModeChangedData { - /// Agent mode before the change (e.g., "interactive", "plan", "autopilot"). - [JsonPropertyName("previousMode")] - public required string PreviousMode { get; set; } - /// Agent mode after the change (e.g., "interactive", "plan", "autopilot"). [JsonPropertyName("newMode")] public required string NewMode { get; set; } + + /// Agent mode before the change (e.g., "interactive", "plan", "autopilot"). + [JsonPropertyName("previousMode")] + public required string PreviousMode { get; set; } } /// Plan file operation details indicating what changed. @@ -1317,131 +1317,111 @@ public partial class SessionPlanChangedData /// Workspace file change details including path and operation type. public partial class SessionWorkspaceFileChangedData { - /// Relative path within the session workspace files directory. - [JsonPropertyName("path")] - public required string Path { get; set; } - /// Whether the file was newly created or updated. [JsonPropertyName("operation")] public required WorkspaceFileChangedOperation Operation { get; set; } + + /// Relative path within the session workspace files directory. + [JsonPropertyName("path")] + public required string Path { get; set; } } /// Session handoff metadata including source, context, and repository information. public partial class SessionHandoffData { + /// Additional context information for the handoff. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("context")] + public string? Context { get; set; } + /// ISO 8601 timestamp when the handoff occurred. [JsonPropertyName("handoffTime")] public required DateTimeOffset HandoffTime { get; set; } - /// Origin type of the session being handed off. - [JsonPropertyName("sourceType")] - public required HandoffSourceType SourceType { get; set; } + /// GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("host")] + public string? Host { get; set; } + + /// Session ID of the remote session being handed off. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("remoteSessionId")] + public string? RemoteSessionId { get; set; } /// Repository context for the handed-off session. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("repository")] public HandoffRepository? Repository { get; set; } - /// Additional context information for the handoff. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("context")] - public string? Context { get; set; } + /// Origin type of the session being handed off. + [JsonPropertyName("sourceType")] + public required HandoffSourceType SourceType { get; set; } /// Summary of the work done in the source session. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("summary")] public string? Summary { get; set; } - - /// Session ID of the remote session being handed off. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("remoteSessionId")] - public string? RemoteSessionId { get; set; } - - /// GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("host")] - public string? Host { get; set; } } /// Conversation truncation statistics including token counts and removed content metrics. public partial class SessionTruncationData { - /// Maximum token count for the model's context window. - [JsonPropertyName("tokenLimit")] - public required double TokenLimit { get; set; } + /// Number of messages removed by truncation. + [JsonPropertyName("messagesRemovedDuringTruncation")] + public required double MessagesRemovedDuringTruncation { get; set; } - /// Total tokens in conversation messages before truncation. - [JsonPropertyName("preTruncationTokensInMessages")] - public required double PreTruncationTokensInMessages { get; set; } + /// Identifier of the component that performed truncation (e.g., "BasicTruncator"). + [JsonPropertyName("performedBy")] + public required string PerformedBy { get; set; } - /// Number of conversation messages before truncation. - [JsonPropertyName("preTruncationMessagesLength")] - public required double PreTruncationMessagesLength { get; set; } + /// Number of conversation messages after truncation. + [JsonPropertyName("postTruncationMessagesLength")] + public required double PostTruncationMessagesLength { get; set; } /// Total tokens in conversation messages after truncation. [JsonPropertyName("postTruncationTokensInMessages")] public required double PostTruncationTokensInMessages { get; set; } - /// Number of conversation messages after truncation. - [JsonPropertyName("postTruncationMessagesLength")] - public required double PostTruncationMessagesLength { get; set; } + /// Number of conversation messages before truncation. + [JsonPropertyName("preTruncationMessagesLength")] + public required double PreTruncationMessagesLength { get; set; } + + /// Total tokens in conversation messages before truncation. + [JsonPropertyName("preTruncationTokensInMessages")] + public required double PreTruncationTokensInMessages { get; set; } + + /// Maximum token count for the model's context window. + [JsonPropertyName("tokenLimit")] + public required double TokenLimit { get; set; } /// Number of tokens removed by truncation. [JsonPropertyName("tokensRemovedDuringTruncation")] public required double TokensRemovedDuringTruncation { get; set; } - - /// Number of messages removed by truncation. - [JsonPropertyName("messagesRemovedDuringTruncation")] - public required double MessagesRemovedDuringTruncation { get; set; } - - /// Identifier of the component that performed truncation (e.g., "BasicTruncator"). - [JsonPropertyName("performedBy")] - public required string PerformedBy { get; set; } } /// Session rewind details including target event and count of removed events. public partial class SessionSnapshotRewindData { - /// Event ID that was rewound to; this event and all after it were removed. - [JsonPropertyName("upToEventId")] - public required string UpToEventId { get; set; } - /// Number of events that were removed by the rewind. [JsonPropertyName("eventsRemoved")] public required double EventsRemoved { get; set; } + + /// Event ID that was rewound to; this event and all after it were removed. + [JsonPropertyName("upToEventId")] + public required string UpToEventId { get; set; } } /// Session termination metrics including usage statistics, code changes, and shutdown reason. public partial class SessionShutdownData { - /// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). - [JsonPropertyName("shutdownType")] - public required ShutdownType ShutdownType { get; set; } - - /// Error description when shutdownType is "error". - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("errorReason")] - public string? ErrorReason { get; set; } - - /// Total number of premium API requests used during the session. - [JsonPropertyName("totalPremiumRequests")] - public required double TotalPremiumRequests { get; set; } - - /// Cumulative time spent in API calls during the session, in milliseconds. - [JsonPropertyName("totalApiDurationMs")] - public required double TotalApiDurationMs { get; set; } - - /// Unix timestamp (milliseconds) when the session started. - [JsonPropertyName("sessionStartTime")] - public required double SessionStartTime { get; set; } - /// Aggregate code change metrics for the session. [JsonPropertyName("codeChanges")] public required ShutdownCodeChanges CodeChanges { get; set; } - /// Per-model usage breakdown, keyed by model identifier. - [JsonPropertyName("modelMetrics")] - public required IDictionary ModelMetrics { get; set; } + /// Non-system message token count at shutdown. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conversationTokens")] + public double? ConversationTokens { get; set; } /// Model that was selected at the time of shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1453,25 +1433,55 @@ public partial class SessionShutdownData [JsonPropertyName("currentTokens")] public double? CurrentTokens { get; set; } + /// Error description when shutdownType is "error". + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorReason")] + public string? ErrorReason { get; set; } + + /// Per-model usage breakdown, keyed by model identifier. + [JsonPropertyName("modelMetrics")] + public required IDictionary ModelMetrics { get; set; } + + /// Unix timestamp (milliseconds) when the session started. + [JsonPropertyName("sessionStartTime")] + public required double SessionStartTime { get; set; } + + /// Whether the session ended normally ("routine") or due to a crash/fatal error ("error"). + [JsonPropertyName("shutdownType")] + public required ShutdownType ShutdownType { get; set; } + /// System message token count at shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("systemTokens")] public double? SystemTokens { get; set; } - /// Non-system message token count at shutdown. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("conversationTokens")] - public double? ConversationTokens { get; set; } - /// Tool definitions token count at shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] public double? ToolDefinitionsTokens { get; set; } + + /// Cumulative time spent in API calls during the session, in milliseconds. + [JsonPropertyName("totalApiDurationMs")] + public required double TotalApiDurationMs { get; set; } + + /// Total number of premium API requests used during the session. + [JsonPropertyName("totalPremiumRequests")] + public required double TotalPremiumRequests { get; set; } } /// Working directory and git context at session start. public partial class SessionContextChangedData { + /// Base commit of current git branch at session start time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } + + /// Current git branch name. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("branch")] + public string? Branch { get; set; } + /// Current working directory path. [JsonPropertyName("cwd")] public required string Cwd { get; set; } @@ -1481,43 +1491,44 @@ public partial class SessionContextChangedData [JsonPropertyName("gitRoot")] public string? GitRoot { get; set; } - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). + /// Head commit of current git branch at session start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("repository")] - public string? Repository { get; set; } + [JsonPropertyName("headCommit")] + public string? HeadCommit { get; set; } /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } - /// Current git branch name. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Head commit of current git branch at session start time. + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// Base commit of current git branch at session start time. + /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } /// Current context window usage statistics including token and message counts. public partial class SessionUsageInfoData { - /// Maximum token count for the model's context window. - [JsonPropertyName("tokenLimit")] - public required double TokenLimit { get; set; } + /// Token count from non-system messages (user, assistant, tool). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conversationTokens")] + public double? ConversationTokens { get; set; } /// Current number of tokens in the context window. [JsonPropertyName("currentTokens")] public required double CurrentTokens { get; set; } + /// Whether this is the first usage_info event emitted in this session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("isInitial")] + public bool? IsInitial { get; set; } + /// Current number of messages in the conversation. [JsonPropertyName("messagesLength")] public required double MessagesLength { get; set; } @@ -1527,35 +1538,29 @@ public partial class SessionUsageInfoData [JsonPropertyName("systemTokens")] public double? SystemTokens { get; set; } - /// Token count from non-system messages (user, assistant, tool). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("conversationTokens")] - public double? ConversationTokens { get; set; } + /// Maximum token count for the model's context window. + [JsonPropertyName("tokenLimit")] + public required double TokenLimit { get; set; } /// Token count from tool definitions. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] public double? ToolDefinitionsTokens { get; set; } - - /// Whether this is the first usage_info event emitted in this session. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("isInitial")] - public bool? IsInitial { get; set; } } /// Context window breakdown at the start of LLM-powered conversation compaction. public partial class SessionCompactionStartData { - /// Token count from system message(s) at compaction start. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("systemTokens")] - public double? SystemTokens { get; set; } - /// Token count from non-system messages (user, assistant, tool) at compaction start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("conversationTokens")] public double? ConversationTokens { get; set; } + /// Token count from system message(s) at compaction start. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("systemTokens")] + public double? SystemTokens { get; set; } + /// Token count from tool definitions at compaction start. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] @@ -1565,19 +1570,35 @@ public partial class SessionCompactionStartData /// Conversation compaction results including success status, metrics, and optional error details. public partial class SessionCompactionCompleteData { - /// Whether compaction completed successfully. - [JsonPropertyName("success")] - public required bool Success { get; set; } + /// Checkpoint snapshot number created for recovery. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("checkpointNumber")] + public double? CheckpointNumber { get; set; } + + /// File path where the checkpoint was stored. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("checkpointPath")] + public string? CheckpointPath { get; set; } + + /// Token usage breakdown for the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("compactionTokensUsed")] + public CompactionCompleteCompactionTokensUsed? CompactionTokensUsed { get; set; } + + /// Token count from non-system messages (user, assistant, tool) after compaction. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("conversationTokens")] + public double? ConversationTokens { get; set; } /// Error message if compaction failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("error")] public string? Error { get; set; } - /// Total tokens in conversation before compaction. + /// Number of messages removed during compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("preCompactionTokens")] - public double? PreCompactionTokens { get; set; } + [JsonPropertyName("messagesRemoved")] + public double? MessagesRemoved { get; set; } /// Total tokens in conversation after compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1589,50 +1610,34 @@ public partial class SessionCompactionCompleteData [JsonPropertyName("preCompactionMessagesLength")] public double? PreCompactionMessagesLength { get; set; } - /// Number of messages removed during compaction. + /// Total tokens in conversation before compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("messagesRemoved")] - public double? MessagesRemoved { get; set; } + [JsonPropertyName("preCompactionTokens")] + public double? PreCompactionTokens { get; set; } - /// Number of tokens removed during compaction. + /// GitHub request tracing ID (x-github-request-id header) for the compaction LLM call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("tokensRemoved")] - public double? TokensRemoved { get; set; } + [JsonPropertyName("requestId")] + public string? RequestId { get; set; } + + /// Whether compaction completed successfully. + [JsonPropertyName("success")] + public required bool Success { get; set; } /// LLM-generated summary of the compacted conversation history. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("summaryContent")] public string? SummaryContent { get; set; } - /// Checkpoint snapshot number created for recovery. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("checkpointNumber")] - public double? CheckpointNumber { get; set; } - - /// File path where the checkpoint was stored. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("checkpointPath")] - public string? CheckpointPath { get; set; } - - /// Token usage breakdown for the compaction LLM call. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("compactionTokensUsed")] - public CompactionCompleteCompactionTokensUsed? CompactionTokensUsed { get; set; } - - /// GitHub request tracing ID (x-github-request-id header) for the compaction LLM call. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("requestId")] - public string? RequestId { get; set; } - /// Token count from system message(s) after compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("systemTokens")] public double? SystemTokens { get; set; } - /// Token count from non-system messages (user, assistant, tool) after compaction. + /// Number of tokens removed during compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("conversationTokens")] - public double? ConversationTokens { get; set; } + [JsonPropertyName("tokensRemoved")] + public double? TokensRemoved { get; set; } /// Token count from tool definitions after compaction. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1643,38 +1648,38 @@ public partial class SessionCompactionCompleteData /// Task completion notification with summary from the agent. public partial class SessionTaskCompleteData { - /// Summary of the completed task, provided by the agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("summary")] - public string? Summary { get; set; } - /// Whether the tool call succeeded. False when validation failed (e.g., invalid arguments). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("success")] public bool? Success { get; set; } + + /// Summary of the completed task, provided by the agent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("summary")] + public string? Summary { get; set; } } /// Event payload for . public partial class UserMessageData { - /// The user's message text as displayed in the timeline. - [JsonPropertyName("content")] - public required string Content { get; set; } - - /// Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching. + /// The agent mode that was active when this message was sent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("transformedContent")] - public string? TransformedContent { get; set; } + [JsonPropertyName("agentMode")] + public UserMessageAgentMode? AgentMode { get; set; } /// Files, selections, or GitHub references attached to the message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("attachments")] public UserMessageAttachment[]? Attachments { get; set; } - /// Normalized document MIME types that were sent natively instead of through tagged_files XML. + /// The user's message text as displayed in the timeline. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// CAPI interaction ID for correlating this user message with its turn. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("supportedNativeDocumentMimeTypes")] - public string[]? SupportedNativeDocumentMimeTypes { get; set; } + [JsonPropertyName("interactionId")] + public string? InteractionId { 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)] @@ -1686,15 +1691,15 @@ public partial class UserMessageData [JsonPropertyName("source")] public string? Source { get; set; } - /// The agent mode that was active when this message was sent. + /// Normalized document MIME types that were sent natively instead of through tagged_files XML. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("agentMode")] - public UserMessageAgentMode? AgentMode { get; set; } + [JsonPropertyName("supportedNativeDocumentMimeTypes")] + public string[]? SupportedNativeDocumentMimeTypes { get; set; } - /// CAPI interaction ID for correlating this user message with its turn. + /// Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + [JsonPropertyName("transformedContent")] + public string? TransformedContent { get; set; } } /// Empty payload; the event signals that the pending message queue has changed. @@ -1705,14 +1710,14 @@ public partial class PendingMessagesModifiedData /// Turn initialization metadata including identifier and interaction tracking. public partial class AssistantTurnStartData { - /// Identifier for this turn within the agentic loop, typically a stringified turn number. - [JsonPropertyName("turnId")] - public required string TurnId { get; set; } - /// CAPI interaction ID for correlating this turn with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("interactionId")] public string? InteractionId { get; set; } + + /// Identifier for this turn within the agentic loop, typically a stringified turn number. + [JsonPropertyName("turnId")] + public required string TurnId { get; set; } } /// Agent intent description for current activity or plan. @@ -1726,25 +1731,25 @@ public partial class AssistantIntentData /// Assistant reasoning content for timeline display with complete thinking text. public partial class AssistantReasoningData { - /// Unique identifier for this reasoning block. - [JsonPropertyName("reasoningId")] - public required string ReasoningId { get; set; } - /// The complete extended thinking text from the model. [JsonPropertyName("content")] public required string Content { get; set; } + + /// Unique identifier for this reasoning block. + [JsonPropertyName("reasoningId")] + public required string ReasoningId { get; set; } } /// Streaming reasoning delta for incremental extended thinking updates. public partial class AssistantReasoningDeltaData { - /// Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event. - [JsonPropertyName("reasoningId")] - public required string ReasoningId { get; set; } - /// Incremental text chunk to append to the reasoning content. [JsonPropertyName("deltaContent")] public required string DeltaContent { get; set; } + + /// Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event. + [JsonPropertyName("reasoningId")] + public required string ReasoningId { get; set; } } /// Streaming response progress with cumulative byte count. @@ -1758,72 +1763,72 @@ public partial class AssistantStreamingDeltaData /// Assistant response containing text content, optional tool requests, and interaction metadata. public partial class AssistantMessageData { - /// Unique identifier for this assistant message. - [JsonPropertyName("messageId")] - public required string MessageId { get; set; } - /// The assistant's text response content. [JsonPropertyName("content")] public required string Content { get; set; } - /// Tool invocations requested by the assistant in this message. + /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolRequests")] - public AssistantMessageToolRequest[]? ToolRequests { get; set; } + [JsonPropertyName("encryptedContent")] + public string? EncryptedContent { get; set; } - /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + /// CAPI interaction ID for correlating this message with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningOpaque")] - public string? ReasoningOpaque { get; set; } + [JsonPropertyName("interactionId")] + public string? InteractionId { get; set; } - /// Readable reasoning text from the model's extended thinking. + /// Unique identifier for this assistant message. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } + + /// Actual output token count from the API response (completion_tokens), used for accurate token accounting. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningText")] - public string? ReasoningText { get; set; } + [JsonPropertyName("outputTokens")] + public double? OutputTokens { get; set; } - /// Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("encryptedContent")] - public string? EncryptedContent { get; set; } + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } /// Generation phase for phased-output models (e.g., thinking vs. response phases). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("phase")] public string? Phase { get; set; } - /// Actual output token count from the API response (completion_tokens), used for accurate token accounting. + /// Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTokens")] - public double? OutputTokens { get; set; } + [JsonPropertyName("reasoningOpaque")] + public string? ReasoningOpaque { get; set; } - /// CAPI interaction ID for correlating this message with upstream telemetry. + /// Readable reasoning text from the model's extended thinking. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interactionId")] - public string? InteractionId { get; set; } + [JsonPropertyName("reasoningText")] + public string? ReasoningText { get; set; } /// GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("requestId")] public string? RequestId { get; set; } - /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. - [Obsolete("This member is deprecated and will be removed in a future version.")] + /// Tool invocations requested by the assistant in this message. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } + [JsonPropertyName("toolRequests")] + public AssistantMessageToolRequest[]? ToolRequests { get; set; } } /// Streaming assistant message delta for incremental response updates. public partial class AssistantMessageDeltaData { - /// Message ID this delta belongs to, matching the corresponding assistant.message event. - [JsonPropertyName("messageId")] - public required string MessageId { get; set; } - /// Incremental text chunk to append to the message content. [JsonPropertyName("deltaContent")] public required string DeltaContent { get; set; } + /// Message ID this delta belongs to, matching the corresponding assistant.message event. + [JsonPropertyName("messageId")] + public required string MessageId { get; set; } + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. [Obsolete("This member is deprecated and will be removed in a future version.")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1842,19 +1847,10 @@ public partial class AssistantTurnEndData /// LLM API call usage metrics including tokens, costs, quotas, and billing information. public partial class AssistantUsageData { - /// Model identifier used for this API call. - [JsonPropertyName("model")] - public required string Model { get; set; } - - /// Number of input tokens consumed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("inputTokens")] - public double? InputTokens { get; set; } - - /// Number of output tokens produced. + /// Completion ID from the model provider (e.g., chatcmpl-abc123). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("outputTokens")] - public double? OutputTokens { get; set; } + [JsonPropertyName("apiCallId")] + public string? ApiCallId { get; set; } /// Number of tokens read from prompt cache. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1866,10 +1862,10 @@ public partial class AssistantUsageData [JsonPropertyName("cacheWriteTokens")] public double? CacheWriteTokens { get; set; } - /// Number of output tokens used for reasoning (e.g., chain-of-thought). + /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("reasoningTokens")] - public double? ReasoningTokens { get; set; } + [JsonPropertyName("copilotUsage")] + public AssistantUsageCopilotUsage? CopilotUsage { get; set; } /// Model multiplier cost for billing purposes. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1881,30 +1877,29 @@ public partial class AssistantUsageData [JsonPropertyName("duration")] public double? Duration { get; set; } - /// Time to first token in milliseconds. Only available for streaming requests. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("ttftMs")] - public double? TtftMs { get; set; } - - /// Average inter-token latency in milliseconds. Only available for streaming requests. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interTokenLatencyMs")] - public double? InterTokenLatencyMs { get; set; } - /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("initiator")] public string? Initiator { get; set; } - /// Completion ID from the model provider (e.g., chatcmpl-abc123). + /// Number of input tokens consumed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("inputTokens")] + public double? InputTokens { get; set; } + + /// Average inter-token latency in milliseconds. Only available for streaming requests. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("apiCallId")] - public string? ApiCallId { get; set; } + [JsonPropertyName("interTokenLatencyMs")] + public double? InterTokenLatencyMs { get; set; } - /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. + /// Model identifier used for this API call. + [JsonPropertyName("model")] + public required string Model { get; set; } + + /// Number of output tokens produced. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("providerCallId")] - public string? ProviderCallId { get; set; } + [JsonPropertyName("outputTokens")] + public double? OutputTokens { get; set; } /// Parent tool call ID when this usage originates from a sub-agent. [Obsolete("This member is deprecated and will be removed in a future version.")] @@ -1912,20 +1907,30 @@ public partial class AssistantUsageData [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } - /// Per-quota resource usage snapshots, keyed by quota identifier. + /// GitHub request tracing ID (x-github-request-id header) for server-side log correlation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("quotaSnapshots")] - public IDictionary? QuotaSnapshots { get; set; } + [JsonPropertyName("providerCallId")] + public string? ProviderCallId { get; set; } - /// Per-request cost and usage data from the CAPI copilot_usage response field. + /// Per-quota resource usage snapshots, keyed by quota identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("copilotUsage")] - public AssistantUsageCopilotUsage? CopilotUsage { get; set; } + [JsonPropertyName("quotaSnapshots")] + public IDictionary? QuotaSnapshots { get; set; } /// Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } + + /// Number of output tokens used for reasoning (e.g., chain-of-thought). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningTokens")] + public double? ReasoningTokens { get; set; } + + /// Time to first token in milliseconds. Only available for streaming requests. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("ttftMs")] + public double? TtftMs { get; set; } } /// Turn abort information including the reason for termination. @@ -1939,31 +1944,23 @@ public partial class AbortData /// User-initiated tool invocation request with tool name and arguments. public partial class ToolUserRequestedData { - /// Unique identifier for this tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - - /// Name of the tool the user wants to invoke. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } - /// Arguments for the tool invocation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] public object? Arguments { get; set; } -} -/// Tool execution startup details including MCP server information when applicable. -public partial class ToolExecutionStartData -{ /// Unique identifier for this tool call. [JsonPropertyName("toolCallId")] public required string ToolCallId { get; set; } - /// Name of the tool being executed. + /// Name of the tool the user wants to invoke. [JsonPropertyName("toolName")] public required string ToolName { get; set; } +} +/// Tool execution startup details including MCP server information when applicable. +public partial class ToolExecutionStartData +{ /// Arguments passed to the tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] @@ -1984,47 +1981,47 @@ public partial class ToolExecutionStartData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } + + /// Unique identifier for this tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } + + /// Name of the tool being executed. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } /// Streaming tool execution output for incremental result display. public partial class ToolExecutionPartialResultData { - /// Tool call ID this partial result belongs to. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - /// Incremental output chunk from the running tool. [JsonPropertyName("partialOutput")] public required string PartialOutput { get; set; } + + /// Tool call ID this partial result belongs to. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } /// Tool execution progress notification with status message. public partial class ToolExecutionProgressData { - /// Tool call ID this progress notification belongs to. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - /// Human-readable progress status message (e.g., from an MCP server). [JsonPropertyName("progressMessage")] public required string ProgressMessage { get; set; } + + /// Tool call ID this progress notification belongs to. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } /// Tool execution completion results including success status, detailed output, and error information. public partial class ToolExecutionCompleteData { - /// Unique identifier for the completed tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - - /// Whether the tool execution completed successfully. - [JsonPropertyName("success")] - public required bool Success { get; set; } - - /// Model identifier that generated this tool call. + /// Error details when the tool execution failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } + [JsonPropertyName("error")] + public ToolExecutionCompleteError? Error { get; set; } /// CAPI interaction ID for correlating this tool execution with upstream telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2036,31 +2033,53 @@ public partial class ToolExecutionCompleteData [JsonPropertyName("isUserRequested")] public bool? IsUserRequested { get; set; } + /// Model identifier that generated this tool call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + + /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. + [Obsolete("This member is deprecated and will be removed in a future version.")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentToolCallId")] + public string? ParentToolCallId { get; set; } + /// Tool execution result on success. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("result")] public ToolExecutionCompleteResult? Result { get; set; } - /// Error details when the tool execution failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public ToolExecutionCompleteError? Error { get; set; } + /// Whether the tool execution completed successfully. + [JsonPropertyName("success")] + public required bool Success { get; set; } + + /// Unique identifier for the completed tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } /// Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolTelemetry")] public IDictionary? ToolTelemetry { get; set; } - - /// Tool call ID of the parent tool invocation when this event originates from a sub-agent. - [Obsolete("This member is deprecated and will be removed in a future version.")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("parentToolCallId")] - public string? ParentToolCallId { get; set; } } /// Skill invocation details including content, allowed tools, and plugin metadata. public partial class SkillInvokedData { + /// Tool names that should be auto-approved when this skill is active. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("allowedTools")] + public string[]? AllowedTools { get; set; } + + /// Full content of the skill file, injected into the conversation for the model. + [JsonPropertyName("content")] + public required string Content { get; set; } + + /// Description of the skill from its SKILL.md frontmatter. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } + /// Name of the invoked skill. [JsonPropertyName("name")] public required string Name { get; set; } @@ -2069,15 +2088,6 @@ public partial class SkillInvokedData [JsonPropertyName("path")] public required string Path { get; set; } - /// Full content of the skill file, injected into the conversation for the model. - [JsonPropertyName("content")] - public required string Content { get; set; } - - /// Tool names that should be auto-approved when this skill is active. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("allowedTools")] - public string[]? AllowedTools { get; set; } - /// Name of the plugin this skill originated from, when applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("pluginName")] @@ -2087,83 +2097,79 @@ public partial class SkillInvokedData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("pluginVersion")] public string? PluginVersion { get; set; } - - /// Description of the skill from its SKILL.md frontmatter. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } } /// Sub-agent startup details including parent tool call and agent information. public partial class SubagentStartedData { - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - - /// Internal name of the sub-agent. - [JsonPropertyName("agentName")] - public required string AgentName { get; set; } + /// Description of what the sub-agent does. + [JsonPropertyName("agentDescription")] + public required string AgentDescription { get; set; } /// Human-readable display name of the sub-agent. [JsonPropertyName("agentDisplayName")] public required string AgentDisplayName { get; set; } - /// Description of what the sub-agent does. - [JsonPropertyName("agentDescription")] - public required string AgentDescription { get; set; } + /// Internal name of the sub-agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } + + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } } /// Sub-agent completion details for successful execution. public partial class SubagentCompletedData { - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Human-readable display name of the sub-agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } /// Internal name of the sub-agent. [JsonPropertyName("agentName")] public required string AgentName { get; set; } - /// Human-readable display name of the sub-agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { get; set; } + /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("durationMs")] + public double? DurationMs { get; set; } /// Model used by the sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("model")] public string? Model { get; set; } - /// Total number of tool calls made by the sub-agent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalToolCalls")] - public double? TotalToolCalls { get; set; } + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } /// Total tokens (input + output) consumed by the sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("totalTokens")] public double? TotalTokens { get; set; } - /// Wall-clock duration of the sub-agent execution in milliseconds. + /// Total number of tool calls made by the sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("durationMs")] - public double? DurationMs { get; set; } + [JsonPropertyName("totalToolCalls")] + public double? TotalToolCalls { get; set; } } /// Sub-agent failure details including error message and agent information. public partial class SubagentFailedData { - /// Tool call ID of the parent tool invocation that spawned this sub-agent. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } + /// Human-readable display name of the sub-agent. + [JsonPropertyName("agentDisplayName")] + public required string AgentDisplayName { get; set; } /// Internal name of the sub-agent. [JsonPropertyName("agentName")] public required string AgentName { get; set; } - /// Human-readable display name of the sub-agent. - [JsonPropertyName("agentDisplayName")] - public required string AgentDisplayName { get; set; } + /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("durationMs")] + public double? DurationMs { get; set; } /// Error message describing why the sub-agent failed. [JsonPropertyName("error")] @@ -2174,33 +2180,32 @@ public partial class SubagentFailedData [JsonPropertyName("model")] public string? Model { get; set; } - /// Total number of tool calls made before the sub-agent failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("totalToolCalls")] - public double? TotalToolCalls { get; set; } + /// Tool call ID of the parent tool invocation that spawned this sub-agent. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } /// Total tokens (input + output) consumed before the sub-agent failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("totalTokens")] public double? TotalTokens { get; set; } - /// Wall-clock duration of the sub-agent execution in milliseconds. + /// Total number of tool calls made before the sub-agent failed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("durationMs")] - public double? DurationMs { get; set; } + [JsonPropertyName("totalToolCalls")] + public double? TotalToolCalls { get; set; } } /// Custom agent selection details including name and available tools. public partial class SubagentSelectedData { - /// Internal name of the selected custom agent. - [JsonPropertyName("agentName")] - public required string AgentName { get; set; } - /// Human-readable display name of the selected custom agent. [JsonPropertyName("agentDisplayName")] public required string AgentDisplayName { get; set; } + /// Internal name of the selected custom agent. + [JsonPropertyName("agentName")] + public required string AgentName { get; set; } + /// List of tool names available to this agent, or null for all tools. [JsonPropertyName("tools")] public string[]? Tools { get; set; } @@ -2231,6 +2236,11 @@ public partial class HookStartData /// Hook invocation completion details including output, success status, and error information. public partial class HookEndData { + /// Error details when the hook failed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public HookEndError? Error { get; set; } + /// Identifier matching the corresponding hook.start event. [JsonPropertyName("hookInvocationId")] public required string HookInvocationId { get; set; } @@ -2247,11 +2257,6 @@ public partial class HookEndData /// Whether the hook completed successfully. [JsonPropertyName("success")] public required bool Success { get; set; } - - /// Error details when the hook failed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public HookEndError? Error { get; set; } } /// System/developer instruction content with role and optional template metadata. @@ -2261,19 +2266,19 @@ public partial class SystemMessageData [JsonPropertyName("content")] public required string Content { get; set; } - /// Message role: "system" for system prompts, "developer" for developer-injected instructions. - [JsonPropertyName("role")] - public required SystemMessageRole Role { get; set; } + /// Metadata about the prompt template and its construction. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("metadata")] + public SystemMessageMetadata? Metadata { get; set; } /// Optional name identifier for the message source. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("name")] public string? Name { get; set; } - /// Metadata about the prompt template and its construction. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("metadata")] - public SystemMessageMetadata? Metadata { get; set; } + /// Message role: "system" for system prompts, "developer" for developer-injected instructions. + [JsonPropertyName("role")] + public required SystemMessageRole Role { get; set; } } /// System-generated notification for runtime events like background task completion. @@ -2291,14 +2296,14 @@ public partial class SystemNotificationData /// Permission request notification requiring client approval with request details. public partial class PermissionRequestedData { - /// Unique identifier for this permission request; used to respond via session.respondToPermission(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - /// Details of the permission being requested. [JsonPropertyName("permissionRequest")] public required PermissionRequest PermissionRequest { get; set; } + /// Unique identifier for this permission request; used to respond via session.respondToPermission(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + /// When true, this permission was already resolved by a permissionRequest hook and requires no client action. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("resolvedByHook")] @@ -2320,23 +2325,23 @@ public partial class PermissionCompletedData /// User input request notification with question and optional predefined choices. public partial class UserInputRequestedData { - /// Unique identifier for this input request; used to respond via session.respondToUserInput(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - - /// The question or prompt to present to the user. - [JsonPropertyName("question")] - public required string Question { get; set; } + /// Whether the user can provide a free-form text response in addition to predefined choices. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("allowFreeform")] + public bool? AllowFreeform { get; set; } /// Predefined choices for the user to select from, if applicable. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("choices")] public string[]? Choices { get; set; } - /// Whether the user can provide a free-form text response in addition to predefined choices. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("allowFreeform")] - public bool? AllowFreeform { get; set; } + /// The question or prompt to present to the user. + [JsonPropertyName("question")] + public required string Question { get; set; } + + /// Unique identifier for this input request; used to respond via session.respondToUserInput(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } /// The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2347,15 +2352,15 @@ public partial class UserInputRequestedData /// User input request completion with the user's response. public partial class UserInputCompletedData { - /// Request ID of the resolved user input request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - /// The user's answer to the input request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("answer")] public string? Answer { get; set; } + /// Request ID of the resolved user input request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + /// Whether the answer was typed as free-form text rather than selected from choices. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("wasFreeform")] @@ -2365,15 +2370,6 @@ public partial class UserInputCompletedData /// Elicitation request; may be form-based (structured input) or URL-based (browser redirect). public partial class ElicitationRequestedData { - /// Unique identifier for this elicitation request; used to respond via session.respondToElicitation(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - - /// Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - /// The source that initiated the request (MCP server name, or absent for agent-initiated). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("elicitationSource")] @@ -2393,6 +2389,15 @@ public partial class ElicitationRequestedData [JsonPropertyName("requestedSchema")] public ElicitationRequestedSchema? RequestedSchema { get; set; } + /// Unique identifier for this elicitation request; used to respond via session.respondToElicitation(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + /// URL to open in the user's browser (url mode only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("url")] @@ -2402,10 +2407,6 @@ public partial class ElicitationRequestedData /// Elicitation request completion with the user's response. public partial class ElicitationCompletedData { - /// Request ID of the resolved elicitation request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - /// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("action")] @@ -2415,11 +2416,19 @@ public partial class ElicitationCompletedData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("content")] public IDictionary? Content { get; set; } + + /// Request ID of the resolved elicitation request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } /// Sampling request from an MCP server; contains the server name and a requestId for correlation. public partial class SamplingRequestedData { + /// The JSON-RPC request ID from the MCP protocol. + [JsonPropertyName("mcpRequestId")] + public required object McpRequestId { get; set; } + /// Unique identifier for this sampling request; used to respond via session.respondToSampling(). [JsonPropertyName("requestId")] public required string RequestId { get; set; } @@ -2427,10 +2436,6 @@ public partial class SamplingRequestedData /// Name of the MCP server that initiated the sampling request. [JsonPropertyName("serverName")] public required string ServerName { get; set; } - - /// The JSON-RPC request ID from the MCP protocol. - [JsonPropertyName("mcpRequestId")] - public required object McpRequestId { get; set; } } /// Sampling request completion notification signaling UI dismissal. @@ -2473,6 +2478,11 @@ public partial class McpOauthCompletedData /// External tool invocation request for client-side tool execution. public partial class ExternalToolRequestedData { + /// Arguments to pass to the external tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("arguments")] + public object? Arguments { get; set; } + /// Unique identifier for this request; used to respond via session.respondToExternalTool(). [JsonPropertyName("requestId")] public required string RequestId { get; set; } @@ -2489,11 +2499,6 @@ public partial class ExternalToolRequestedData [JsonPropertyName("toolName")] public required string ToolName { get; set; } - /// Arguments to pass to the external tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("arguments")] - public object? Arguments { get; set; } - /// W3C Trace Context traceparent header for the execute_tool span. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("traceparent")] @@ -2516,21 +2521,21 @@ public partial class ExternalToolCompletedData /// Queued slash command dispatch request for client execution. public partial class CommandQueuedData { - /// Unique identifier for this request; used to respond via session.respondToQueuedCommand(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - /// The slash command text to be executed (e.g., /help, /clear). [JsonPropertyName("command")] public required string Command { get; set; } + + /// Unique identifier for this request; used to respond via session.respondToQueuedCommand(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } /// Registered command dispatch request routed to the owning client. public partial class CommandExecuteData { - /// Unique identifier; used to respond via session.commands.handlePendingCommand(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } + /// Raw argument string after the command name. + [JsonPropertyName("args")] + public required string Args { get; set; } /// The full command text (e.g., /deploy production). [JsonPropertyName("command")] @@ -2540,9 +2545,9 @@ public partial class CommandExecuteData [JsonPropertyName("commandName")] public required string CommandName { get; set; } - /// Raw argument string after the command name. - [JsonPropertyName("args")] - public required string Args { get; set; } + /// Unique identifier; used to respond via session.commands.handlePendingCommand(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } } /// Queued command completion notification signaling UI dismissal. @@ -2573,44 +2578,35 @@ public partial class CapabilitiesChangedData /// Plan approval request with plan content and available user actions. public partial class ExitPlanModeRequestedData { - /// Unique identifier for this request; used to respond via session.respondToExitPlanMode(). - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - - /// Summary of the plan that was created. - [JsonPropertyName("summary")] - public required string Summary { get; set; } + /// Available actions the user can take (e.g., approve, edit, reject). + [JsonPropertyName("actions")] + public required string[] Actions { get; set; } /// Full content of the plan file. [JsonPropertyName("planContent")] public required string PlanContent { get; set; } - /// Available actions the user can take (e.g., approve, edit, reject). - [JsonPropertyName("actions")] - public required string[] Actions { get; set; } - /// The recommended action for the user to take. [JsonPropertyName("recommendedAction")] public required string RecommendedAction { get; set; } + + /// Unique identifier for this request; used to respond via session.respondToExitPlanMode(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Summary of the plan that was created. + [JsonPropertyName("summary")] + public required string Summary { get; set; } } /// Plan mode exit completion with the user's approval decision and optional feedback. public partial class ExitPlanModeCompletedData { - /// Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request. - [JsonPropertyName("requestId")] - public required string RequestId { get; set; } - /// Whether the plan was approved by the user. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("approved")] public bool? Approved { get; set; } - /// Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only'). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("selectedAction")] - public string? SelectedAction { get; set; } - /// Whether edits should be auto-approved without confirmation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("autoApproveEdits")] @@ -2620,6 +2616,15 @@ public partial class ExitPlanModeCompletedData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("feedback")] public string? Feedback { get; set; } + + /// Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only'). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("selectedAction")] + public string? SelectedAction { get; set; } } /// Event payload for . @@ -2650,13 +2655,13 @@ public partial class SessionCustomAgentsUpdatedData [JsonPropertyName("agents")] public required CustomAgentsUpdatedAgent[] Agents { get; set; } - /// Non-fatal warnings from agent loading. - [JsonPropertyName("warnings")] - public required string[] Warnings { get; set; } - /// Fatal errors from agent loading. [JsonPropertyName("errors")] public required string[] Errors { get; set; } + + /// Non-fatal warnings from agent loading. + [JsonPropertyName("warnings")] + public required string[] Warnings { get; set; } } /// Event payload for . @@ -2691,6 +2696,16 @@ public partial class SessionExtensionsLoadedData /// Nested data type for WorkingDirectoryContext. public partial class WorkingDirectoryContext { + /// Base commit of current git branch at session start time. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("baseCommit")] + public string? BaseCommit { get; set; } + + /// Current git branch name. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("branch")] + public string? Branch { get; set; } + /// Current working directory path. [JsonPropertyName("cwd")] public required string Cwd { get; set; } @@ -2700,54 +2715,53 @@ public partial class WorkingDirectoryContext [JsonPropertyName("gitRoot")] public string? GitRoot { get; set; } - /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). + /// Head commit of current git branch at session start time. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("repository")] - public string? Repository { get; set; } + [JsonPropertyName("headCommit")] + public string? HeadCommit { get; set; } /// Hosting platform type of the repository (github or ado). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } - /// Current git branch name. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } - - /// Head commit of current git branch at session start time. + /// Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("headCommit")] - public string? HeadCommit { get; set; } + [JsonPropertyName("repository")] + public string? Repository { get; set; } - /// Base commit of current git branch at session start time. + /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("baseCommit")] - public string? BaseCommit { get; set; } + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } } /// Repository context for the handed-off session. /// Nested data type for HandoffRepository. public partial class HandoffRepository { - /// Repository owner (user or organization). - [JsonPropertyName("owner")] - public required string Owner { get; set; } + /// Git branch name, if applicable. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("branch")] + public string? Branch { get; set; } /// Repository name. [JsonPropertyName("name")] public required string Name { get; set; } - /// Git branch name, if applicable. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("branch")] - public string? Branch { get; set; } + /// Repository owner (user or organization). + [JsonPropertyName("owner")] + public required string Owner { get; set; } } /// Aggregate code change metrics for the session. /// Nested data type for ShutdownCodeChanges. public partial class ShutdownCodeChanges { + /// List of file paths that were modified during the session. + [JsonPropertyName("filesModified")] + public required string[] FilesModified { get; set; } + /// Total number of lines added during the session. [JsonPropertyName("linesAdded")] public required double LinesAdded { get; set; } @@ -2755,16 +2769,67 @@ public partial class ShutdownCodeChanges /// Total number of lines removed during the session. [JsonPropertyName("linesRemoved")] public required double LinesRemoved { get; set; } +} - /// List of file paths that were modified during the session. - [JsonPropertyName("filesModified")] - public required string[] FilesModified { get; set; } +/// Request count and cost metrics. +/// Nested data type for ShutdownModelMetricRequests. +public partial class ShutdownModelMetricRequests +{ + /// Cumulative cost multiplier for requests to this model. + [JsonPropertyName("cost")] + public required double Cost { get; set; } + + /// Total number of API requests made to this model. + [JsonPropertyName("count")] + public required double Count { get; set; } +} + +/// Token usage breakdown. +/// Nested data type for ShutdownModelMetricUsage. +public partial class ShutdownModelMetricUsage +{ + /// Total tokens read from prompt cache across all requests. + [JsonPropertyName("cacheReadTokens")] + public required double CacheReadTokens { get; set; } + + /// Total tokens written to prompt cache across all requests. + [JsonPropertyName("cacheWriteTokens")] + public required double CacheWriteTokens { get; set; } + + /// Total input tokens consumed across all requests to this model. + [JsonPropertyName("inputTokens")] + public required double InputTokens { get; set; } + + /// Total output tokens produced across all requests to this model. + [JsonPropertyName("outputTokens")] + public required double OutputTokens { get; set; } + + /// Total reasoning tokens produced across all requests to this model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningTokens")] + public double? ReasoningTokens { get; set; } +} + +/// Nested data type for ShutdownModelMetric. +public partial class ShutdownModelMetric +{ + /// Request count and cost metrics. + [JsonPropertyName("requests")] + public required ShutdownModelMetricRequests Requests { get; set; } + + /// Token usage breakdown. + [JsonPropertyName("usage")] + public required ShutdownModelMetricUsage Usage { get; set; } } /// Token usage breakdown for the compaction LLM call. /// Nested data type for CompactionCompleteCompactionTokensUsed. public partial class CompactionCompleteCompactionTokensUsed { + /// Cached input tokens reused in the compaction LLM call. + [JsonPropertyName("cachedInput")] + public required double CachedInput { get; set; } + /// Input tokens consumed by the compaction LLM call. [JsonPropertyName("input")] public required double Input { get; set; } @@ -2772,23 +2837,19 @@ public partial class CompactionCompleteCompactionTokensUsed /// Output tokens produced by the compaction LLM call. [JsonPropertyName("output")] public required double Output { get; set; } - - /// Cached input tokens reused in the compaction LLM call. - [JsonPropertyName("cachedInput")] - public required double CachedInput { get; set; } } /// Optional line range to scope the attachment to a specific section of the file. /// Nested data type for UserMessageAttachmentFileLineRange. public partial class UserMessageAttachmentFileLineRange { - /// Start line number (1-based). - [JsonPropertyName("start")] - public required double Start { get; set; } - /// End line number (1-based, inclusive). [JsonPropertyName("end")] public required double End { get; set; } + + /// Start line number (1-based). + [JsonPropertyName("start")] + public required double Start { get; set; } } /// File attachment. @@ -2799,10 +2860,6 @@ public partial class UserMessageAttachmentFile : UserMessageAttachment [JsonIgnore] public override string Type => "file"; - /// Absolute file path. - [JsonPropertyName("path")] - public required string Path { get; set; } - /// User-facing display name for the attachment. [JsonPropertyName("displayName")] public required string DisplayName { get; set; } @@ -2811,6 +2868,10 @@ public partial class UserMessageAttachmentFile : UserMessageAttachment [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("lineRange")] public UserMessageAttachmentFileLineRange? LineRange { get; set; } + + /// Absolute file path. + [JsonPropertyName("path")] + public required string Path { get; set; } } /// Directory attachment. @@ -2821,52 +2882,52 @@ public partial class UserMessageAttachmentDirectory : UserMessageAttachment [JsonIgnore] public override string Type => "directory"; - /// Absolute directory path. - [JsonPropertyName("path")] - public required string Path { get; set; } - /// User-facing display name for the attachment. [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } -} - -/// Start position of the selection. -/// Nested data type for UserMessageAttachmentSelectionDetailsStart. -public partial class UserMessageAttachmentSelectionDetailsStart -{ - /// Start line number (0-based). - [JsonPropertyName("line")] - public required double Line { get; set; } + public required string DisplayName { get; set; } - /// Start character offset within the line (0-based). - [JsonPropertyName("character")] - public required double Character { get; set; } + /// Absolute directory path. + [JsonPropertyName("path")] + public required string Path { get; set; } } /// End position of the selection. /// Nested data type for UserMessageAttachmentSelectionDetailsEnd. public partial class UserMessageAttachmentSelectionDetailsEnd { + /// End character offset within the line (0-based). + [JsonPropertyName("character")] + public required double Character { get; set; } + /// End line number (0-based). [JsonPropertyName("line")] public required double Line { get; set; } +} - /// End character offset within the line (0-based). +/// Start position of the selection. +/// Nested data type for UserMessageAttachmentSelectionDetailsStart. +public partial class UserMessageAttachmentSelectionDetailsStart +{ + /// Start character offset within the line (0-based). [JsonPropertyName("character")] public required double Character { get; set; } + + /// Start line number (0-based). + [JsonPropertyName("line")] + public required double Line { get; set; } } /// Position range of the selection within the file. /// Nested data type for UserMessageAttachmentSelectionDetails. public partial class UserMessageAttachmentSelectionDetails { - /// Start position of the selection. - [JsonPropertyName("start")] - public required UserMessageAttachmentSelectionDetailsStart Start { get; set; } - /// End position of the selection. [JsonPropertyName("end")] public required UserMessageAttachmentSelectionDetailsEnd End { get; set; } + + /// Start position of the selection. + [JsonPropertyName("start")] + public required UserMessageAttachmentSelectionDetailsStart Start { get; set; } } /// Code selection attachment from an editor. @@ -2877,21 +2938,21 @@ public partial class UserMessageAttachmentSelection : UserMessageAttachment [JsonIgnore] public override string Type => "selection"; - /// Absolute path to the file containing the selection. - [JsonPropertyName("filePath")] - public required string FilePath { get; set; } - /// User-facing display name for the selection. [JsonPropertyName("displayName")] public required string DisplayName { get; set; } - /// The selected text content. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Absolute path to the file containing the selection. + [JsonPropertyName("filePath")] + public required string FilePath { get; set; } /// Position range of the selection within the file. [JsonPropertyName("selection")] public required UserMessageAttachmentSelectionDetails Selection { get; set; } + + /// The selected text content. + [JsonPropertyName("text")] + public required string Text { get; set; } } /// GitHub issue, pull request, or discussion reference. @@ -2906,10 +2967,6 @@ public partial class UserMessageAttachmentGithubReference : UserMessageAttachmen [JsonPropertyName("number")] public required double Number { get; set; } - /// Title of the referenced item. - [JsonPropertyName("title")] - public required string Title { get; set; } - /// Type of GitHub reference. [JsonPropertyName("referenceType")] public required UserMessageAttachmentGithubReferenceType ReferenceType { get; set; } @@ -2918,6 +2975,10 @@ public partial class UserMessageAttachmentGithubReference : UserMessageAttachmen [JsonPropertyName("state")] public required string State { get; set; } + /// Title of the referenced item. + [JsonPropertyName("title")] + public required string Title { get; set; } + /// URL to the referenced item on GitHub. [JsonPropertyName("url")] public required string Url { get; set; } @@ -2936,14 +2997,14 @@ public partial class UserMessageAttachmentBlob : UserMessageAttachment [JsonPropertyName("data")] public required string Data { get; set; } - /// MIME type of the inline data. - [JsonPropertyName("mimeType")] - public required string MimeType { get; set; } - /// User-facing display name for the attachment. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("displayName")] public string? DisplayName { get; set; } + + /// MIME type of the inline data. + [JsonPropertyName("mimeType")] + public required string MimeType { get; set; } } /// A user message attachment — a file, directory, code selection, blob, or GitHub reference. @@ -2968,38 +3029,38 @@ public partial class UserMessageAttachment /// Nested data type for AssistantMessageToolRequest. public partial class AssistantMessageToolRequest { - /// Unique identifier for this tool call. - [JsonPropertyName("toolCallId")] - public required string ToolCallId { get; set; } - - /// Name of the tool being invoked. - [JsonPropertyName("name")] - public required string Name { get; set; } - /// Arguments to pass to the tool, format depends on the tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("arguments")] public object? Arguments { get; set; } - /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("type")] - public AssistantMessageToolRequestType? Type { get; set; } - - /// Human-readable display title for the tool. + /// Resolved intention summary describing what this specific call does. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolTitle")] - public string? ToolTitle { get; set; } + [JsonPropertyName("intentionSummary")] + public string? IntentionSummary { get; set; } /// Name of the MCP server hosting this tool, when the tool is an MCP tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mcpServerName")] public string? McpServerName { get; set; } - /// Resolved intention summary describing what this specific call does. + /// Name of the tool being invoked. + [JsonPropertyName("name")] + public required string Name { get; set; } + + /// Unique identifier for this tool call. + [JsonPropertyName("toolCallId")] + public required string ToolCallId { get; set; } + + /// Human-readable display title for the tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("intentionSummary")] - public string? IntentionSummary { get; set; } + [JsonPropertyName("toolTitle")] + public string? ToolTitle { get; set; } + + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("type")] + public AssistantMessageToolRequestType? Type { get; set; } } /// Token usage detail for a single billing category. @@ -3036,6 +3097,57 @@ public partial class AssistantUsageCopilotUsage public required double TotalNanoAiu { get; set; } } +/// Nested data type for AssistantUsageQuotaSnapshot. +public partial class AssistantUsageQuotaSnapshot +{ + /// Total requests allowed by the entitlement. + [JsonPropertyName("entitlementRequests")] + public required double EntitlementRequests { get; set; } + + /// Whether the user has an unlimited usage entitlement. + [JsonPropertyName("isUnlimitedEntitlement")] + public required bool IsUnlimitedEntitlement { get; set; } + + /// Number of requests over the entitlement limit. + [JsonPropertyName("overage")] + public required double Overage { get; set; } + + /// Whether overage is allowed when quota is exhausted. + [JsonPropertyName("overageAllowedWithExhaustedQuota")] + public required bool OverageAllowedWithExhaustedQuota { get; set; } + + /// Percentage of quota remaining (0.0 to 1.0). + [JsonPropertyName("remainingPercentage")] + public required double RemainingPercentage { get; set; } + + /// Date when the quota resets. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resetDate")] + public DateTimeOffset? ResetDate { get; set; } + + /// Whether usage is still permitted after quota exhaustion. + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + public required bool UsageAllowedWithExhaustedQuota { get; set; } + + /// Number of requests already consumed. + [JsonPropertyName("usedRequests")] + public required double UsedRequests { get; set; } +} + +/// Error details when the tool execution failed. +/// Nested data type for ToolExecutionCompleteError. +public partial class ToolExecutionCompleteError +{ + /// Machine-readable error code. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("code")] + public string? Code { get; set; } + + /// Human-readable error message. + [JsonPropertyName("message")] + public required string Message { get; set; } +} + /// Plain text content block. /// The text variant of . public partial class ToolExecutionCompleteContentText : ToolExecutionCompleteContent @@ -3057,19 +3169,19 @@ public partial class ToolExecutionCompleteContentTerminal : ToolExecutionComplet [JsonIgnore] public override string Type => "terminal"; - /// Terminal/shell output text. - [JsonPropertyName("text")] - public required string Text { get; set; } + /// Working directory where the command was executed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cwd")] + public string? Cwd { get; set; } /// Process exit code, if the command has completed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("exitCode")] public double? ExitCode { get; set; } - /// Working directory where the command was executed. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("cwd")] - public string? Cwd { get; set; } + /// Terminal/shell output text. + [JsonPropertyName("text")] + public required string Text { get; set; } } /// Image content block with base64-encoded data. @@ -3112,10 +3224,6 @@ public partial class ToolExecutionCompleteContentAudio : ToolExecutionCompleteCo /// Nested data type for ToolExecutionCompleteContentResourceLinkIcon. public partial class ToolExecutionCompleteContentResourceLinkIcon { - /// URL or path to the icon image. - [JsonPropertyName("src")] - public required string Src { get; set; } - /// MIME type of the icon image. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mimeType")] @@ -3126,6 +3234,10 @@ public partial class ToolExecutionCompleteContentResourceLinkIcon [JsonPropertyName("sizes")] public string[]? Sizes { get; set; } + /// URL or path to the icon image. + [JsonPropertyName("src")] + public required string Src { get; set; } + /// Theme variant this icon is intended for. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("theme")] @@ -3140,15 +3252,30 @@ public partial class ToolExecutionCompleteContentResourceLink : ToolExecutionCom [JsonIgnore] public override string Type => "resource_link"; + /// Human-readable description of the resource. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } + /// Icons associated with this resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("icons")] public ToolExecutionCompleteContentResourceLinkIcon[]? Icons { get; set; } + /// MIME type of the resource content. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mimeType")] + public string? MimeType { get; set; } + /// Resource name identifier. [JsonPropertyName("name")] public required string Name { get; set; } + /// Size of the resource in bytes. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("size")] + public double? Size { get; set; } + /// Human-readable display title for the resource. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("title")] @@ -3157,21 +3284,6 @@ public partial class ToolExecutionCompleteContentResourceLink : ToolExecutionCom /// URI identifying the resource. [JsonPropertyName("uri")] public required string Uri { get; set; } - - /// Human-readable description of the resource. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } - - /// MIME type of the resource content. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("mimeType")] - public string? MimeType { get; set; } - - /// Size of the resource in bytes. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("size")] - public double? Size { get; set; } } /// Embedded resource content block with inline text or binary data. @@ -3214,29 +3326,15 @@ public partial class ToolExecutionCompleteResult [JsonPropertyName("content")] public required string Content { get; set; } - /// Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("detailedContent")] - public string? DetailedContent { get; set; } - /// Structured content blocks (text, images, audio, resources) returned by the tool in their native format. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("contents")] public ToolExecutionCompleteContent[]? Contents { get; set; } -} - -/// Error details when the tool execution failed. -/// Nested data type for ToolExecutionCompleteError. -public partial class ToolExecutionCompleteError -{ - /// Human-readable error message. - [JsonPropertyName("message")] - public required string Message { get; set; } - /// Machine-readable error code. + /// Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("code")] - public string? Code { get; set; } + [JsonPropertyName("detailedContent")] + public string? DetailedContent { get; set; } } /// Error details when the hook failed. @@ -3283,10 +3381,6 @@ public partial class SystemNotificationAgentCompleted : SystemNotification [JsonPropertyName("agentType")] public required string AgentType { get; set; } - /// Whether the agent completed successfully or failed. - [JsonPropertyName("status")] - public required SystemNotificationAgentCompletedStatus Status { get; set; } - /// Human-readable description of the agent task. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("description")] @@ -3296,6 +3390,10 @@ public partial class SystemNotificationAgentCompleted : SystemNotification [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("prompt")] public string? Prompt { get; set; } + + /// Whether the agent completed successfully or failed. + [JsonPropertyName("status")] + public required SystemNotificationAgentCompletedStatus Status { get; set; } } /// The agent_idle variant of . @@ -3319,6 +3417,30 @@ public partial class SystemNotificationAgentIdle : SystemNotification public string? Description { get; set; } } +/// The new_inbox_message variant of . +public partial class SystemNotificationNewInboxMessage : SystemNotification +{ + /// + [JsonIgnore] + public override string Type => "new_inbox_message"; + + /// Unique identifier of the inbox entry. + [JsonPropertyName("entryId")] + public required string EntryId { get; set; } + + /// Human-readable name of the sender. + [JsonPropertyName("senderName")] + public required string SenderName { get; set; } + + /// Category of the sender (e.g., ambient-agent, plugin, hook). + [JsonPropertyName("senderType")] + public required string SenderType { get; set; } + + /// Short summary shown before the agent decides whether to read the inbox. + [JsonPropertyName("summary")] + public required string Summary { get; set; } +} + /// The shell_completed variant of . public partial class SystemNotificationShellCompleted : SystemNotification { @@ -3326,19 +3448,19 @@ public partial class SystemNotificationShellCompleted : SystemNotification [JsonIgnore] public override string Type => "shell_completed"; - /// Unique identifier of the shell session. - [JsonPropertyName("shellId")] - public required string ShellId { get; set; } + /// Human-readable description of the command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("description")] + public string? Description { get; set; } /// Exit code of the shell command, if available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("exitCode")] public double? ExitCode { get; set; } - /// Human-readable description of the command. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("description")] - public string? Description { get; set; } + /// Unique identifier of the shell session. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } /// The shell_detached_completed variant of . @@ -3348,14 +3470,14 @@ public partial class SystemNotificationShellDetachedCompleted : SystemNotificati [JsonIgnore] public override string Type => "shell_detached_completed"; - /// Unique identifier of the detached shell session. - [JsonPropertyName("shellId")] - public required string ShellId { get; set; } - /// Human-readable description of the command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("description")] public string? Description { get; set; } + + /// Unique identifier of the detached shell session. + [JsonPropertyName("shellId")] + public required string ShellId { get; set; } } /// Structured metadata identifying what triggered this notification. @@ -3365,6 +3487,7 @@ public partial class SystemNotificationShellDetachedCompleted : SystemNotificati UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] [JsonDerivedType(typeof(SystemNotificationAgentCompleted), "agent_completed")] [JsonDerivedType(typeof(SystemNotificationAgentIdle), "agent_idle")] +[JsonDerivedType(typeof(SystemNotificationNewInboxMessage), "new_inbox_message")] [JsonDerivedType(typeof(SystemNotificationShellCompleted), "shell_completed")] [JsonDerivedType(typeof(SystemNotificationShellDetachedCompleted), "shell_detached_completed")] public partial class SystemNotification @@ -3403,22 +3526,25 @@ public partial class PermissionRequestShell : PermissionRequest [JsonIgnore] public override string Kind => "shell"; - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Whether the UI can offer session-wide approval for this command pattern. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Parsed command identifiers found in the command text. + [JsonPropertyName("commands")] + public required PermissionRequestShellCommand[] Commands { get; set; } /// The complete shell command text to be executed. [JsonPropertyName("fullCommandText")] public required string FullCommandText { get; set; } + /// Whether the command includes a file write redirection (e.g., > or >>). + [JsonPropertyName("hasWriteFileRedirection")] + public required bool HasWriteFileRedirection { get; set; } + /// Human-readable description of what the command intends to do. [JsonPropertyName("intention")] - public required string Intention { get; set; } - - /// Parsed command identifiers found in the command text. - [JsonPropertyName("commands")] - public required PermissionRequestShellCommand[] Commands { get; set; } + public required string Intention { get; set; } /// File paths that may be read or written by the command. [JsonPropertyName("possiblePaths")] @@ -3428,13 +3554,10 @@ public partial class PermissionRequestShell : PermissionRequest [JsonPropertyName("possibleUrls")] public required PermissionRequestShellPossibleUrl[] PossibleUrls { get; set; } - /// Whether the command includes a file write redirection (e.g., > or >>). - [JsonPropertyName("hasWriteFileRedirection")] - public required bool HasWriteFileRedirection { get; set; } - - /// Whether the UI can offer session-wide approval for this command pattern. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } /// Optional warning message about risks of running this command. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3450,31 +3573,31 @@ public partial class PermissionRequestWrite : PermissionRequest [JsonIgnore] public override string Kind => "write"; - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + /// Whether the UI can offer session-wide approval for file write operations. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } - /// Human-readable description of the intended file change. - [JsonPropertyName("intention")] - public required string Intention { get; set; } + /// Unified diff showing the proposed changes. + [JsonPropertyName("diff")] + public required string Diff { get; set; } /// Path of the file being written to. [JsonPropertyName("fileName")] public required string FileName { get; set; } - /// Unified diff showing the proposed changes. - [JsonPropertyName("diff")] - public required string Diff { get; set; } + /// Human-readable description of the intended file change. + [JsonPropertyName("intention")] + public required string Intention { get; set; } /// Complete new file contents for newly created files. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("newFileContents")] public string? NewFileContents { get; set; } - /// Whether the UI can offer session-wide approval for file write operations. - [JsonPropertyName("canOfferSessionApproval")] - public required bool CanOfferSessionApproval { get; set; } + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } /// File or directory read permission request. @@ -3485,11 +3608,6 @@ public partial class PermissionRequestRead : PermissionRequest [JsonIgnore] public override string Kind => "read"; - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - /// Human-readable description of why the file is being read. [JsonPropertyName("intention")] public required string Intention { get; set; } @@ -3497,6 +3615,11 @@ public partial class PermissionRequestRead : PermissionRequest /// Path of the file or directory being read. [JsonPropertyName("path")] public required string Path { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } /// MCP tool invocation permission request. @@ -3507,15 +3630,24 @@ public partial class PermissionRequestMcp : PermissionRequest [JsonIgnore] public override string Kind => "mcp"; - /// Tool call ID that triggered this permission request. + /// Arguments to pass to the MCP tool. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } + [JsonPropertyName("args")] + public object? Args { get; set; } + + /// Whether this MCP tool is read-only (no side effects). + [JsonPropertyName("readOnly")] + public required bool ReadOnly { get; set; } /// Name of the MCP server providing the tool. [JsonPropertyName("serverName")] public required string ServerName { get; set; } + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + /// Internal name of the MCP tool. [JsonPropertyName("toolName")] public required string ToolName { get; set; } @@ -3523,15 +3655,6 @@ public partial class PermissionRequestMcp : PermissionRequest /// Human-readable title of the MCP tool. [JsonPropertyName("toolTitle")] public required string ToolTitle { get; set; } - - /// Arguments to pass to the MCP tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public object? Args { get; set; } - - /// Whether this MCP tool is read-only (no side effects). - [JsonPropertyName("readOnly")] - public required bool ReadOnly { get; set; } } /// URL access permission request. @@ -3542,15 +3665,15 @@ public partial class PermissionRequestUrl : PermissionRequest [JsonIgnore] public override string Kind => "url"; + /// Human-readable description of why the URL is being accessed. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] public string? ToolCallId { get; set; } - /// Human-readable description of why the URL is being accessed. - [JsonPropertyName("intention")] - public required string Intention { get; set; } - /// URL to be fetched. [JsonPropertyName("url")] public required string Url { get; set; } @@ -3564,25 +3687,11 @@ public partial class PermissionRequestMemory : PermissionRequest [JsonIgnore] public override string Kind => "memory"; - /// Tool call ID that triggered this permission request. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - /// Whether this is a store or vote memory operation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("action")] public PermissionRequestMemoryAction? Action { get; set; } - /// Topic or subject of the memory (store only). - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("subject")] - public string? Subject { get; set; } - - /// The fact being stored or voted on. - [JsonPropertyName("fact")] - public required string Fact { get; set; } - /// Source references for the stored fact (store only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("citations")] @@ -3593,10 +3702,24 @@ public partial class PermissionRequestMemory : PermissionRequest [JsonPropertyName("direction")] public PermissionRequestMemoryDirection? Direction { get; set; } + /// The fact being stored or voted on. + [JsonPropertyName("fact")] + public required string Fact { get; set; } + /// Reason for the vote (vote only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reason")] public string? Reason { get; set; } + + /// Topic or subject of the memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("subject")] + public string? Subject { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } /// Custom tool invocation permission request. @@ -3607,23 +3730,23 @@ public partial class PermissionRequestCustomTool : PermissionRequest [JsonIgnore] public override string Kind => "custom-tool"; + /// Arguments to pass to the custom tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public object? Args { get; set; } + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolCallId")] public string? ToolCallId { get; set; } - /// Name of the custom tool. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } - /// Description of what the custom tool does. [JsonPropertyName("toolDescription")] public required string ToolDescription { get; set; } - /// Arguments to pass to the custom tool. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("args")] - public object? Args { get; set; } + /// Name of the custom tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } /// Hook confirmation permission request. @@ -3634,24 +3757,24 @@ public partial class PermissionRequestHook : PermissionRequest [JsonIgnore] public override string Kind => "hook"; - /// Tool call ID that triggered this permission request. + /// Optional message from the hook explaining why confirmation is needed. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("toolCallId")] - public string? ToolCallId { get; set; } - - /// Name of the tool the hook is gating. - [JsonPropertyName("toolName")] - public required string ToolName { get; set; } + [JsonPropertyName("hookMessage")] + public string? HookMessage { get; set; } /// Arguments of the tool call being gated. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolArgs")] public object? ToolArgs { get; set; } - /// Optional message from the hook explaining why confirmation is needed. + /// Tool call ID that triggered this permission request. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("hookMessage")] - public string? HookMessage { get; set; } + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Name of the tool the hook is gating. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } } /// Details of the permission being requested. @@ -3688,10 +3811,6 @@ public partial class PermissionCompletedResult /// Nested data type for ElicitationRequestedSchema. public partial class ElicitationRequestedSchema { - /// Schema type indicator (always 'object'). - [JsonPropertyName("type")] - public required string Type { get; set; } - /// Form field definitions, keyed by field name. [JsonPropertyName("properties")] public required IDictionary Properties { get; set; } @@ -3700,6 +3819,10 @@ public partial class ElicitationRequestedSchema [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("required")] public string[]? Required { get; set; } + + /// Schema type indicator (always 'object'). + [JsonPropertyName("type")] + public required string Type { get; set; } } /// Static OAuth client configuration, if the server specifies one. @@ -3719,14 +3842,14 @@ public partial class McpOauthRequiredStaticClientConfig /// Nested data type for CommandsChangedCommand. public partial class CommandsChangedCommand { - /// Gets or sets the name value. - [JsonPropertyName("name")] - public required string Name { get; set; } - /// Gets or sets the description value. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("description")] public string? Description { get; set; } + + /// Gets or sets the name value. + [JsonPropertyName("name")] + public required string Name { get; set; } } /// UI capability changes. @@ -3742,51 +3865,56 @@ public partial class CapabilitiesChangedUI /// Nested data type for SkillsLoadedSkill. public partial class SkillsLoadedSkill { - /// Unique identifier for the skill. - [JsonPropertyName("name")] - public required string Name { get; set; } - /// Description of what the skill does. [JsonPropertyName("description")] public required string Description { get; set; } - /// Source location type of the skill (e.g., project, personal, plugin). - [JsonPropertyName("source")] - public required string Source { get; set; } - - /// Whether the skill can be invoked by the user as a slash command. - [JsonPropertyName("userInvocable")] - public required bool UserInvocable { get; set; } - /// Whether the skill is currently enabled. [JsonPropertyName("enabled")] public required bool Enabled { get; set; } + /// Unique identifier for the skill. + [JsonPropertyName("name")] + public required string Name { get; set; } + /// Absolute path to the skill file, if available. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("path")] public string? Path { get; set; } + + /// Source location type of the skill (e.g., project, personal, plugin). + [JsonPropertyName("source")] + public required string Source { get; set; } + + /// Whether the skill can be invoked by the user as a slash command. + [JsonPropertyName("userInvocable")] + public required bool UserInvocable { get; set; } } /// Nested data type for CustomAgentsUpdatedAgent. public partial class CustomAgentsUpdatedAgent { + /// Description of what the agent does. + [JsonPropertyName("description")] + public required string Description { get; set; } + + /// Human-readable display name. + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + /// Unique identifier for the agent. [JsonPropertyName("id")] public required string Id { get; set; } + /// Model override for this agent, if set. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } + /// Internal name of the agent. [JsonPropertyName("name")] public required string Name { get; set; } - /// Human-readable display name. - [JsonPropertyName("displayName")] - public required string DisplayName { get; set; } - - /// Description of what the agent does. - [JsonPropertyName("description")] - public required string Description { get; set; } - /// Source location: user, project, inherited, remote, or plugin. [JsonPropertyName("source")] public required string Source { get; set; } @@ -3798,33 +3926,28 @@ public partial class CustomAgentsUpdatedAgent /// Whether the agent can be selected by the user. [JsonPropertyName("userInvocable")] public required bool UserInvocable { get; set; } - - /// Model override for this agent, if set. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("model")] - public string? Model { get; set; } } /// Nested data type for McpServersLoadedServer. public partial class McpServersLoadedServer { + /// Error message if the server failed to connect. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("error")] + public string? Error { get; set; } + /// Server name (config key). [JsonPropertyName("name")] public required string Name { get; set; } - /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. - [JsonPropertyName("status")] - public required McpServersLoadedServerStatus Status { get; set; } - /// Configuration source: user, workspace, plugin, or builtin. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] public string? Source { get; set; } - /// Error message if the server failed to connect. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("error")] - public string? Error { get; set; } + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + [JsonPropertyName("status")] + public required McpServersLoadedServerStatus Status { get; set; } } /// Nested data type for ExtensionsLoadedExtension. @@ -3910,21 +4033,6 @@ public enum ShutdownType Error, } -/// Type of GitHub reference. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum UserMessageAttachmentGithubReferenceType -{ - /// The issue variant. - [JsonStringEnumMemberName("issue")] - Issue, - /// The pr variant. - [JsonStringEnumMemberName("pr")] - Pr, - /// The discussion variant. - [JsonStringEnumMemberName("discussion")] - Discussion, -} - /// The agent mode that was active when this message was sent. [JsonConverter(typeof(JsonStringEnumConverter))] public enum UserMessageAgentMode @@ -3943,6 +4051,21 @@ public enum UserMessageAgentMode Shell, } +/// Type of GitHub reference. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum UserMessageAttachmentGithubReferenceType +{ + /// The issue variant. + [JsonStringEnumMemberName("issue")] + Issue, + /// The pr variant. + [JsonStringEnumMemberName("pr")] + Pr, + /// The discussion variant. + [JsonStringEnumMemberName("discussion")] + Discussion, +} + /// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. [JsonConverter(typeof(JsonStringEnumConverter))] public enum AssistantMessageToolRequestType @@ -4172,6 +4295,7 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(AssistantUsageCopilotUsageTokenDetail))] [JsonSerializable(typeof(AssistantUsageData))] [JsonSerializable(typeof(AssistantUsageEvent))] +[JsonSerializable(typeof(AssistantUsageQuotaSnapshot))] [JsonSerializable(typeof(CapabilitiesChangedData))] [JsonSerializable(typeof(CapabilitiesChangedEvent))] [JsonSerializable(typeof(CapabilitiesChangedUI))] @@ -4292,6 +4416,9 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(SessionWorkspaceFileChangedData))] [JsonSerializable(typeof(SessionWorkspaceFileChangedEvent))] [JsonSerializable(typeof(ShutdownCodeChanges))] +[JsonSerializable(typeof(ShutdownModelMetric))] +[JsonSerializable(typeof(ShutdownModelMetricRequests))] +[JsonSerializable(typeof(ShutdownModelMetricUsage))] [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] [JsonSerializable(typeof(SkillsLoadedSkill))] @@ -4313,6 +4440,7 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(SystemNotificationAgentIdle))] [JsonSerializable(typeof(SystemNotificationData))] [JsonSerializable(typeof(SystemNotificationEvent))] +[JsonSerializable(typeof(SystemNotificationNewInboxMessage))] [JsonSerializable(typeof(SystemNotificationShellCompleted))] [JsonSerializable(typeof(SystemNotificationShellDetachedCompleted))] [JsonSerializable(typeof(ToolExecutionCompleteContent))] diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 20d6525b8..455cecdba 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -607,15 +607,15 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission { return; } - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, result); + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { Kind = result.Kind.Value }); } catch (Exception) { try { - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionRequestResult + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { - Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser + Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser.Value }); } catch (IOException) diff --git a/dotnet/src/SessionFsProvider.cs b/dotnet/src/SessionFsProvider.cs new file mode 100644 index 000000000..6007dd081 --- /dev/null +++ b/dotnet/src/SessionFsProvider.cs @@ -0,0 +1,216 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using GitHub.Copilot.SDK.Rpc; + +namespace GitHub.Copilot.SDK; + +/// +/// Base class for session filesystem providers. Subclasses override the +/// virtual methods and use normal C# patterns (return values, throw exceptions). +/// The base class catches exceptions and converts them to +/// results expected by the runtime. +/// +public abstract class SessionFsProvider : ISessionFsHandler +{ + /// Reads the full content of a file. Throw if the file does not exist. + /// SessionFs-relative path. + /// Cancellation token. + /// The file content as a UTF-8 string. + protected abstract Task ReadFileAsync(string path, CancellationToken cancellationToken); + + /// Writes content to a file, creating it (and parent directories) if needed. + /// SessionFs-relative path. + /// Content to write. + /// Optional POSIX-style permission mode. Null means use OS default. + /// Cancellation token. + protected abstract Task WriteFileAsync(string path, string content, int? mode, CancellationToken cancellationToken); + + /// Appends content to a file, creating it (and parent directories) if needed. + /// SessionFs-relative path. + /// Content to append. + /// Optional POSIX-style permission mode. Null means use OS default. + /// Cancellation token. + protected abstract Task AppendFileAsync(string path, string content, int? mode, CancellationToken cancellationToken); + + /// Checks whether a path exists. + /// SessionFs-relative path. + /// Cancellation token. + /// true if the path exists, false otherwise. + protected abstract Task ExistsAsync(string path, CancellationToken cancellationToken); + + /// Gets metadata about a file or directory. Throw if the path does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task StatAsync(string path, CancellationToken cancellationToken); + + /// Creates a directory (and optionally parents). Does not fail if it already exists. + /// SessionFs-relative path. + /// Whether to create parent directories. + /// Optional POSIX-style permission mode (e.g., 0x1FF for 0777). Null means use OS default. + /// Cancellation token. + protected abstract Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken); + + /// Lists entry names in a directory. Throw if the directory does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task> ReaddirAsync(string path, CancellationToken cancellationToken); + + /// Lists entries with type info in a directory. Throw if the directory does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken); + + /// Removes a file or directory. Throw if the path does not exist (unless is true). + /// SessionFs-relative path. + /// Whether to remove directory contents recursively. + /// If true, do not throw when the path does not exist. + /// Cancellation token. + protected abstract Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken); + + /// Renames/moves a file or directory. + /// Source path. + /// Destination path. + /// Cancellation token. + protected abstract Task RenameAsync(string src, string dest, CancellationToken cancellationToken); + + // ---- ISessionFsHandler implementation (private, handles error mapping) ---- + + async Task ISessionFsHandler.ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken) + { + try + { + var content = await ReadFileAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReadFileResult { Content = content }; + } + catch (Exception ex) + { + return new SessionFsReadFileResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken) + { + try + { + await WriteFileAsync(request.Path, request.Content, (int?)request.Mode, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken) + { + try + { + await AppendFileAsync(request.Path, request.Content, (int?)request.Mode, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken) + { + try + { + var exists = await ExistsAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsExistsResult { Exists = exists }; + } + catch + { + return new SessionFsExistsResult { Exists = false }; + } + } + + async Task ISessionFsHandler.StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken) + { + try + { + return await StatAsync(request.Path, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + return new SessionFsStatResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken) + { + try + { + await MkdirAsync(request.Path, request.Recursive ?? false, (int?)request.Mode, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken) + { + try + { + var entries = await ReaddirAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReaddirResult { Entries = entries }; + } + catch (Exception ex) + { + return new SessionFsReaddirResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken) + { + try + { + var entries = await ReaddirWithTypesAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReaddirWithTypesResult { Entries = entries }; + } + catch (Exception ex) + { + return new SessionFsReaddirWithTypesResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken) + { + try + { + await RmAsync(request.Path, request.Recursive ?? false, request.Force ?? false, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken) + { + try + { + await RenameAsync(request.Src, request.Dest, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + private static SessionFsError ToSessionFsError(Exception ex) + { + var code = ex is FileNotFoundException or DirectoryNotFoundException + ? SessionFsErrorCode.ENOENT + : SessionFsErrorCode.UNKNOWN; + return new SessionFsError { Code = code, Message = ex.Message }; + } +} diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 131362055..e42c34f5d 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1932,7 +1932,7 @@ protected SessionConfig(SessionConfig? other) /// Supplies a handler for session filesystem operations. /// This is used only when is configured. ///
- public Func? CreateSessionFsHandler { get; set; } + public Func? CreateSessionFsHandler { get; set; } /// /// Creates a shallow clone of this instance. @@ -2179,7 +2179,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// Supplies a handler for session filesystem operations. /// This is used only when is configured. /// - public Func? CreateSessionFsHandler { get; set; } + public Func? CreateSessionFsHandler { get; set; } /// /// Creates a shallow clone of this instance. diff --git a/dotnet/test/CompactionTests.cs b/dotnet/test/CompactionTests.cs index c1cbc42df..f70bf5ecb 100644 --- a/dotnet/test/CompactionTests.cs +++ b/dotnet/test/CompactionTests.cs @@ -11,7 +11,7 @@ namespace GitHub.Copilot.SDK.Test; public class CompactionTests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "compaction", output) { - [Fact] + [Fact(Skip = "Compaction tests are skipped due to flakiness — re-enable once stabilized")] public async Task Should_Trigger_Compaction_With_Low_Threshold_And_Emit_Events() { // Create session with very low compaction thresholds to trigger compaction quickly @@ -81,7 +81,7 @@ await session.SendAndWaitAsync(new MessageOptions Assert.Contains("dragon", answer.Data.Content.ToLower()); } - [Fact] + [Fact(Skip = "Compaction tests are skipped due to flakiness — re-enable once stabilized")] public async Task Should_Not_Emit_Compaction_Events_When_Infinite_Sessions_Disabled() { var session = await CreateSessionAsync(new SessionConfig diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs index d1756ea61..46162b50f 100644 --- a/dotnet/test/Harness/E2ETestBase.cs +++ b/dotnet/test/Harness/E2ETestBase.cs @@ -5,6 +5,7 @@ using System.Data; using System.Reflection; using GitHub.Copilot.SDK.Test.Harness; +using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; @@ -24,6 +25,26 @@ protected E2ETestBase(E2ETestFixture fixture, string snapshotCategory, ITestOutp _fixture = fixture; _snapshotCategory = snapshotCategory; _testName = GetTestName(output); + Logger = new XunitLogger(output); + + // Wire logger into the shared context so all clients created via Ctx.CreateClient get it. + Ctx.Logger = Logger; + } + + /// Logger that forwards warnings and above to xunit test output. + protected ILogger Logger { get; } + + /// Bridges to xunit's . + private sealed class XunitLogger(ITestOutputHelper output) : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull => null; + public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) return; + try { output.WriteLine($"[{logLevel}] {formatter(state, exception)}"); } + catch (InvalidOperationException) { /* test already finished */ } + } } private static string GetTestName(ITestOutputHelper output) diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 47c8b2c4d..7b47ab0b7 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; namespace GitHub.Copilot.SDK.Test.Harness; @@ -13,6 +14,9 @@ public sealed class E2ETestContext : IAsyncDisposable public string WorkDir { get; } public string ProxyUrl { get; } + /// Optional logger injected by tests; applied to all clients created via . + public ILogger? Logger { get; set; } + private readonly CapiProxy _proxy; private readonly string _repoRoot; @@ -99,6 +103,7 @@ public CopilotClient CreateClient(bool useStdio = true, CopilotClientOptions? op options.Cwd ??= WorkDir; options.Environment ??= GetEnvironment(); options.UseStdio = useStdio; + options.Logger ??= Logger; if (string.IsNullOrEmpty(options.CliUrl)) { diff --git a/dotnet/test/SessionEventSerializationTests.cs b/dotnet/test/SessionEventSerializationTests.cs index 476867a4d..93e5ae935 100644 --- a/dotnet/test/SessionEventSerializationTests.cs +++ b/dotnet/test/SessionEventSerializationTests.cs @@ -93,22 +93,19 @@ public class SessionEventSerializationTests LinesRemoved = 0, FilesModified = ["README.md"], }, - ModelMetrics = new Dictionary + ModelMetrics = new Dictionary { - ["gpt-5.4"] = ParseJsonElement(""" + ["gpt-5.4"] = new ShutdownModelMetric + { + Requests = new ShutdownModelMetricRequests { Count = 1, Cost = 1 }, + Usage = new ShutdownModelMetricUsage { - "requests": { - "count": 1, - "cost": 1 - }, - "usage": { - "inputTokens": 10, - "outputTokens": 5, - "cacheReadTokens": 0, - "cacheWriteTokens": 0 - } - } - """), + InputTokens = 10, + OutputTokens = 5, + CacheReadTokens = 0, + CacheWriteTokens = 0, + }, + }, }, CurrentModel = "gpt-5.4", }, diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs index 8c55b1120..1d0e6d2e5 100644 --- a/dotnet/test/SessionFsTests.cs +++ b/dotnet/test/SessionFsTests.cs @@ -56,7 +56,7 @@ public async Task Should_Load_Session_Data_From_Fs_Provider_On_Resume() try { await using var client = CreateSessionFsClient(providerRoot); - Func createSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot); + Func createSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot); var session1 = await client.CreateSessionAsync(new SessionConfig { @@ -95,7 +95,7 @@ public async Task Should_Reject_SetProvider_When_Sessions_Already_Exist() try { await using var client1 = CreateSessionFsClient(providerRoot, useStdio: false); - var createSessionFsHandler = (Func)(s => new TestSessionFsHandler(s.SessionId, providerRoot)); + var createSessionFsHandler = (Func)(s => new TestSessionFsHandler(s.SessionId, providerRoot)); _ = await client1.CreateSessionAsync(new SessionConfig { @@ -227,6 +227,70 @@ await WaitForConditionAsync(async () => } } + [Fact] + public async Task Should_Write_Workspace_Metadata_Via_SessionFs() + { + var providerRoot = CreateProviderRoot(); + try + { + await using var client = CreateSessionFsClient(providerRoot); + var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + }); + + var msg = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 7 * 8?" }); + Assert.Contains("56", msg?.Data.Content ?? string.Empty); + + // WorkspaceManager should have created workspace.yaml via sessionFs + var workspaceYamlPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/workspace.yaml"); + await WaitForConditionAsync(() => File.Exists(workspaceYamlPath)); + var yaml = await ReadAllTextSharedAsync(workspaceYamlPath); + Assert.Contains("id:", yaml); + + // Checkpoint index should also exist + var indexPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/checkpoints/index.md"); + await WaitForConditionAsync(() => File.Exists(indexPath)); + + await session.DisposeAsync(); + } + finally + { + await TryDeleteDirectoryAsync(providerRoot); + } + } + + [Fact] + public async Task Should_Persist_Plan_Md_Via_SessionFs() + { + var providerRoot = CreateProviderRoot(); + try + { + await using var client = CreateSessionFsClient(providerRoot); + var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + }); + + // Write a plan via the session RPC + await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 3?" }); + await session.Rpc.Plan.UpdateAsync("# Test Plan\n\nThis is a test."); + + var planPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/plan.md"); + await WaitForConditionAsync(() => File.Exists(planPath)); + var content = await ReadAllTextSharedAsync(planPath); + Assert.Contains("# Test Plan", content); + + await session.DisposeAsync(); + } + finally + { + await TryDeleteDirectoryAsync(providerRoot); + } + } + private CopilotClient CreateSessionFsClient(string providerRoot, bool useStdio = true) { Directory.CreateDirectory(providerRoot); @@ -367,40 +431,36 @@ private static string NormalizeRelativePathSegment(string segment, string paramN return normalized; } - private sealed class TestSessionFsHandler(string sessionId, string rootDir) : ISessionFsHandler + private sealed class TestSessionFsHandler(string sessionId, string rootDir) : SessionFsProvider { - public async Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default) + protected override async Task ReadFileAsync(string path, CancellationToken cancellationToken) { - var content = await File.ReadAllTextAsync(ResolvePath(request.Path), cancellationToken); - return new SessionFsReadFileResult { Content = content }; + return await File.ReadAllTextAsync(ResolvePath(path), cancellationToken); } - public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) + protected override async Task WriteFileAsync(string path, string content, int? mode, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); - await File.WriteAllTextAsync(fullPath, request.Content, cancellationToken); + await File.WriteAllTextAsync(fullPath, content, cancellationToken); } - public async Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default) + protected override async Task AppendFileAsync(string path, string content, int? mode, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); - await File.AppendAllTextAsync(fullPath, request.Content, cancellationToken); + await File.AppendAllTextAsync(fullPath, content, cancellationToken); } - public Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) + protected override Task ExistsAsync(string path, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); - return Task.FromResult(new SessionFsExistsResult - { - Exists = File.Exists(fullPath) || Directory.Exists(fullPath), - }); + var fullPath = ResolvePath(path); + return Task.FromResult(File.Exists(fullPath) || Directory.Exists(fullPath)); } - public Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default) + protected override Task StatAsync(string path, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); if (File.Exists(fullPath)) { var info = new FileInfo(fullPath); @@ -417,7 +477,7 @@ public Task StatAsync(SessionFsStatRequest request, Cancell var dirInfo = new DirectoryInfo(fullPath); if (!dirInfo.Exists) { - throw new FileNotFoundException($"Path does not exist: {request.Path}"); + throw new DirectoryNotFoundException($"Path does not exist: {path}"); } return Task.FromResult(new SessionFsStatResult @@ -430,41 +490,39 @@ public Task StatAsync(SessionFsStatRequest request, Cancell }); } - public Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) + protected override Task MkdirAsync(string path, bool recursive, int? mode, CancellationToken cancellationToken) { - Directory.CreateDirectory(ResolvePath(request.Path)); + Directory.CreateDirectory(ResolvePath(path)); return Task.CompletedTask; } - public Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) + protected override Task> ReaddirAsync(string path, CancellationToken cancellationToken) { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) + IList entries = Directory + .EnumerateFileSystemEntries(ResolvePath(path)) .Select(Path.GetFileName) .Where(name => name is not null) .Cast() .ToList(); - - return Task.FromResult(new SessionFsReaddirResult { Entries = entries }); + return Task.FromResult(entries); } - public Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default) + protected override Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken) { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(path => new SessionFsReaddirWithTypesEntry + IList entries = Directory + .EnumerateFileSystemEntries(ResolvePath(path)) + .Select(p => new SessionFsReaddirWithTypesEntry { - Name = Path.GetFileName(path), - Type = Directory.Exists(path) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, + Name = Path.GetFileName(p), + Type = Directory.Exists(p) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, }) .ToList(); - - return Task.FromResult(new SessionFsReaddirWithTypesResult { Entries = entries }); + return Task.FromResult(entries); } - public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) + protected override Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); if (File.Exists(fullPath)) { @@ -474,31 +532,31 @@ public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationTo if (Directory.Exists(fullPath)) { - Directory.Delete(fullPath, request.Recursive ?? false); + Directory.Delete(fullPath, recursive); return Task.CompletedTask; } - if (request.Force == true) + if (force) { return Task.CompletedTask; } - throw new FileNotFoundException($"Path does not exist: {request.Path}"); + throw new FileNotFoundException($"Path does not exist: {path}"); } - public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) + protected override Task RenameAsync(string src, string dest, CancellationToken cancellationToken) { - var src = ResolvePath(request.Src); - var dest = ResolvePath(request.Dest); - Directory.CreateDirectory(Path.GetDirectoryName(dest)!); + var srcPath = ResolvePath(src); + var destPath = ResolvePath(dest); + Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); - if (Directory.Exists(src)) + if (Directory.Exists(srcPath)) { - Directory.Move(src, dest); + Directory.Move(srcPath, destPath); } else { - File.Move(src, dest, overwrite: true); + File.Move(srcPath, destPath, overwrite: true); } return Task.CompletedTask; diff --git a/go/client.go b/go/client.go index 74e4839be..4eb56e639 100644 --- a/go/client.go +++ b/go/client.go @@ -676,7 +676,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses c.sessionsMux.Unlock() return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options") } - session.clientSessionApis.SessionFs = config.CreateSessionFsHandler(session) + session.clientSessionApis.SessionFs = newSessionFsAdapter(config.CreateSessionFsHandler(session)) } result, err := c.client.Request("session.create", req) @@ -835,7 +835,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, c.sessionsMux.Unlock() return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options") } - session.clientSessionApis.SessionFs = config.CreateSessionFsHandler(session) + session.clientSessionApis.SessionFs = newSessionFsAdapter(config.CreateSessionFsHandler(session)) } result, err := c.client.Request("session.resume", req) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 95ace9123..d0bbde414 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -620,1282 +620,1186 @@ const ( SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" ) -// Session initialization metadata including context and configuration -type SessionStartData struct { - // Unique identifier for the session - SessionID string `json:"sessionId"` - // Schema version number for the session event format - Version float64 `json:"version"` - // Identifier of the software producing the events (e.g., "copilot-agent") - Producer string `json:"producer"` - // Version string of the Copilot application - CopilotVersion string `json:"copilotVersion"` - // ISO 8601 timestamp when the session was created - StartTime time.Time `json:"startTime"` - // Model selected at session creation time, if any - SelectedModel *string `json:"selectedModel,omitempty"` - // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - ReasoningEffort *string `json:"reasoningEffort,omitempty"` - // Working directory and git context at session start - Context *WorkingDirectoryContext `json:"context,omitempty"` - // Whether the session was already in use by another client at start time - AlreadyInUse *bool `json:"alreadyInUse,omitempty"` - // Whether this session supports remote steering via Mission Control - RemoteSteerable *bool `json:"remoteSteerable,omitempty"` +// Agent intent description for current activity or plan +type AssistantIntentData struct { + // Short description of what the agent is currently doing or planning to do + Intent string `json:"intent"` } -func (*SessionStartData) sessionEventData() {} +func (*AssistantIntentData) sessionEventData() {} -// Session resume metadata including current context and event count -type SessionResumeData struct { - // ISO 8601 timestamp when the session was resumed - ResumeTime time.Time `json:"resumeTime"` - // Total number of persisted events in the session at the time of resume - EventCount float64 `json:"eventCount"` - // Model currently selected at resume time - SelectedModel *string `json:"selectedModel,omitempty"` - // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - ReasoningEffort *string `json:"reasoningEffort,omitempty"` - // Updated working directory and git context at resume time - Context *WorkingDirectoryContext `json:"context,omitempty"` - // Whether the session was already in use by another client at resume time - AlreadyInUse *bool `json:"alreadyInUse,omitempty"` - // Whether this session supports remote steering via Mission Control - RemoteSteerable *bool `json:"remoteSteerable,omitempty"` +// Agent mode change details including previous and new modes +type SessionModeChangedData struct { + // Agent mode after the change (e.g., "interactive", "plan", "autopilot") + NewMode string `json:"newMode"` + // Agent mode before the change (e.g., "interactive", "plan", "autopilot") + PreviousMode string `json:"previousMode"` } -func (*SessionResumeData) sessionEventData() {} +func (*SessionModeChangedData) sessionEventData() {} -// Notifies Mission Control that the session's remote steering capability has changed -type SessionRemoteSteerableChangedData struct { - // Whether this session now supports remote steering via Mission Control - RemoteSteerable bool `json:"remoteSteerable"` +// Assistant reasoning content for timeline display with complete thinking text +type AssistantReasoningData struct { + // The complete extended thinking text from the model + Content string `json:"content"` + // Unique identifier for this reasoning block + ReasoningID string `json:"reasoningId"` } -func (*SessionRemoteSteerableChangedData) sessionEventData() {} +func (*AssistantReasoningData) sessionEventData() {} -// Error details for timeline display including message and optional diagnostic information -type SessionErrorData struct { - // Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") - ErrorType string `json:"errorType"` - // Human-readable error message - Message string `json:"message"` - // Error stack trace, when available - Stack *string `json:"stack,omitempty"` - // HTTP status code from the upstream request, if applicable - StatusCode *int64 `json:"statusCode,omitempty"` +// Assistant response containing text content, optional tool requests, and interaction metadata +type AssistantMessageData struct { + // The assistant's text response content + Content string `json:"content"` + // Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. + EncryptedContent *string `json:"encryptedContent,omitempty"` + // CAPI interaction ID for correlating this message with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` + // Unique identifier for this assistant message + MessageID string `json:"messageId"` + // Actual output token count from the API response (completion_tokens), used for accurate token accounting + OutputTokens *float64 `json:"outputTokens,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. + ParentToolCallID *string `json:"parentToolCallId,omitempty"` + // Generation phase for phased-output models (e.g., thinking vs. response phases) + Phase *string `json:"phase,omitempty"` + // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` + // Readable reasoning text from the model's extended thinking + ReasoningText *string `json:"reasoningText,omitempty"` // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - ProviderCallID *string `json:"providerCallId,omitempty"` - // Optional URL associated with this error that the user can open in a browser - URL *string `json:"url,omitempty"` -} - -func (*SessionErrorData) sessionEventData() {} - -// Payload indicating the session is idle with no background agents in flight -type SessionIdleData struct { - // True when the preceding agentic loop was cancelled via abort signal - Aborted *bool `json:"aborted,omitempty"` + RequestID *string `json:"requestId,omitempty"` + // Tool invocations requested by the assistant in this message + ToolRequests []AssistantMessageToolRequest `json:"toolRequests,omitempty"` } -func (*SessionIdleData) sessionEventData() {} +func (*AssistantMessageData) sessionEventData() {} -// Session title change payload containing the new display title -type SessionTitleChangedData struct { - // The new display title for the session - Title string `json:"title"` +// Context window breakdown at the start of LLM-powered conversation compaction +type SessionCompactionStartData struct { + // Token count from non-system messages (user, assistant, tool) at compaction start + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Token count from system message(s) at compaction start + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Token count from tool definitions at compaction start + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` } -func (*SessionTitleChangedData) sessionEventData() {} +func (*SessionCompactionStartData) sessionEventData() {} -// Informational message for timeline display with categorization -type SessionInfoData struct { - // Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") - InfoType string `json:"infoType"` - // Human-readable informational message for display in the timeline - Message string `json:"message"` - // Optional URL associated with this message that the user can open in a browser - URL *string `json:"url,omitempty"` +// Conversation compaction results including success status, metrics, and optional error details +type SessionCompactionCompleteData struct { + // Checkpoint snapshot number created for recovery + CheckpointNumber *float64 `json:"checkpointNumber,omitempty"` + // File path where the checkpoint was stored + CheckpointPath *string `json:"checkpointPath,omitempty"` + // Token usage breakdown for the compaction LLM call + CompactionTokensUsed *CompactionCompleteCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` + // Token count from non-system messages (user, assistant, tool) after compaction + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Error message if compaction failed + Error *string `json:"error,omitempty"` + // Number of messages removed during compaction + MessagesRemoved *float64 `json:"messagesRemoved,omitempty"` + // Total tokens in conversation after compaction + PostCompactionTokens *float64 `json:"postCompactionTokens,omitempty"` + // Number of messages before compaction + PreCompactionMessagesLength *float64 `json:"preCompactionMessagesLength,omitempty"` + // Total tokens in conversation before compaction + PreCompactionTokens *float64 `json:"preCompactionTokens,omitempty"` + // GitHub request tracing ID (x-github-request-id header) for the compaction LLM call + RequestID *string `json:"requestId,omitempty"` + // Whether compaction completed successfully + Success bool `json:"success"` + // LLM-generated summary of the compacted conversation history + SummaryContent *string `json:"summaryContent,omitempty"` + // Token count from system message(s) after compaction + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Number of tokens removed during compaction + TokensRemoved *float64 `json:"tokensRemoved,omitempty"` + // Token count from tool definitions after compaction + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` } -func (*SessionInfoData) sessionEventData() {} +func (*SessionCompactionCompleteData) sessionEventData() {} -// Warning message for timeline display with categorization -type SessionWarningData struct { - // Category of warning (e.g., "subscription", "policy", "mcp") - WarningType string `json:"warningType"` - // Human-readable warning message for display in the timeline - Message string `json:"message"` - // Optional URL associated with this warning that the user can open in a browser - URL *string `json:"url,omitempty"` +// Conversation truncation statistics including token counts and removed content metrics +type SessionTruncationData struct { + // Number of messages removed by truncation + MessagesRemovedDuringTruncation float64 `json:"messagesRemovedDuringTruncation"` + // Identifier of the component that performed truncation (e.g., "BasicTruncator") + PerformedBy string `json:"performedBy"` + // Number of conversation messages after truncation + PostTruncationMessagesLength float64 `json:"postTruncationMessagesLength"` + // Total tokens in conversation messages after truncation + PostTruncationTokensInMessages float64 `json:"postTruncationTokensInMessages"` + // Number of conversation messages before truncation + PreTruncationMessagesLength float64 `json:"preTruncationMessagesLength"` + // Total tokens in conversation messages before truncation + PreTruncationTokensInMessages float64 `json:"preTruncationTokensInMessages"` + // Maximum token count for the model's context window + TokenLimit float64 `json:"tokenLimit"` + // Number of tokens removed by truncation + TokensRemovedDuringTruncation float64 `json:"tokensRemovedDuringTruncation"` } -func (*SessionWarningData) sessionEventData() {} +func (*SessionTruncationData) sessionEventData() {} -// Model change details including previous and new model identifiers -type SessionModelChangeData struct { - // Model that was previously selected, if any - PreviousModel *string `json:"previousModel,omitempty"` - // Newly selected model identifier - NewModel string `json:"newModel"` - // Reasoning effort level before the model change, if applicable - PreviousReasoningEffort *string `json:"previousReasoningEffort,omitempty"` - // Reasoning effort level after the model change, if applicable - ReasoningEffort *string `json:"reasoningEffort,omitempty"` +// Current context window usage statistics including token and message counts +type SessionUsageInfoData struct { + // Token count from non-system messages (user, assistant, tool) + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Current number of tokens in the context window + CurrentTokens float64 `json:"currentTokens"` + // Whether this is the first usage_info event emitted in this session + IsInitial *bool `json:"isInitial,omitempty"` + // Current number of messages in the conversation + MessagesLength float64 `json:"messagesLength"` + // Token count from system message(s) + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Maximum token count for the model's context window + TokenLimit float64 `json:"tokenLimit"` + // Token count from tool definitions + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` } -func (*SessionModelChangeData) sessionEventData() {} +func (*SessionUsageInfoData) sessionEventData() {} -// Agent mode change details including previous and new modes -type SessionModeChangedData struct { - // Agent mode before the change (e.g., "interactive", "plan", "autopilot") - PreviousMode string `json:"previousMode"` - // Agent mode after the change (e.g., "interactive", "plan", "autopilot") - NewMode string `json:"newMode"` +// Custom agent selection details including name and available tools +type SubagentSelectedData struct { + // Human-readable display name of the selected custom agent + AgentDisplayName string `json:"agentDisplayName"` + // Internal name of the selected custom agent + AgentName string `json:"agentName"` + // List of tool names available to this agent, or null for all tools + Tools []string `json:"tools"` } -func (*SessionModeChangedData) sessionEventData() {} +func (*SubagentSelectedData) sessionEventData() {} -// Plan file operation details indicating what changed -type SessionPlanChangedData struct { - // The type of operation performed on the plan file - Operation PlanChangedOperation `json:"operation"` +// Elicitation request completion with the user's response +type ElicitationCompletedData struct { + // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) + Action *ElicitationCompletedAction `json:"action,omitempty"` + // The submitted form data when action is 'accept'; keys match the requested schema fields + Content map[string]any `json:"content,omitempty"` + // Request ID of the resolved elicitation request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` } -func (*SessionPlanChangedData) sessionEventData() {} +func (*ElicitationCompletedData) sessionEventData() {} -// Workspace file change details including path and operation type -type SessionWorkspaceFileChangedData struct { - // Relative path within the session workspace files directory - Path string `json:"path"` - // Whether the file was newly created or updated - Operation WorkspaceFileChangedOperation `json:"operation"` -} - -func (*SessionWorkspaceFileChangedData) sessionEventData() {} - -// Session handoff metadata including source, context, and repository information -type SessionHandoffData struct { - // ISO 8601 timestamp when the handoff occurred - HandoffTime time.Time `json:"handoffTime"` - // Origin type of the session being handed off - SourceType HandoffSourceType `json:"sourceType"` - // Repository context for the handed-off session - Repository *HandoffRepository `json:"repository,omitempty"` - // Additional context information for the handoff - Context *string `json:"context,omitempty"` - // Summary of the work done in the source session - Summary *string `json:"summary,omitempty"` - // Session ID of the remote session being handed off - RemoteSessionID *string `json:"remoteSessionId,omitempty"` - // GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) - Host *string `json:"host,omitempty"` +// Elicitation request; may be form-based (structured input) or URL-based (browser redirect) +type ElicitationRequestedData struct { + // The source that initiated the request (MCP server name, or absent for agent-initiated) + ElicitationSource *string `json:"elicitationSource,omitempty"` + // Message describing what information is needed from the user + Message string `json:"message"` + // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. + Mode *ElicitationRequestedMode `json:"mode,omitempty"` + // JSON Schema describing the form fields to present to the user (form mode only) + RequestedSchema *ElicitationRequestedSchema `json:"requestedSchema,omitempty"` + // Unique identifier for this elicitation request; used to respond via session.respondToElicitation() + RequestID string `json:"requestId"` + // Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs + ToolCallID *string `json:"toolCallId,omitempty"` + // URL to open in the user's browser (url mode only) + URL *string `json:"url,omitempty"` } -func (*SessionHandoffData) sessionEventData() {} +func (*ElicitationRequestedData) sessionEventData() {} -// Conversation truncation statistics including token counts and removed content metrics -type SessionTruncationData struct { - // Maximum token count for the model's context window - TokenLimit float64 `json:"tokenLimit"` - // Total tokens in conversation messages before truncation - PreTruncationTokensInMessages float64 `json:"preTruncationTokensInMessages"` - // Number of conversation messages before truncation - PreTruncationMessagesLength float64 `json:"preTruncationMessagesLength"` - // Total tokens in conversation messages after truncation - PostTruncationTokensInMessages float64 `json:"postTruncationTokensInMessages"` - // Number of conversation messages after truncation - PostTruncationMessagesLength float64 `json:"postTruncationMessagesLength"` - // Number of tokens removed by truncation - TokensRemovedDuringTruncation float64 `json:"tokensRemovedDuringTruncation"` - // Number of messages removed by truncation - MessagesRemovedDuringTruncation float64 `json:"messagesRemovedDuringTruncation"` - // Identifier of the component that performed truncation (e.g., "BasicTruncator") - PerformedBy string `json:"performedBy"` +// Empty payload; the event signals that the custom agent was deselected, returning to the default agent +type SubagentDeselectedData struct { } -func (*SessionTruncationData) sessionEventData() {} +func (*SubagentDeselectedData) sessionEventData() {} -// Session rewind details including target event and count of removed events -type SessionSnapshotRewindData struct { - // Event ID that was rewound to; this event and all after it were removed - UpToEventID string `json:"upToEventId"` - // Number of events that were removed by the rewind - EventsRemoved float64 `json:"eventsRemoved"` +// Empty payload; the event signals that the pending message queue has changed +type PendingMessagesModifiedData struct { } -func (*SessionSnapshotRewindData) sessionEventData() {} +func (*PendingMessagesModifiedData) sessionEventData() {} -// Session termination metrics including usage statistics, code changes, and shutdown reason -type SessionShutdownData struct { - // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") - ShutdownType ShutdownType `json:"shutdownType"` - // Error description when shutdownType is "error" - ErrorReason *string `json:"errorReason,omitempty"` - // Total number of premium API requests used during the session - TotalPremiumRequests float64 `json:"totalPremiumRequests"` - // Cumulative time spent in API calls during the session, in milliseconds - TotalAPIDurationMs float64 `json:"totalApiDurationMs"` - // Unix timestamp (milliseconds) when the session started - SessionStartTime float64 `json:"sessionStartTime"` - // Aggregate code change metrics for the session - CodeChanges ShutdownCodeChanges `json:"codeChanges"` - // Per-model usage breakdown, keyed by model identifier - ModelMetrics map[string]ShutdownModelMetric `json:"modelMetrics"` - // Model that was selected at the time of shutdown - CurrentModel *string `json:"currentModel,omitempty"` - // Total tokens in context window at shutdown - CurrentTokens *float64 `json:"currentTokens,omitempty"` - // System message token count at shutdown - SystemTokens *float64 `json:"systemTokens,omitempty"` - // Non-system message token count at shutdown - ConversationTokens *float64 `json:"conversationTokens,omitempty"` - // Tool definitions token count at shutdown - ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +// Error details for timeline display including message and optional diagnostic information +type SessionErrorData struct { + // Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + ErrorType string `json:"errorType"` + // Human-readable error message + Message string `json:"message"` + // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + ProviderCallID *string `json:"providerCallId,omitempty"` + // Error stack trace, when available + Stack *string `json:"stack,omitempty"` + // HTTP status code from the upstream request, if applicable + StatusCode *int64 `json:"statusCode,omitempty"` + // Optional URL associated with this error that the user can open in a browser + URL *string `json:"url,omitempty"` } -func (*SessionShutdownData) sessionEventData() {} +func (*SessionErrorData) sessionEventData() {} -// Working directory and git context at session start -type SessionContextChangedData struct { - // Current working directory path - Cwd string `json:"cwd"` - // Root directory of the git repository, resolved via git rev-parse - GitRoot *string `json:"gitRoot,omitempty"` - // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - Repository *string `json:"repository,omitempty"` - // Hosting platform type of the repository (github or ado) - HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` - // Current git branch name - Branch *string `json:"branch,omitempty"` - // Head commit of current git branch at session start time - HeadCommit *string `json:"headCommit,omitempty"` - // Base commit of current git branch at session start time - BaseCommit *string `json:"baseCommit,omitempty"` +// External tool completion notification signaling UI dismissal +type ExternalToolCompletedData struct { + // Request ID of the resolved external tool request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` } -func (*SessionContextChangedData) sessionEventData() {} +func (*ExternalToolCompletedData) sessionEventData() {} -// Current context window usage statistics including token and message counts -type SessionUsageInfoData struct { - // Maximum token count for the model's context window - TokenLimit float64 `json:"tokenLimit"` - // Current number of tokens in the context window - CurrentTokens float64 `json:"currentTokens"` - // Current number of messages in the conversation - MessagesLength float64 `json:"messagesLength"` - // Token count from system message(s) - SystemTokens *float64 `json:"systemTokens,omitempty"` - // Token count from non-system messages (user, assistant, tool) - ConversationTokens *float64 `json:"conversationTokens,omitempty"` - // Token count from tool definitions - ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` - // Whether this is the first usage_info event emitted in this session - IsInitial *bool `json:"isInitial,omitempty"` +// External tool invocation request for client-side tool execution +type ExternalToolRequestedData struct { + // Arguments to pass to the external tool + Arguments any `json:"arguments,omitempty"` + // Unique identifier for this request; used to respond via session.respondToExternalTool() + RequestID string `json:"requestId"` + // Session ID that this external tool request belongs to + SessionID string `json:"sessionId"` + // Tool call ID assigned to this external tool invocation + ToolCallID string `json:"toolCallId"` + // Name of the external tool to invoke + ToolName string `json:"toolName"` + // W3C Trace Context traceparent header for the execute_tool span + Traceparent *string `json:"traceparent,omitempty"` + // W3C Trace Context tracestate header for the execute_tool span + Tracestate *string `json:"tracestate,omitempty"` } -func (*SessionUsageInfoData) sessionEventData() {} +func (*ExternalToolRequestedData) sessionEventData() {} -// Context window breakdown at the start of LLM-powered conversation compaction -type SessionCompactionStartData struct { - // Token count from system message(s) at compaction start - SystemTokens *float64 `json:"systemTokens,omitempty"` - // Token count from non-system messages (user, assistant, tool) at compaction start - ConversationTokens *float64 `json:"conversationTokens,omitempty"` - // Token count from tool definitions at compaction start - ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +// Hook invocation completion details including output, success status, and error information +type HookEndData struct { + // Error details when the hook failed + Error *HookEndError `json:"error,omitempty"` + // Identifier matching the corresponding hook.start event + HookInvocationID string `json:"hookInvocationId"` + // Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + HookType string `json:"hookType"` + // Output data produced by the hook + Output any `json:"output,omitempty"` + // Whether the hook completed successfully + Success bool `json:"success"` } -func (*SessionCompactionStartData) sessionEventData() {} +func (*HookEndData) sessionEventData() {} -// Conversation compaction results including success status, metrics, and optional error details -type SessionCompactionCompleteData struct { - // Whether compaction completed successfully - Success bool `json:"success"` - // Error message if compaction failed - Error *string `json:"error,omitempty"` - // Total tokens in conversation before compaction - PreCompactionTokens *float64 `json:"preCompactionTokens,omitempty"` - // Total tokens in conversation after compaction - PostCompactionTokens *float64 `json:"postCompactionTokens,omitempty"` - // Number of messages before compaction - PreCompactionMessagesLength *float64 `json:"preCompactionMessagesLength,omitempty"` - // Number of messages removed during compaction - MessagesRemoved *float64 `json:"messagesRemoved,omitempty"` - // Number of tokens removed during compaction - TokensRemoved *float64 `json:"tokensRemoved,omitempty"` - // LLM-generated summary of the compacted conversation history - SummaryContent *string `json:"summaryContent,omitempty"` - // Checkpoint snapshot number created for recovery - CheckpointNumber *float64 `json:"checkpointNumber,omitempty"` - // File path where the checkpoint was stored - CheckpointPath *string `json:"checkpointPath,omitempty"` - // Token usage breakdown for the compaction LLM call - CompactionTokensUsed *CompactionCompleteCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` - // GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - RequestID *string `json:"requestId,omitempty"` - // Token count from system message(s) after compaction - SystemTokens *float64 `json:"systemTokens,omitempty"` - // Token count from non-system messages (user, assistant, tool) after compaction - ConversationTokens *float64 `json:"conversationTokens,omitempty"` - // Token count from tool definitions after compaction - ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +// Hook invocation start details including type and input data +type HookStartData struct { + // Unique identifier for this hook invocation + HookInvocationID string `json:"hookInvocationId"` + // Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + HookType string `json:"hookType"` + // Input data passed to the hook + Input any `json:"input,omitempty"` } -func (*SessionCompactionCompleteData) sessionEventData() {} +func (*HookStartData) sessionEventData() {} -// Task completion notification with summary from the agent -type SessionTaskCompleteData struct { - // Summary of the completed task, provided by the agent - Summary *string `json:"summary,omitempty"` - // Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - Success *bool `json:"success,omitempty"` +// Informational message for timeline display with categorization +type SessionInfoData struct { + // Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") + InfoType string `json:"infoType"` + // Human-readable informational message for display in the timeline + Message string `json:"message"` + // Optional URL associated with this message that the user can open in a browser + URL *string `json:"url,omitempty"` } -func (*SessionTaskCompleteData) sessionEventData() {} - -// UserMessageData holds the payload for user.message events. -type UserMessageData struct { - // The user's message text as displayed in the timeline - Content string `json:"content"` - // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching - TransformedContent *string `json:"transformedContent,omitempty"` - // Files, selections, or GitHub references attached to the message - Attachments []UserMessageAttachment `json:"attachments,omitempty"` - // Normalized document MIME types that were sent natively instead of through tagged_files XML - SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,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"` - // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) - Source *string `json:"source,omitempty"` - // The agent mode that was active when this message was sent - AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` - // CAPI interaction ID for correlating this user message with its turn - InteractionID *string `json:"interactionId,omitempty"` -} - -func (*UserMessageData) sessionEventData() {} - -// Empty payload; the event signals that the pending message queue has changed -type PendingMessagesModifiedData struct { -} - -func (*PendingMessagesModifiedData) sessionEventData() {} - -// Turn initialization metadata including identifier and interaction tracking -type AssistantTurnStartData struct { - // Identifier for this turn within the agentic loop, typically a stringified turn number - TurnID string `json:"turnId"` - // CAPI interaction ID for correlating this turn with upstream telemetry - InteractionID *string `json:"interactionId,omitempty"` -} - -func (*AssistantTurnStartData) sessionEventData() {} - -// Agent intent description for current activity or plan -type AssistantIntentData struct { - // Short description of what the agent is currently doing or planning to do - Intent string `json:"intent"` -} - -func (*AssistantIntentData) sessionEventData() {} - -// Assistant reasoning content for timeline display with complete thinking text -type AssistantReasoningData struct { - // Unique identifier for this reasoning block - ReasoningID string `json:"reasoningId"` - // The complete extended thinking text from the model - Content string `json:"content"` -} - -func (*AssistantReasoningData) sessionEventData() {} - -// Streaming reasoning delta for incremental extended thinking updates -type AssistantReasoningDeltaData struct { - // Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event - ReasoningID string `json:"reasoningId"` - // Incremental text chunk to append to the reasoning content - DeltaContent string `json:"deltaContent"` -} - -func (*AssistantReasoningDeltaData) sessionEventData() {} - -// Streaming response progress with cumulative byte count -type AssistantStreamingDeltaData struct { - // Cumulative total bytes received from the streaming response so far - TotalResponseSizeBytes float64 `json:"totalResponseSizeBytes"` -} - -func (*AssistantStreamingDeltaData) sessionEventData() {} - -// Assistant response containing text content, optional tool requests, and interaction metadata -type AssistantMessageData struct { - // Unique identifier for this assistant message - MessageID string `json:"messageId"` - // The assistant's text response content - Content string `json:"content"` - // Tool invocations requested by the assistant in this message - ToolRequests []AssistantMessageToolRequest `json:"toolRequests,omitempty"` - // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. - ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` - // Readable reasoning text from the model's extended thinking - ReasoningText *string `json:"reasoningText,omitempty"` - // Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. - EncryptedContent *string `json:"encryptedContent,omitempty"` - // Generation phase for phased-output models (e.g., thinking vs. response phases) - Phase *string `json:"phase,omitempty"` - // Actual output token count from the API response (completion_tokens), used for accurate token accounting - OutputTokens *float64 `json:"outputTokens,omitempty"` - // CAPI interaction ID for correlating this message with upstream telemetry - InteractionID *string `json:"interactionId,omitempty"` - // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - RequestID *string `json:"requestId,omitempty"` - // Tool call ID of the parent tool invocation when this event originates from a sub-agent - // Deprecated: ParentToolCallID is deprecated. - ParentToolCallID *string `json:"parentToolCallId,omitempty"` -} - -func (*AssistantMessageData) sessionEventData() {} - -// Streaming assistant message delta for incremental response updates -type AssistantMessageDeltaData struct { - // Message ID this delta belongs to, matching the corresponding assistant.message event - MessageID string `json:"messageId"` - // Incremental text chunk to append to the message content - DeltaContent string `json:"deltaContent"` - // Tool call ID of the parent tool invocation when this event originates from a sub-agent - // Deprecated: ParentToolCallID is deprecated. - ParentToolCallID *string `json:"parentToolCallId,omitempty"` -} - -func (*AssistantMessageDeltaData) sessionEventData() {} - -// Turn completion metadata including the turn identifier -type AssistantTurnEndData struct { - // Identifier of the turn that has ended, matching the corresponding assistant.turn_start event - TurnID string `json:"turnId"` -} - -func (*AssistantTurnEndData) sessionEventData() {} +func (*SessionInfoData) sessionEventData() {} // LLM API call usage metrics including tokens, costs, quotas, and billing information type AssistantUsageData struct { - // Model identifier used for this API call - Model string `json:"model"` - // Number of input tokens consumed - InputTokens *float64 `json:"inputTokens,omitempty"` - // Number of output tokens produced - OutputTokens *float64 `json:"outputTokens,omitempty"` + // Completion ID from the model provider (e.g., chatcmpl-abc123) + APICallID *string `json:"apiCallId,omitempty"` // Number of tokens read from prompt cache CacheReadTokens *float64 `json:"cacheReadTokens,omitempty"` // Number of tokens written to prompt cache CacheWriteTokens *float64 `json:"cacheWriteTokens,omitempty"` - // Number of output tokens used for reasoning (e.g., chain-of-thought) - ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` + // Per-request cost and usage data from the CAPI copilot_usage response field + CopilotUsage *AssistantUsageCopilotUsage `json:"copilotUsage,omitempty"` // Model multiplier cost for billing purposes Cost *float64 `json:"cost,omitempty"` // Duration of the API call in milliseconds Duration *float64 `json:"duration,omitempty"` - // Time to first token in milliseconds. Only available for streaming requests - TtftMs *float64 `json:"ttftMs,omitempty"` - // Average inter-token latency in milliseconds. Only available for streaming requests - InterTokenLatencyMs *float64 `json:"interTokenLatencyMs,omitempty"` // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls Initiator *string `json:"initiator,omitempty"` - // Completion ID from the model provider (e.g., chatcmpl-abc123) - APICallID *string `json:"apiCallId,omitempty"` - // GitHub request tracing ID (x-github-request-id header) for server-side log correlation - ProviderCallID *string `json:"providerCallId,omitempty"` + // Number of input tokens consumed + InputTokens *float64 `json:"inputTokens,omitempty"` + // Average inter-token latency in milliseconds. Only available for streaming requests + InterTokenLatencyMs *float64 `json:"interTokenLatencyMs,omitempty"` + // Model identifier used for this API call + Model string `json:"model"` + // Number of output tokens produced + OutputTokens *float64 `json:"outputTokens,omitempty"` // Parent tool call ID when this usage originates from a sub-agent // Deprecated: ParentToolCallID is deprecated. ParentToolCallID *string `json:"parentToolCallId,omitempty"` + // GitHub request tracing ID (x-github-request-id header) for server-side log correlation + ProviderCallID *string `json:"providerCallId,omitempty"` // Per-quota resource usage snapshots, keyed by quota identifier QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitempty"` - // Per-request cost and usage data from the CAPI copilot_usage response field - CopilotUsage *AssistantUsageCopilotUsage `json:"copilotUsage,omitempty"` // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Number of output tokens used for reasoning (e.g., chain-of-thought) + ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` + // Time to first token in milliseconds. Only available for streaming requests + TtftMs *float64 `json:"ttftMs,omitempty"` } func (*AssistantUsageData) sessionEventData() {} -// Turn abort information including the reason for termination -type AbortData struct { - // Reason the current turn was aborted (e.g., "user initiated") - Reason string `json:"reason"` +// MCP OAuth request completion notification +type McpOauthCompletedData struct { + // Request ID of the resolved OAuth request + RequestID string `json:"requestId"` } -func (*AbortData) sessionEventData() {} +func (*McpOauthCompletedData) sessionEventData() {} -// User-initiated tool invocation request with tool name and arguments -type ToolUserRequestedData struct { - // Unique identifier for this tool call - ToolCallID string `json:"toolCallId"` - // Name of the tool the user wants to invoke - ToolName string `json:"toolName"` - // Arguments for the tool invocation - Arguments any `json:"arguments,omitempty"` +// Model change details including previous and new model identifiers +type SessionModelChangeData struct { + // Newly selected model identifier + NewModel string `json:"newModel"` + // Model that was previously selected, if any + PreviousModel *string `json:"previousModel,omitempty"` + // Reasoning effort level before the model change, if applicable + PreviousReasoningEffort *string `json:"previousReasoningEffort,omitempty"` + // Reasoning effort level after the model change, if applicable + ReasoningEffort *string `json:"reasoningEffort,omitempty"` } -func (*ToolUserRequestedData) sessionEventData() {} +func (*SessionModelChangeData) sessionEventData() {} -// Tool execution startup details including MCP server information when applicable -type ToolExecutionStartData struct { - // Unique identifier for this tool call - ToolCallID string `json:"toolCallId"` - // Name of the tool being executed - ToolName string `json:"toolName"` - // Arguments passed to the tool - Arguments any `json:"arguments,omitempty"` - // Name of the MCP server hosting this tool, when the tool is an MCP tool - McpServerName *string `json:"mcpServerName,omitempty"` - // Original tool name on the MCP server, when the tool is an MCP tool - McpToolName *string `json:"mcpToolName,omitempty"` - // Tool call ID of the parent tool invocation when this event originates from a sub-agent - // Deprecated: ParentToolCallID is deprecated. - ParentToolCallID *string `json:"parentToolCallId,omitempty"` +// Notifies Mission Control that the session's remote steering capability has changed +type SessionRemoteSteerableChangedData struct { + // Whether this session now supports remote steering via Mission Control + RemoteSteerable bool `json:"remoteSteerable"` } -func (*ToolExecutionStartData) sessionEventData() {} +func (*SessionRemoteSteerableChangedData) sessionEventData() {} -// Streaming tool execution output for incremental result display -type ToolExecutionPartialResultData struct { - // Tool call ID this partial result belongs to - ToolCallID string `json:"toolCallId"` - // Incremental output chunk from the running tool - PartialOutput string `json:"partialOutput"` +// OAuth authentication request for an MCP server +type McpOauthRequiredData struct { + // Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() + RequestID string `json:"requestId"` + // Display name of the MCP server that requires OAuth + ServerName string `json:"serverName"` + // URL of the MCP server that requires OAuth + ServerURL string `json:"serverUrl"` + // Static OAuth client configuration, if the server specifies one + StaticClientConfig *McpOauthRequiredStaticClientConfig `json:"staticClientConfig,omitempty"` } -func (*ToolExecutionPartialResultData) sessionEventData() {} +func (*McpOauthRequiredData) sessionEventData() {} -// Tool execution progress notification with status message -type ToolExecutionProgressData struct { - // Tool call ID this progress notification belongs to - ToolCallID string `json:"toolCallId"` - // Human-readable progress status message (e.g., from an MCP server) - ProgressMessage string `json:"progressMessage"` +// Payload indicating the session is idle with no background agents in flight +type SessionIdleData struct { + // True when the preceding agentic loop was cancelled via abort signal + Aborted *bool `json:"aborted,omitempty"` } -func (*ToolExecutionProgressData) sessionEventData() {} +func (*SessionIdleData) sessionEventData() {} -// Tool execution completion results including success status, detailed output, and error information -type ToolExecutionCompleteData struct { - // Unique identifier for the completed tool call - ToolCallID string `json:"toolCallId"` - // Whether the tool execution completed successfully - Success bool `json:"success"` - // Model identifier that generated this tool call - Model *string `json:"model,omitempty"` - // CAPI interaction ID for correlating this tool execution with upstream telemetry - InteractionID *string `json:"interactionId,omitempty"` - // Whether this tool call was explicitly requested by the user rather than the assistant - IsUserRequested *bool `json:"isUserRequested,omitempty"` - // Tool execution result on success - Result *ToolExecutionCompleteResult `json:"result,omitempty"` - // Error details when the tool execution failed - Error *ToolExecutionCompleteError `json:"error,omitempty"` - // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` - // Tool call ID of the parent tool invocation when this event originates from a sub-agent - // Deprecated: ParentToolCallID is deprecated. - ParentToolCallID *string `json:"parentToolCallId,omitempty"` +// Permission request completion notification signaling UI dismissal +type PermissionCompletedData struct { + // Request ID of the resolved permission request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // The result of the permission request + Result PermissionCompletedResult `json:"result"` } -func (*ToolExecutionCompleteData) sessionEventData() {} +func (*PermissionCompletedData) sessionEventData() {} -// Skill invocation details including content, allowed tools, and plugin metadata -type SkillInvokedData struct { - // Name of the invoked skill - Name string `json:"name"` - // File path to the SKILL.md definition - Path string `json:"path"` - // Full content of the skill file, injected into the conversation for the model - Content string `json:"content"` - // Tool names that should be auto-approved when this skill is active - AllowedTools []string `json:"allowedTools,omitempty"` - // Name of the plugin this skill originated from, when applicable - PluginName *string `json:"pluginName,omitempty"` - // Version of the plugin this skill originated from, when applicable - PluginVersion *string `json:"pluginVersion,omitempty"` - // Description of the skill from its SKILL.md frontmatter - Description *string `json:"description,omitempty"` +// Permission request notification requiring client approval with request details +type PermissionRequestedData struct { + // Details of the permission being requested + PermissionRequest PermissionRequest `json:"permissionRequest"` + // Unique identifier for this permission request; used to respond via session.respondToPermission() + RequestID string `json:"requestId"` + // When true, this permission was already resolved by a permissionRequest hook and requires no client action + ResolvedByHook *bool `json:"resolvedByHook,omitempty"` } -func (*SkillInvokedData) sessionEventData() {} +func (*PermissionRequestedData) sessionEventData() {} -// Sub-agent startup details including parent tool call and agent information -type SubagentStartedData struct { - // Tool call ID of the parent tool invocation that spawned this sub-agent - ToolCallID string `json:"toolCallId"` - // Internal name of the sub-agent - AgentName string `json:"agentName"` - // Human-readable display name of the sub-agent - AgentDisplayName string `json:"agentDisplayName"` - // Description of what the sub-agent does - AgentDescription string `json:"agentDescription"` +// Plan approval request with plan content and available user actions +type ExitPlanModeRequestedData struct { + // Available actions the user can take (e.g., approve, edit, reject) + Actions []string `json:"actions"` + // Full content of the plan file + PlanContent string `json:"planContent"` + // The recommended action for the user to take + RecommendedAction string `json:"recommendedAction"` + // Unique identifier for this request; used to respond via session.respondToExitPlanMode() + RequestID string `json:"requestId"` + // Summary of the plan that was created + Summary string `json:"summary"` } -func (*SubagentStartedData) sessionEventData() {} +func (*ExitPlanModeRequestedData) sessionEventData() {} -// Sub-agent completion details for successful execution -type SubagentCompletedData struct { - // Tool call ID of the parent tool invocation that spawned this sub-agent - ToolCallID string `json:"toolCallId"` - // Internal name of the sub-agent - AgentName string `json:"agentName"` - // Human-readable display name of the sub-agent - AgentDisplayName string `json:"agentDisplayName"` - // Model used by the sub-agent - Model *string `json:"model,omitempty"` - // Total number of tool calls made by the sub-agent - TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` - // Total tokens (input + output) consumed by the sub-agent - TotalTokens *float64 `json:"totalTokens,omitempty"` - // Wall-clock duration of the sub-agent execution in milliseconds - DurationMs *float64 `json:"durationMs,omitempty"` +// Plan file operation details indicating what changed +type SessionPlanChangedData struct { + // The type of operation performed on the plan file + Operation PlanChangedOperation `json:"operation"` } -func (*SubagentCompletedData) sessionEventData() {} +func (*SessionPlanChangedData) sessionEventData() {} -// Sub-agent failure details including error message and agent information -type SubagentFailedData struct { - // Tool call ID of the parent tool invocation that spawned this sub-agent - ToolCallID string `json:"toolCallId"` - // Internal name of the sub-agent - AgentName string `json:"agentName"` - // Human-readable display name of the sub-agent - AgentDisplayName string `json:"agentDisplayName"` - // Error message describing why the sub-agent failed - Error string `json:"error"` - // Model used by the sub-agent (if any model calls succeeded before failure) - Model *string `json:"model,omitempty"` - // Total number of tool calls made before the sub-agent failed - TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` - // Total tokens (input + output) consumed before the sub-agent failed - TotalTokens *float64 `json:"totalTokens,omitempty"` - // Wall-clock duration of the sub-agent execution in milliseconds - DurationMs *float64 `json:"durationMs,omitempty"` +// Plan mode exit completion with the user's approval decision and optional feedback +type ExitPlanModeCompletedData struct { + // Whether the plan was approved by the user + Approved *bool `json:"approved,omitempty"` + // Whether edits should be auto-approved without confirmation + AutoApproveEdits *bool `json:"autoApproveEdits,omitempty"` + // Free-form feedback from the user if they requested changes to the plan + Feedback *string `json:"feedback,omitempty"` + // Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') + SelectedAction *string `json:"selectedAction,omitempty"` } -func (*SubagentFailedData) sessionEventData() {} +func (*ExitPlanModeCompletedData) sessionEventData() {} -// Custom agent selection details including name and available tools -type SubagentSelectedData struct { - // Internal name of the selected custom agent - AgentName string `json:"agentName"` - // Human-readable display name of the selected custom agent - AgentDisplayName string `json:"agentDisplayName"` - // List of tool names available to this agent, or null for all tools - Tools []string `json:"tools"` +// Queued command completion notification signaling UI dismissal +type CommandCompletedData struct { + // Request ID of the resolved command request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` } -func (*SubagentSelectedData) sessionEventData() {} +func (*CommandCompletedData) sessionEventData() {} -// Empty payload; the event signals that the custom agent was deselected, returning to the default agent -type SubagentDeselectedData struct { +// Queued slash command dispatch request for client execution +type CommandQueuedData struct { + // The slash command text to be executed (e.g., /help, /clear) + Command string `json:"command"` + // Unique identifier for this request; used to respond via session.respondToQueuedCommand() + RequestID string `json:"requestId"` } -func (*SubagentDeselectedData) sessionEventData() {} +func (*CommandQueuedData) sessionEventData() {} -// Hook invocation start details including type and input data -type HookStartData struct { - // Unique identifier for this hook invocation - HookInvocationID string `json:"hookInvocationId"` - // Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - HookType string `json:"hookType"` - // Input data passed to the hook - Input any `json:"input,omitempty"` +// Registered command dispatch request routed to the owning client +type CommandExecuteData struct { + // Raw argument string after the command name + Args string `json:"args"` + // The full command text (e.g., /deploy production) + Command string `json:"command"` + // Command name without leading / + CommandName string `json:"commandName"` + // Unique identifier; used to respond via session.commands.handlePendingCommand() + RequestID string `json:"requestId"` } -func (*HookStartData) sessionEventData() {} +func (*CommandExecuteData) sessionEventData() {} -// Hook invocation completion details including output, success status, and error information -type HookEndData struct { - // Identifier matching the corresponding hook.start event - HookInvocationID string `json:"hookInvocationId"` - // Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - HookType string `json:"hookType"` - // Output data produced by the hook - Output any `json:"output,omitempty"` - // Whether the hook completed successfully - Success bool `json:"success"` - // Error details when the hook failed - Error *HookEndError `json:"error,omitempty"` +// SDK command registration change notification +type CommandsChangedData struct { + // Current list of registered SDK commands + Commands []CommandsChangedCommand `json:"commands"` } -func (*HookEndData) sessionEventData() {} +func (*CommandsChangedData) sessionEventData() {} -// System/developer instruction content with role and optional template metadata -type SystemMessageData struct { - // The system or developer prompt text sent as model input - Content string `json:"content"` - // Message role: "system" for system prompts, "developer" for developer-injected instructions - Role SystemMessageRole `json:"role"` - // Optional name identifier for the message source - Name *string `json:"name,omitempty"` - // Metadata about the prompt template and its construction - Metadata *SystemMessageMetadata `json:"metadata,omitempty"` +// Sampling request completion notification signaling UI dismissal +type SamplingCompletedData struct { + // Request ID of the resolved sampling request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` } -func (*SystemMessageData) sessionEventData() {} +func (*SamplingCompletedData) sessionEventData() {} -// System-generated notification for runtime events like background task completion -type SystemNotificationData struct { - // The notification text, typically wrapped in XML tags - Content string `json:"content"` - // Structured metadata identifying what triggered this notification - Kind SystemNotification `json:"kind"` +// Sampling request from an MCP server; contains the server name and a requestId for correlation +type SamplingRequestedData struct { + // The JSON-RPC request ID from the MCP protocol + McpRequestID any `json:"mcpRequestId"` + // Unique identifier for this sampling request; used to respond via session.respondToSampling() + RequestID string `json:"requestId"` + // Name of the MCP server that initiated the sampling request + ServerName string `json:"serverName"` } -func (*SystemNotificationData) sessionEventData() {} +func (*SamplingRequestedData) sessionEventData() {} -// Permission request notification requiring client approval with request details -type PermissionRequestedData struct { - // Unique identifier for this permission request; used to respond via session.respondToPermission() - RequestID string `json:"requestId"` - // Details of the permission being requested - PermissionRequest PermissionRequest `json:"permissionRequest"` - // When true, this permission was already resolved by a permissionRequest hook and requires no client action - ResolvedByHook *bool `json:"resolvedByHook,omitempty"` +// Session capability change notification +type CapabilitiesChangedData struct { + // UI capability changes + UI *CapabilitiesChangedUI `json:"ui,omitempty"` } -func (*PermissionRequestedData) sessionEventData() {} +func (*CapabilitiesChangedData) sessionEventData() {} -// Permission request completion notification signaling UI dismissal -type PermissionCompletedData struct { - // Request ID of the resolved permission request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` - // The result of the permission request - Result PermissionCompletedResult `json:"result"` +// Session handoff metadata including source, context, and repository information +type SessionHandoffData struct { + // Additional context information for the handoff + Context *string `json:"context,omitempty"` + // ISO 8601 timestamp when the handoff occurred + HandoffTime time.Time `json:"handoffTime"` + // GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) + Host *string `json:"host,omitempty"` + // Session ID of the remote session being handed off + RemoteSessionID *string `json:"remoteSessionId,omitempty"` + // Repository context for the handed-off session + Repository *HandoffRepository `json:"repository,omitempty"` + // Origin type of the session being handed off + SourceType HandoffSourceType `json:"sourceType"` + // Summary of the work done in the source session + Summary *string `json:"summary,omitempty"` } -func (*PermissionCompletedData) sessionEventData() {} +func (*SessionHandoffData) sessionEventData() {} -// User input request notification with question and optional predefined choices -type UserInputRequestedData struct { - // Unique identifier for this input request; used to respond via session.respondToUserInput() - RequestID string `json:"requestId"` - // The question or prompt to present to the user - Question string `json:"question"` - // Predefined choices for the user to select from, if applicable - Choices []string `json:"choices,omitempty"` - // Whether the user can provide a free-form text response in addition to predefined choices - AllowFreeform *bool `json:"allowFreeform,omitempty"` - // The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses - ToolCallID *string `json:"toolCallId,omitempty"` -} - -func (*UserInputRequestedData) sessionEventData() {} - -// User input request completion with the user's response -type UserInputCompletedData struct { - // Request ID of the resolved user input request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` - // The user's answer to the input request - Answer *string `json:"answer,omitempty"` - // Whether the answer was typed as free-form text rather than selected from choices - WasFreeform *bool `json:"wasFreeform,omitempty"` +// Session initialization metadata including context and configuration +type SessionStartData struct { + // Whether the session was already in use by another client at start time + AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Working directory and git context at session start + Context *WorkingDirectoryContext `json:"context,omitempty"` + // Version string of the Copilot application + CopilotVersion string `json:"copilotVersion"` + // Identifier of the software producing the events (e.g., "copilot-agent") + Producer string `json:"producer"` + // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Whether this session supports remote steering via Mission Control + RemoteSteerable *bool `json:"remoteSteerable,omitempty"` + // Model selected at session creation time, if any + SelectedModel *string `json:"selectedModel,omitempty"` + // Unique identifier for the session + SessionID string `json:"sessionId"` + // ISO 8601 timestamp when the session was created + StartTime time.Time `json:"startTime"` + // Schema version number for the session event format + Version float64 `json:"version"` } -func (*UserInputCompletedData) sessionEventData() {} +func (*SessionStartData) sessionEventData() {} -// Elicitation request; may be form-based (structured input) or URL-based (browser redirect) -type ElicitationRequestedData struct { - // Unique identifier for this elicitation request; used to respond via session.respondToElicitation() - RequestID string `json:"requestId"` - // Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs - ToolCallID *string `json:"toolCallId,omitempty"` - // The source that initiated the request (MCP server name, or absent for agent-initiated) - ElicitationSource *string `json:"elicitationSource,omitempty"` - // Message describing what information is needed from the user - Message string `json:"message"` - // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. - Mode *ElicitationRequestedMode `json:"mode,omitempty"` - // JSON Schema describing the form fields to present to the user (form mode only) - RequestedSchema *ElicitationRequestedSchema `json:"requestedSchema,omitempty"` - // URL to open in the user's browser (url mode only) - URL *string `json:"url,omitempty"` +// Session resume metadata including current context and event count +type SessionResumeData struct { + // Whether the session was already in use by another client at resume time + AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Updated working directory and git context at resume time + Context *WorkingDirectoryContext `json:"context,omitempty"` + // Total number of persisted events in the session at the time of resume + EventCount float64 `json:"eventCount"` + // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Whether this session supports remote steering via Mission Control + RemoteSteerable *bool `json:"remoteSteerable,omitempty"` + // ISO 8601 timestamp when the session was resumed + ResumeTime time.Time `json:"resumeTime"` + // Model currently selected at resume time + SelectedModel *string `json:"selectedModel,omitempty"` } -func (*ElicitationRequestedData) sessionEventData() {} +func (*SessionResumeData) sessionEventData() {} -// Elicitation request completion with the user's response -type ElicitationCompletedData struct { - // Request ID of the resolved elicitation request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` - // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) - Action *ElicitationCompletedAction `json:"action,omitempty"` - // The submitted form data when action is 'accept'; keys match the requested schema fields - Content map[string]any `json:"content,omitempty"` +// Session rewind details including target event and count of removed events +type SessionSnapshotRewindData struct { + // Number of events that were removed by the rewind + EventsRemoved float64 `json:"eventsRemoved"` + // Event ID that was rewound to; this event and all after it were removed + UpToEventID string `json:"upToEventId"` } -func (*ElicitationCompletedData) sessionEventData() {} +func (*SessionSnapshotRewindData) sessionEventData() {} -// Sampling request from an MCP server; contains the server name and a requestId for correlation -type SamplingRequestedData struct { - // Unique identifier for this sampling request; used to respond via session.respondToSampling() - RequestID string `json:"requestId"` - // Name of the MCP server that initiated the sampling request - ServerName string `json:"serverName"` - // The JSON-RPC request ID from the MCP protocol - McpRequestID any `json:"mcpRequestId"` +// Session termination metrics including usage statistics, code changes, and shutdown reason +type SessionShutdownData struct { + // Aggregate code change metrics for the session + CodeChanges ShutdownCodeChanges `json:"codeChanges"` + // Non-system message token count at shutdown + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Model that was selected at the time of shutdown + CurrentModel *string `json:"currentModel,omitempty"` + // Total tokens in context window at shutdown + CurrentTokens *float64 `json:"currentTokens,omitempty"` + // Error description when shutdownType is "error" + ErrorReason *string `json:"errorReason,omitempty"` + // Per-model usage breakdown, keyed by model identifier + ModelMetrics map[string]ShutdownModelMetric `json:"modelMetrics"` + // Unix timestamp (milliseconds) when the session started + SessionStartTime float64 `json:"sessionStartTime"` + // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") + ShutdownType ShutdownType `json:"shutdownType"` + // System message token count at shutdown + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Tool definitions token count at shutdown + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` + // Cumulative time spent in API calls during the session, in milliseconds + TotalAPIDurationMs float64 `json:"totalApiDurationMs"` + // Total number of premium API requests used during the session + TotalPremiumRequests float64 `json:"totalPremiumRequests"` } -func (*SamplingRequestedData) sessionEventData() {} +func (*SessionShutdownData) sessionEventData() {} -// Sampling request completion notification signaling UI dismissal -type SamplingCompletedData struct { - // Request ID of the resolved sampling request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` +// Session title change payload containing the new display title +type SessionTitleChangedData struct { + // The new display title for the session + Title string `json:"title"` } -func (*SamplingCompletedData) sessionEventData() {} +func (*SessionTitleChangedData) sessionEventData() {} -// OAuth authentication request for an MCP server -type McpOauthRequiredData struct { - // Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() - RequestID string `json:"requestId"` - // Display name of the MCP server that requires OAuth - ServerName string `json:"serverName"` - // URL of the MCP server that requires OAuth - ServerURL string `json:"serverUrl"` - // Static OAuth client configuration, if the server specifies one - StaticClientConfig *McpOauthRequiredStaticClientConfig `json:"staticClientConfig,omitempty"` +// SessionBackgroundTasksChangedData holds the payload for session.background_tasks_changed events. +type SessionBackgroundTasksChangedData struct { } -func (*McpOauthRequiredData) sessionEventData() {} +func (*SessionBackgroundTasksChangedData) sessionEventData() {} -// MCP OAuth request completion notification -type McpOauthCompletedData struct { - // Request ID of the resolved OAuth request - RequestID string `json:"requestId"` +// SessionCustomAgentsUpdatedData holds the payload for session.custom_agents_updated events. +type SessionCustomAgentsUpdatedData struct { + // Array of loaded custom agent metadata + Agents []CustomAgentsUpdatedAgent `json:"agents"` + // Fatal errors from agent loading + Errors []string `json:"errors"` + // Non-fatal warnings from agent loading + Warnings []string `json:"warnings"` } -func (*McpOauthCompletedData) sessionEventData() {} +func (*SessionCustomAgentsUpdatedData) sessionEventData() {} -// External tool invocation request for client-side tool execution -type ExternalToolRequestedData struct { - // Unique identifier for this request; used to respond via session.respondToExternalTool() - RequestID string `json:"requestId"` - // Session ID that this external tool request belongs to - SessionID string `json:"sessionId"` - // Tool call ID assigned to this external tool invocation - ToolCallID string `json:"toolCallId"` - // Name of the external tool to invoke - ToolName string `json:"toolName"` - // Arguments to pass to the external tool - Arguments any `json:"arguments,omitempty"` - // W3C Trace Context traceparent header for the execute_tool span - Traceparent *string `json:"traceparent,omitempty"` - // W3C Trace Context tracestate header for the execute_tool span - Tracestate *string `json:"tracestate,omitempty"` +// SessionExtensionsLoadedData holds the payload for session.extensions_loaded events. +type SessionExtensionsLoadedData struct { + // Array of discovered extensions and their status + Extensions []ExtensionsLoadedExtension `json:"extensions"` } -func (*ExternalToolRequestedData) sessionEventData() {} +func (*SessionExtensionsLoadedData) sessionEventData() {} -// External tool completion notification signaling UI dismissal -type ExternalToolCompletedData struct { - // Request ID of the resolved external tool request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` +// SessionMcpServerStatusChangedData holds the payload for session.mcp_server_status_changed events. +type SessionMcpServerStatusChangedData struct { + // Name of the MCP server whose status changed + ServerName string `json:"serverName"` + // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status McpServerStatusChangedStatus `json:"status"` } -func (*ExternalToolCompletedData) sessionEventData() {} +func (*SessionMcpServerStatusChangedData) sessionEventData() {} -// Queued slash command dispatch request for client execution -type CommandQueuedData struct { - // Unique identifier for this request; used to respond via session.respondToQueuedCommand() - RequestID string `json:"requestId"` - // The slash command text to be executed (e.g., /help, /clear) - Command string `json:"command"` +// SessionMcpServersLoadedData holds the payload for session.mcp_servers_loaded events. +type SessionMcpServersLoadedData struct { + // Array of MCP server status summaries + Servers []McpServersLoadedServer `json:"servers"` } -func (*CommandQueuedData) sessionEventData() {} +func (*SessionMcpServersLoadedData) sessionEventData() {} -// Registered command dispatch request routed to the owning client -type CommandExecuteData struct { - // Unique identifier; used to respond via session.commands.handlePendingCommand() - RequestID string `json:"requestId"` - // The full command text (e.g., /deploy production) - Command string `json:"command"` - // Command name without leading / - CommandName string `json:"commandName"` - // Raw argument string after the command name - Args string `json:"args"` +// SessionSkillsLoadedData holds the payload for session.skills_loaded events. +type SessionSkillsLoadedData struct { + // Array of resolved skill metadata + Skills []SkillsLoadedSkill `json:"skills"` } -func (*CommandExecuteData) sessionEventData() {} +func (*SessionSkillsLoadedData) sessionEventData() {} -// Queued command completion notification signaling UI dismissal -type CommandCompletedData struct { - // Request ID of the resolved command request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` +// SessionToolsUpdatedData holds the payload for session.tools_updated events. +type SessionToolsUpdatedData struct { + Model string `json:"model"` } -func (*CommandCompletedData) sessionEventData() {} +func (*SessionToolsUpdatedData) sessionEventData() {} -// SDK command registration change notification -type CommandsChangedData struct { - // Current list of registered SDK commands - Commands []CommandsChangedCommand `json:"commands"` +// Skill invocation details including content, allowed tools, and plugin metadata +type SkillInvokedData struct { + // Tool names that should be auto-approved when this skill is active + AllowedTools []string `json:"allowedTools,omitempty"` + // Full content of the skill file, injected into the conversation for the model + Content string `json:"content"` + // Description of the skill from its SKILL.md frontmatter + Description *string `json:"description,omitempty"` + // Name of the invoked skill + Name string `json:"name"` + // File path to the SKILL.md definition + Path string `json:"path"` + // Name of the plugin this skill originated from, when applicable + PluginName *string `json:"pluginName,omitempty"` + // Version of the plugin this skill originated from, when applicable + PluginVersion *string `json:"pluginVersion,omitempty"` } -func (*CommandsChangedData) sessionEventData() {} +func (*SkillInvokedData) sessionEventData() {} -// Session capability change notification -type CapabilitiesChangedData struct { - // UI capability changes - UI *CapabilitiesChangedUI `json:"ui,omitempty"` +// Streaming assistant message delta for incremental response updates +type AssistantMessageDeltaData struct { + // Incremental text chunk to append to the message content + DeltaContent string `json:"deltaContent"` + // Message ID this delta belongs to, matching the corresponding assistant.message event + MessageID string `json:"messageId"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. + ParentToolCallID *string `json:"parentToolCallId,omitempty"` } -func (*CapabilitiesChangedData) sessionEventData() {} +func (*AssistantMessageDeltaData) sessionEventData() {} -// Plan approval request with plan content and available user actions -type ExitPlanModeRequestedData struct { - // Unique identifier for this request; used to respond via session.respondToExitPlanMode() - RequestID string `json:"requestId"` - // Summary of the plan that was created - Summary string `json:"summary"` - // Full content of the plan file - PlanContent string `json:"planContent"` - // Available actions the user can take (e.g., approve, edit, reject) - Actions []string `json:"actions"` - // The recommended action for the user to take - RecommendedAction string `json:"recommendedAction"` +// Streaming reasoning delta for incremental extended thinking updates +type AssistantReasoningDeltaData struct { + // Incremental text chunk to append to the reasoning content + DeltaContent string `json:"deltaContent"` + // Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event + ReasoningID string `json:"reasoningId"` } -func (*ExitPlanModeRequestedData) sessionEventData() {} +func (*AssistantReasoningDeltaData) sessionEventData() {} -// Plan mode exit completion with the user's approval decision and optional feedback -type ExitPlanModeCompletedData struct { - // Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request - RequestID string `json:"requestId"` - // Whether the plan was approved by the user - Approved *bool `json:"approved,omitempty"` - // Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') - SelectedAction *string `json:"selectedAction,omitempty"` - // Whether edits should be auto-approved without confirmation - AutoApproveEdits *bool `json:"autoApproveEdits,omitempty"` - // Free-form feedback from the user if they requested changes to the plan - Feedback *string `json:"feedback,omitempty"` +// Streaming response progress with cumulative byte count +type AssistantStreamingDeltaData struct { + // Cumulative total bytes received from the streaming response so far + TotalResponseSizeBytes float64 `json:"totalResponseSizeBytes"` } -func (*ExitPlanModeCompletedData) sessionEventData() {} +func (*AssistantStreamingDeltaData) sessionEventData() {} -// SessionToolsUpdatedData holds the payload for session.tools_updated events. -type SessionToolsUpdatedData struct { - Model string `json:"model"` +// Streaming tool execution output for incremental result display +type ToolExecutionPartialResultData struct { + // Incremental output chunk from the running tool + PartialOutput string `json:"partialOutput"` + // Tool call ID this partial result belongs to + ToolCallID string `json:"toolCallId"` } -func (*SessionToolsUpdatedData) sessionEventData() {} +func (*ToolExecutionPartialResultData) sessionEventData() {} -// SessionBackgroundTasksChangedData holds the payload for session.background_tasks_changed events. -type SessionBackgroundTasksChangedData struct { +// Sub-agent completion details for successful execution +type SubagentCompletedData struct { + // Human-readable display name of the sub-agent + AgentDisplayName string `json:"agentDisplayName"` + // Internal name of the sub-agent + AgentName string `json:"agentName"` + // Wall-clock duration of the sub-agent execution in milliseconds + DurationMs *float64 `json:"durationMs,omitempty"` + // Model used by the sub-agent + Model *string `json:"model,omitempty"` + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` + // Total tokens (input + output) consumed by the sub-agent + TotalTokens *float64 `json:"totalTokens,omitempty"` + // Total number of tool calls made by the sub-agent + TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` } -func (*SessionBackgroundTasksChangedData) sessionEventData() {} +func (*SubagentCompletedData) sessionEventData() {} -// SessionSkillsLoadedData holds the payload for session.skills_loaded events. -type SessionSkillsLoadedData struct { - // Array of resolved skill metadata - Skills []SkillsLoadedSkill `json:"skills"` +// Sub-agent failure details including error message and agent information +type SubagentFailedData struct { + // Human-readable display name of the sub-agent + AgentDisplayName string `json:"agentDisplayName"` + // Internal name of the sub-agent + AgentName string `json:"agentName"` + // Wall-clock duration of the sub-agent execution in milliseconds + DurationMs *float64 `json:"durationMs,omitempty"` + // Error message describing why the sub-agent failed + Error string `json:"error"` + // Model used by the sub-agent (if any model calls succeeded before failure) + Model *string `json:"model,omitempty"` + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` + // Total tokens (input + output) consumed before the sub-agent failed + TotalTokens *float64 `json:"totalTokens,omitempty"` + // Total number of tool calls made before the sub-agent failed + TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` } -func (*SessionSkillsLoadedData) sessionEventData() {} +func (*SubagentFailedData) sessionEventData() {} -// SessionCustomAgentsUpdatedData holds the payload for session.custom_agents_updated events. -type SessionCustomAgentsUpdatedData struct { - // Array of loaded custom agent metadata - Agents []CustomAgentsUpdatedAgent `json:"agents"` - // Non-fatal warnings from agent loading - Warnings []string `json:"warnings"` - // Fatal errors from agent loading - Errors []string `json:"errors"` +// Sub-agent startup details including parent tool call and agent information +type SubagentStartedData struct { + // Description of what the sub-agent does + AgentDescription string `json:"agentDescription"` + // Human-readable display name of the sub-agent + AgentDisplayName string `json:"agentDisplayName"` + // Internal name of the sub-agent + AgentName string `json:"agentName"` + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` } -func (*SessionCustomAgentsUpdatedData) sessionEventData() {} +func (*SubagentStartedData) sessionEventData() {} -// SessionMcpServersLoadedData holds the payload for session.mcp_servers_loaded events. -type SessionMcpServersLoadedData struct { - // Array of MCP server status summaries - Servers []McpServersLoadedServer `json:"servers"` +// System-generated notification for runtime events like background task completion +type SystemNotificationData struct { + // The notification text, typically wrapped in XML tags + Content string `json:"content"` + // Structured metadata identifying what triggered this notification + Kind SystemNotification `json:"kind"` } -func (*SessionMcpServersLoadedData) sessionEventData() {} +func (*SystemNotificationData) sessionEventData() {} -// SessionMcpServerStatusChangedData holds the payload for session.mcp_server_status_changed events. -type SessionMcpServerStatusChangedData struct { - // Name of the MCP server whose status changed - ServerName string `json:"serverName"` - // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status McpServerStatusChangedStatus `json:"status"` +// System/developer instruction content with role and optional template metadata +type SystemMessageData struct { + // The system or developer prompt text sent as model input + Content string `json:"content"` + // Metadata about the prompt template and its construction + Metadata *SystemMessageMetadata `json:"metadata,omitempty"` + // Optional name identifier for the message source + Name *string `json:"name,omitempty"` + // Message role: "system" for system prompts, "developer" for developer-injected instructions + Role SystemMessageRole `json:"role"` } -func (*SessionMcpServerStatusChangedData) sessionEventData() {} +func (*SystemMessageData) sessionEventData() {} -// SessionExtensionsLoadedData holds the payload for session.extensions_loaded events. -type SessionExtensionsLoadedData struct { - // Array of discovered extensions and their status - Extensions []ExtensionsLoadedExtension `json:"extensions"` +// Task completion notification with summary from the agent +type SessionTaskCompleteData struct { + // Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + Success *bool `json:"success,omitempty"` + // Summary of the completed task, provided by the agent + Summary *string `json:"summary,omitempty"` } -func (*SessionExtensionsLoadedData) sessionEventData() {} +func (*SessionTaskCompleteData) sessionEventData() {} -// Working directory and git context at session start -type WorkingDirectoryContext struct { - // Current working directory path - Cwd string `json:"cwd"` - // Root directory of the git repository, resolved via git rev-parse - GitRoot *string `json:"gitRoot,omitempty"` - // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - Repository *string `json:"repository,omitempty"` - // Hosting platform type of the repository (github or ado) - HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` - // Current git branch name - Branch *string `json:"branch,omitempty"` - // Head commit of current git branch at session start time - HeadCommit *string `json:"headCommit,omitempty"` - // Base commit of current git branch at session start time - BaseCommit *string `json:"baseCommit,omitempty"` +// Tool execution completion results including success status, detailed output, and error information +type ToolExecutionCompleteData struct { + // Error details when the tool execution failed + Error *ToolExecutionCompleteError `json:"error,omitempty"` + // CAPI interaction ID for correlating this tool execution with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` + // Whether this tool call was explicitly requested by the user rather than the assistant + IsUserRequested *bool `json:"isUserRequested,omitempty"` + // Model identifier that generated this tool call + Model *string `json:"model,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. + ParentToolCallID *string `json:"parentToolCallId,omitempty"` + // Tool execution result on success + Result *ToolExecutionCompleteResult `json:"result,omitempty"` + // Whether the tool execution completed successfully + Success bool `json:"success"` + // Unique identifier for the completed tool call + ToolCallID string `json:"toolCallId"` + // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } -// Repository context for the handed-off session -type HandoffRepository struct { - // Repository owner (user or organization) - Owner string `json:"owner"` - // Repository name - Name string `json:"name"` - // Git branch name, if applicable - Branch *string `json:"branch,omitempty"` -} +func (*ToolExecutionCompleteData) sessionEventData() {} -// Aggregate code change metrics for the session -type ShutdownCodeChanges struct { - // Total number of lines added during the session - LinesAdded float64 `json:"linesAdded"` - // Total number of lines removed during the session - LinesRemoved float64 `json:"linesRemoved"` - // List of file paths that were modified during the session - FilesModified []string `json:"filesModified"` +// Tool execution progress notification with status message +type ToolExecutionProgressData struct { + // Human-readable progress status message (e.g., from an MCP server) + ProgressMessage string `json:"progressMessage"` + // Tool call ID this progress notification belongs to + ToolCallID string `json:"toolCallId"` } -// Request count and cost metrics -type ShutdownModelMetricRequests struct { - // Total number of API requests made to this model - Count float64 `json:"count"` - // Cumulative cost multiplier for requests to this model - Cost float64 `json:"cost"` -} +func (*ToolExecutionProgressData) sessionEventData() {} -// Token usage breakdown -type ShutdownModelMetricUsage struct { - // Total input tokens consumed across all requests to this model - InputTokens float64 `json:"inputTokens"` - // Total output tokens produced across all requests to this model - OutputTokens float64 `json:"outputTokens"` - // Total tokens read from prompt cache across all requests - CacheReadTokens float64 `json:"cacheReadTokens"` - // Total tokens written to prompt cache across all requests - CacheWriteTokens float64 `json:"cacheWriteTokens"` - // Total reasoning tokens produced across all requests to this model - ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` +// Tool execution startup details including MCP server information when applicable +type ToolExecutionStartData struct { + // Arguments passed to the tool + Arguments any `json:"arguments,omitempty"` + // Name of the MCP server hosting this tool, when the tool is an MCP tool + McpServerName *string `json:"mcpServerName,omitempty"` + // Original tool name on the MCP server, when the tool is an MCP tool + McpToolName *string `json:"mcpToolName,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + // Deprecated: ParentToolCallID is deprecated. + ParentToolCallID *string `json:"parentToolCallId,omitempty"` + // Unique identifier for this tool call + ToolCallID string `json:"toolCallId"` + // Name of the tool being executed + ToolName string `json:"toolName"` } -type ShutdownModelMetric struct { - // Request count and cost metrics - Requests ShutdownModelMetricRequests `json:"requests"` - // Token usage breakdown - Usage ShutdownModelMetricUsage `json:"usage"` -} +func (*ToolExecutionStartData) sessionEventData() {} -// Token usage breakdown for the compaction LLM call -type CompactionCompleteCompactionTokensUsed struct { - // Input tokens consumed by the compaction LLM call - Input float64 `json:"input"` - // Output tokens produced by the compaction LLM call - Output float64 `json:"output"` - // Cached input tokens reused in the compaction LLM call - CachedInput float64 `json:"cachedInput"` +// Turn abort information including the reason for termination +type AbortData struct { + // Reason the current turn was aborted (e.g., "user initiated") + Reason string `json:"reason"` } -// Optional line range to scope the attachment to a specific section of the file -type UserMessageAttachmentFileLineRange struct { - // Start line number (1-based) - Start float64 `json:"start"` - // End line number (1-based, inclusive) - End float64 `json:"end"` -} +func (*AbortData) sessionEventData() {} -// Start position of the selection -type UserMessageAttachmentSelectionDetailsStart struct { - // Start line number (0-based) - Line float64 `json:"line"` - // Start character offset within the line (0-based) - Character float64 `json:"character"` +// Turn completion metadata including the turn identifier +type AssistantTurnEndData struct { + // Identifier of the turn that has ended, matching the corresponding assistant.turn_start event + TurnID string `json:"turnId"` } -// End position of the selection -type UserMessageAttachmentSelectionDetailsEnd struct { - // End line number (0-based) - Line float64 `json:"line"` - // End character offset within the line (0-based) - Character float64 `json:"character"` +func (*AssistantTurnEndData) sessionEventData() {} + +// Turn initialization metadata including identifier and interaction tracking +type AssistantTurnStartData struct { + // CAPI interaction ID for correlating this turn with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` + // Identifier for this turn within the agentic loop, typically a stringified turn number + TurnID string `json:"turnId"` } -// Position range of the selection within the file -type UserMessageAttachmentSelectionDetails struct { - // Start position of the selection - Start UserMessageAttachmentSelectionDetailsStart `json:"start"` - // End position of the selection - End UserMessageAttachmentSelectionDetailsEnd `json:"end"` +func (*AssistantTurnStartData) sessionEventData() {} + +// User input request completion with the user's response +type UserInputCompletedData struct { + // The user's answer to the input request + Answer *string `json:"answer,omitempty"` + // Request ID of the resolved user input request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // Whether the answer was typed as free-form text rather than selected from choices + WasFreeform *bool `json:"wasFreeform,omitempty"` } -// A user message attachment — a file, directory, code selection, blob, or GitHub reference -type UserMessageAttachment struct { - // Type discriminator - Type UserMessageAttachmentType `json:"type"` - // Absolute file path - Path *string `json:"path,omitempty"` - // User-facing display name for the attachment - DisplayName *string `json:"displayName,omitempty"` - // Optional line range to scope the attachment to a specific section of the file - LineRange *UserMessageAttachmentFileLineRange `json:"lineRange,omitempty"` - // Absolute path to the file containing the selection - FilePath *string `json:"filePath,omitempty"` - // The selected text content - Text *string `json:"text,omitempty"` - // Position range of the selection within the file - Selection *UserMessageAttachmentSelectionDetails `json:"selection,omitempty"` - // Issue, pull request, or discussion number - Number *float64 `json:"number,omitempty"` - // Title of the referenced item - Title *string `json:"title,omitempty"` - // Type of GitHub reference - ReferenceType *UserMessageAttachmentGithubReferenceType `json:"referenceType,omitempty"` - // Current state of the referenced item (e.g., open, closed, merged) - State *string `json:"state,omitempty"` - // URL to the referenced item on GitHub - URL *string `json:"url,omitempty"` - // Base64-encoded content - Data *string `json:"data,omitempty"` - // MIME type of the inline data - MIMEType *string `json:"mimeType,omitempty"` +func (*UserInputCompletedData) sessionEventData() {} + +// User input request notification with question and optional predefined choices +type UserInputRequestedData struct { + // Whether the user can provide a free-form text response in addition to predefined choices + AllowFreeform *bool `json:"allowFreeform,omitempty"` + // Predefined choices for the user to select from, if applicable + Choices []string `json:"choices,omitempty"` + // The question or prompt to present to the user + Question string `json:"question"` + // Unique identifier for this input request; used to respond via session.respondToUserInput() + RequestID string `json:"requestId"` + // The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses + ToolCallID *string `json:"toolCallId,omitempty"` } -// A tool invocation request from the assistant -type AssistantMessageToolRequest struct { +func (*UserInputRequestedData) sessionEventData() {} + +// User-initiated tool invocation request with tool name and arguments +type ToolUserRequestedData struct { + // Arguments for the tool invocation + Arguments any `json:"arguments,omitempty"` // Unique identifier for this tool call ToolCallID string `json:"toolCallId"` - // Name of the tool being invoked - Name string `json:"name"` - // Arguments to pass to the tool, format depends on the tool - Arguments any `json:"arguments,omitempty"` - // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - Type *AssistantMessageToolRequestType `json:"type,omitempty"` - // Human-readable display title for the tool - ToolTitle *string `json:"toolTitle,omitempty"` - // Name of the MCP server hosting this tool, when the tool is an MCP tool - McpServerName *string `json:"mcpServerName,omitempty"` - // Resolved intention summary describing what this specific call does - IntentionSummary *string `json:"intentionSummary,omitempty"` + // Name of the tool the user wants to invoke + ToolName string `json:"toolName"` } -type AssistantUsageQuotaSnapshot struct { - // Whether the user has an unlimited usage entitlement - IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` - // Total requests allowed by the entitlement - EntitlementRequests float64 `json:"entitlementRequests"` - // Number of requests already consumed - UsedRequests float64 `json:"usedRequests"` - // Whether usage is still permitted after quota exhaustion - UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` - // Number of requests over the entitlement limit - Overage float64 `json:"overage"` - // Whether overage is allowed when quota is exhausted - OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` - // Percentage of quota remaining (0.0 to 1.0) - RemainingPercentage float64 `json:"remainingPercentage"` - // Date when the quota resets - ResetDate *time.Time `json:"resetDate,omitempty"` +func (*ToolUserRequestedData) sessionEventData() {} + +// UserMessageData holds the payload for user.message events. +type UserMessageData struct { + // The agent mode that was active when this message was sent + AgentMode *UserMessageAgentMode `json:"agentMode,omitempty"` + // Files, selections, or GitHub references attached to the message + Attachments []UserMessageAttachment `json:"attachments,omitempty"` + // The user's message text as displayed in the timeline + Content string `json:"content"` + // CAPI interaction ID for correlating this user message with its turn + InteractionID *string `json:"interactionId,omitempty"` + // Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit + NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitempty"` + // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + Source *string `json:"source,omitempty"` + // Normalized document MIME types that were sent natively instead of through tagged_files XML + SupportedNativeDocumentMIMETypes []string `json:"supportedNativeDocumentMimeTypes,omitempty"` + // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + TransformedContent *string `json:"transformedContent,omitempty"` } -// Token usage detail for a single billing category -type AssistantUsageCopilotUsageTokenDetail struct { - // Number of tokens in this billing batch - BatchSize float64 `json:"batchSize"` - // Cost per batch of tokens - CostPerBatch float64 `json:"costPerBatch"` - // Total token count for this entry - TokenCount float64 `json:"tokenCount"` - // Token category (e.g., "input", "output") - TokenType string `json:"tokenType"` +func (*UserMessageData) sessionEventData() {} + +// Warning message for timeline display with categorization +type SessionWarningData struct { + // Human-readable warning message for display in the timeline + Message string `json:"message"` + // Optional URL associated with this warning that the user can open in a browser + URL *string `json:"url,omitempty"` + // Category of warning (e.g., "subscription", "policy", "mcp") + WarningType string `json:"warningType"` } -// Per-request cost and usage data from the CAPI copilot_usage response field -type AssistantUsageCopilotUsage struct { - // Itemized token usage breakdown - TokenDetails []AssistantUsageCopilotUsageTokenDetail `json:"tokenDetails"` - // Total cost in nano-AIU (AI Units) for this request - TotalNanoAiu float64 `json:"totalNanoAiu"` +func (*SessionWarningData) sessionEventData() {} + +// Working directory and git context at session start +type SessionContextChangedData struct { + // Base commit of current git branch at session start time + BaseCommit *string `json:"baseCommit,omitempty"` + // Current git branch name + Branch *string `json:"branch,omitempty"` + // Current working directory path + Cwd string `json:"cwd"` + // Root directory of the git repository, resolved via git rev-parse + GitRoot *string `json:"gitRoot,omitempty"` + // Head commit of current git branch at session start time + HeadCommit *string `json:"headCommit,omitempty"` + // Hosting platform type of the repository (github or ado) + HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + Repository *string `json:"repository,omitempty"` + // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") + RepositoryHost *string `json:"repositoryHost,omitempty"` +} + +func (*SessionContextChangedData) sessionEventData() {} + +// Workspace file change details including path and operation type +type SessionWorkspaceFileChangedData struct { + // Whether the file was newly created or updated + Operation WorkspaceFileChangedOperation `json:"operation"` + // Relative path within the session workspace files directory + Path string `json:"path"` } -// Icon image for a resource -type ToolExecutionCompleteContentResourceLinkIcon struct { - // URL or path to the icon image - Src string `json:"src"` - // MIME type of the icon image - MIMEType *string `json:"mimeType,omitempty"` - // Available icon sizes (e.g., ['16x16', '32x32']) - Sizes []string `json:"sizes,omitempty"` - // Theme variant this icon is intended for - Theme *ToolExecutionCompleteContentResourceLinkIconTheme `json:"theme,omitempty"` -} +func (*SessionWorkspaceFileChangedData) sessionEventData() {} // A content block within a tool result, which may be text, terminal output, image, audio, or a resource type ToolExecutionCompleteContent struct { // Type discriminator Type ToolExecutionCompleteContentType `json:"type"` - // The text content - Text *string `json:"text,omitempty"` - // Process exit code, if the command has completed - ExitCode *float64 `json:"exitCode,omitempty"` // Working directory where the command was executed Cwd *string `json:"cwd,omitempty"` // Base64-encoded image data Data *string `json:"data,omitempty"` - // MIME type of the image (e.g., image/png, image/jpeg) - MIMEType *string `json:"mimeType,omitempty"` + // Human-readable description of the resource + Description *string `json:"description,omitempty"` + // Process exit code, if the command has completed + ExitCode *float64 `json:"exitCode,omitempty"` // Icons associated with this resource Icons []ToolExecutionCompleteContentResourceLinkIcon `json:"icons,omitempty"` + // MIME type of the image (e.g., image/png, image/jpeg) + MIMEType *string `json:"mimeType,omitempty"` // Resource name identifier Name *string `json:"name,omitempty"` + // The embedded resource contents, either text or base64-encoded binary + Resource any `json:"resource,omitempty"` + // Size of the resource in bytes + Size *float64 `json:"size,omitempty"` + // The text content + Text *string `json:"text,omitempty"` // Human-readable display title for the resource Title *string `json:"title,omitempty"` // URI identifying the resource URI *string `json:"uri,omitempty"` - // Human-readable description of the resource - Description *string `json:"description,omitempty"` - // Size of the resource in bytes - Size *float64 `json:"size,omitempty"` - // The embedded resource contents, either text or base64-encoded binary - Resource any `json:"resource,omitempty"` } -// Tool execution result on success -type ToolExecutionCompleteResult struct { - // Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency - Content string `json:"content"` - // Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. - DetailedContent *string `json:"detailedContent,omitempty"` - // Structured content blocks (text, images, audio, resources) returned by the tool in their native format - Contents []ToolExecutionCompleteContent `json:"contents,omitempty"` +// A tool invocation request from the assistant +type AssistantMessageToolRequest struct { + // Arguments to pass to the tool, format depends on the tool + Arguments any `json:"arguments,omitempty"` + // Resolved intention summary describing what this specific call does + IntentionSummary *string `json:"intentionSummary,omitempty"` + // Name of the MCP server hosting this tool, when the tool is an MCP tool + McpServerName *string `json:"mcpServerName,omitempty"` + // Name of the tool being invoked + Name string `json:"name"` + // Unique identifier for this tool call + ToolCallID string `json:"toolCallId"` + // Human-readable display title for the tool + ToolTitle *string `json:"toolTitle,omitempty"` + // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + Type *AssistantMessageToolRequestType `json:"type,omitempty"` } -// Error details when the tool execution failed -type ToolExecutionCompleteError struct { - // Human-readable error message - Message string `json:"message"` - // Machine-readable error code - Code *string `json:"code,omitempty"` +// A user message attachment — a file, directory, code selection, blob, or GitHub reference +type UserMessageAttachment struct { + // Type discriminator + Type UserMessageAttachmentType `json:"type"` + // Base64-encoded content + Data *string `json:"data,omitempty"` + // User-facing display name for the attachment + DisplayName *string `json:"displayName,omitempty"` + // Absolute path to the file containing the selection + FilePath *string `json:"filePath,omitempty"` + // Optional line range to scope the attachment to a specific section of the file + LineRange *UserMessageAttachmentFileLineRange `json:"lineRange,omitempty"` + // MIME type of the inline data + MIMEType *string `json:"mimeType,omitempty"` + // Issue, pull request, or discussion number + Number *float64 `json:"number,omitempty"` + // Absolute file path + Path *string `json:"path,omitempty"` + // Type of GitHub reference + ReferenceType *UserMessageAttachmentGithubReferenceType `json:"referenceType,omitempty"` + // Position range of the selection within the file + Selection *UserMessageAttachmentSelectionDetails `json:"selection,omitempty"` + // Current state of the referenced item (e.g., open, closed, merged) + State *string `json:"state,omitempty"` + // The selected text content + Text *string `json:"text,omitempty"` + // Title of the referenced item + Title *string `json:"title,omitempty"` + // URL to the referenced item on GitHub + URL *string `json:"url,omitempty"` +} + +// Aggregate code change metrics for the session +type ShutdownCodeChanges struct { + // List of file paths that were modified during the session + FilesModified []string `json:"filesModified"` + // Total number of lines added during the session + LinesAdded float64 `json:"linesAdded"` + // Total number of lines removed during the session + LinesRemoved float64 `json:"linesRemoved"` +} + +// Details of the permission being requested +type PermissionRequest struct { + // Kind discriminator + Kind PermissionRequestKind `json:"kind"` + // Whether this is a store or vote memory operation + Action *PermissionRequestMemoryAction `json:"action,omitempty"` + // Arguments to pass to the MCP tool + Args any `json:"args,omitempty"` + // Whether the UI can offer session-wide approval for this command pattern + CanOfferSessionApproval *bool `json:"canOfferSessionApproval,omitempty"` + // Source references for the stored fact (store only) + Citations *string `json:"citations,omitempty"` + // Parsed command identifiers found in the command text + Commands []PermissionRequestShellCommand `json:"commands,omitempty"` + // Unified diff showing the proposed changes + Diff *string `json:"diff,omitempty"` + // Vote direction (vote only) + Direction *PermissionRequestMemoryDirection `json:"direction,omitempty"` + // The fact being stored or voted on + Fact *string `json:"fact,omitempty"` + // Path of the file being written to + FileName *string `json:"fileName,omitempty"` + // The complete shell command text to be executed + FullCommandText *string `json:"fullCommandText,omitempty"` + // Whether the command includes a file write redirection (e.g., > or >>) + HasWriteFileRedirection *bool `json:"hasWriteFileRedirection,omitempty"` + // Optional message from the hook explaining why confirmation is needed + HookMessage *string `json:"hookMessage,omitempty"` + // Human-readable description of what the command intends to do + Intention *string `json:"intention,omitempty"` + // Complete new file contents for newly created files + NewFileContents *string `json:"newFileContents,omitempty"` + // Path of the file or directory being read + Path *string `json:"path,omitempty"` + // File paths that may be read or written by the command + PossiblePaths []string `json:"possiblePaths,omitempty"` + // URLs that may be accessed by the command + PossibleUrls []PermissionRequestShellPossibleURL `json:"possibleUrls,omitempty"` + // Whether this MCP tool is read-only (no side effects) + ReadOnly *bool `json:"readOnly,omitempty"` + // Reason for the vote (vote only) + Reason *string `json:"reason,omitempty"` + // Name of the MCP server providing the tool + ServerName *string `json:"serverName,omitempty"` + // Topic or subject of the memory (store only) + Subject *string `json:"subject,omitempty"` + // Arguments of the tool call being gated + ToolArgs any `json:"toolArgs,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` + // Description of what the custom tool does + ToolDescription *string `json:"toolDescription,omitempty"` + // Internal name of the MCP tool + ToolName *string `json:"toolName,omitempty"` + // Human-readable title of the MCP tool + ToolTitle *string `json:"toolTitle,omitempty"` + // URL to be fetched + URL *string `json:"url,omitempty"` + // Optional warning message about risks of running this command + Warning *string `json:"warning,omitempty"` +} + +// End position of the selection +type UserMessageAttachmentSelectionDetailsEnd struct { + // End character offset within the line (0-based) + Character float64 `json:"character"` + // End line number (0-based) + Line float64 `json:"line"` } // Error details when the hook failed @@ -1906,6 +1810,36 @@ type HookEndError struct { Stack *string `json:"stack,omitempty"` } +// Error details when the tool execution failed +type ToolExecutionCompleteError struct { + // Machine-readable error code + Code *string `json:"code,omitempty"` + // Human-readable error message + Message string `json:"message"` +} + +// Icon image for a resource +type ToolExecutionCompleteContentResourceLinkIcon struct { + // MIME type of the icon image + MIMEType *string `json:"mimeType,omitempty"` + // Available icon sizes (e.g., ['16x16', '32x32']) + Sizes []string `json:"sizes,omitempty"` + // URL or path to the icon image + Src string `json:"src"` + // Theme variant this icon is intended for + Theme *ToolExecutionCompleteContentResourceLinkIconTheme `json:"theme,omitempty"` +} + +// JSON Schema describing the form fields to present to the user (form mode only) +type ElicitationRequestedSchema struct { + // Form field definitions, keyed by field name + Properties map[string]any `json:"properties"` + // List of required field names + Required []string `json:"required,omitempty"` + // Schema type indicator (always 'object') + Type string `json:"type"` +} + // Metadata about the prompt template and its construction type SystemMessageMetadata struct { // Version identifier of the prompt template used @@ -1914,6 +1848,64 @@ type SystemMessageMetadata struct { Variables map[string]any `json:"variables,omitempty"` } +// Optional line range to scope the attachment to a specific section of the file +type UserMessageAttachmentFileLineRange struct { + // End line number (1-based, inclusive) + End float64 `json:"end"` + // Start line number (1-based) + Start float64 `json:"start"` +} + +// Per-request cost and usage data from the CAPI copilot_usage response field +type AssistantUsageCopilotUsage struct { + // Itemized token usage breakdown + TokenDetails []AssistantUsageCopilotUsageTokenDetail `json:"tokenDetails"` + // Total cost in nano-AIU (AI Units) for this request + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + +// Position range of the selection within the file +type UserMessageAttachmentSelectionDetails struct { + // End position of the selection + End UserMessageAttachmentSelectionDetailsEnd `json:"end"` + // Start position of the selection + Start UserMessageAttachmentSelectionDetailsStart `json:"start"` +} + +// Repository context for the handed-off session +type HandoffRepository struct { + // Git branch name, if applicable + Branch *string `json:"branch,omitempty"` + // Repository name + Name string `json:"name"` + // Repository owner (user or organization) + Owner string `json:"owner"` +} + +// Request count and cost metrics +type ShutdownModelMetricRequests struct { + // Cumulative cost multiplier for requests to this model + Cost float64 `json:"cost"` + // Total number of API requests made to this model + Count float64 `json:"count"` +} + +// Start position of the selection +type UserMessageAttachmentSelectionDetailsStart struct { + // Start character offset within the line (0-based) + Character float64 `json:"character"` + // Start line number (0-based) + Line float64 `json:"line"` +} + +// Static OAuth client configuration, if the server specifies one +type McpOauthRequiredStaticClientConfig struct { + // OAuth client ID for the server + ClientID string `json:"clientId"` + // Whether this is a public OAuth client + PublicClient *bool `json:"publicClient,omitempty"` +} + // Structured metadata identifying what triggered this notification type SystemNotification struct { // Type discriminator @@ -1922,90 +1914,24 @@ type SystemNotification struct { AgentID *string `json:"agentId,omitempty"` // Type of the agent (e.g., explore, task, general-purpose) AgentType *string `json:"agentType,omitempty"` - // Whether the agent completed successfully or failed - Status *SystemNotificationAgentCompletedStatus `json:"status,omitempty"` // Human-readable description of the agent task Description *string `json:"description,omitempty"` + // Unique identifier of the inbox entry + EntryID *string `json:"entryId,omitempty"` + // Exit code of the shell command, if available + ExitCode *float64 `json:"exitCode,omitempty"` // The full prompt given to the background agent Prompt *string `json:"prompt,omitempty"` + // Human-readable name of the sender + SenderName *string `json:"senderName,omitempty"` + // Category of the sender (e.g., ambient-agent, plugin, hook) + SenderType *string `json:"senderType,omitempty"` // Unique identifier of the shell session ShellID *string `json:"shellId,omitempty"` - // Exit code of the shell command, if available - ExitCode *float64 `json:"exitCode,omitempty"` -} - -type PermissionRequestShellCommand struct { - // Command identifier (e.g., executable name) - Identifier string `json:"identifier"` - // Whether this command is read-only (no side effects) - ReadOnly bool `json:"readOnly"` -} - -type PermissionRequestShellPossibleUrl struct { - // URL that may be accessed by the command - URL string `json:"url"` -} - -// Details of the permission being requested -type PermissionRequest struct { - // Kind discriminator - Kind PermissionRequestKind `json:"kind"` - // Tool call ID that triggered this permission request - ToolCallID *string `json:"toolCallId,omitempty"` - // The complete shell command text to be executed - FullCommandText *string `json:"fullCommandText,omitempty"` - // Human-readable description of what the command intends to do - Intention *string `json:"intention,omitempty"` - // Parsed command identifiers found in the command text - Commands []PermissionRequestShellCommand `json:"commands,omitempty"` - // File paths that may be read or written by the command - PossiblePaths []string `json:"possiblePaths,omitempty"` - // URLs that may be accessed by the command - PossibleUrls []PermissionRequestShellPossibleUrl `json:"possibleUrls,omitempty"` - // Whether the command includes a file write redirection (e.g., > or >>) - HasWriteFileRedirection *bool `json:"hasWriteFileRedirection,omitempty"` - // Whether the UI can offer session-wide approval for this command pattern - CanOfferSessionApproval *bool `json:"canOfferSessionApproval,omitempty"` - // Optional warning message about risks of running this command - Warning *string `json:"warning,omitempty"` - // Path of the file being written to - FileName *string `json:"fileName,omitempty"` - // Unified diff showing the proposed changes - Diff *string `json:"diff,omitempty"` - // Complete new file contents for newly created files - NewFileContents *string `json:"newFileContents,omitempty"` - // Path of the file or directory being read - Path *string `json:"path,omitempty"` - // Name of the MCP server providing the tool - ServerName *string `json:"serverName,omitempty"` - // Internal name of the MCP tool - ToolName *string `json:"toolName,omitempty"` - // Human-readable title of the MCP tool - ToolTitle *string `json:"toolTitle,omitempty"` - // Arguments to pass to the MCP tool - Args any `json:"args,omitempty"` - // Whether this MCP tool is read-only (no side effects) - ReadOnly *bool `json:"readOnly,omitempty"` - // URL to be fetched - URL *string `json:"url,omitempty"` - // Whether this is a store or vote memory operation - Action *PermissionRequestMemoryAction `json:"action,omitempty"` - // Topic or subject of the memory (store only) - Subject *string `json:"subject,omitempty"` - // The fact being stored or voted on - Fact *string `json:"fact,omitempty"` - // Source references for the stored fact (store only) - Citations *string `json:"citations,omitempty"` - // Vote direction (vote only) - Direction *PermissionRequestMemoryDirection `json:"direction,omitempty"` - // Reason for the vote (vote only) - Reason *string `json:"reason,omitempty"` - // Description of what the custom tool does - ToolDescription *string `json:"toolDescription,omitempty"` - // Arguments of the tool call being gated - ToolArgs any `json:"toolArgs,omitempty"` - // Optional message from the hook explaining why confirmation is needed - HookMessage *string `json:"hookMessage,omitempty"` + // Whether the agent completed successfully or failed + Status *SystemNotificationAgentCompletedStatus `json:"status,omitempty"` + // Short summary shown before the agent decides whether to read the inbox + Summary *string `json:"summary,omitempty"` } // The result of the permission request @@ -2014,27 +1940,50 @@ type PermissionCompletedResult struct { Kind PermissionCompletedKind `json:"kind"` } -// JSON Schema describing the form fields to present to the user (form mode only) -type ElicitationRequestedSchema struct { - // Schema type indicator (always 'object') - Type string `json:"type"` - // Form field definitions, keyed by field name - Properties map[string]any `json:"properties"` - // List of required field names - Required []string `json:"required,omitempty"` +// Token usage breakdown +type ShutdownModelMetricUsage struct { + // Total tokens read from prompt cache across all requests + CacheReadTokens float64 `json:"cacheReadTokens"` + // Total tokens written to prompt cache across all requests + CacheWriteTokens float64 `json:"cacheWriteTokens"` + // Total input tokens consumed across all requests to this model + InputTokens float64 `json:"inputTokens"` + // Total output tokens produced across all requests to this model + OutputTokens float64 `json:"outputTokens"` + // Total reasoning tokens produced across all requests to this model + ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` } -// Static OAuth client configuration, if the server specifies one -type McpOauthRequiredStaticClientConfig struct { - // OAuth client ID for the server - ClientID string `json:"clientId"` - // Whether this is a public OAuth client - PublicClient *bool `json:"publicClient,omitempty"` +// Token usage breakdown for the compaction LLM call +type CompactionCompleteCompactionTokensUsed struct { + // Cached input tokens reused in the compaction LLM call + CachedInput float64 `json:"cachedInput"` + // Input tokens consumed by the compaction LLM call + Input float64 `json:"input"` + // Output tokens produced by the compaction LLM call + Output float64 `json:"output"` } -type CommandsChangedCommand struct { - Name string `json:"name"` - Description *string `json:"description,omitempty"` +// Token usage detail for a single billing category +type AssistantUsageCopilotUsageTokenDetail struct { + // Number of tokens in this billing batch + BatchSize float64 `json:"batchSize"` + // Cost per batch of tokens + CostPerBatch float64 `json:"costPerBatch"` + // Total token count for this entry + TokenCount float64 `json:"tokenCount"` + // Token category (e.g., "input", "output") + TokenType string `json:"tokenType"` +} + +// Tool execution result on success +type ToolExecutionCompleteResult struct { + // Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency + Content string `json:"content"` + // Structured content blocks (text, images, audio, resources) returned by the tool in their native format + Contents []ToolExecutionCompleteContent `json:"contents,omitempty"` + // Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. + DetailedContent *string `json:"detailedContent,omitempty"` } // UI capability changes @@ -2043,121 +1992,211 @@ type CapabilitiesChangedUI struct { Elicitation *bool `json:"elicitation,omitempty"` } -type SkillsLoadedSkill struct { - // Unique identifier for the skill - Name string `json:"name"` - // Description of what the skill does - Description string `json:"description"` - // Source location type of the skill (e.g., project, personal, plugin) - Source string `json:"source"` - // Whether the skill can be invoked by the user as a slash command - UserInvocable bool `json:"userInvocable"` - // Whether the skill is currently enabled - Enabled bool `json:"enabled"` - // Absolute path to the skill file, if available - Path *string `json:"path,omitempty"` +// Working directory and git context at session start +type WorkingDirectoryContext struct { + // Base commit of current git branch at session start time + BaseCommit *string `json:"baseCommit,omitempty"` + // Current git branch name + Branch *string `json:"branch,omitempty"` + // Current working directory path + Cwd string `json:"cwd"` + // Root directory of the git repository, resolved via git rev-parse + GitRoot *string `json:"gitRoot,omitempty"` + // Head commit of current git branch at session start time + HeadCommit *string `json:"headCommit,omitempty"` + // Hosting platform type of the repository (github or ado) + HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + Repository *string `json:"repository,omitempty"` + // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") + RepositoryHost *string `json:"repositoryHost,omitempty"` +} + +type AssistantUsageQuotaSnapshot struct { + // Total requests allowed by the entitlement + EntitlementRequests float64 `json:"entitlementRequests"` + // Whether the user has an unlimited usage entitlement + IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` + // Number of requests over the entitlement limit + Overage float64 `json:"overage"` + // Whether overage is allowed when quota is exhausted + OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` + // Percentage of quota remaining (0.0 to 1.0) + RemainingPercentage float64 `json:"remainingPercentage"` + // Date when the quota resets + ResetDate *time.Time `json:"resetDate,omitempty"` + // Whether usage is still permitted after quota exhaustion + UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` + // Number of requests already consumed + UsedRequests float64 `json:"usedRequests"` +} + +type CommandsChangedCommand struct { + Description *string `json:"description,omitempty"` + Name string `json:"name"` } type CustomAgentsUpdatedAgent struct { + // Description of what the agent does + Description string `json:"description"` + // Human-readable display name + DisplayName string `json:"displayName"` // Unique identifier for the agent ID string `json:"id"` + // Model override for this agent, if set + Model *string `json:"model,omitempty"` // Internal name of the agent Name string `json:"name"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Description of what the agent does - Description string `json:"description"` // Source location: user, project, inherited, remote, or plugin Source string `json:"source"` // List of tool names available to this agent Tools []string `json:"tools"` // Whether the agent can be selected by the user UserInvocable bool `json:"userInvocable"` - // Model override for this agent, if set - Model *string `json:"model,omitempty"` +} + +type ExtensionsLoadedExtension struct { + // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') + ID string `json:"id"` + // Extension name (directory name) + Name string `json:"name"` + // Discovery source + Source ExtensionsLoadedExtensionSource `json:"source"` + // Current status: running, disabled, failed, or starting + Status ExtensionsLoadedExtensionStatus `json:"status"` } type McpServersLoadedServer struct { + // Error message if the server failed to connect + Error *string `json:"error,omitempty"` // Server name (config key) Name string `json:"name"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status McpServersLoadedServerStatus `json:"status"` // Configuration source: user, workspace, plugin, or builtin Source *string `json:"source,omitempty"` - // Error message if the server failed to connect - Error *string `json:"error,omitempty"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status McpServersLoadedServerStatus `json:"status"` } -type ExtensionsLoadedExtension struct { - // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') - ID string `json:"id"` - // Extension name (directory name) +type PermissionRequestShellCommand struct { + // Command identifier (e.g., executable name) + Identifier string `json:"identifier"` + // Whether this command is read-only (no side effects) + ReadOnly bool `json:"readOnly"` +} + +type PermissionRequestShellPossibleURL struct { + // URL that may be accessed by the command + URL string `json:"url"` +} + +type ShutdownModelMetric struct { + // Request count and cost metrics + Requests ShutdownModelMetricRequests `json:"requests"` + // Token usage breakdown + Usage ShutdownModelMetricUsage `json:"usage"` +} + +type SkillsLoadedSkill struct { + // Description of what the skill does + Description string `json:"description"` + // Whether the skill is currently enabled + Enabled bool `json:"enabled"` + // Unique identifier for the skill Name string `json:"name"` - // Discovery source - Source ExtensionsLoadedExtensionSource `json:"source"` - // Current status: running, disabled, failed, or starting - Status ExtensionsLoadedExtensionStatus `json:"status"` + // Absolute path to the skill file, if available + Path *string `json:"path,omitempty"` + // Source location type of the skill (e.g., project, personal, plugin) + Source string `json:"source"` + // Whether the skill can be invoked by the user as a slash command + UserInvocable bool `json:"userInvocable"` } -// Hosting platform type of the repository (github or ado) -type WorkingDirectoryContextHostType string +// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +type McpServersLoadedServerStatus string const ( - WorkingDirectoryContextHostTypeGithub WorkingDirectoryContextHostType = "github" - WorkingDirectoryContextHostTypeAdo WorkingDirectoryContextHostType = "ado" + McpServersLoadedServerStatusConnected McpServersLoadedServerStatus = "connected" + McpServersLoadedServerStatusFailed McpServersLoadedServerStatus = "failed" + McpServersLoadedServerStatusNeedsAuth McpServersLoadedServerStatus = "needs-auth" + McpServersLoadedServerStatusPending McpServersLoadedServerStatus = "pending" + McpServersLoadedServerStatusDisabled McpServersLoadedServerStatus = "disabled" + McpServersLoadedServerStatusNotConfigured McpServersLoadedServerStatus = "not_configured" ) -// The type of operation performed on the plan file -type PlanChangedOperation string +// Current status: running, disabled, failed, or starting +type ExtensionsLoadedExtensionStatus string const ( - PlanChangedOperationCreate PlanChangedOperation = "create" - PlanChangedOperationUpdate PlanChangedOperation = "update" - PlanChangedOperationDelete PlanChangedOperation = "delete" + ExtensionsLoadedExtensionStatusRunning ExtensionsLoadedExtensionStatus = "running" + ExtensionsLoadedExtensionStatusDisabled ExtensionsLoadedExtensionStatus = "disabled" + ExtensionsLoadedExtensionStatusFailed ExtensionsLoadedExtensionStatus = "failed" + ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting" ) -// Whether the file was newly created or updated -type WorkspaceFileChangedOperation string +// Discovery source +type ExtensionsLoadedExtensionSource string const ( - WorkspaceFileChangedOperationCreate WorkspaceFileChangedOperation = "create" - WorkspaceFileChangedOperationUpdate WorkspaceFileChangedOperation = "update" + ExtensionsLoadedExtensionSourceProject ExtensionsLoadedExtensionSource = "project" + ExtensionsLoadedExtensionSourceUser ExtensionsLoadedExtensionSource = "user" ) -// Origin type of the session being handed off -type HandoffSourceType string +// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. +type ElicitationRequestedMode string const ( - HandoffSourceTypeRemote HandoffSourceType = "remote" - HandoffSourceTypeLocal HandoffSourceType = "local" + ElicitationRequestedModeForm ElicitationRequestedMode = "form" + ElicitationRequestedModeURL ElicitationRequestedMode = "url" ) -// Whether the session ended normally ("routine") or due to a crash/fatal error ("error") -type ShutdownType string +// Hosting platform type of the repository (github or ado) +type WorkingDirectoryContextHostType string const ( - ShutdownTypeRoutine ShutdownType = "routine" - ShutdownTypeError ShutdownType = "error" + WorkingDirectoryContextHostTypeGithub WorkingDirectoryContextHostType = "github" + WorkingDirectoryContextHostTypeAdo WorkingDirectoryContextHostType = "ado" ) -// Type discriminator for UserMessageAttachment. -type UserMessageAttachmentType string +// Kind discriminator for PermissionRequest. +type PermissionRequestKind string const ( - UserMessageAttachmentTypeFile UserMessageAttachmentType = "file" - UserMessageAttachmentTypeDirectory UserMessageAttachmentType = "directory" - UserMessageAttachmentTypeSelection UserMessageAttachmentType = "selection" - UserMessageAttachmentTypeGithubReference UserMessageAttachmentType = "github_reference" - UserMessageAttachmentTypeBlob UserMessageAttachmentType = "blob" + PermissionRequestKindShell PermissionRequestKind = "shell" + PermissionRequestKindWrite PermissionRequestKind = "write" + PermissionRequestKindRead PermissionRequestKind = "read" + PermissionRequestKindMcp PermissionRequestKind = "mcp" + PermissionRequestKindURL PermissionRequestKind = "url" + PermissionRequestKindMemory PermissionRequestKind = "memory" + PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" + PermissionRequestKindHook PermissionRequestKind = "hook" ) -// Type of GitHub reference -type UserMessageAttachmentGithubReferenceType string +// Message role: "system" for system prompts, "developer" for developer-injected instructions +type SystemMessageRole string const ( - UserMessageAttachmentGithubReferenceTypeIssue UserMessageAttachmentGithubReferenceType = "issue" - UserMessageAttachmentGithubReferenceTypePr UserMessageAttachmentGithubReferenceType = "pr" - UserMessageAttachmentGithubReferenceTypeDiscussion UserMessageAttachmentGithubReferenceType = "discussion" + SystemMessageRoleSystem SystemMessageRole = "system" + SystemMessageRoleDeveloper SystemMessageRole = "developer" +) + +// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured +type McpServerStatusChangedStatus string + +const ( + McpServerStatusChangedStatusConnected McpServerStatusChangedStatus = "connected" + McpServerStatusChangedStatusFailed McpServerStatusChangedStatus = "failed" + McpServerStatusChangedStatusNeedsAuth McpServerStatusChangedStatus = "needs-auth" + McpServerStatusChangedStatusPending McpServerStatusChangedStatus = "pending" + McpServerStatusChangedStatusDisabled McpServerStatusChangedStatus = "disabled" + McpServerStatusChangedStatusNotConfigured McpServerStatusChangedStatus = "not_configured" +) + +// Origin type of the session being handed off +type HandoffSourceType string + +const ( + HandoffSourceTypeRemote HandoffSourceType = "remote" + HandoffSourceTypeLocal HandoffSourceType = "local" ) // The agent mode that was active when this message was sent @@ -2170,24 +2209,34 @@ const ( UserMessageAgentModeShell UserMessageAgentMode = "shell" ) -// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. -type AssistantMessageToolRequestType string +// The outcome of the permission request +type PermissionCompletedKind string const ( - AssistantMessageToolRequestTypeFunction AssistantMessageToolRequestType = "function" - AssistantMessageToolRequestTypeCustom AssistantMessageToolRequestType = "custom" + PermissionCompletedKindApproved PermissionCompletedKind = "approved" + PermissionCompletedKindDeniedByRules PermissionCompletedKind = "denied-by-rules" + PermissionCompletedKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionCompletedKindDeniedInteractivelyByUser PermissionCompletedKind = "denied-interactively-by-user" + PermissionCompletedKindDeniedByContentExclusionPolicy PermissionCompletedKind = "denied-by-content-exclusion-policy" + PermissionCompletedKindDeniedByPermissionRequestHook PermissionCompletedKind = "denied-by-permission-request-hook" ) -// Type discriminator for ToolExecutionCompleteContent. -type ToolExecutionCompleteContentType string +// The type of operation performed on the plan file +type PlanChangedOperation string const ( - ToolExecutionCompleteContentTypeText ToolExecutionCompleteContentType = "text" - ToolExecutionCompleteContentTypeTerminal ToolExecutionCompleteContentType = "terminal" - ToolExecutionCompleteContentTypeImage ToolExecutionCompleteContentType = "image" - ToolExecutionCompleteContentTypeAudio ToolExecutionCompleteContentType = "audio" - ToolExecutionCompleteContentTypeResourceLink ToolExecutionCompleteContentType = "resource_link" - ToolExecutionCompleteContentTypeResource ToolExecutionCompleteContentType = "resource" + PlanChangedOperationCreate PlanChangedOperation = "create" + PlanChangedOperationUpdate PlanChangedOperation = "update" + PlanChangedOperationDelete PlanChangedOperation = "delete" +) + +// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) +type ElicitationCompletedAction string + +const ( + ElicitationCompletedActionAccept ElicitationCompletedAction = "accept" + ElicitationCompletedActionDecline ElicitationCompletedAction = "decline" + ElicitationCompletedActionCancel ElicitationCompletedAction = "cancel" ) // Theme variant this icon is intended for @@ -2198,12 +2247,12 @@ const ( ToolExecutionCompleteContentResourceLinkIconThemeDark ToolExecutionCompleteContentResourceLinkIconTheme = "dark" ) -// Message role: "system" for system prompts, "developer" for developer-injected instructions -type SystemMessageRole string +// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. +type AssistantMessageToolRequestType string const ( - SystemMessageRoleSystem SystemMessageRole = "system" - SystemMessageRoleDeveloper SystemMessageRole = "developer" + AssistantMessageToolRequestTypeFunction AssistantMessageToolRequestType = "function" + AssistantMessageToolRequestTypeCustom AssistantMessageToolRequestType = "custom" ) // Type discriminator for SystemNotification. @@ -2212,38 +2261,41 @@ type SystemNotificationType string const ( SystemNotificationTypeAgentCompleted SystemNotificationType = "agent_completed" SystemNotificationTypeAgentIdle SystemNotificationType = "agent_idle" + SystemNotificationTypeNewInboxMessage SystemNotificationType = "new_inbox_message" SystemNotificationTypeShellCompleted SystemNotificationType = "shell_completed" SystemNotificationTypeShellDetachedCompleted SystemNotificationType = "shell_detached_completed" ) -// Whether the agent completed successfully or failed -type SystemNotificationAgentCompletedStatus string +// Type discriminator for ToolExecutionCompleteContent. +type ToolExecutionCompleteContentType string const ( - SystemNotificationAgentCompletedStatusCompleted SystemNotificationAgentCompletedStatus = "completed" - SystemNotificationAgentCompletedStatusFailed SystemNotificationAgentCompletedStatus = "failed" + ToolExecutionCompleteContentTypeText ToolExecutionCompleteContentType = "text" + ToolExecutionCompleteContentTypeTerminal ToolExecutionCompleteContentType = "terminal" + ToolExecutionCompleteContentTypeImage ToolExecutionCompleteContentType = "image" + ToolExecutionCompleteContentTypeAudio ToolExecutionCompleteContentType = "audio" + ToolExecutionCompleteContentTypeResourceLink ToolExecutionCompleteContentType = "resource_link" + ToolExecutionCompleteContentTypeResource ToolExecutionCompleteContentType = "resource" ) -// Kind discriminator for PermissionRequest. -type PermissionRequestKind string +// Type discriminator for UserMessageAttachment. +type UserMessageAttachmentType string const ( - PermissionRequestKindShell PermissionRequestKind = "shell" - PermissionRequestKindWrite PermissionRequestKind = "write" - PermissionRequestKindRead PermissionRequestKind = "read" - PermissionRequestKindMcp PermissionRequestKind = "mcp" - PermissionRequestKindURL PermissionRequestKind = "url" - PermissionRequestKindMemory PermissionRequestKind = "memory" - PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" - PermissionRequestKindHook PermissionRequestKind = "hook" + UserMessageAttachmentTypeFile UserMessageAttachmentType = "file" + UserMessageAttachmentTypeDirectory UserMessageAttachmentType = "directory" + UserMessageAttachmentTypeSelection UserMessageAttachmentType = "selection" + UserMessageAttachmentTypeGithubReference UserMessageAttachmentType = "github_reference" + UserMessageAttachmentTypeBlob UserMessageAttachmentType = "blob" ) -// Whether this is a store or vote memory operation -type PermissionRequestMemoryAction string +// Type of GitHub reference +type UserMessageAttachmentGithubReferenceType string const ( - PermissionRequestMemoryActionStore PermissionRequestMemoryAction = "store" - PermissionRequestMemoryActionVote PermissionRequestMemoryAction = "vote" + UserMessageAttachmentGithubReferenceTypeIssue UserMessageAttachmentGithubReferenceType = "issue" + UserMessageAttachmentGithubReferenceTypePr UserMessageAttachmentGithubReferenceType = "pr" + UserMessageAttachmentGithubReferenceTypeDiscussion UserMessageAttachmentGithubReferenceType = "discussion" ) // Vote direction (vote only) @@ -2254,81 +2306,42 @@ const ( PermissionRequestMemoryDirectionDownvote PermissionRequestMemoryDirection = "downvote" ) -// The outcome of the permission request -type PermissionCompletedKind string - -const ( - PermissionCompletedKindApproved PermissionCompletedKind = "approved" - PermissionCompletedKindDeniedByRules PermissionCompletedKind = "denied-by-rules" - PermissionCompletedKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedKind = "denied-no-approval-rule-and-could-not-request-from-user" - PermissionCompletedKindDeniedInteractivelyByUser PermissionCompletedKind = "denied-interactively-by-user" - PermissionCompletedKindDeniedByContentExclusionPolicy PermissionCompletedKind = "denied-by-content-exclusion-policy" - PermissionCompletedKindDeniedByPermissionRequestHook PermissionCompletedKind = "denied-by-permission-request-hook" -) - -// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. -type ElicitationRequestedMode string - -const ( - ElicitationRequestedModeForm ElicitationRequestedMode = "form" - ElicitationRequestedModeURL ElicitationRequestedMode = "url" -) - -// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) -type ElicitationCompletedAction string - -const ( - ElicitationCompletedActionAccept ElicitationCompletedAction = "accept" - ElicitationCompletedActionDecline ElicitationCompletedAction = "decline" - ElicitationCompletedActionCancel ElicitationCompletedAction = "cancel" -) - -// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type McpServersLoadedServerStatus string +// Whether the agent completed successfully or failed +type SystemNotificationAgentCompletedStatus string const ( - McpServersLoadedServerStatusConnected McpServersLoadedServerStatus = "connected" - McpServersLoadedServerStatusFailed McpServersLoadedServerStatus = "failed" - McpServersLoadedServerStatusNeedsAuth McpServersLoadedServerStatus = "needs-auth" - McpServersLoadedServerStatusPending McpServersLoadedServerStatus = "pending" - McpServersLoadedServerStatusDisabled McpServersLoadedServerStatus = "disabled" - McpServersLoadedServerStatusNotConfigured McpServersLoadedServerStatus = "not_configured" + SystemNotificationAgentCompletedStatusCompleted SystemNotificationAgentCompletedStatus = "completed" + SystemNotificationAgentCompletedStatusFailed SystemNotificationAgentCompletedStatus = "failed" ) -// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type McpServerStatusChangedStatus string +// Whether the file was newly created or updated +type WorkspaceFileChangedOperation string const ( - McpServerStatusChangedStatusConnected McpServerStatusChangedStatus = "connected" - McpServerStatusChangedStatusFailed McpServerStatusChangedStatus = "failed" - McpServerStatusChangedStatusNeedsAuth McpServerStatusChangedStatus = "needs-auth" - McpServerStatusChangedStatusPending McpServerStatusChangedStatus = "pending" - McpServerStatusChangedStatusDisabled McpServerStatusChangedStatus = "disabled" - McpServerStatusChangedStatusNotConfigured McpServerStatusChangedStatus = "not_configured" + WorkspaceFileChangedOperationCreate WorkspaceFileChangedOperation = "create" + WorkspaceFileChangedOperationUpdate WorkspaceFileChangedOperation = "update" ) -// Discovery source -type ExtensionsLoadedExtensionSource string +// Whether the session ended normally ("routine") or due to a crash/fatal error ("error") +type ShutdownType string const ( - ExtensionsLoadedExtensionSourceProject ExtensionsLoadedExtensionSource = "project" - ExtensionsLoadedExtensionSourceUser ExtensionsLoadedExtensionSource = "user" + ShutdownTypeRoutine ShutdownType = "routine" + ShutdownTypeError ShutdownType = "error" ) -// Current status: running, disabled, failed, or starting -type ExtensionsLoadedExtensionStatus string +// Whether this is a store or vote memory operation +type PermissionRequestMemoryAction string const ( - ExtensionsLoadedExtensionStatusRunning ExtensionsLoadedExtensionStatus = "running" - ExtensionsLoadedExtensionStatusDisabled ExtensionsLoadedExtensionStatus = "disabled" - ExtensionsLoadedExtensionStatusFailed ExtensionsLoadedExtensionStatus = "failed" - ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting" + PermissionRequestMemoryActionStore PermissionRequestMemoryAction = "store" + PermissionRequestMemoryActionVote PermissionRequestMemoryAction = "vote" ) // Type aliases for convenience. type ( PermissionRequestCommand = PermissionRequestShellCommand - PossibleURL = PermissionRequestShellPossibleUrl + PossibleURL = PermissionRequestShellPossibleURL Attachment = UserMessageAttachment AttachmentType = UserMessageAttachmentType ) diff --git a/go/internal/e2e/compaction_test.go b/go/internal/e2e/compaction_test.go index c980e558d..a4c5471fc 100644 --- a/go/internal/e2e/compaction_test.go +++ b/go/internal/e2e/compaction_test.go @@ -9,6 +9,7 @@ import ( ) func TestCompaction(t *testing.T) { + t.Skip("Compaction tests are skipped due to flakiness — re-enable once stabilized") ctx := testharness.NewTestContext(t) client := ctx.NewClient() t.Cleanup(func() { client.ForceStop() }) diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go index 7fba219f7..05cbd23b4 100644 --- a/go/internal/e2e/session_fs_test.go +++ b/go/internal/e2e/session_fs_test.go @@ -17,7 +17,7 @@ import ( func TestSessionFs(t *testing.T) { ctx := testharness.NewTestContext(t) providerRoot := t.TempDir() - createSessionFsHandler := func(session *copilot.Session) rpc.SessionFsHandler { + createSessionFsHandler := func(session *copilot.Session) copilot.SessionFsProvider { return &testSessionFsHandler{ root: providerRoot, sessionID: session.SessionID, @@ -245,6 +245,90 @@ func TestSessionFs(t *testing.T) { t.Fatalf("Timed out waiting for checkpoint rewrite: %v", err) } }) + t.Run("should write workspace metadata via sessionFs", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + CreateSessionFsHandler: createSessionFsHandler, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "What is 7 * 8?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + content := "" + if msg != nil { + if d, ok := msg.Data.(*copilot.AssistantMessageData); ok { + content = d.Content + } + } + if !strings.Contains(content, "56") { + t.Fatalf("Expected response to contain 56, got %q", content) + } + + // WorkspaceManager should have created workspace.yaml via sessionFs + workspaceYamlPath := p(session.SessionID, "/session-state/workspace.yaml") + if err := waitForFile(workspaceYamlPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for workspace.yaml: %v", err) + } + yaml, err := os.ReadFile(workspaceYamlPath) + if err != nil { + t.Fatalf("Failed to read workspace.yaml: %v", err) + } + if !strings.Contains(string(yaml), "id:") { + t.Fatalf("Expected workspace.yaml to contain 'id:', got %q", string(yaml)) + } + + // Checkpoint index should also exist + indexPath := p(session.SessionID, "/session-state/checkpoints/index.md") + if err := waitForFile(indexPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for checkpoints/index.md: %v", err) + } + + if err := session.Disconnect(); err != nil { + t.Fatalf("Failed to disconnect session: %v", err) + } + }) + + t.Run("should persist plan.md via sessionFs", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + CreateSessionFsHandler: createSessionFsHandler, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Write a plan via the session RPC + if _, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "What is 2 + 3?"}); err != nil { + t.Fatalf("Failed to send message: %v", err) + } + if _, err := session.RPC.Plan.Update(t.Context(), &rpc.PlanUpdateRequest{Content: "# Test Plan\n\nThis is a test."}); err != nil { + t.Fatalf("Failed to update plan: %v", err) + } + + planPath := p(session.SessionID, "/session-state/plan.md") + if err := waitForFile(planPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for plan.md: %v", err) + } + planContent, err := os.ReadFile(planPath) + if err != nil { + t.Fatalf("Failed to read plan.md: %v", err) + } + if !strings.Contains(string(planContent), "# Test Plan") { + t.Fatalf("Expected plan.md to contain '# Test Plan', got %q", string(planContent)) + } + + if err := session.Disconnect(); err != nil { + t.Fatalf("Failed to disconnect session: %v", err) + } + }) } var sessionFsConfig = &copilot.SessionFsConfig{ @@ -258,65 +342,62 @@ type testSessionFsHandler struct { sessionID string } -func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileRequest) (*rpc.SessionFSReadFileResult, error) { - content, err := os.ReadFile(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) ReadFile(path string) (string, error) { + content, err := os.ReadFile(providerPath(h.root, h.sessionID, path)) if err != nil { - return nil, err + return "", err } - return &rpc.SessionFSReadFileResult{Content: string(content)}, nil + return string(content), nil } -func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSWriteFileResult, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) WriteFile(path string, content string, mode *int) error { + fullPath := providerPath(h.root, h.sessionID, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + return err } - mode := os.FileMode(0o666) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) + perm := os.FileMode(0o666) + if mode != nil { + perm = os.FileMode(*mode) } - return &rpc.SessionFSWriteFileResult{}, os.WriteFile(path, []byte(request.Content), mode) + return os.WriteFile(fullPath, []byte(content), perm) } -func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSAppendFileResult, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) AppendFile(path string, content string, mode *int) error { + fullPath := providerPath(h.root, h.sessionID, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + return err } - mode := os.FileMode(0o666) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) + perm := os.FileMode(0o666) + if mode != nil { + perm = os.FileMode(*mode) } - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, mode) + f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, perm) if err != nil { - return nil, err + return err } defer f.Close() - _, err = f.WriteString(request.Content) - if err != nil { - return nil, err - } - return &rpc.SessionFSAppendFileResult{}, nil + _, err = f.WriteString(content) + return err } -func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { - _, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Exists(path string) (bool, error) { + _, err := os.Stat(providerPath(h.root, h.sessionID, path)) if err == nil { - return &rpc.SessionFSExistsResult{Exists: true}, nil + return true, nil } if os.IsNotExist(err) { - return &rpc.SessionFSExistsResult{Exists: false}, nil + return false, nil } - return nil, err + return false, err } -func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.SessionFSStatResult, error) { - info, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Stat(path string) (*copilot.SessionFsFileInfo, error) { + info, err := os.Stat(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } ts := info.ModTime().UTC() - return &rpc.SessionFSStatResult{ + return &copilot.SessionFsFileInfo{ IsFile: !info.IsDir(), IsDirectory: info.IsDir(), Size: info.Size(), @@ -325,20 +406,20 @@ func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.Ses }, nil } -func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSMkdirResult, error) { - path := providerPath(h.root, h.sessionID, request.Path) - mode := os.FileMode(0o777) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) +func (h *testSessionFsHandler) Mkdir(path string, recursive bool, mode *int) error { + fullPath := providerPath(h.root, h.sessionID, path) + perm := os.FileMode(0o777) + if mode != nil { + perm = os.FileMode(*mode) } - if request.Recursive != nil && *request.Recursive { - return &rpc.SessionFSMkdirResult{}, os.MkdirAll(path, mode) + if recursive { + return os.MkdirAll(fullPath, perm) } - return &rpc.SessionFSMkdirResult{}, os.Mkdir(path, mode) + return os.Mkdir(fullPath, perm) } -func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { - entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Readdir(path string) ([]string, error) { + entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } @@ -346,11 +427,11 @@ func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*r for _, entry := range entries { names = append(names, entry.Name()) } - return &rpc.SessionFSReaddirResult{Entries: names}, nil + return names, nil } -func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesRequest) (*rpc.SessionFSReaddirWithTypesResult, error) { - entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) ReaddirWithTypes(path string) ([]rpc.SessionFSReaddirWithTypesEntry, error) { + entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } @@ -365,34 +446,29 @@ func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWit Type: entryType, }) } - return &rpc.SessionFSReaddirWithTypesResult{Entries: result}, nil + return result, nil } -func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSRmResult, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if request.Recursive != nil && *request.Recursive { - err := os.RemoveAll(path) - if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return &rpc.SessionFSRmResult{}, nil - } - return &rpc.SessionFSRmResult{}, err +func (h *testSessionFsHandler) Rm(path string, recursive bool, force bool) error { + fullPath := providerPath(h.root, h.sessionID, path) + var err error + if recursive { + err = os.RemoveAll(fullPath) + } else { + err = os.Remove(fullPath) } - err := os.Remove(path) - if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return &rpc.SessionFSRmResult{}, nil + if err != nil && force && os.IsNotExist(err) { + return nil } - return &rpc.SessionFSRmResult{}, err + return err } -func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSRenameResult, error) { - dest := providerPath(h.root, h.sessionID, request.Dest) - if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) Rename(src string, dest string) error { + destPath := providerPath(h.root, h.sessionID, dest) + if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil { + return err } - return &rpc.SessionFSRenameResult{}, os.Rename( - providerPath(h.root, h.sessionID, request.Src), - dest, - ) + return os.Rename(providerPath(h.root, h.sessionID, src), destPath) } func providerPath(root string, sessionID string, path string) string { diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 528a933b5..683fb2a5c 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -12,74 +12,273 @@ import ( "time" ) -type UIElicitationResponseContent map[string]*UIElicitationFieldValue +type RPCTypes struct { + AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` + AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` + AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` + AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` + AgentInfo AgentInfo `json:"AgentInfo"` + AgentList AgentList `json:"AgentList"` + AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` + AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` + AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` + CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` + CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` + CurrentModel CurrentModel `json:"CurrentModel"` + DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` + DiscoveredMCPServerSource MCPServerSource `json:"DiscoveredMcpServerSource"` + DiscoveredMCPServerType DiscoveredMCPServerType `json:"DiscoveredMcpServerType"` + Extension Extension `json:"Extension"` + ExtensionList ExtensionList `json:"ExtensionList"` + ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` + ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` + ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` + ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` + ExtensionSource ExtensionSource `json:"ExtensionSource"` + ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` + ExtensionStatus ExtensionStatus `json:"ExtensionStatus"` + FilterMapping *FilterMapping `json:"FilterMapping"` + FilterMappingString FilterMappingString `json:"FilterMappingString"` + FilterMappingValue FilterMappingString `json:"FilterMappingValue"` + FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` + FleetStartResult FleetStartResult `json:"FleetStartResult"` + HandleToolCallResult HandleToolCallResult `json:"HandleToolCallResult"` + HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` + HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` + HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` + HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` + InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` + InstructionsSources InstructionsSources `json:"InstructionsSources"` + InstructionsSourcesLocation InstructionsSourcesLocation `json:"InstructionsSourcesLocation"` + InstructionsSourcesType InstructionsSourcesType `json:"InstructionsSourcesType"` + LogRequest LogRequest `json:"LogRequest"` + LogResult LogResult `json:"LogResult"` + MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` + MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` + MCPConfigList MCPConfigList `json:"McpConfigList"` + MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` + MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` + MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` + MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` + MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` + MCPDisableResult MCPDisableResult `json:"McpDisableResult"` + MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` + MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` + MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` + MCPEnableResult MCPEnableResult `json:"McpEnableResult"` + MCPReloadResult MCPReloadResult `json:"McpReloadResult"` + MCPServer MCPServer `json:"McpServer"` + MCPServerConfig MCPServerConfig `json:"McpServerConfig"` + MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` + MCPServerConfigHTTPType MCPServerConfigHTTPType `json:"McpServerConfigHttpType"` + MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` + MCPServerConfigLocalType MCPServerConfigLocalType `json:"McpServerConfigLocalType"` + MCPServerList MCPServerList `json:"McpServerList"` + MCPServerSource MCPServerSource `json:"McpServerSource"` + MCPServerStatus MCPServerStatus `json:"McpServerStatus"` + Model ModelElement `json:"Model"` + ModelBilling ModelBilling `json:"ModelBilling"` + ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` + ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` + ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` + ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` + ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` + ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` + ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` + ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` + ModelList ModelList `json:"ModelList"` + ModelPolicy ModelPolicy `json:"ModelPolicy"` + ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` + ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` + ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` + ModeSetResult ModeSetResult `json:"ModeSetResult"` + NameGetResult NameGetResult `json:"NameGetResult"` + NameSetRequest NameSetRequest `json:"NameSetRequest"` + NameSetResult NameSetResult `json:"NameSetResult"` + PermissionDecision PermissionDecision `json:"PermissionDecision"` + PermissionDecisionApproved PermissionDecisionApproved `json:"PermissionDecisionApproved"` + PermissionDecisionDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicy `json:"PermissionDecisionDeniedByContentExclusionPolicy"` + PermissionDecisionDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHook `json:"PermissionDecisionDeniedByPermissionRequestHook"` + PermissionDecisionDeniedByRules PermissionDecisionDeniedByRules `json:"PermissionDecisionDeniedByRules"` + PermissionDecisionDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUser `json:"PermissionDecisionDeniedInteractivelyByUser"` + PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser `json:"PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"` + PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` + PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` + PingRequest PingRequest `json:"PingRequest"` + PingResult PingResult `json:"PingResult"` + PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` + PlanReadResult PlanReadResult `json:"PlanReadResult"` + PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` + PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` + Plugin PluginElement `json:"Plugin"` + PluginList PluginList `json:"PluginList"` + ServerSkill ServerSkill `json:"ServerSkill"` + ServerSkillList ServerSkillList `json:"ServerSkillList"` + SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` + SessionFSError SessionFSError `json:"SessionFsError"` + SessionFSErrorCode SessionFSErrorCode `json:"SessionFsErrorCode"` + SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` + SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` + SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` + SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` + SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` + SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` + SessionFSReaddirWithTypesEntryType SessionFSReaddirWithTypesEntryType `json:"SessionFsReaddirWithTypesEntryType"` + SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` + SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` + SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` + SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` + SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` + SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` + SessionFSSetProviderConventions SessionFSSetProviderConventions `json:"SessionFsSetProviderConventions"` + SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` + SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` + SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` + SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` + SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` + SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` + SessionMode SessionMode `json:"SessionMode"` + SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` + SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` + ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` + ShellExecResult ShellExecResult `json:"ShellExecResult"` + ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` + ShellKillResult ShellKillResult `json:"ShellKillResult"` + ShellKillSignal ShellKillSignal `json:"ShellKillSignal"` + Skill Skill `json:"Skill"` + SkillList SkillList `json:"SkillList"` + SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` + SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` + SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` + SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` + SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` + SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` + SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` + SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` + Tool Tool `json:"Tool"` + ToolCallResult ToolCallResult `json:"ToolCallResult"` + ToolList ToolList `json:"ToolList"` + ToolsHandlePendingToolCall *ToolsHandlePendingToolCall `json:"ToolsHandlePendingToolCall"` + ToolsHandlePendingToolCallRequest ToolsHandlePendingToolCallRequest `json:"ToolsHandlePendingToolCallRequest"` + ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` + UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` + UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` + UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` + UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` + UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` + UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` + UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` + UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` + UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` + UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` + UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` + UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` + UIElicitationSchemaProperty UIElicitationSchemaProperty `json:"UIElicitationSchemaProperty"` + UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` + UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` + UIElicitationSchemaPropertyNumberType UIElicitationSchemaPropertyNumberTypeEnum `json:"UIElicitationSchemaPropertyNumberType"` + UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` + UIElicitationSchemaPropertyStringFormat UIElicitationSchemaPropertyStringFormat `json:"UIElicitationSchemaPropertyStringFormat"` + UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` + UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` + UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` + UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` + UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` + UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` + UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` + UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` + UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` + WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` + WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` + WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` + WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` + WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` + WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` +} -// Model capabilities and limits -type ModelCapabilities struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` +type AccountGetQuotaResult struct { + // Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) + QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesLimits struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - // Maximum number of output/completion tokens - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - // Maximum number of prompt/input tokens - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - // Vision-specific limits - Vision *PurpleModelCapabilitiesLimitsVision `json:"vision,omitempty"` +type AccountQuotaSnapshot struct { + // Number of requests included in the entitlement + EntitlementRequests int64 `json:"entitlementRequests"` + // Whether the user has an unlimited usage entitlement + IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` + // Number of overage requests made this period + Overage float64 `json:"overage"` + // Whether overage is allowed when quota is exhausted + OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` + // Percentage of entitlement remaining + RemainingPercentage float64 `json:"remainingPercentage"` + // Date when the quota resets (ISO 8601 string) + ResetDate *string `json:"resetDate,omitempty"` + // Whether usage is still permitted after quota exhaustion + UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` + // Number of requests used so far this period + UsedRequests int64 `json:"usedRequests"` } -// Vision-specific limits -type PurpleModelCapabilitiesLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize int64 `json:"max_prompt_image_size"` - // Maximum number of images per prompt - MaxPromptImages int64 `json:"max_prompt_images"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types"` +// Experimental: AgentDeselectResult is part of an experimental API and may change or be removed. +type AgentDeselectResult struct { } -// Feature flags indicating what the model supports -type ModelCapabilitiesSupports struct { - // Whether this model supports reasoning effort configuration - ReasoningEffort *bool `json:"reasoningEffort,omitempty"` - // Whether this model supports vision/image input - Vision *bool `json:"vision,omitempty"` +// Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. +type AgentGetCurrentResult struct { + // Currently selected custom agent, or null if using the default agent + Agent *AgentInfo `json:"agent"` } -// Vision-specific limits -type ModelCapabilitiesLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize int64 `json:"max_prompt_image_size"` - // Maximum number of images per prompt - MaxPromptImages int64 `json:"max_prompt_images"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types"` +// The newly selected custom agent +type AgentInfo struct { + // Description of the agent's purpose + Description string `json:"description"` + // Human-readable display name + DisplayName string `json:"displayName"` + // Unique identifier of the custom agent + Name string `json:"name"` } -// MCP server configuration (local/stdio or remote/http) -type MCPServerConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - // Timeout in milliseconds for tool calls to this server. - Timeout *int64 `json:"timeout,omitempty"` - // Tools to include. Defaults to all tools if not specified. - Tools []string `json:"tools,omitempty"` - // Remote transport type. Defaults to "http" when omitted. - Type *MCPServerConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` +// Experimental: AgentList is part of an experimental API and may change or be removed. +type AgentList struct { + // Available custom agents + Agents []AgentInfo `json:"agents"` +} + +// Experimental: AgentReloadResult is part of an experimental API and may change or be removed. +type AgentReloadResult struct { + // Reloaded custom agents + Agents []AgentInfo `json:"agents"` +} + +// Experimental: AgentSelectRequest is part of an experimental API and may change or be removed. +type AgentSelectRequest struct { + // Name of the custom agent to select + Name string `json:"name"` +} + +// Experimental: AgentSelectResult is part of an experimental API and may change or be removed. +type AgentSelectResult struct { + // The newly selected custom agent + Agent AgentInfo `json:"agent"` +} + +type CommandsHandlePendingCommandRequest struct { + // Error message if the command handler failed + Error *string `json:"error,omitempty"` + // Request ID from the command invocation event + RequestID string `json:"requestId"` +} + +type CommandsHandlePendingCommandResult struct { + // Whether the command was handled successfully + Success bool `json:"success"` +} + +type CurrentModel struct { + // Currently active model identifier + ModelID *string `json:"modelId,omitempty"` } type DiscoveredMCPServer struct { @@ -93,262 +292,287 @@ type DiscoveredMCPServer struct { Type *DiscoveredMCPServerType `json:"type,omitempty"` } -type ServerSkillList struct { - // All discovered skills across all sources - Skills []SkillElement `json:"skills"` +type Extension struct { + // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') + ID string `json:"id"` + // Extension name (directory name) + Name string `json:"name"` + // Process ID if the extension is running + PID *int64 `json:"pid,omitempty"` + // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) + Source ExtensionSource `json:"source"` + // Current status: running, disabled, failed, or starting + Status ExtensionStatus `json:"status"` } -type SkillElement struct { - // Description of what the skill does - Description string `json:"description"` - // Whether the skill is currently enabled (based on global config) - Enabled bool `json:"enabled"` - // Unique identifier for the skill - Name string `json:"name"` - // Absolute path to the skill file - Path *string `json:"path,omitempty"` - // The project path this skill belongs to (only for project/inherited skills) - ProjectPath *string `json:"projectPath,omitempty"` - // Source location type (e.g., project, personal-copilot, plugin, builtin) - Source string `json:"source"` - // Whether the skill can be invoked by the user as a slash command - UserInvocable bool `json:"userInvocable"` +// Experimental: ExtensionList is part of an experimental API and may change or be removed. +type ExtensionList struct { + // Discovered extensions and their current status + Extensions []Extension `json:"extensions"` } -type ServerSkill struct { - // Description of what the skill does - Description string `json:"description"` - // Whether the skill is currently enabled (based on global config) - Enabled bool `json:"enabled"` - // Unique identifier for the skill - Name string `json:"name"` - // Absolute path to the skill file - Path *string `json:"path,omitempty"` - // The project path this skill belongs to (only for project/inherited skills) - ProjectPath *string `json:"projectPath,omitempty"` - // Source location type (e.g., project, personal-copilot, plugin, builtin) - Source string `json:"source"` - // Whether the skill can be invoked by the user as a slash command - UserInvocable bool `json:"userInvocable"` +// Experimental: ExtensionsDisableRequest is part of an experimental API and may change or be removed. +type ExtensionsDisableRequest struct { + // Source-qualified extension ID to disable + ID string `json:"id"` } -type CurrentModel struct { - // Currently active model identifier - ModelID *string `json:"modelId,omitempty"` +// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be removed. +type ExtensionsDisableResult struct { } -// Override individual model capabilities resolved by the runtime -type ModelCapabilitiesOverride struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` +// Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be removed. +type ExtensionsEnableRequest struct { + // Source-qualified extension ID to enable + ID string `json:"id"` } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesOverrideLimits struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - Vision *PurpleModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` +// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be removed. +type ExtensionsEnableResult struct { } -type PurpleModelCapabilitiesOverrideLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` - // Maximum number of images per prompt - MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types,omitempty"` +// Experimental: ExtensionsReloadResult is part of an experimental API and may change or be removed. +type ExtensionsReloadResult struct { } -// Feature flags indicating what the model supports -type ModelCapabilitiesOverrideSupports struct { - ReasoningEffort *bool `json:"reasoningEffort,omitempty"` - Vision *bool `json:"vision,omitempty"` +// Experimental: FleetStartRequest is part of an experimental API and may change or be removed. +type FleetStartRequest struct { + // Optional user prompt to combine with fleet instructions + Prompt *string `json:"prompt,omitempty"` } -type AgentInfo struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` +// Experimental: FleetStartResult is part of an experimental API and may change or be removed. +type FleetStartResult struct { + // Whether fleet mode was successfully activated + Started bool `json:"started"` } -type MCPServerList struct { - // Configured MCP servers - Servers []MCPServer `json:"servers"` +type HandleToolCallResult struct { + // Whether the tool call result was handled successfully + Success bool `json:"success"` } -type MCPServer struct { - // Error message if the server failed to connect - Error *string `json:"error,omitempty"` - // Server name (config key) +// Post-compaction context window usage breakdown +type HistoryCompactContextWindow struct { + // Token count from non-system messages (user, assistant, tool) + ConversationTokens *int64 `json:"conversationTokens,omitempty"` + // Current total tokens in the context window (system + conversation + tool definitions) + CurrentTokens int64 `json:"currentTokens"` + // Current number of messages in the conversation + MessagesLength int64 `json:"messagesLength"` + // Token count from system message(s) + SystemTokens *int64 `json:"systemTokens,omitempty"` + // Maximum token count for the model's context window + TokenLimit int64 `json:"tokenLimit"` + // Token count from tool definitions + ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` +} + +// Experimental: HistoryCompactResult is part of an experimental API and may change or be removed. +type HistoryCompactResult struct { + // Post-compaction context window usage breakdown + ContextWindow *HistoryCompactContextWindow `json:"contextWindow,omitempty"` + // Number of messages removed during compaction + MessagesRemoved int64 `json:"messagesRemoved"` + // Whether compaction completed successfully + Success bool `json:"success"` + // Number of tokens freed by compaction + TokensRemoved int64 `json:"tokensRemoved"` +} + +// Experimental: HistoryTruncateRequest is part of an experimental API and may change or be removed. +type HistoryTruncateRequest struct { + // Event ID to truncate to. This event and all events after it are removed from the session. + EventID string `json:"eventId"` +} + +// Experimental: HistoryTruncateResult is part of an experimental API and may change or be removed. +type HistoryTruncateResult struct { + // Number of events that were removed + EventsRemoved int64 `json:"eventsRemoved"` +} + +type InstructionsGetSourcesResult struct { + // Instruction sources for the session + Sources []InstructionsSources `json:"sources"` +} + +type InstructionsSources struct { + // Glob pattern from frontmatter — when set, this instruction applies only to matching files + ApplyTo *string `json:"applyTo,omitempty"` + // Raw content of the instruction file + Content string `json:"content"` + // Short description (body after frontmatter) for use in instruction tables + Description *string `json:"description,omitempty"` + // Unique identifier for this source (used for toggling) + ID string `json:"id"` + // Human-readable label + Label string `json:"label"` + // Where this source lives — used for UI grouping + Location InstructionsSourcesLocation `json:"location"` + // File path relative to repo or absolute for home + SourcePath string `json:"sourcePath"` + // Category of instruction source — used for merge logic + Type InstructionsSourcesType `json:"type"` +} + +type LogRequest struct { + // When true, the message is transient and not persisted to the session event log on disk + Ephemeral *bool `json:"ephemeral,omitempty"` + // Log severity level. Determines how the message is displayed in the timeline. Defaults to + // "info". + Level *SessionLogLevel `json:"level,omitempty"` + // Human-readable message + Message string `json:"message"` + // Optional URL the user can open in their browser for more details + URL *string `json:"url,omitempty"` +} + +type LogResult struct { + // The unique identifier of the emitted session event + EventID string `json:"eventId"` +} + +type MCPConfigAddRequest struct { + // MCP server configuration (local/stdio or remote/http) + Config MCPServerConfig `json:"config"` + // Unique name for the MCP server Name string `json:"name"` - // Configuration source: user, workspace, plugin, or builtin - Source *MCPServerSource `json:"source,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status MCPServerStatus `json:"status"` } -type ToolCallResult struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Type of the tool result - ResultType *string `json:"resultType,omitempty"` - // Text result to send back to the LLM - TextResultForLlm string `json:"textResultForLlm"` - // Telemetry data from tool execution - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` +// MCP server configuration (local/stdio or remote/http) +type MCPServerConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + // Remote transport type. Defaults to "http" when omitted. + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` } -type HandleToolCallResult struct { - // Whether the tool call result was handled successfully - Success bool `json:"success"` +type MCPConfigAddResult struct { } -type UIElicitationStringEnumField struct { - Default *string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Enum []string `json:"enum"` - EnumNames []string `json:"enumNames,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationStringEnumFieldType `json:"type"` +type MCPConfigList struct { + // All MCP servers from user config, keyed by name + Servers map[string]MCPServerConfig `json:"servers"` } -type UIElicitationStringOneOfField struct { - Default *string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf"` - Title *string `json:"title,omitempty"` - Type UIElicitationStringEnumFieldType `json:"type"` +type MCPConfigRemoveRequest struct { + // Name of the MCP server to remove + Name string `json:"name"` } -type UIElicitationStringOneOfFieldOneOf struct { - Const string `json:"const"` - Title string `json:"title"` +type MCPConfigRemoveResult struct { } -type UIElicitationArrayEnumField struct { - Default []string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Items UIElicitationArrayEnumFieldItems `json:"items"` - MaxItems *float64 `json:"maxItems,omitempty"` - MinItems *float64 `json:"minItems,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationArrayEnumFieldType `json:"type"` +type MCPConfigUpdateRequest struct { + // MCP server configuration (local/stdio or remote/http) + Config MCPServerConfig `json:"config"` + // Name of the MCP server to update + Name string `json:"name"` } -type UIElicitationArrayEnumFieldItems struct { - Enum []string `json:"enum"` - Type UIElicitationStringEnumFieldType `json:"type"` +type MCPConfigUpdateResult struct { } -type UIElicitationArrayAnyOfField struct { - Default []string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Items UIElicitationArrayAnyOfFieldItems `json:"items"` - MaxItems *float64 `json:"maxItems,omitempty"` - MinItems *float64 `json:"minItems,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationArrayEnumFieldType `json:"type"` +type MCPDisableRequest struct { + // Name of the MCP server to disable + ServerName string `json:"serverName"` } -type UIElicitationArrayAnyOfFieldItems struct { - AnyOf []PurpleUIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf"` +type MCPDisableResult struct { } -type PurpleUIElicitationArrayAnyOfFieldItemsAnyOf struct { - Const string `json:"const"` - Title string `json:"title"` +type MCPDiscoverRequest struct { + // Working directory used as context for discovery (e.g., plugin resolution) + WorkingDirectory *string `json:"workingDirectory,omitempty"` } -// The elicitation response (accept with form values, decline, or cancel) -type UIElicitationResponse struct { - // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - Action UIElicitationResponseAction `json:"action"` - // The form values submitted by the user (present when action is 'accept') - Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` +type MCPDiscoverResult struct { + // MCP servers discovered from all sources + Servers []DiscoveredMCPServer `json:"servers"` } -type UIHandlePendingElicitationRequest struct { - // The unique request ID from the elicitation.requested event - RequestID string `json:"requestId"` - // The elicitation response (accept with form values, decline, or cancel) - Result UIElicitationResponse `json:"result"` +type MCPEnableRequest struct { + // Name of the MCP server to enable + ServerName string `json:"serverName"` } -type UIElicitationResult struct { - // Whether the response was accepted. False if the request was already resolved by another - // client. - Success bool `json:"success"` +type MCPEnableResult struct { } -type PermissionDecisionRequest struct { - // Request ID of the pending permission request - RequestID string `json:"requestId"` - Result PermissionDecision `json:"result"` +type MCPReloadResult struct { } -type PermissionDecision struct { - // The permission request was approved - // - // Denied because approval rules explicitly blocked it - // - // Denied because no approval rule matched and user confirmation was unavailable - // - // Denied by the user during an interactive prompt - // - // Denied by the organization's content exclusion policy - // - // Denied by a permission request hook registered by an extension or plugin - Kind Kind `json:"kind"` - // Rules that denied the request - Rules []any `json:"rules,omitempty"` - // Optional feedback from the user explaining the denial - Feedback *string `json:"feedback,omitempty"` - // Human-readable explanation of why the path was excluded - // - // Optional message from the hook explaining the denial - Message *string `json:"message,omitempty"` - // File path that triggered the exclusion - Path *string `json:"path,omitempty"` - // Whether to interrupt the current agent turn - Interrupt *bool `json:"interrupt,omitempty"` +type MCPServer struct { + // Error message if the server failed to connect + Error *string `json:"error,omitempty"` + // Server name (config key) + Name string `json:"name"` + // Configuration source: user, workspace, plugin, or builtin + Source *MCPServerSource `json:"source,omitempty"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status MCPServerStatus `json:"status"` } -type PermissionRequestResult struct { - // Whether the permission request was handled successfully - Success bool `json:"success"` +type MCPServerConfigHTTP struct { + FilterMapping *FilterMapping `json:"filterMapping"` + Headers map[string]string `json:"headers,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + // Remote transport type. Defaults to "http" when omitted. + Type *MCPServerConfigHTTPType `json:"type,omitempty"` + URL string `json:"url"` } -type PingResult struct { - // Echoed message (or default greeting) - Message string `json:"message"` - // Server protocol version number - ProtocolVersion int64 `json:"protocolVersion"` - // Server timestamp in milliseconds - Timestamp int64 `json:"timestamp"` +type MCPServerConfigLocal struct { + Args []string `json:"args"` + Command string `json:"command"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + Type *MCPServerConfigLocalType `json:"type,omitempty"` } -type PingRequest struct { - // Optional message to echo back - Message *string `json:"message,omitempty"` +type MCPServerList struct { + // Configured MCP servers + Servers []MCPServer `json:"servers"` } -type ModelList struct { - // List of available models with full metadata - Models []ModelElement `json:"models"` +type ModeSetRequest struct { + // The agent mode. Valid values: "interactive", "plan", "autopilot". + Mode SessionMode `json:"mode"` +} + +type ModeSetResult struct { } type ModelElement struct { // Billing information Billing *ModelBilling `json:"billing,omitempty"` // Model capabilities and limits - Capabilities CapabilitiesClass `json:"capabilities"` + Capabilities ModelCapabilities `json:"capabilities"` // Default reasoning effort level (only present if model supports reasoning effort) DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` // Model identifier (e.g., "claude-sonnet-4.5") @@ -368,15 +592,15 @@ type ModelBilling struct { } // Model capabilities and limits -type CapabilitiesClass struct { +type ModelCapabilities struct { // Token limits for prompts, outputs, and context window - Limits *CapabilitiesLimits `json:"limits,omitempty"` + Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` // Feature flags indicating what the model supports - Supports *CapabilitiesSupports `json:"supports,omitempty"` + Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` } // Token limits for prompts, outputs, and context window -type CapabilitiesLimits struct { +type ModelCapabilitiesLimits struct { // Maximum total context window size in tokens MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` // Maximum number of output/completion tokens @@ -384,11 +608,11 @@ type CapabilitiesLimits struct { // Maximum number of prompt/input tokens MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` // Vision-specific limits - Vision *FluffyModelCapabilitiesLimitsVision `json:"vision,omitempty"` + Vision *ModelCapabilitiesLimitsVision `json:"vision,omitempty"` } // Vision-specific limits -type FluffyModelCapabilitiesLimitsVision struct { +type ModelCapabilitiesLimitsVision struct { // Maximum image size in bytes MaxPromptImageSize int64 `json:"max_prompt_image_size"` // Maximum number of images per prompt @@ -398,7 +622,7 @@ type FluffyModelCapabilitiesLimitsVision struct { } // Feature flags indicating what the model supports -type CapabilitiesSupports struct { +type ModelCapabilitiesSupports struct { // Whether this model supports reasoning effort configuration ReasoningEffort *bool `json:"reasoningEffort,omitempty"` // Whether this model supports vision/image input @@ -413,268 +637,165 @@ type ModelPolicy struct { Terms string `json:"terms"` } -type ToolList struct { - // List of available built-in tools with metadata - Tools []Tool `json:"tools"` -} - -type Tool struct { - // Description of what the tool does - Description string `json:"description"` - // Optional instructions for how to use this tool effectively - Instructions *string `json:"instructions,omitempty"` - // Tool identifier (e.g., "bash", "grep", "str_replace_editor") - Name string `json:"name"` - // Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP - // tools) - NamespacedName *string `json:"namespacedName,omitempty"` - // JSON Schema for the tool's input parameters - Parameters map[string]any `json:"parameters,omitempty"` -} - -type ToolsListRequest struct { - // Optional model ID — when provided, the returned tool list reflects model-specific - // overrides - Model *string `json:"model,omitempty"` -} - -type AccountGetQuotaResult struct { - // Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) - QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` -} - -type AccountQuotaSnapshot struct { - // Number of requests included in the entitlement - EntitlementRequests int64 `json:"entitlementRequests"` - // Number of overage requests made this period - Overage int64 `json:"overage"` - // Whether pay-per-request usage is allowed when quota is exhausted - OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` - // Percentage of entitlement remaining - RemainingPercentage float64 `json:"remainingPercentage"` - // Date when the quota resets (ISO 8601) - ResetDate *time.Time `json:"resetDate,omitempty"` - // Number of requests used so far this period - UsedRequests int64 `json:"usedRequests"` -} - -type MCPConfigList struct { - // All MCP servers from user config, keyed by name - Servers map[string]MCPServerConfigValue `json:"servers"` -} - -// MCP server configuration (local/stdio or remote/http) -type MCPServerConfigValue struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - // Timeout in milliseconds for tool calls to this server. - Timeout *int64 `json:"timeout,omitempty"` - // Tools to include. Defaults to all tools if not specified. - Tools []string `json:"tools,omitempty"` - // Remote transport type. Defaults to "http" when omitted. - Type *MCPServerConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` -} - -type MCPConfigAddResult struct { -} - -type MCPConfigAddRequest struct { - // MCP server configuration (local/stdio or remote/http) - Config MCPConfigAddRequestMCPServerConfig `json:"config"` - // Unique name for the MCP server - Name string `json:"name"` -} - -// MCP server configuration (local/stdio or remote/http) -type MCPConfigAddRequestMCPServerConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - // Timeout in milliseconds for tool calls to this server. - Timeout *int64 `json:"timeout,omitempty"` - // Tools to include. Defaults to all tools if not specified. - Tools []string `json:"tools,omitempty"` - // Remote transport type. Defaults to "http" when omitted. - Type *MCPServerConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` -} - -type MCPConfigUpdateResult struct { -} - -type MCPConfigUpdateRequest struct { - // MCP server configuration (local/stdio or remote/http) - Config MCPConfigUpdateRequestMCPServerConfig `json:"config"` - // Name of the MCP server to update - Name string `json:"name"` -} - -// MCP server configuration (local/stdio or remote/http) -type MCPConfigUpdateRequestMCPServerConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - // Timeout in milliseconds for tool calls to this server. - Timeout *int64 `json:"timeout,omitempty"` - // Tools to include. Defaults to all tools if not specified. - Tools []string `json:"tools,omitempty"` - // Remote transport type. Defaults to "http" when omitted. - Type *MCPServerConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` -} - -type MCPConfigRemoveResult struct { -} - -type MCPConfigRemoveRequest struct { - // Name of the MCP server to remove - Name string `json:"name"` -} - -type MCPDiscoverResult struct { - // MCP servers discovered from all sources - Servers []ServerElement `json:"servers"` -} - -type ServerElement struct { - // Whether the server is enabled (not in the disabled list) - Enabled bool `json:"enabled"` - // Server name (config key) - Name string `json:"name"` - // Configuration source - Source MCPServerSource `json:"source"` - // Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) - Type *DiscoveredMCPServerType `json:"type,omitempty"` -} - -type MCPDiscoverRequest struct { - // Working directory used as context for discovery (e.g., plugin resolution) - WorkingDirectory *string `json:"workingDirectory,omitempty"` -} - -type SkillsConfigSetDisabledSkillsResult struct { -} - -type SkillsConfigSetDisabledSkillsRequest struct { - // List of skill names to disable - DisabledSkills []string `json:"disabledSkills"` -} - -type SkillsDiscoverRequest struct { - // Optional list of project directory paths to scan for project-scoped skills - ProjectPaths []string `json:"projectPaths,omitempty"` - // Optional list of additional skill directory paths to include - SkillDirectories []string `json:"skillDirectories,omitempty"` -} - -type SessionFSSetProviderResult struct { - // Whether the provider was set successfully - Success bool `json:"success"` +// Override individual model capabilities resolved by the runtime +type ModelCapabilitiesOverride struct { + // Token limits for prompts, outputs, and context window + Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` + // Feature flags indicating what the model supports + Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` } -type SessionFSSetProviderRequest struct { - // Path conventions used by this filesystem - Conventions SessionFSSetProviderConventions `json:"conventions"` - // Initial working directory for sessions - InitialCwd string `json:"initialCwd"` - // Path within each session's SessionFs where the runtime stores files for that session - SessionStatePath string `json:"sessionStatePath"` +// Token limits for prompts, outputs, and context window +type ModelCapabilitiesOverrideLimits struct { + // Maximum total context window size in tokens + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` + Vision *ModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` } -// Experimental: SessionsForkResult is part of an experimental API and may change or be removed. -type SessionsForkResult struct { - // The new forked session's ID - SessionID string `json:"sessionId"` +type ModelCapabilitiesOverrideLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` + // Maximum number of images per prompt + MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types,omitempty"` } -// Experimental: SessionsForkRequest is part of an experimental API and may change or be removed. -type SessionsForkRequest struct { - // Source session ID to fork from - SessionID string `json:"sessionId"` - // Optional event ID boundary. When provided, the fork includes only events before this ID - // (exclusive). When omitted, all events are included. - ToEventID *string `json:"toEventId,omitempty"` +// Feature flags indicating what the model supports +type ModelCapabilitiesOverrideSupports struct { + ReasoningEffort *bool `json:"reasoningEffort,omitempty"` + Vision *bool `json:"vision,omitempty"` } -type ModelSwitchToResult struct { - // Currently active model identifier after the switch - ModelID *string `json:"modelId,omitempty"` +type ModelList struct { + // List of available models with full metadata + Models []ModelElement `json:"models"` } type ModelSwitchToRequest struct { // Override individual model capabilities resolved by the runtime - ModelCapabilities *ModelCapabilitiesClass `json:"modelCapabilities,omitempty"` + ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` // Model identifier to switch to ModelID string `json:"modelId"` // Reasoning effort level to use for the model ReasoningEffort *string `json:"reasoningEffort,omitempty"` } -// Override individual model capabilities resolved by the runtime -type ModelCapabilitiesClass struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesLimitsClass `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` +type ModelSwitchToResult struct { + // Currently active model identifier after the switch + ModelID *string `json:"modelId,omitempty"` } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesLimitsClass struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - Vision *FluffyModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` +type NameGetResult struct { + // The session name, falling back to the auto-generated summary, or null if neither exists + Name *string `json:"name"` } -type FluffyModelCapabilitiesOverrideLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` - // Maximum number of images per prompt - MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types,omitempty"` +type NameSetRequest struct { + // New session name (1–100 characters, trimmed of leading/trailing whitespace) + Name string `json:"name"` } -type ModeSetResult struct { +type NameSetResult struct { } -type ModeSetRequest struct { - // The agent mode. Valid values: "interactive", "plan", "autopilot". - Mode SessionMode `json:"mode"` +type PermissionDecision struct { + // The permission request was approved + // + // Denied because approval rules explicitly blocked it + // + // Denied because no approval rule matched and user confirmation was unavailable + // + // Denied by the user during an interactive prompt + // + // Denied by the organization's content exclusion policy + // + // Denied by a permission request hook registered by an extension or plugin + Kind PermissionDecisionKind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules,omitempty"` + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Human-readable explanation of why the path was excluded + // + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` + // File path that triggered the exclusion + Path *string `json:"path,omitempty"` + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` } -type NameGetResult struct { - // The session name, falling back to the auto-generated summary, or null if neither exists - Name *string `json:"name"` +type PermissionDecisionApproved struct { + // The permission request was approved + Kind PermissionDecisionApprovedKind `json:"kind"` } -type NameSetResult struct { +type PermissionDecisionDeniedByContentExclusionPolicy struct { + // Denied by the organization's content exclusion policy + Kind PermissionDecisionDeniedByContentExclusionPolicyKind `json:"kind"` + // Human-readable explanation of why the path was excluded + Message string `json:"message"` + // File path that triggered the exclusion + Path string `json:"path"` } -type NameSetRequest struct { - // New session name (1–100 characters, trimmed of leading/trailing whitespace) - Name string `json:"name"` +type PermissionDecisionDeniedByPermissionRequestHook struct { + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` + // Denied by a permission request hook registered by an extension or plugin + Kind PermissionDecisionDeniedByPermissionRequestHookKind `json:"kind"` + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` +} + +type PermissionDecisionDeniedByRules struct { + // Denied because approval rules explicitly blocked it + Kind PermissionDecisionDeniedByRulesKind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules"` +} + +type PermissionDecisionDeniedInteractivelyByUser struct { + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Denied by the user during an interactive prompt + Kind PermissionDecisionDeniedInteractivelyByUserKind `json:"kind"` +} + +type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser struct { + // Denied because no approval rule matched and user confirmation was unavailable + Kind PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind `json:"kind"` +} + +type PermissionDecisionRequest struct { + // Request ID of the pending permission request + RequestID string `json:"requestId"` + Result PermissionDecision `json:"result"` +} + +type PermissionRequestResult struct { + // Whether the permission request was handled successfully + Success bool `json:"success"` +} + +type PingRequest struct { + // Optional message to echo back + Message *string `json:"message,omitempty"` +} + +type PingResult struct { + // Echoed message (or default greeting) + Message string `json:"message"` + // Server protocol version number + ProtocolVersion int64 `json:"protocolVersion"` + // Server timestamp in milliseconds + Timestamp int64 `json:"timestamp"` +} + +type PlanDeleteResult struct { } type PlanReadResult struct { @@ -686,169 +807,250 @@ type PlanReadResult struct { Path *string `json:"path"` } -type PlanUpdateResult struct { -} - type PlanUpdateRequest struct { // The new content for the plan file Content string `json:"content"` } -type PlanDeleteResult struct { +type PlanUpdateResult struct { } -type WorkspacesGetWorkspaceResult struct { - // Current workspace metadata, or null if not available - Workspace *WorkspaceClass `json:"workspace"` +type PluginElement struct { + // Whether the plugin is currently enabled + Enabled bool `json:"enabled"` + // Marketplace the plugin came from + Marketplace string `json:"marketplace"` + // Plugin name + Name string `json:"name"` + // Installed version + Version *string `json:"version,omitempty"` } -type WorkspaceClass struct { - Branch *string `json:"branch,omitempty"` - ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Cwd *string `json:"cwd,omitempty"` - GitRoot *string `json:"git_root,omitempty"` - HostType *HostType `json:"host_type,omitempty"` - ID string `json:"id"` - McLastEventID *string `json:"mc_last_event_id,omitempty"` - McSessionID *string `json:"mc_session_id,omitempty"` - McTaskID *string `json:"mc_task_id,omitempty"` - Name *string `json:"name,omitempty"` - PRCreateSyncDismissed *bool `json:"pr_create_sync_dismissed,omitempty"` - Repository *string `json:"repository,omitempty"` - SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` - Summary *string `json:"summary,omitempty"` - SummaryCount *int64 `json:"summary_count,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` +// Experimental: PluginList is part of an experimental API and may change or be removed. +type PluginList struct { + // Installed plugins + Plugins []PluginElement `json:"plugins"` } -type WorkspacesListFilesResult struct { - // Relative file paths in the workspace files directory - Files []string `json:"files"` +type ServerSkill struct { + // Description of what the skill does + Description string `json:"description"` + // Whether the skill is currently enabled (based on global config) + Enabled bool `json:"enabled"` + // Unique identifier for the skill + Name string `json:"name"` + // Absolute path to the skill file + Path *string `json:"path,omitempty"` + // The project path this skill belongs to (only for project/inherited skills) + ProjectPath *string `json:"projectPath,omitempty"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source string `json:"source"` + // Whether the skill can be invoked by the user as a slash command + UserInvocable bool `json:"userInvocable"` } -type WorkspacesReadFileResult struct { - // File content as a UTF-8 string +type ServerSkillList struct { + // All discovered skills across all sources + Skills []ServerSkill `json:"skills"` +} + +type SessionFSAppendFileRequest struct { + // Content to append Content string `json:"content"` + // Optional POSIX-style mode for newly created files + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type WorkspacesReadFileRequest struct { - // Relative path within the workspace files directory +// Describes a filesystem error. +type SessionFSError struct { + // Error classification + Code SessionFSErrorCode `json:"code"` + // Free-form detail about the error, for logging/diagnostics + Message *string `json:"message,omitempty"` +} + +type SessionFSExistsRequest struct { + // Path using SessionFs conventions Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type WorkspacesCreateFileResult struct { +type SessionFSExistsResult struct { + // Whether the path exists + Exists bool `json:"exists"` } -type WorkspacesCreateFileRequest struct { - // File content to write as a UTF-8 string - Content string `json:"content"` - // Relative path within the workspace files directory +type SessionFSMkdirRequest struct { + // Optional POSIX-style mode for newly created directories + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions Path string `json:"path"` + // Create parent directories as needed + Recursive *bool `json:"recursive,omitempty"` + // Target session identifier + SessionID string `json:"sessionId"` } -type InstructionsGetSourcesResult struct { - // Instruction sources for the session - Sources []InstructionsSources `json:"sources"` +type SessionFSReadFileRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type InstructionsSources struct { - // Glob pattern from frontmatter — when set, this instruction applies only to matching files - ApplyTo *string `json:"applyTo,omitempty"` - // Raw content of the instruction file +type SessionFSReadFileResult struct { + // File content as UTF-8 string Content string `json:"content"` - // Short description (body after frontmatter) for use in instruction tables - Description *string `json:"description,omitempty"` - // Unique identifier for this source (used for toggling) - ID string `json:"id"` - // Human-readable label - Label string `json:"label"` - // Where this source lives — used for UI grouping - Location InstructionsSourcesLocation `json:"location"` - // File path relative to repo or absolute for home - SourcePath string `json:"sourcePath"` - // Category of instruction source — used for merge logic - Type InstructionsSourcesType `json:"type"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` +} + +type SessionFSReaddirRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` +} + +type SessionFSReaddirResult struct { + // Entry names in the directory + Entries []string `json:"entries"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` +} + +type SessionFSReaddirWithTypesEntry struct { + // Entry name + Name string `json:"name"` + // Entry type + Type SessionFSReaddirWithTypesEntryType `json:"type"` +} + +type SessionFSReaddirWithTypesRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` +} + +type SessionFSReaddirWithTypesResult struct { + // Directory entries with type information + Entries []SessionFSReaddirWithTypesEntry `json:"entries"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` } -// Experimental: FleetStartResult is part of an experimental API and may change or be removed. -type FleetStartResult struct { - // Whether fleet mode was successfully activated - Started bool `json:"started"` +type SessionFSRenameRequest struct { + // Destination path using SessionFs conventions + Dest string `json:"dest"` + // Target session identifier + SessionID string `json:"sessionId"` + // Source path using SessionFs conventions + Src string `json:"src"` } -// Experimental: FleetStartRequest is part of an experimental API and may change or be removed. -type FleetStartRequest struct { - // Optional user prompt to combine with fleet instructions - Prompt *string `json:"prompt,omitempty"` +type SessionFSRmRequest struct { + // Ignore errors if the path does not exist + Force *bool `json:"force,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Remove directories and their contents recursively + Recursive *bool `json:"recursive,omitempty"` + // Target session identifier + SessionID string `json:"sessionId"` } -// Experimental: AgentList is part of an experimental API and may change or be removed. -type AgentList struct { - // Available custom agents - Agents []AgentListAgent `json:"agents"` +type SessionFSSetProviderRequest struct { + // Path conventions used by this filesystem + Conventions SessionFSSetProviderConventions `json:"conventions"` + // Initial working directory for sessions + InitialCwd string `json:"initialCwd"` + // Path within each session's SessionFs where the runtime stores files for that session + SessionStatePath string `json:"sessionStatePath"` } -type AgentListAgent struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` +type SessionFSSetProviderResult struct { + // Whether the provider was set successfully + Success bool `json:"success"` } -// Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. -type AgentGetCurrentResult struct { - // Currently selected custom agent, or null if using the default agent - Agent *AgentReloadResultAgent `json:"agent"` +type SessionFSStatRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -// Experimental: AgentSelectResult is part of an experimental API and may change or be removed. -type AgentSelectResult struct { - // The newly selected custom agent - Agent AgentSelectResultAgent `json:"agent"` +type SessionFSStatResult struct { + // ISO 8601 timestamp of creation + Birthtime time.Time `json:"birthtime"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` + // Whether the path is a directory + IsDirectory bool `json:"isDirectory"` + // Whether the path is a file + IsFile bool `json:"isFile"` + // ISO 8601 timestamp of last modification + Mtime time.Time `json:"mtime"` + // File size in bytes + Size int64 `json:"size"` } -// The newly selected custom agent -type AgentSelectResultAgent struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` +type SessionFSWriteFileRequest struct { + // Content to write + Content string `json:"content"` + // Optional POSIX-style mode for newly created files + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -// Experimental: AgentSelectRequest is part of an experimental API and may change or be removed. -type AgentSelectRequest struct { - // Name of the custom agent to select - Name string `json:"name"` +// Experimental: SessionsForkRequest is part of an experimental API and may change or be removed. +type SessionsForkRequest struct { + // Source session ID to fork from + SessionID string `json:"sessionId"` + // Optional event ID boundary. When provided, the fork includes only events before this ID + // (exclusive). When omitted, all events are included. + ToEventID *string `json:"toEventId,omitempty"` } -// Experimental: AgentDeselectResult is part of an experimental API and may change or be removed. -type AgentDeselectResult struct { +// Experimental: SessionsForkResult is part of an experimental API and may change or be removed. +type SessionsForkResult struct { + // The new forked session's ID + SessionID string `json:"sessionId"` } -// Experimental: AgentReloadResult is part of an experimental API and may change or be removed. -type AgentReloadResult struct { - // Reloaded custom agents - Agents []AgentReloadResultAgent `json:"agents"` +type ShellExecRequest struct { + // Shell command to execute + Command string `json:"command"` + // Working directory (defaults to session working directory) + Cwd *string `json:"cwd,omitempty"` + // Timeout in milliseconds (default: 30000) + Timeout *int64 `json:"timeout,omitempty"` } -type AgentReloadResultAgent struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` +type ShellExecResult struct { + // Unique identifier for tracking streamed output + ProcessID string `json:"processId"` } -// Experimental: SkillList is part of an experimental API and may change or be removed. -type SkillList struct { - // Available skills - Skills []Skill `json:"skills"` +type ShellKillRequest struct { + // Process identifier returned by shell.exec + ProcessID string `json:"processId"` + // Signal to send (default: SIGTERM) + Signal *ShellKillSignal `json:"signal,omitempty"` +} + +type ShellKillResult struct { + // Whether the signal was sent successfully + Killed bool `json:"killed"` } type Skill struct { @@ -866,18 +1068,18 @@ type Skill struct { UserInvocable bool `json:"userInvocable"` } -// Experimental: SkillsEnableResult is part of an experimental API and may change or be removed. -type SkillsEnableResult struct { +// Experimental: SkillList is part of an experimental API and may change or be removed. +type SkillList struct { + // Available skills + Skills []Skill `json:"skills"` } -// Experimental: SkillsEnableRequest is part of an experimental API and may change or be removed. -type SkillsEnableRequest struct { - // Name of the skill to enable - Name string `json:"name"` +type SkillsConfigSetDisabledSkillsRequest struct { + // List of skill names to disable + DisabledSkills []string `json:"disabledSkills"` } -// Experimental: SkillsDisableResult is part of an experimental API and may change or be removed. -type SkillsDisableResult struct { +type SkillsConfigSetDisabledSkillsResult struct { } // Experimental: SkillsDisableRequest is part of an experimental API and may change or be removed. @@ -886,108 +1088,108 @@ type SkillsDisableRequest struct { Name string `json:"name"` } -// Experimental: SkillsReloadResult is part of an experimental API and may change or be removed. -type SkillsReloadResult struct { -} - -type MCPEnableResult struct { -} - -type MCPEnableRequest struct { - // Name of the MCP server to enable - ServerName string `json:"serverName"` +// Experimental: SkillsDisableResult is part of an experimental API and may change or be removed. +type SkillsDisableResult struct { } -type MCPDisableResult struct { +type SkillsDiscoverRequest struct { + // Optional list of project directory paths to scan for project-scoped skills + ProjectPaths []string `json:"projectPaths,omitempty"` + // Optional list of additional skill directory paths to include + SkillDirectories []string `json:"skillDirectories,omitempty"` } -type MCPDisableRequest struct { - // Name of the MCP server to disable - ServerName string `json:"serverName"` +// Experimental: SkillsEnableRequest is part of an experimental API and may change or be removed. +type SkillsEnableRequest struct { + // Name of the skill to enable + Name string `json:"name"` } -type MCPReloadResult struct { +// Experimental: SkillsEnableResult is part of an experimental API and may change or be removed. +type SkillsEnableResult struct { } -// Experimental: PluginList is part of an experimental API and may change or be removed. -type PluginList struct { - // Installed plugins - Plugins []PluginElement `json:"plugins"` +// Experimental: SkillsReloadResult is part of an experimental API and may change or be removed. +type SkillsReloadResult struct { } -type PluginElement struct { - // Whether the plugin is currently enabled - Enabled bool `json:"enabled"` - // Marketplace the plugin came from - Marketplace string `json:"marketplace"` - // Plugin name +type Tool struct { + // Description of what the tool does + Description string `json:"description"` + // Optional instructions for how to use this tool effectively + Instructions *string `json:"instructions,omitempty"` + // Tool identifier (e.g., "bash", "grep", "str_replace_editor") Name string `json:"name"` - // Installed version - Version *string `json:"version,omitempty"` -} - -// Experimental: ExtensionList is part of an experimental API and may change or be removed. -type ExtensionList struct { - // Discovered extensions and their current status - Extensions []Extension `json:"extensions"` + // Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP + // tools) + NamespacedName *string `json:"namespacedName,omitempty"` + // JSON Schema for the tool's input parameters + Parameters map[string]any `json:"parameters,omitempty"` } -type Extension struct { - // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') - ID string `json:"id"` - // Extension name (directory name) - Name string `json:"name"` - // Process ID if the extension is running - PID *int64 `json:"pid,omitempty"` - // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) - Source ExtensionSource `json:"source"` - // Current status: running, disabled, failed, or starting - Status ExtensionStatus `json:"status"` +type ToolCallResult struct { + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Type of the tool result + ResultType *string `json:"resultType,omitempty"` + // Text result to send back to the LLM + TextResultForLlm string `json:"textResultForLlm"` + // Telemetry data from tool execution + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } -// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be removed. -type ExtensionsEnableResult struct { +type ToolList struct { + // List of available built-in tools with metadata + Tools []Tool `json:"tools"` } -// Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be removed. -type ExtensionsEnableRequest struct { - // Source-qualified extension ID to enable - ID string `json:"id"` +type ToolsHandlePendingToolCallRequest struct { + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Request ID of the pending tool call + RequestID string `json:"requestId"` + // Tool call result (string or expanded result object) + Result *ToolsHandlePendingToolCall `json:"result"` } -// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be removed. -type ExtensionsDisableResult struct { +type ToolsListRequest struct { + // Optional model ID — when provided, the returned tool list reflects model-specific + // overrides + Model *string `json:"model,omitempty"` } -// Experimental: ExtensionsDisableRequest is part of an experimental API and may change or be removed. -type ExtensionsDisableRequest struct { - // Source-qualified extension ID to disable - ID string `json:"id"` +type UIElicitationArrayAnyOfField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayAnyOfFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayAnyOfFieldType `json:"type"` } -// Experimental: ExtensionsReloadResult is part of an experimental API and may change or be removed. -type ExtensionsReloadResult struct { +type UIElicitationArrayAnyOfFieldItems struct { + AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf"` } -type ToolsHandlePendingToolCallRequest struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Request ID of the pending tool call - RequestID string `json:"requestId"` - // Tool call result (string or expanded result object) - Result *ToolsHandlePendingToolCall `json:"result"` +type UIElicitationArrayAnyOfFieldItemsAnyOf struct { + Const string `json:"const"` + Title string `json:"title"` } -type CommandsHandlePendingCommandResult struct { - // Whether the command was handled successfully - Success bool `json:"success"` +type UIElicitationArrayEnumField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayEnumFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayAnyOfFieldType `json:"type"` } -type CommandsHandlePendingCommandRequest struct { - // Error message if the command handler failed - Error *string `json:"error,omitempty"` - // Request ID from the command invocation event - RequestID string `json:"requestId"` +type UIElicitationArrayEnumFieldItems struct { + Enum []string `json:"enum"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } type UIElicitationRequest struct { @@ -1004,7 +1206,7 @@ type UIElicitationSchema struct { // List of required field names Required []string `json:"required,omitempty"` // Schema type indicator (always 'object') - Type RequestedSchemaType `json:"type"` + Type UIElicitationSchemaType `json:"type"` } type UIElicitationSchemaProperty struct { @@ -1013,8 +1215,8 @@ type UIElicitationSchemaProperty struct { Enum []string `json:"enum,omitempty"` EnumNames []string `json:"enumNames,omitempty"` Title *string `json:"title,omitempty"` - Type UIElicitationSchemaPropertyNumberType `json:"type"` - OneOf []UIElicitationSchemaPropertyOneOf `json:"oneOf,omitempty"` + Type UIElicitationSchemaPropertyType `json:"type"` + OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf,omitempty"` Items *UIElicitationArrayFieldItems `json:"items,omitempty"` MaxItems *float64 `json:"maxItems,omitempty"` MinItems *float64 `json:"minItems,omitempty"` @@ -1026,102 +1228,78 @@ type UIElicitationSchemaProperty struct { } type UIElicitationArrayFieldItems struct { - Enum []string `json:"enum,omitempty"` - Type *UIElicitationStringEnumFieldType `json:"type,omitempty"` - AnyOf []FluffyUIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` -} - -type FluffyUIElicitationArrayAnyOfFieldItemsAnyOf struct { - Const string `json:"const"` - Title string `json:"title"` + Enum []string `json:"enum,omitempty"` + Type *UIElicitationArrayEnumFieldItemsType `json:"type,omitempty"` + AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` } -type UIElicitationSchemaPropertyOneOf struct { +type UIElicitationStringOneOfFieldOneOf struct { Const string `json:"const"` Title string `json:"title"` } -type LogResult struct { - // The unique identifier of the emitted session event - EventID string `json:"eventId"` -} - -type LogRequest struct { - // When true, the message is transient and not persisted to the session event log on disk - Ephemeral *bool `json:"ephemeral,omitempty"` - // Log severity level. Determines how the message is displayed in the timeline. Defaults to - // "info". - Level *SessionLogLevel `json:"level,omitempty"` - // Human-readable message - Message string `json:"message"` - // Optional URL the user can open in their browser for more details - URL *string `json:"url,omitempty"` -} - -type ShellExecResult struct { - // Unique identifier for tracking streamed output - ProcessID string `json:"processId"` +// The elicitation response (accept with form values, decline, or cancel) +type UIElicitationResponse struct { + // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + Action UIElicitationResponseAction `json:"action"` + // The form values submitted by the user (present when action is 'accept') + Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` } -type ShellExecRequest struct { - // Shell command to execute - Command string `json:"command"` - // Working directory (defaults to session working directory) - Cwd *string `json:"cwd,omitempty"` - // Timeout in milliseconds (default: 30000) - Timeout *int64 `json:"timeout,omitempty"` +type UIElicitationResult struct { + // Whether the response was accepted. False if the request was already resolved by another + // client. + Success bool `json:"success"` } -type ShellKillResult struct { - // Whether the signal was sent successfully - Killed bool `json:"killed"` +type UIElicitationSchemaPropertyBoolean struct { + Default *bool `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationSchemaPropertyBooleanType `json:"type"` } -type ShellKillRequest struct { - // Process identifier returned by shell.exec - ProcessID string `json:"processId"` - // Signal to send (default: SIGTERM) - Signal *ShellKillSignal `json:"signal,omitempty"` +type UIElicitationSchemaPropertyNumber struct { + Default *float64 `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Maximum *float64 `json:"maximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationSchemaPropertyNumberTypeEnum `json:"type"` } -// Experimental: HistoryCompactResult is part of an experimental API and may change or be removed. -type HistoryCompactResult struct { - // Post-compaction context window usage breakdown - ContextWindow *HistoryCompactContextWindow `json:"contextWindow,omitempty"` - // Number of messages removed during compaction - MessagesRemoved int64 `json:"messagesRemoved"` - // Whether compaction completed successfully - Success bool `json:"success"` - // Number of tokens freed by compaction - TokensRemoved int64 `json:"tokensRemoved"` +type UIElicitationSchemaPropertyString struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Format *UIElicitationSchemaPropertyStringFormat `json:"format,omitempty"` + MaxLength *float64 `json:"maxLength,omitempty"` + MinLength *float64 `json:"minLength,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Post-compaction context window usage breakdown -type HistoryCompactContextWindow struct { - // Token count from non-system messages (user, assistant, tool) - ConversationTokens *int64 `json:"conversationTokens,omitempty"` - // Current total tokens in the context window (system + conversation + tool definitions) - CurrentTokens int64 `json:"currentTokens"` - // Current number of messages in the conversation - MessagesLength int64 `json:"messagesLength"` - // Token count from system message(s) - SystemTokens *int64 `json:"systemTokens,omitempty"` - // Maximum token count for the model's context window - TokenLimit int64 `json:"tokenLimit"` - // Token count from tool definitions - ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` +type UIElicitationStringEnumField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Enum []string `json:"enum"` + EnumNames []string `json:"enumNames,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Experimental: HistoryTruncateResult is part of an experimental API and may change or be removed. -type HistoryTruncateResult struct { - // Number of events that were removed - EventsRemoved int64 `json:"eventsRemoved"` +type UIElicitationStringOneOfField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Experimental: HistoryTruncateRequest is part of an experimental API and may change or be removed. -type HistoryTruncateRequest struct { - // Event ID to truncate to. This event and all events after it are removed from the session. - EventID string `json:"eventId"` +type UIHandlePendingElicitationRequest struct { + // The unique request ID from the elicitation.requested event + RequestID string `json:"requestId"` + // The elicitation response (accept with form values, decline, or cancel) + Result UIElicitationResponse `json:"result"` } // Experimental: UsageGetMetricsResult is part of an experimental API and may change or be removed. @@ -1186,166 +1364,55 @@ type UsageMetricsModelMetricUsage struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } -type SessionFSReadFileResult struct { - // File content as UTF-8 string - Content string `json:"content"` -} - -type SessionFSReadFileRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSWriteFileResult struct { -} - -type SessionFSWriteFileRequest struct { - // Content to write - Content string `json:"content"` - // Optional POSIX-style mode for newly created files - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSAppendFileResult struct { -} - -type SessionFSAppendFileRequest struct { - // Content to append - Content string `json:"content"` - // Optional POSIX-style mode for newly created files - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSExistsResult struct { - // Whether the path exists - Exists bool `json:"exists"` -} - -type SessionFSExistsRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSStatResult struct { - // ISO 8601 timestamp of creation - Birthtime time.Time `json:"birthtime"` - // Whether the path is a directory - IsDirectory bool `json:"isDirectory"` - // Whether the path is a file - IsFile bool `json:"isFile"` - // ISO 8601 timestamp of last modification - Mtime time.Time `json:"mtime"` - // File size in bytes - Size int64 `json:"size"` -} - -type SessionFSStatRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSMkdirResult struct { -} - -type SessionFSMkdirRequest struct { - // Optional POSIX-style mode for newly created directories - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions - Path string `json:"path"` - // Create parent directories as needed - Recursive *bool `json:"recursive,omitempty"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSReaddirResult struct { - // Entry names in the directory - Entries []string `json:"entries"` -} - -type SessionFSReaddirRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSReaddirWithTypesResult struct { - // Directory entries with type information - Entries []SessionFSReaddirWithTypesEntry `json:"entries"` -} - -type SessionFSReaddirWithTypesEntry struct { - // Entry name - Name string `json:"name"` - // Entry type - Type SessionFSReaddirWithTypesEntryType `json:"type"` -} - -type SessionFSReaddirWithTypesRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSRmResult struct { -} - -type SessionFSRmRequest struct { - // Ignore errors if the path does not exist - Force *bool `json:"force,omitempty"` - // Path using SessionFs conventions +type WorkspacesCreateFileRequest struct { + // File content to write as a UTF-8 string + Content string `json:"content"` + // Relative path within the workspace files directory Path string `json:"path"` - // Remove directories and their contents recursively - Recursive *bool `json:"recursive,omitempty"` - // Target session identifier - SessionID string `json:"sessionId"` } -type SessionFSRenameResult struct { +type WorkspacesCreateFileResult struct { } -type SessionFSRenameRequest struct { - // Destination path using SessionFs conventions - Dest string `json:"dest"` - // Target session identifier - SessionID string `json:"sessionId"` - // Source path using SessionFs conventions - Src string `json:"src"` +type WorkspacesGetWorkspaceResult struct { + // Current workspace metadata, or null if not available + Workspace *WorkspaceClass `json:"workspace"` } -type FilterMappingString string +type WorkspaceClass struct { + Branch *string `json:"branch,omitempty"` + ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Cwd *string `json:"cwd,omitempty"` + GitRoot *string `json:"git_root,omitempty"` + HostType *HostType `json:"host_type,omitempty"` + ID string `json:"id"` + McLastEventID *string `json:"mc_last_event_id,omitempty"` + McSessionID *string `json:"mc_session_id,omitempty"` + McTaskID *string `json:"mc_task_id,omitempty"` + Name *string `json:"name,omitempty"` + RemoteSteerable *bool `json:"remote_steerable,omitempty"` + Repository *string `json:"repository,omitempty"` + SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` + Summary *string `json:"summary,omitempty"` + SummaryCount *int64 `json:"summary_count,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} -const ( - FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" - FilterMappingStringMarkdown FilterMappingString = "markdown" - FilterMappingStringNone FilterMappingString = "none" -) +type WorkspacesListFilesResult struct { + // Relative file paths in the workspace files directory + Files []string `json:"files"` +} -// Remote transport type. Defaults to "http" when omitted. -type MCPServerConfigType string +type WorkspacesReadFileRequest struct { + // Relative path within the workspace files directory + Path string `json:"path"` +} -const ( - MCPServerConfigTypeHTTP MCPServerConfigType = "http" - MCPServerConfigTypeLocal MCPServerConfigType = "local" - MCPServerConfigTypeSSE MCPServerConfigType = "sse" - MCPServerConfigTypeStdio MCPServerConfigType = "stdio" -) +type WorkspacesReadFileResult struct { + // File content as a UTF-8 string + Content string `json:"content"` +} // Configuration source // @@ -1369,6 +1436,73 @@ const ( DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" ) +// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) +type ExtensionSource string + +const ( + ExtensionSourceUser ExtensionSource = "user" + ExtensionSourceProject ExtensionSource = "project" +) + +// Current status: running, disabled, failed, or starting +type ExtensionStatus string + +const ( + ExtensionStatusDisabled ExtensionStatus = "disabled" + ExtensionStatusFailed ExtensionStatus = "failed" + ExtensionStatusRunning ExtensionStatus = "running" + ExtensionStatusStarting ExtensionStatus = "starting" +) + +type FilterMappingString string + +const ( + FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" + FilterMappingStringMarkdown FilterMappingString = "markdown" + FilterMappingStringNone FilterMappingString = "none" +) + +// Where this source lives — used for UI grouping +type InstructionsSourcesLocation string + +const ( + InstructionsSourcesLocationUser InstructionsSourcesLocation = "user" + InstructionsSourcesLocationRepository InstructionsSourcesLocation = "repository" + InstructionsSourcesLocationWorkingDirectory InstructionsSourcesLocation = "working-directory" +) + +// Category of instruction source — used for merge logic +type InstructionsSourcesType string + +const ( + InstructionsSourcesTypeChildInstructions InstructionsSourcesType = "child-instructions" + InstructionsSourcesTypeHome InstructionsSourcesType = "home" + InstructionsSourcesTypeModel InstructionsSourcesType = "model" + InstructionsSourcesTypeNestedAgents InstructionsSourcesType = "nested-agents" + InstructionsSourcesTypeRepo InstructionsSourcesType = "repo" + InstructionsSourcesTypeVscode InstructionsSourcesType = "vscode" +) + +// Log severity level. Determines how the message is displayed in the timeline. Defaults to +// "info". +type SessionLogLevel string + +const ( + SessionLogLevelError SessionLogLevel = "error" + SessionLogLevelInfo SessionLogLevel = "info" + SessionLogLevelWarning SessionLogLevel = "warning" +) + +// Remote transport type. Defaults to "http" when omitted. +type MCPServerConfigType string + +const ( + MCPServerConfigTypeHTTP MCPServerConfigType = "http" + MCPServerConfigTypeLocal MCPServerConfigType = "local" + MCPServerConfigTypeSSE MCPServerConfigType = "sse" + MCPServerConfigTypeStdio MCPServerConfigType = "stdio" +) + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured type MCPServerStatus string @@ -1381,107 +1515,120 @@ const ( MCPServerStatusPending MCPServerStatus = "pending" ) -type UIElicitationStringEnumFieldType string +// Remote transport type. Defaults to "http" when omitted. +type MCPServerConfigHTTPType string const ( - UIElicitationStringEnumFieldTypeString UIElicitationStringEnumFieldType = "string" + MCPServerConfigHTTPTypeHTTP MCPServerConfigHTTPType = "http" + MCPServerConfigHTTPTypeSSE MCPServerConfigHTTPType = "sse" ) -type UIElicitationArrayEnumFieldType string +type MCPServerConfigLocalType string const ( - UIElicitationArrayEnumFieldTypeArray UIElicitationArrayEnumFieldType = "array" + MCPServerConfigLocalTypeLocal MCPServerConfigLocalType = "local" + MCPServerConfigLocalTypeStdio MCPServerConfigLocalType = "stdio" ) -// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) -type UIElicitationResponseAction string +// The agent mode. Valid values: "interactive", "plan", "autopilot". +type SessionMode string const ( - UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" - UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" - UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" + SessionModeAutopilot SessionMode = "autopilot" + SessionModeInteractive SessionMode = "interactive" + SessionModePlan SessionMode = "plan" ) -type Kind string +type PermissionDecisionKind string const ( - KindApproved Kind = "approved" - KindDeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" - KindDeniedByPermissionRequestHook Kind = "denied-by-permission-request-hook" - KindDeniedByRules Kind = "denied-by-rules" - KindDeniedInteractivelyByUser Kind = "denied-interactively-by-user" - KindDeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionDecisionKindApproved PermissionDecisionKind = "approved" + PermissionDecisionKindDeniedByContentExclusionPolicy PermissionDecisionKind = "denied-by-content-exclusion-policy" + PermissionDecisionKindDeniedByPermissionRequestHook PermissionDecisionKind = "denied-by-permission-request-hook" + PermissionDecisionKindDeniedByRules PermissionDecisionKind = "denied-by-rules" + PermissionDecisionKindDeniedInteractivelyByUser PermissionDecisionKind = "denied-interactively-by-user" + PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionKind = "denied-no-approval-rule-and-could-not-request-from-user" ) -// Path conventions used by this filesystem -type SessionFSSetProviderConventions string +type PermissionDecisionApprovedKind string const ( - SessionFSSetProviderConventionsPosix SessionFSSetProviderConventions = "posix" - SessionFSSetProviderConventionsWindows SessionFSSetProviderConventions = "windows" + PermissionDecisionApprovedKindApproved PermissionDecisionApprovedKind = "approved" ) -// The agent mode. Valid values: "interactive", "plan", "autopilot". -type SessionMode string +type PermissionDecisionDeniedByContentExclusionPolicyKind string const ( - SessionModeAutopilot SessionMode = "autopilot" - SessionModeInteractive SessionMode = "interactive" - SessionModePlan SessionMode = "plan" + PermissionDecisionDeniedByContentExclusionPolicyKindDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicyKind = "denied-by-content-exclusion-policy" ) -type HostType string +type PermissionDecisionDeniedByPermissionRequestHookKind string const ( - HostTypeAdo HostType = "ado" - HostTypeGithub HostType = "github" + PermissionDecisionDeniedByPermissionRequestHookKindDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHookKind = "denied-by-permission-request-hook" ) -type SessionSyncLevel string +type PermissionDecisionDeniedByRulesKind string const ( - SessionSyncLevelRepoAndUser SessionSyncLevel = "repo_and_user" - SessionSyncLevelLocal SessionSyncLevel = "local" - SessionSyncLevelUser SessionSyncLevel = "user" + PermissionDecisionDeniedByRulesKindDeniedByRules PermissionDecisionDeniedByRulesKind = "denied-by-rules" ) -// Where this source lives — used for UI grouping -type InstructionsSourcesLocation string +type PermissionDecisionDeniedInteractivelyByUserKind string const ( - InstructionsSourcesLocationUser InstructionsSourcesLocation = "user" - InstructionsSourcesLocationRepository InstructionsSourcesLocation = "repository" - InstructionsSourcesLocationWorkingDirectory InstructionsSourcesLocation = "working-directory" + PermissionDecisionDeniedInteractivelyByUserKindDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUserKind = "denied-interactively-by-user" ) -// Category of instruction source — used for merge logic -type InstructionsSourcesType string +type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind string const ( - InstructionsSourcesTypeChildInstructions InstructionsSourcesType = "child-instructions" - InstructionsSourcesTypeHome InstructionsSourcesType = "home" - InstructionsSourcesTypeModel InstructionsSourcesType = "model" - InstructionsSourcesTypeNestedAgents InstructionsSourcesType = "nested-agents" - InstructionsSourcesTypeRepo InstructionsSourcesType = "repo" - InstructionsSourcesTypeVscode InstructionsSourcesType = "vscode" + PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind = "denied-no-approval-rule-and-could-not-request-from-user" ) -// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) -type ExtensionSource string +// Error classification +type SessionFSErrorCode string const ( - ExtensionSourceUser ExtensionSource = "user" - ExtensionSourceProject ExtensionSource = "project" + SessionFSErrorCodeENOENT SessionFSErrorCode = "ENOENT" + SessionFSErrorCodeUNKNOWN SessionFSErrorCode = "UNKNOWN" ) -// Current status: running, disabled, failed, or starting -type ExtensionStatus string +// Entry type +type SessionFSReaddirWithTypesEntryType string const ( - ExtensionStatusDisabled ExtensionStatus = "disabled" - ExtensionStatusFailed ExtensionStatus = "failed" - ExtensionStatusRunning ExtensionStatus = "running" - ExtensionStatusStarting ExtensionStatus = "starting" + SessionFSReaddirWithTypesEntryTypeDirectory SessionFSReaddirWithTypesEntryType = "directory" + SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" +) + +// Path conventions used by this filesystem +type SessionFSSetProviderConventions string + +const ( + SessionFSSetProviderConventionsPosix SessionFSSetProviderConventions = "posix" + SessionFSSetProviderConventionsWindows SessionFSSetProviderConventions = "windows" +) + +// Signal to send (default: SIGTERM) +type ShellKillSignal string + +const ( + ShellKillSignalSIGINT ShellKillSignal = "SIGINT" + ShellKillSignalSIGKILL ShellKillSignal = "SIGKILL" + ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" +) + +type UIElicitationArrayAnyOfFieldType string + +const ( + UIElicitationArrayAnyOfFieldTypeArray UIElicitationArrayAnyOfFieldType = "array" +) + +type UIElicitationArrayEnumFieldItemsType string + +const ( + UIElicitationArrayEnumFieldItemsTypeString UIElicitationArrayEnumFieldItemsType = "string" ) type UIElicitationSchemaPropertyStringFormat string @@ -1493,47 +1640,57 @@ const ( UIElicitationSchemaPropertyStringFormatURI UIElicitationSchemaPropertyStringFormat = "uri" ) -type UIElicitationSchemaPropertyNumberType string +type UIElicitationSchemaPropertyType string const ( - UIElicitationSchemaPropertyNumberTypeBoolean UIElicitationSchemaPropertyNumberType = "boolean" - UIElicitationSchemaPropertyNumberTypeInteger UIElicitationSchemaPropertyNumberType = "integer" - UIElicitationSchemaPropertyNumberTypeNumber UIElicitationSchemaPropertyNumberType = "number" - UIElicitationSchemaPropertyNumberTypeArray UIElicitationSchemaPropertyNumberType = "array" - UIElicitationSchemaPropertyNumberTypeString UIElicitationSchemaPropertyNumberType = "string" + UIElicitationSchemaPropertyTypeInteger UIElicitationSchemaPropertyType = "integer" + UIElicitationSchemaPropertyTypeNumber UIElicitationSchemaPropertyType = "number" + UIElicitationSchemaPropertyTypeArray UIElicitationSchemaPropertyType = "array" + UIElicitationSchemaPropertyTypeBoolean UIElicitationSchemaPropertyType = "boolean" + UIElicitationSchemaPropertyTypeString UIElicitationSchemaPropertyType = "string" ) -type RequestedSchemaType string +type UIElicitationSchemaType string const ( - RequestedSchemaTypeObject RequestedSchemaType = "object" + UIElicitationSchemaTypeObject UIElicitationSchemaType = "object" ) -// Log severity level. Determines how the message is displayed in the timeline. Defaults to -// "info". -type SessionLogLevel string +// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) +type UIElicitationResponseAction string const ( - SessionLogLevelError SessionLogLevel = "error" - SessionLogLevelInfo SessionLogLevel = "info" - SessionLogLevelWarning SessionLogLevel = "warning" + UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" + UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" + UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" ) -// Signal to send (default: SIGTERM) -type ShellKillSignal string +type UIElicitationSchemaPropertyBooleanType string const ( - ShellKillSignalSIGINT ShellKillSignal = "SIGINT" - ShellKillSignalSIGKILL ShellKillSignal = "SIGKILL" - ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" + UIElicitationSchemaPropertyBooleanTypeBoolean UIElicitationSchemaPropertyBooleanType = "boolean" ) -// Entry type -type SessionFSReaddirWithTypesEntryType string +type UIElicitationSchemaPropertyNumberTypeEnum string const ( - SessionFSReaddirWithTypesEntryTypeDirectory SessionFSReaddirWithTypesEntryType = "directory" - SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" + UIElicitationSchemaPropertyNumberTypeEnumInteger UIElicitationSchemaPropertyNumberTypeEnum = "integer" + UIElicitationSchemaPropertyNumberTypeEnumNumber UIElicitationSchemaPropertyNumberTypeEnum = "number" +) + +type HostType string + +const ( + HostTypeAdo HostType = "ado" + HostTypeGithub HostType = "github" +) + +type SessionSyncLevel string + +const ( + SessionSyncLevelRepoAndUser SessionSyncLevel = "repo_and_user" + SessionSyncLevelLocal SessionSyncLevel = "local" + SessionSyncLevelUser SessionSyncLevel = "user" ) type FilterMapping struct { @@ -1541,6 +1698,12 @@ type FilterMapping struct { EnumMap map[string]FilterMappingString } +// Tool call result (string or expanded result object) +type ToolsHandlePendingToolCall struct { + String *string + ToolCallResult *ToolCallResult +} + type UIElicitationFieldValue struct { Bool *bool Double *float64 @@ -1548,12 +1711,6 @@ type UIElicitationFieldValue struct { StringArray []string } -// Tool call result (string or expanded result object) -type ToolsHandlePendingToolCall struct { - String *string - ToolCallResult *ToolCallResult -} - type serverApi struct { client *jsonrpc2.Client } @@ -2550,15 +2707,15 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { type SessionFsHandler interface { ReadFile(request *SessionFSReadFileRequest) (*SessionFSReadFileResult, error) - WriteFile(request *SessionFSWriteFileRequest) (*SessionFSWriteFileResult, error) - AppendFile(request *SessionFSAppendFileRequest) (*SessionFSAppendFileResult, error) + WriteFile(request *SessionFSWriteFileRequest) (*SessionFSError, error) + AppendFile(request *SessionFSAppendFileRequest) (*SessionFSError, error) Exists(request *SessionFSExistsRequest) (*SessionFSExistsResult, error) Stat(request *SessionFSStatRequest) (*SessionFSStatResult, error) - Mkdir(request *SessionFSMkdirRequest) (*SessionFSMkdirResult, error) + Mkdir(request *SessionFSMkdirRequest) (*SessionFSError, error) Readdir(request *SessionFSReaddirRequest) (*SessionFSReaddirResult, error) ReaddirWithTypes(request *SessionFSReaddirWithTypesRequest) (*SessionFSReaddirWithTypesResult, error) - Rm(request *SessionFSRmRequest) (*SessionFSRmResult, error) - Rename(request *SessionFSRenameRequest) (*SessionFSRenameResult, error) + Rm(request *SessionFSRmRequest) (*SessionFSError, error) + Rename(request *SessionFSRenameRequest) (*SessionFSError, error) } // ClientSessionApiHandlers provides all client session API handler groups for a session. diff --git a/go/session.go b/go/session.go index bf42bf03a..99256856d 100644 --- a/go/session.go +++ b/go/session.go @@ -704,10 +704,10 @@ func (ui *SessionUI) Confirm(ctx context.Context, message string) (bool, error) rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "confirmed": { - Type: rpc.UIElicitationSchemaPropertyNumberTypeBoolean, + Type: rpc.UIElicitationSchemaPropertyTypeBoolean, Default: defaultTrue, }, }, @@ -734,10 +734,10 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "selection": { - Type: rpc.UIElicitationSchemaPropertyNumberTypeString, + Type: rpc.UIElicitationSchemaPropertyTypeString, Enum: options, }, }, @@ -761,7 +761,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio if err := ui.session.assertElicitation(); err != nil { return "", false, err } - prop := rpc.UIElicitationSchemaProperty{Type: rpc.UIElicitationSchemaPropertyNumberTypeString} + prop := rpc.UIElicitationSchemaProperty{Type: rpc.UIElicitationSchemaPropertyTypeString} if opts != nil { if opts.Title != "" { prop.Title = &opts.Title @@ -788,7 +788,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "value": prop, }, @@ -1029,7 +1029,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) } @@ -1044,7 +1044,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) return @@ -1056,7 +1056,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.Kind(result.Kind), + Kind: rpc.PermissionDecisionKind(result.Kind), Rules: result.Rules, Feedback: nil, }, @@ -1213,7 +1213,7 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti params := &rpc.ModelSwitchToRequest{ModelID: model} if opts != nil { params.ReasoningEffort = opts.ReasoningEffort - params.ModelCapabilities = convertModelCapabilitiesToClass(opts.ModelCapabilities) + params.ModelCapabilities = opts.ModelCapabilities } _, err := s.RPC.Model.SwitchTo(ctx, params) if err != nil { @@ -1223,34 +1223,6 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti return nil } -// convertModelCapabilitiesToClass converts from ModelCapabilitiesOverride -// (used in the public API) to ModelCapabilitiesClass (used internally by -// the ModelSwitchToRequest RPC). The two types are structurally identical -// but have different Go types due to code generation. -func convertModelCapabilitiesToClass(src *rpc.ModelCapabilitiesOverride) *rpc.ModelCapabilitiesClass { - if src == nil { - return nil - } - dst := &rpc.ModelCapabilitiesClass{ - Supports: src.Supports, - } - if src.Limits != nil { - dst.Limits = &rpc.ModelCapabilitiesLimitsClass{ - MaxContextWindowTokens: src.Limits.MaxContextWindowTokens, - MaxOutputTokens: src.Limits.MaxOutputTokens, - MaxPromptTokens: src.Limits.MaxPromptTokens, - } - if src.Limits.Vision != nil { - dst.Limits.Vision = &rpc.FluffyModelCapabilitiesOverrideLimitsVision{ - MaxPromptImageSize: src.Limits.Vision.MaxPromptImageSize, - MaxPromptImages: src.Limits.Vision.MaxPromptImages, - SupportedMediaTypes: src.Limits.Vision.SupportedMediaTypes, - } - } - } - return dst -} - type LogOptions struct { // Level sets the log severity. Valid values are [rpc.SessionLogLevelInfo] (default), // [rpc.SessionLogLevelWarning], and [rpc.SessionLogLevelError]. diff --git a/go/session_fs_provider.go b/go/session_fs_provider.go new file mode 100644 index 000000000..eb7107581 --- /dev/null +++ b/go/session_fs_provider.go @@ -0,0 +1,174 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package copilot + +import ( + "errors" + "os" + "time" + + "github.com/github/copilot-sdk/go/rpc" +) + +// SessionFsProvider is the interface that SDK users implement to provide +// a session filesystem. Methods use idiomatic Go error handling: return an +// error for failures (the adapter maps os.ErrNotExist → ENOENT automatically). +type SessionFsProvider interface { + // ReadFile reads the full content of a file. Return os.ErrNotExist (or wrap it) + // if the file does not exist. + ReadFile(path string) (string, error) + // WriteFile writes content to a file, creating it and parent directories if needed. + // mode is an optional POSIX-style permission mode. Pass nil to use the OS default. + WriteFile(path string, content string, mode *int) error + // AppendFile appends content to a file, creating it and parent directories if needed. + // mode is an optional POSIX-style permission mode. Pass nil to use the OS default. + AppendFile(path string, content string, mode *int) error + // Exists checks whether the given path exists. + Exists(path string) (bool, error) + // Stat returns metadata about a file or directory. + // Return os.ErrNotExist if the path does not exist. + Stat(path string) (*SessionFsFileInfo, error) + // Mkdir creates a directory. If recursive is true, create parent directories as needed. + // mode is an optional POSIX-style permission mode (e.g., 0o755). Pass nil to use the OS default. + Mkdir(path string, recursive bool, mode *int) error + // Readdir lists the names of entries in a directory. + // Return os.ErrNotExist if the directory does not exist. + Readdir(path string) ([]string, error) + // ReaddirWithTypes lists entries with type information. + // Return os.ErrNotExist if the directory does not exist. + ReaddirWithTypes(path string) ([]rpc.SessionFSReaddirWithTypesEntry, error) + // Rm removes a file or directory. If recursive is true, remove contents too. + // If force is true, do not return an error when the path does not exist. + Rm(path string, recursive bool, force bool) error + // Rename moves/renames a file or directory. + Rename(src string, dest string) error +} + +// SessionFsFileInfo holds file metadata returned by SessionFsProvider.Stat. +type SessionFsFileInfo struct { + IsFile bool + IsDirectory bool + Size int64 + Mtime time.Time + Birthtime time.Time +} + +// sessionFsAdapter wraps a SessionFsProvider to implement rpc.SessionFsHandler, +// converting idiomatic Go errors into SessionFSError results. +type sessionFsAdapter struct { + provider SessionFsProvider +} + +func newSessionFsAdapter(provider SessionFsProvider) rpc.SessionFsHandler { + return &sessionFsAdapter{provider: provider} +} + +func (a *sessionFsAdapter) ReadFile(request *rpc.SessionFSReadFileRequest) (*rpc.SessionFSReadFileResult, error) { + content, err := a.provider.ReadFile(request.Path) + if err != nil { + return &rpc.SessionFSReadFileResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReadFileResult{Content: content}, nil +} + +func (a *sessionFsAdapter) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSError, error) { + var mode *int + if request.Mode != nil { + m := int(*request.Mode) + mode = &m + } + if err := a.provider.WriteFile(request.Path, request.Content, mode); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSError, error) { + var mode *int + if request.Mode != nil { + m := int(*request.Mode) + mode = &m + } + if err := a.provider.AppendFile(request.Path, request.Content, mode); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { + exists, err := a.provider.Exists(request.Path) + if err != nil { + return &rpc.SessionFSExistsResult{Exists: false}, nil + } + return &rpc.SessionFSExistsResult{Exists: exists}, nil +} + +func (a *sessionFsAdapter) Stat(request *rpc.SessionFSStatRequest) (*rpc.SessionFSStatResult, error) { + info, err := a.provider.Stat(request.Path) + if err != nil { + return &rpc.SessionFSStatResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSStatResult{ + IsFile: info.IsFile, + IsDirectory: info.IsDirectory, + Size: info.Size, + Mtime: info.Mtime, + Birthtime: info.Birthtime, + }, nil +} + +func (a *sessionFsAdapter) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSError, error) { + recursive := request.Recursive != nil && *request.Recursive + var mode *int + if request.Mode != nil { + m := int(*request.Mode) + mode = &m + } + if err := a.provider.Mkdir(request.Path, recursive, mode); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { + entries, err := a.provider.Readdir(request.Path) + if err != nil { + return &rpc.SessionFSReaddirResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReaddirResult{Entries: entries}, nil +} + +func (a *sessionFsAdapter) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesRequest) (*rpc.SessionFSReaddirWithTypesResult, error) { + entries, err := a.provider.ReaddirWithTypes(request.Path) + if err != nil { + return &rpc.SessionFSReaddirWithTypesResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReaddirWithTypesResult{Entries: entries}, nil +} + +func (a *sessionFsAdapter) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSError, error) { + recursive := request.Recursive != nil && *request.Recursive + force := request.Force != nil && *request.Force + if err := a.provider.Rm(request.Path, recursive, force); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSError, error) { + if err := a.provider.Rename(request.Src, request.Dest); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func toSessionFsError(err error) *rpc.SessionFSError { + code := rpc.SessionFSErrorCodeUNKNOWN + if errors.Is(err, os.ErrNotExist) { + code = rpc.SessionFSErrorCodeENOENT + } + msg := err.Error() + return &rpc.SessionFSError{Code: code, Message: &msg} +} diff --git a/go/types.go b/go/types.go index e9f78e276..e11d21402 100644 --- a/go/types.go +++ b/go/types.go @@ -573,7 +573,7 @@ type SessionConfig struct { OnEvent SessionEventHandler // CreateSessionFsHandler supplies a handler for session filesystem operations. // This takes effect only when ClientOptions.SessionFs is configured. - CreateSessionFsHandler func(session *Session) rpc.SessionFsHandler + CreateSessionFsHandler func(session *Session) SessionFsProvider // Commands registers slash-commands for this session. Each command appears as // /name in the CLI TUI for the user to invoke. The Handler is called when the // command is executed. @@ -789,7 +789,7 @@ type ResumeSessionConfig struct { OnEvent SessionEventHandler // CreateSessionFsHandler supplies a handler for session filesystem operations. // This takes effect only when ClientOptions.SessionFs is configured. - CreateSessionFsHandler func(session *Session) rpc.SessionFsHandler + CreateSessionFsHandler func(session *Session) SessionFsProvider // Commands registers slash-commands for this session. See SessionConfig.Commands. Commands []CommandDefinition // OnElicitationRequest is a handler for elicitation requests from the server. @@ -876,7 +876,7 @@ type ( ModelCapabilitiesOverride = rpc.ModelCapabilitiesOverride ModelCapabilitiesOverrideSupports = rpc.ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideLimits = rpc.ModelCapabilitiesOverrideLimits - ModelCapabilitiesOverrideLimitsVision = rpc.PurpleModelCapabilitiesOverrideLimitsVision + ModelCapabilitiesOverrideLimitsVision = rpc.ModelCapabilitiesOverrideLimitsVision ) // ModelPolicy contains model policy state diff --git a/nodejs/docs/agent-author.md b/nodejs/docs/agent-author.md index 8b3d93593..787bb6a32 100644 --- a/nodejs/docs/agent-author.md +++ b/nodejs/docs/agent-author.md @@ -18,6 +18,7 @@ For user-scoped extensions (persist across all repos), add `location: "user"`. ### Step 2: Edit the extension file Modify the generated `extension.mjs` using `edit` or `create` tools. The file must: + - Be named `extension.mjs` (only `.mjs` is supported) - Use ES module syntax (`import`/`export`) - Call `joinSession({ ... })` @@ -48,6 +49,7 @@ Check that the extension loaded successfully and isn't marked as "failed". ``` Discovery rules: + - The CLI scans `.github/extensions/` relative to the git root - It also scans the user's copilot config extensions directory - Only immediate subdirectories are checked (not recursive) @@ -62,8 +64,8 @@ Discovery rules: import { joinSession } from "@github/copilot-sdk/extension"; await joinSession({ - tools: [], // Optional — custom tools - hooks: {}, // Optional — lifecycle hooks + tools: [], // Optional — custom tools + hooks: {}, // Optional — lifecycle hooks }); ``` @@ -74,9 +76,10 @@ await joinSession({ ```js tools: [ { - name: "tool_name", // Required. Must be globally unique across all extensions. + name: "tool_name", // Required. Must be globally unique across all extensions. description: "What it does", // Required. Shown to the agent in tool descriptions. - parameters: { // Optional. JSON Schema for the arguments. + parameters: { + // Optional. JSON Schema for the arguments. type: "object", properties: { arg1: { type: "string", description: "..." }, @@ -96,10 +99,11 @@ tools: [ return `Result: ${args.arg1}`; }, }, -] +]; ``` **Constraints:** + - Tool names must be unique across ALL loaded extensions. Collisions cause the second extension to fail to load. - Handler must return a string or `{ textResultForLlm: string, resultType?: string }`. - Handler receives `(args, invocation)` — the second argument has `sessionId`, `toolCallId`, `toolName`. @@ -195,6 +199,7 @@ After `joinSession()`, the returned `session` provides: ### session.send(options) Send a message programmatically: + ```js await session.send({ prompt: "Analyze the test results." }); await session.send({ @@ -206,6 +211,7 @@ await session.send({ ### session.sendAndWait(options, timeout?) Send and block until the agent finishes (resolves on `session.idle`): + ```js const response = await session.sendAndWait({ prompt: "What is 2+2?" }); // response?.data.content contains the agent's reply @@ -214,6 +220,7 @@ const response = await session.sendAndWait({ prompt: "What is 2+2?" }); ### session.log(message, options?) Log to the CLI timeline: + ```js await session.log("Extension ready"); await session.log("Rate limit approaching", { level: "warning" }); @@ -224,6 +231,7 @@ await session.log("Processing...", { ephemeral: true }); // transient, not persi ### session.on(eventType, handler) Subscribe to session events. Returns an unsubscribe function. + ```js const unsub = session.on("tool.execution_complete", (event) => { // event.data.toolName, event.data.success, event.data.result @@ -232,16 +240,16 @@ const unsub = session.on("tool.execution_complete", (event) => { ### Key Event Types -| Event | Key Data Fields | -|-------|----------------| -| `assistant.message` | `content`, `messageId` | -| `tool.execution_start` | `toolCallId`, `toolName`, `arguments` | +| Event | Key Data Fields | +| ------------------------- | ------------------------------------------------------ | +| `assistant.message` | `content`, `messageId` | +| `tool.execution_start` | `toolCallId`, `toolName`, `arguments` | | `tool.execution_complete` | `toolCallId`, `toolName`, `success`, `result`, `error` | -| `user.message` | `content`, `attachments`, `source` | -| `session.idle` | `backgroundTasks` | -| `session.error` | `errorType`, `message`, `stack` | -| `permission.requested` | `requestId`, `permissionRequest.kind` | -| `session.shutdown` | `shutdownType`, `totalPremiumRequests` | +| `user.message` | `content`, `attachments`, `source` | +| `session.idle` | `backgroundTasks` | +| `session.error` | `errorType`, `message`, `stack` | +| `permission.requested` | `requestId`, `permissionRequest.kind` | +| `session.shutdown` | `shutdownType`, `totalPremiumRequests` | ### session.workspacePath diff --git a/nodejs/docs/examples.md b/nodejs/docs/examples.md index 1461a2f39..a3483d8d4 100644 --- a/nodejs/docs/examples.md +++ b/nodejs/docs/examples.md @@ -10,14 +10,19 @@ Every extension starts with the same boilerplate: import { joinSession } from "@github/copilot-sdk/extension"; const session = await joinSession({ - hooks: { /* ... */ }, - tools: [ /* ... */ ], + hooks: { + /* ... */ + }, + tools: [ + /* ... */ + ], }); ``` `joinSession` returns a `CopilotSession` object you can use to send messages and subscribe to events. > **Platform notes (Windows vs macOS/Linux):** +> > - Use `process.platform === "win32"` to detect Windows at runtime. > - Clipboard: `pbcopy` on macOS, `clip` on Windows. > - Use `exec()` instead of `execFile()` for `.cmd` scripts like `code`, `npx`, `npm` on Windows. @@ -71,7 +76,7 @@ tools: [ return `Processed: ${args.input}`; }, }, -] +]; ``` ### Tool that invokes an external shell command @@ -136,7 +141,7 @@ handler: async (args, invocation) => { // invocation.toolCallId — unique ID for this tool call // invocation.toolName — name of the tool being called return "done"; -} +}; ``` --- @@ -147,14 +152,14 @@ Hooks intercept and modify behavior at key lifecycle points. Register them in th ### Available Hooks -| Hook | Fires When | Can Modify | -|------|-----------|------------| -| `onUserPromptSubmitted` | User sends a message | The prompt text, add context | -| `onPreToolUse` | Before a tool executes | Tool args, permission decision, add context | -| `onPostToolUse` | After a tool executes | Tool result, add context | -| `onSessionStart` | Session starts or resumes | Add context, modify config | -| `onSessionEnd` | Session ends | Cleanup actions, summary | -| `onErrorOccurred` | An error occurs | Error handling strategy (retry/skip/abort) | +| Hook | Fires When | Can Modify | +| ----------------------- | ------------------------- | ------------------------------------------- | +| `onUserPromptSubmitted` | User sends a message | The prompt text, add context | +| `onPreToolUse` | Before a tool executes | Tool args, permission decision, add context | +| `onPostToolUse` | After a tool executes | Tool result, add context | +| `onSessionStart` | Session starts or resumes | Add context, modify config | +| `onSessionEnd` | Session ends | Cleanup actions, summary | +| `onErrorOccurred` | An error occurs | Error handling strategy (retry/skip/abort) | All hook inputs include `timestamp` (unix ms) and `cwd` (working directory). @@ -400,18 +405,18 @@ session.on("assistant.message", (event) => { ### Top 10 Most Useful Event Types -| Event Type | Description | Key Data Fields | -|-----------|-------------|-----------------| -| `assistant.message` | Agent's final response | `content`, `messageId`, `toolRequests` | -| `assistant.streaming_delta` | Token-by-token streaming (ephemeral) | `totalResponseSizeBytes` | -| `tool.execution_start` | A tool is about to run | `toolCallId`, `toolName`, `arguments` | -| `tool.execution_complete` | A tool finished running | `toolCallId`, `toolName`, `success`, `result`, `error` | -| `user.message` | User sent a message | `content`, `attachments`, `source` | -| `session.idle` | Session finished processing a turn | `backgroundTasks` | -| `session.error` | An error occurred | `errorType`, `message`, `stack` | -| `permission.requested` | Agent needs permission (shell, file write, etc.) | `requestId`, `permissionRequest.kind` | -| `session.shutdown` | Session is ending | `shutdownType`, `totalPremiumRequests`, `codeChanges` | -| `assistant.turn_start` | Agent begins a new thinking/response cycle | `turnId` | +| Event Type | Description | Key Data Fields | +| --------------------------- | ------------------------------------------------ | ------------------------------------------------------ | +| `assistant.message` | Agent's final response | `content`, `messageId`, `toolRequests` | +| `assistant.streaming_delta` | Token-by-token streaming (ephemeral) | `totalResponseSizeBytes` | +| `tool.execution_start` | A tool is about to run | `toolCallId`, `toolName`, `arguments` | +| `tool.execution_complete` | A tool finished running | `toolCallId`, `toolName`, `success`, `result`, `error` | +| `user.message` | User sent a message | `content`, `attachments`, `source` | +| `session.idle` | Session finished processing a turn | `backgroundTasks` | +| `session.error` | An error occurred | `errorType`, `message`, `stack` | +| `permission.requested` | Agent needs permission (shell, file write, etc.) | `requestId`, `permissionRequest.kind` | +| `session.shutdown` | Session is ending | `shutdownType`, `totalPremiumRequests`, `codeChanges` | +| `assistant.turn_start` | Agent begins a new thinking/response cycle | `turnId` | ### Example: Detecting when the plan file is created or edited @@ -435,8 +440,10 @@ if (workspace) { // Track agent edits to suppress false triggers session.on("tool.execution_start", (event) => { - if ((event.data.toolName === "edit" || event.data.toolName === "create") - && String(event.data.arguments?.path || "").endsWith("plan.md")) { + if ( + (event.data.toolName === "edit" || event.data.toolName === "create") && + String(event.data.arguments?.path || "").endsWith("plan.md") + ) { agentEdits.add(event.data.toolCallId); recentAgentPaths.add(planPath); } @@ -539,9 +546,7 @@ const response = await session.sendAndWait({ prompt: "What is 2 + 2?" }); ```js await session.send({ prompt: "Review this file", - attachments: [ - { type: "file", path: "./src/index.ts" }, - ], + attachments: [{ type: "file", path: "./src/index.ts" }], }); ``` @@ -617,7 +622,7 @@ const session = await joinSession({ onPreToolUse: async (input) => { if (input.toolName === "bash") { const cmd = String(input.toolArgs?.command || ""); - if (/rm\\s+-rf\\s+\\//i.test(cmd) || /Remove-Item\\s+.*-Recurse/i.test(cmd)) { + if (/rm\\s+-rf\\s+\\/ / i.test(cmd) || /Remove-Item\\s+.*-Recurse/i.test(cmd)) { return { permissionDecision: "deny" }; } } @@ -665,4 +670,3 @@ session.on("tool.execution_complete", (event) => { // event.data.success, event.data.toolName, event.data.result }); ``` - diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 4725ac205..574bc86a9 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,3646 +1,3646 @@ { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@github/copilot": "^1.0.32", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "devDependencies": { - "@platformatic/vfs": "^0.3.0", - "@types/node": "^25.2.0", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "esbuild": "^0.27.2", - "eslint": "^9.0.0", - "glob": "^13.0.1", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.8.1", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.18" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "11.9.3", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", - "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.15", - "js-yaml": "^4.1.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/philsturgeon" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", - "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", - "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@github/copilot": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.32.tgz", - "integrity": "sha512-ydEYAztJQa1sLQw+WPmnkkt3Sf/k2Smn/7szzYvt1feUOdNIak1gHpQhKcgPr2w252gjVLRWjOiynoeLVW0Fbw==", - "license": "SEE LICENSE IN LICENSE.md", - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.32", - "@github/copilot-darwin-x64": "1.0.32", - "@github/copilot-linux-arm64": "1.0.32", - "@github/copilot-linux-x64": "1.0.32", - "@github/copilot-win32-arm64": "1.0.32", - "@github/copilot-win32-x64": "1.0.32" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.32.tgz", - "integrity": "sha512-RtGHpnrbP1eVtpzitLqC0jkBlo63PJiByv6W/NTtLw4ZAllumb5kMk8JaTtydKl9DCOHA0wfXbG5/JkGXuQ81g==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.32.tgz", - "integrity": "sha512-eyF6uy8gcZ4m/0UdM9UoykMDotZ8hZPJ1xIg0iHy4wrNtkYOaAspAoVpOkm50ODOQAHJ5PVV+9LuT6IoeL+wHQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.32.tgz", - "integrity": "sha512-acRAu5ehFPnw3hQSIxcmi7wzv8PAYd+nqdxZXizOi++en3QWgez7VEXiKLe9Ukf50iiGReg19yvWV4iDOGC0HQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.32.tgz", - "integrity": "sha512-lw86YDwkTKwmeVpfnPErDe9DhemrOHN+l92xOU9wQSH5/d+HguXwRb3e4cQjlxsGLS+/fWRGtwf+u2fbQ37avw==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.32.tgz", - "integrity": "sha512-+eZpuzgBbLHMIzltH541wfbbMy0HEdG91ISzRae3qPCssf3Ad85sat6k7FWTRBSZBFrN7z4yMQm5gROqDJYGSA==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.32", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.32.tgz", - "integrity": "sha512-R6SW1dsEVmPMhrN/WRTetS4gVxcuYcxi2zfDPOfcjW3W0iD0Vwpt3MlqwBaU2UL36j+rnTnmiOA+g82FIBCYVg==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, - "node_modules/@glideapps/ts-necessities": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", - "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jsdevtools/ono": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", - "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@platformatic/vfs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@platformatic/vfs/-/vfs-0.3.0.tgz", - "integrity": "sha512-BGXVOAz59HYPZCgI9v/MtiTF/ng8YAWtkooxVwOPR3TatNgGy0WZ/t15ScqytiZi5NdSRqWNRfuAbXKeAlKDdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 22" - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.56.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitest/expect": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", - "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.0.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "chai": "^6.2.1", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", - "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.0.18", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", - "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", - "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.0.18", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", - "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", - "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", - "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.0.18", - "tinyrainbow": "^3.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "license": "MIT", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browser-or-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", - "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/collection-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", - "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", - "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-url": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-schema-to-typescript": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", - "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@apidevtools/json-schema-ref-parser": "^11.5.5", - "@types/json-schema": "^7.0.15", - "@types/lodash": "^4.17.7", - "is-glob": "^4.0.3", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "minimist": "^1.2.8", - "prettier": "^3.2.5", - "tinyglobby": "^0.2.9" - }, - "bin": { - "json2ts": "dist/src/cli.js" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true, - "license": "(MIT AND Zlib)" - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pluralize": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", - "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/quicktype-core": { - "version": "23.2.6", - "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.2.6.tgz", - "integrity": "sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@glideapps/ts-necessities": "2.2.3", - "browser-or-node": "^3.0.0", - "collection-utils": "^1.0.1", - "cross-fetch": "^4.0.0", - "is-url": "^1.2.4", - "js-base64": "^3.7.7", - "lodash": "^4.17.21", - "pako": "^1.0.6", - "pluralize": "^8.0.0", - "readable-stream": "4.5.2", - "unicode-properties": "^1.4.1", - "urijs": "^1.19.1", - "wordwrap": "^1.0.0", - "yaml": "^2.4.1" - } - }, - "node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/rimraf": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", - "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "glob": "^13.0.3", - "package-json-from-dist": "^1.0.1" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", - "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", - "dev": true, - "license": "MIT" - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", - "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/unicode-properties": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", - "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.0", - "unicode-trie": "^2.0.0" - } - }, - "node_modules/unicode-trie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", - "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pako": "^0.2.5", - "tiny-inflate": "^1.0.0" - } - }, - "node_modules/unicode-trie/node_modules/pako": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "dev": true, - "license": "MIT" - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urijs": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", - "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", - "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.0.18", - "@vitest/mocker": "4.0.18", - "@vitest/pretty-format": "4.0.18", - "@vitest/runner": "4.0.18", - "@vitest/snapshot": "4.0.18", - "@vitest/spy": "4.0.18", - "@vitest/utils": "4.0.18", - "es-module-lexer": "^1.7.0", - "expect-type": "^1.2.2", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^3.10.0", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.0.3", - "vite": "^6.0.0 || ^7.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.0.18", - "@vitest/browser-preview": "4.0.18", - "@vitest/browser-webdriverio": "4.0.18", - "@vitest/ui": "4.0.18", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true + "name": "@github/copilot-sdk", + "version": "0.1.8", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@github/copilot-sdk", + "version": "0.1.8", + "license": "MIT", + "dependencies": { + "@github/copilot": "^1.0.35-0", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@platformatic/vfs": "^0.3.0", + "@types/node": "^25.2.0", + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "esbuild": "^0.27.2", + "eslint": "^9.0.0", + "glob": "^13.0.1", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.8.1", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz", + "integrity": "sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@github/copilot": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.35-0.tgz", + "integrity": "sha512-daPkiDXeXwsoEHy4XvZywVX3Voyaubir27qm/3uyifxeruMGOcUT/XC8tkJhE6VfSy3nvtjV4xXrZ43Wr0x2cg==", + "license": "SEE LICENSE IN LICENSE.md", + "bin": { + "copilot": "npm-loader.js" + }, + "optionalDependencies": { + "@github/copilot-darwin-arm64": "1.0.35-0", + "@github/copilot-darwin-x64": "1.0.35-0", + "@github/copilot-linux-arm64": "1.0.35-0", + "@github/copilot-linux-x64": "1.0.35-0", + "@github/copilot-win32-arm64": "1.0.35-0", + "@github/copilot-win32-x64": "1.0.35-0" + } + }, + "node_modules/@github/copilot-darwin-arm64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.35-0.tgz", + "integrity": "sha512-Uc3PIw60y/9fk1F2JlLqBl0VkParTiCIxlLWKFs8N6TJwFafKmLt7B5r4nqoFhsYZOov6ww4nIxxaMiVdFF0YA==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-arm64": "copilot" + } + }, + "node_modules/@github/copilot-darwin-x64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.35-0.tgz", + "integrity": "sha512-5R5hkZ4Z2CnHVdXnKMNjkFi00mdBYF9H9kkzQjmaN8cG4JwZFf209lo1bEzpXWKHl136LXNwLVhHCYfi3FgzXQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-x64": "copilot" + } + }, + "node_modules/@github/copilot-linux-arm64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.35-0.tgz", + "integrity": "sha512-I+kDV2xhvq2t6ux2/ZmWoRkReq8fNlYgW1GfWRmp4c+vQKvH+WsQ5P0WWSt8BmmQGK9hUrTcXg2nvVAPQJ2D8Q==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-arm64": "copilot" + } + }, + "node_modules/@github/copilot-linux-x64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.35-0.tgz", + "integrity": "sha512-mnG6lpzmWvkasdYgmvotb2PQKW/GaCAdZbuv34iOT84Iz3VyEamcUNurw+KCrxitCYRa68cnCQFbGMf8p6Q22A==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-x64": "copilot" + } + }, + "node_modules/@github/copilot-win32-arm64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.35-0.tgz", + "integrity": "sha512-suB5kxHQtD5Hu7NUqH3bUkNBg6e0rPLSf54jCN8UjyxJBfV2mL7BZeqr77Du3UzHHkRKxqITiZ4LBZH8q0bOEg==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-arm64": "copilot.exe" + } + }, + "node_modules/@github/copilot-win32-x64": { + "version": "1.0.35-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.35-0.tgz", + "integrity": "sha512-KKuxw+rKpfEn/575l+3aef72/MiGlH8D9CIX6+3+qPQqojt7YBDlEqgL3/aAk9JUrQbiqSUXXKD3mMEHdgNoWQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-x64": "copilot.exe" + } + }, + "node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@glideapps/ts-necessities/-/ts-necessities-2.2.3.tgz", + "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@platformatic/vfs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@platformatic/vfs/-/vfs-0.3.0.tgz", + "integrity": "sha512-BGXVOAz59HYPZCgI9v/MtiTF/ng8YAWtkooxVwOPR3TatNgGy0WZ/t15ScqytiZi5NdSRqWNRfuAbXKeAlKDdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.3.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", + "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", + "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", + "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", + "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", + "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", + "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", + "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", + "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", + "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", + "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", + "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-or-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-3.0.0.tgz", + "integrity": "sha512-iczIdVJzGEYhP5DqQxYM9Hh7Ztpqqi+CXZpSmX8ALFs9ecXkQIeqRyM6TfxEfMVpwhl3dSuDvxdzzo9sUOIVBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/collection-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collection-utils/-/collection-utils-1.0.1.tgz", + "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-base64": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", + "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.4.tgz", + "integrity": "sha512-Su9oK8DR4xCmDsLlyvadkXzX6+GGXJpbhwoLtOGArAG61dvbW4YQmSEno2y66ahpIdmLMg6YUf/QHLgiwvkrHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", + "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/quicktype-core": { + "version": "23.2.6", + "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.2.6.tgz", + "integrity": "sha512-asfeSv7BKBNVb9WiYhFRBvBZHcRutPRBwJMxW0pefluK4kkKu4lv0IvZBwFKvw2XygLcL1Rl90zxWDHYgkwCmA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", + "collection-utils": "^1.0.1", + "cross-fetch": "^4.0.0", + "is-url": "^1.2.4", + "js-base64": "^3.7.7", + "lodash": "^4.17.21", + "pako": "^1.0.6", + "pluralize": "^8.0.0", + "readable-stream": "4.5.2", + "unicode-properties": "^1.4.1", + "urijs": "^1.19.1", + "wordwrap": "^1.0.0", + "yaml": "^2.4.1" + } + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } - } - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", - "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } - } } diff --git a/nodejs/package.json b/nodejs/package.json index 220e76aef..c33b8cb2c 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -1,89 +1,89 @@ { - "name": "@github/copilot-sdk", - "repository": { - "type": "git", - "url": "https://github.com/github/copilot-sdk.git" - }, - "version": "0.1.8", - "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", - "main": "./dist/cjs/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "import": { - "types": "./dist/index.d.ts", - "default": "./dist/index.js" - }, - "require": { - "types": "./dist/index.d.ts", - "default": "./dist/cjs/index.js" - } + "name": "@github/copilot-sdk", + "repository": { + "type": "git", + "url": "https://github.com/github/copilot-sdk.git" }, - "./extension": { - "import": { - "types": "./dist/extension.d.ts", - "default": "./dist/extension.js" - }, - "require": { - "types": "./dist/extension.d.ts", - "default": "./dist/cjs/extension.js" - } - } - }, - "type": "module", - "scripts": { - "clean": "rimraf --glob dist *.tgz", - "build": "tsx esbuild-copilotsdk-nodejs.ts", - "test": "vitest run", - "test:watch": "vitest", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", - "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", - "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", - "lint:fix": "eslint --fix \"src/**/*.ts\" \"test/**/*.ts\"", - "typecheck": "tsc --noEmit", - "generate": "cd ../scripts/codegen && npm run generate", - "update:protocol-version": "tsx scripts/update-protocol-version.ts", - "prepublishOnly": "npm run build", - "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.1.0 --no-git-tag-version --allow-same-version" - }, - "keywords": [ - "github", - "copilot", - "sdk", - "jsonrpc", - "agent" - ], - "author": "GitHub", - "license": "MIT", - "dependencies": { - "@github/copilot": "^1.0.32", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "devDependencies": { - "@platformatic/vfs": "^0.3.0", - "@types/node": "^25.2.0", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "esbuild": "^0.27.2", - "eslint": "^9.0.0", - "glob": "^13.0.1", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.8.1", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.18" - }, - "engines": { - "node": ">=20.0.0" - }, - "files": [ - "dist/**/*", - "docs/**/*", - "README.md" - ] + "version": "0.1.8", + "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", + "main": "./dist/cjs/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/cjs/index.js" + } + }, + "./extension": { + "import": { + "types": "./dist/extension.d.ts", + "default": "./dist/extension.js" + }, + "require": { + "types": "./dist/extension.d.ts", + "default": "./dist/cjs/extension.js" + } + } + }, + "type": "module", + "scripts": { + "clean": "rimraf --glob dist *.tgz", + "build": "tsx esbuild-copilotsdk-nodejs.ts", + "test": "vitest run", + "test:watch": "vitest", + "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", + "format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", + "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"", + "lint:fix": "eslint --fix \"src/**/*.ts\" \"test/**/*.ts\"", + "typecheck": "tsc --noEmit", + "generate": "cd ../scripts/codegen && npm run generate", + "update:protocol-version": "tsx scripts/update-protocol-version.ts", + "prepublishOnly": "npm run build", + "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.1.0 --no-git-tag-version --allow-same-version" + }, + "keywords": [ + "github", + "copilot", + "sdk", + "jsonrpc", + "agent" + ], + "author": "GitHub", + "license": "MIT", + "dependencies": { + "@github/copilot": "^1.0.35-0", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@platformatic/vfs": "^0.3.0", + "@types/node": "^25.2.0", + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "esbuild": "^0.27.2", + "eslint": "^9.0.0", + "glob": "^13.0.1", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.8.1", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=20.0.0" + }, + "files": [ + "dist/**/*", + "docs/**/*", + "README.md" + ] } diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 37dda6dc4..32128f5e7 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -1,611 +1,611 @@ { - "name": "copilot-sdk-sample", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "copilot-sdk-sample", - "dependencies": { - "@github/copilot-sdk": "file:.." - }, - "devDependencies": { - "@types/node": "^22.0.0", - "tsx": "^4.20.6" - } - }, - "..": { - "name": "@github/copilot-sdk", - "version": "0.1.8", - "license": "MIT", - "dependencies": { - "@github/copilot": "^1.0.32", - "vscode-jsonrpc": "^8.2.1", - "zod": "^4.3.6" - }, - "devDependencies": { - "@platformatic/vfs": "^0.3.0", - "@types/node": "^25.2.0", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "esbuild": "^0.27.2", - "eslint": "^9.0.0", - "glob": "^13.0.1", - "json-schema": "^0.4.0", - "json-schema-to-typescript": "^15.0.4", - "prettier": "^3.8.1", - "quicktype-core": "^23.2.6", - "rimraf": "^6.1.2", - "semver": "^7.7.3", - "tsx": "^4.20.6", - "typescript": "^5.0.0", - "vitest": "^4.0.18" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@github/copilot-sdk": { - "resolved": "..", - "link": true - }, - "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-tsconfig": { - "version": "4.13.6", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", - "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" - }, - "bin": { - "tsx": "dist/cli.mjs" - }, - "engines": { - "node": ">=18.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" + "name": "copilot-sdk-sample", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "copilot-sdk-sample", + "dependencies": { + "@github/copilot-sdk": "file:.." + }, + "devDependencies": { + "@types/node": "^22.0.0", + "tsx": "^4.20.6" + } + }, + "..": { + "name": "@github/copilot-sdk", + "version": "0.1.8", + "license": "MIT", + "dependencies": { + "@github/copilot": "^1.0.32", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.6" + }, + "devDependencies": { + "@platformatic/vfs": "^0.3.0", + "@types/node": "^25.2.0", + "@typescript-eslint/eslint-plugin": "^8.54.0", + "@typescript-eslint/parser": "^8.54.0", + "esbuild": "^0.27.2", + "eslint": "^9.0.0", + "glob": "^13.0.1", + "json-schema": "^0.4.0", + "json-schema-to-typescript": "^15.0.4", + "prettier": "^3.8.1", + "quicktype-core": "^23.2.6", + "rimraf": "^6.1.2", + "semver": "^7.7.3", + "tsx": "^4.20.6", + "typescript": "^5.0.0", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@github/copilot-sdk": { + "resolved": "..", + "link": true + }, + "node_modules/@types/node": { + "version": "22.19.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", + "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + } } - } } diff --git a/nodejs/samples/package.json b/nodejs/samples/package.json index 7ff4cd9f5..f5e8147c2 100644 --- a/nodejs/samples/package.json +++ b/nodejs/samples/package.json @@ -1,14 +1,14 @@ { - "name": "copilot-sdk-sample", - "type": "module", - "scripts": { - "start": "npx tsx chat.ts" - }, - "dependencies": { - "@github/copilot-sdk": "file:.." - }, - "devDependencies": { - "tsx": "^4.20.6", - "@types/node": "^22.0.0" - } + "name": "copilot-sdk-sample", + "type": "module", + "scripts": { + "start": "npx tsx chat.ts" + }, + "dependencies": { + "@github/copilot-sdk": "file:.." + }, + "devDependencies": { + "tsx": "^4.20.6", + "@types/node": "^22.0.0" + } } diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index f4aa1e44f..a8eba8c37 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -27,6 +27,7 @@ import { import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js"; import { getSdkProtocolVersion } from "./sdkProtocolVersion.js"; import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js"; +import { createSessionFsAdapter } from "./sessionFsProvider.js"; import { getTraceContext } from "./telemetry.js"; import type { ConnectionState, @@ -711,7 +712,9 @@ export class CopilotClient { this.sessions.set(sessionId, session); if (this.sessionFsConfig) { if (config.createSessionFsHandler) { - session.clientSessionApis.sessionFs = config.createSessionFsHandler(session); + session.clientSessionApis.sessionFs = createSessionFsAdapter( + config.createSessionFsHandler(session) + ); } else { throw new Error( "createSessionFsHandler is required in session config when sessionFs is enabled in client options." @@ -850,7 +853,9 @@ export class CopilotClient { this.sessions.set(sessionId, session); if (this.sessionFsConfig) { if (config.createSessionFsHandler) { - session.clientSessionApis.sessionFs = config.createSessionFsHandler(session); + session.clientSessionApis.sessionFs = createSessionFsAdapter( + config.createSessionFsHandler(session) + ); } else { throw new Error( "createSessionFsHandler is required in session config when sessionFs is enabled in client options." diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index dedfa8068..b40ffc701 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -6,137 +6,57 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; /** - * MCP server configuration (local/stdio or remote/http) + * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerConfig". + * via the `definition` "DiscoveredMcpServerType". */ -export type McpServerConfig = - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; +export type DiscoveredMcpServerType = "stdio" | "http" | "sse" | "memory"; +/** + * Configuration source + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "DiscoveredMcpServerSource". + */ +export type DiscoveredMcpServerSource = "user" | "workspace" | "plugin" | "builtin"; +/** + * Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExtensionSource". + */ +export type ExtensionSource = "project" | "user"; +/** + * Current status: running, disabled, failed, or starting + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExtensionStatus". + */ +export type ExtensionStatus = "running" | "disabled" | "failed" | "starting"; export type FilterMapping = | { - [k: string]: "none" | "markdown" | "hidden_characters"; + [k: string]: FilterMappingValue; } - | ("none" | "markdown" | "hidden_characters"); + | FilterMappingString; + +export type FilterMappingValue = "none" | "markdown" | "hidden_characters"; + +export type FilterMappingString = "none" | "markdown" | "hidden_characters"; /** - * The agent mode. Valid values: "interactive", "plan", "autopilot". + * Category of instruction source — used for merge logic * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionMode". + * via the `definition` "InstructionsSourcesType". */ -export type SessionMode = "interactive" | "plan" | "autopilot"; - -export type UIElicitationFieldValue = string | number | boolean | string[]; +export type InstructionsSourcesType = "home" | "repo" | "model" | "vscode" | "nested-agents" | "child-instructions"; /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * Where this source lives — used for UI grouping * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponseAction". + * via the `definition` "InstructionsSourcesLocation". */ -export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; - -export type PermissionDecision = - | { - /** - * The permission request was approved - */ - kind: "approved"; - } - | { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; +export type InstructionsSourcesLocation = "user" | "repository" | "working-directory"; /** * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". * @@ -148,321 +68,155 @@ export type SessionLogLevel = "info" | "warning" | "error"; * MCP server configuration (local/stdio or remote/http) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_McpServerConfig". + * via the `definition` "McpServerConfig". */ -export type $Defs_McpServerConfig = - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; +export type McpServerConfig = McpServerConfigLocal | McpServerConfigHttp; -export type $Defs_FilterMapping = - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); +export type McpServerConfigLocalType = "local" | "stdio"; /** - * The agent mode. Valid values: "interactive", "plan", "autopilot". + * Remote transport type. Defaults to "http" when omitted. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_SessionMode". + * via the `definition` "McpServerConfigHttpType". */ -export type $Defs_SessionMode = "interactive" | "plan" | "autopilot"; +export type McpServerConfigHttpType = "http" | "sse"; /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponseAction". + * via the `definition` "McpServerStatus". */ -export type $Defs_UIElicitationResponseAction = "accept" | "decline" | "cancel"; - -export type $Defs_UIElicitationFieldValue = string | number | boolean | string[]; - -export type $Defs_PermissionDecision = - | { - /** - * The permission request was approved - */ - kind: "approved"; - } - | { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; +export type McpServerStatus = "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; /** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + * Configuration source: user, workspace, plugin, or builtin + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerSource". + */ +export type McpServerSource = "user" | "workspace" | "plugin" | "builtin"; +/** + * The agent mode. Valid values: "interactive", "plan", "autopilot". * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_SessionLogLevel". + * via the `definition` "SessionMode". */ -export type $Defs_SessionLogLevel = "info" | "warning" | "error"; +export type SessionMode = "interactive" | "plan" | "autopilot"; +export type PermissionDecision = + | PermissionDecisionApproved + | PermissionDecisionDeniedByRules + | PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + | PermissionDecisionDeniedInteractivelyByUser + | PermissionDecisionDeniedByContentExclusionPolicy + | PermissionDecisionDeniedByPermissionRequestHook; /** - * Model capabilities and limits + * Error classification * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ModelCapabilities". + * via the `definition` "SessionFsErrorCode". */ -export interface ModelCapabilities { - /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision; - }; -} +export type SessionFsErrorCode = "ENOENT" | "UNKNOWN"; /** - * Vision-specific limits + * Entry type + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsReaddirWithTypesEntryType". */ -export interface ModelCapabilitiesLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; -} +export type SessionFsReaddirWithTypesEntryType = "file" | "directory"; /** - * Vision-specific limits + * Path conventions used by this filesystem * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ModelCapabilitiesLimitsVision". + * via the `definition` "SessionFsSetProviderConventions". */ -export interface ModelCapabilitiesLimitsVision1 { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; -} +export type SessionFsSetProviderConventions = "windows" | "posix"; +/** + * Signal to send (default: SIGTERM) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ShellKillSignal". + */ +export type ShellKillSignal = "SIGTERM" | "SIGKILL" | "SIGINT"; +/** + * Tool call result (string or expanded result object) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ToolsHandlePendingToolCall". + */ +export type ToolsHandlePendingToolCall = string | ToolCallResult; -export interface DiscoveredMcpServer { - /** - * Server name (config key) - */ - name: string; - /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) - */ - type?: "stdio" | "http" | "sse" | "memory"; - /** - * Configuration source - */ - source: "user" | "workspace" | "plugin" | "builtin"; - /** - * Whether the server is enabled (not in the disabled list) - */ - enabled: boolean; -} +export type UIElicitationFieldValue = string | number | boolean | string[]; -export interface ServerSkillList { +export type UIElicitationSchemaProperty = + | UIElicitationStringEnumField + | UIElicitationStringOneOfField + | UIElicitationArrayEnumField + | UIElicitationArrayAnyOfField + | UIElicitationSchemaPropertyBoolean + | UIElicitationSchemaPropertyString + | UIElicitationSchemaPropertyNumber; + +export type UIElicitationSchemaPropertyStringFormat = "email" | "uri" | "date" | "date-time"; + +export type UIElicitationSchemaPropertyNumberType = "number" | "integer"; +/** + * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponseAction". + */ +export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; + +export interface AccountGetQuotaResult { /** - * All discovered skills across all sources + * Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) */ - skills: ServerSkill[]; + quotaSnapshots: { + [k: string]: AccountQuotaSnapshot; + }; } -export interface ServerSkill { +export interface AccountQuotaSnapshot { /** - * Unique identifier for the skill + * Whether the user has an unlimited usage entitlement */ - name: string; + isUnlimitedEntitlement: boolean; /** - * Description of what the skill does + * Number of requests included in the entitlement */ - description: string; + entitlementRequests: number; /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Number of requests used so far this period */ - source: string; + usedRequests: number; /** - * Whether the skill can be invoked by the user as a slash command + * Whether usage is still permitted after quota exhaustion */ - userInvocable: boolean; + usageAllowedWithExhaustedQuota: boolean; /** - * Whether the skill is currently enabled (based on global config) + * Percentage of entitlement remaining */ - enabled: boolean; + remainingPercentage: number; /** - * Absolute path to the skill file + * Number of overage requests made this period */ - path?: string; + overage: number; /** - * The project path this skill belongs to (only for project/inherited skills) + * Whether overage is allowed when quota is exhausted */ - projectPath?: string; -} - -export interface CurrentModel { + overageAllowedWithExhaustedQuota: boolean; /** - * Currently active model identifier + * Date when the quota resets (ISO 8601 string) */ - modelId?: string; + resetDate?: string; } -/** - * Override individual model capabilities resolved by the runtime - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ModelCapabilitiesOverride". - */ -export interface ModelCapabilitiesOverride { + +/** @experimental */ +export interface AgentGetCurrentResult { /** - * Feature flags indicating what the model supports + * Currently selected custom agent, or null if using the default agent */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; - }; + agent?: AgentInfo | null; } export interface AgentInfo { @@ -481,342 +235,306 @@ export interface AgentInfo { } /** @experimental */ -export interface McpServerList { +export interface AgentList { /** - * Configured MCP servers + * Available custom agents */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: "user" | "workspace" | "plugin" | "builtin"; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; + agents: AgentInfo[]; } -export interface ToolCallResult { +/** @experimental */ +export interface AgentReloadResult { /** - * Text result to send back to the LLM + * Reloaded custom agents */ - textResultForLlm: string; + agents: AgentInfo[]; +} + +/** @experimental */ +export interface AgentSelectRequest { /** - * Type of the tool result + * Name of the custom agent to select */ - resultType?: string; + name: string; +} + +/** @experimental */ +export interface AgentSelectResult { + agent: AgentInfo; +} + +export interface CommandsHandlePendingCommandRequest { /** - * Error message if the tool call failed + * Request ID from the command invocation event */ - error?: string; + requestId: string; /** - * Telemetry data from tool execution + * Error message if the command handler failed */ - toolTelemetry?: { - [k: string]: unknown; - }; + error?: string; } -export interface HandleToolCallResult { +export interface CommandsHandlePendingCommandResult { /** - * Whether the tool call result was handled successfully + * Whether the command was handled successfully */ success: boolean; } -export interface UIElicitationStringEnumField { - type: "string"; - description?: string; - enum: string[]; - enumNames?: string[]; - default?: string; +export interface CurrentModel { + /** + * Currently active model identifier + */ + modelId?: string; } -export interface UIElicitationStringOneOfField { - type: "string"; - description?: string; - oneOf: { - const: string; - }[]; - default?: string; +export interface DiscoveredMcpServer { + /** + * Server name (config key) + */ + name: string; + type?: DiscoveredMcpServerType; + source: DiscoveredMcpServerSource; + /** + * Whether the server is enabled (not in the disabled list) + */ + enabled: boolean; } -export interface UIElicitationArrayEnumField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; +export interface Extension { + /** + * Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') + */ + id: string; + /** + * Extension name (directory name) + */ + name: string; + source: ExtensionSource; + status: ExtensionStatus; + /** + * Process ID if the extension is running + */ + pid?: number; } -export interface UIElicitationArrayAnyOfField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - }[]; - }; - default?: string[]; +/** @experimental */ +export interface ExtensionList { + /** + * Discovered extensions and their current status + */ + extensions: Extension[]; } -/** - * The elicitation response (accept with form values, decline, or cancel) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponse". - */ -export interface UIElicitationResponse { + +/** @experimental */ +export interface ExtensionsDisableRequest { /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * Source-qualified extension ID to disable */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent; + id: string; } -/** - * The form values submitted by the user (present when action is 'accept') - */ -export interface UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; + +/** @experimental */ +export interface ExtensionsEnableRequest { + /** + * Source-qualified extension ID to enable + */ + id: string; } -/** - * The form values submitted by the user (present when action is 'accept') - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponseContent". - */ -export interface UIElicitationResponseContent1 { - [k: string]: UIElicitationFieldValue; + +/** @experimental */ +export interface FleetStartRequest { + /** + * Optional user prompt to combine with fleet instructions + */ + prompt?: string; } -export interface UIHandlePendingElicitationRequest { +/** @experimental */ +export interface FleetStartResult { /** - * The unique request ID from the elicitation.requested event + * Whether fleet mode was successfully activated */ - requestId: string; - result: UIElicitationResponse1; + started: boolean; +} + +export interface HandleToolCallResult { + /** + * Whether the tool call result was handled successfully + */ + success: boolean; } /** - * The elicitation response (accept with form values, decline, or cancel) + * Post-compaction context window usage breakdown + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryCompactContextWindow". */ -export interface UIElicitationResponse1 { +export interface HistoryCompactContextWindow { /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * Maximum token count for the model's context window */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent; + tokenLimit: number; + /** + * Current total tokens in the context window (system + conversation + tool definitions) + */ + currentTokens: number; + /** + * Current number of messages in the conversation + */ + messagesLength: number; + /** + * Token count from system message(s) + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) + */ + conversationTokens?: number; + /** + * Token count from tool definitions + */ + toolDefinitionsTokens?: number; } -export interface UIElicitationResult { +/** @experimental */ +export interface HistoryCompactResult { /** - * Whether the response was accepted. False if the request was already resolved by another client. + * Whether compaction completed successfully */ success: boolean; + /** + * Number of tokens freed by compaction + */ + tokensRemoved: number; + /** + * Number of messages removed during compaction + */ + messagesRemoved: number; + contextWindow?: HistoryCompactContextWindow; } -export interface PermissionDecisionRequest { +/** @experimental */ +export interface HistoryTruncateRequest { /** - * Request ID of the pending permission request + * Event ID to truncate to. This event and all events after it are removed from the session. */ - requestId: string; - result: PermissionDecision; + eventId: string; } -export interface PermissionRequestResult { +/** @experimental */ +export interface HistoryTruncateResult { /** - * Whether the permission request was handled successfully + * Number of events that were removed */ - success: boolean; + eventsRemoved: number; } -export interface PingResult { +export interface InstructionsGetSourcesResult { /** - * Echoed message (or default greeting) + * Instruction sources for the session */ - message: string; + sources: InstructionsSources[]; +} + +export interface InstructionsSources { /** - * Server timestamp in milliseconds + * Unique identifier for this source (used for toggling) */ - timestamp: number; + id: string; /** - * Server protocol version number + * Human-readable label */ - protocolVersion: number; -} - -export interface PingRequest { + label: string; /** - * Optional message to echo back + * File path relative to repo or absolute for home */ - message?: string; + sourcePath: string; + /** + * Raw content of the instruction file + */ + content: string; + type: InstructionsSourcesType; + location: InstructionsSourcesLocation; + /** + * Glob pattern from frontmatter — when set, this instruction applies only to matching files + */ + applyTo?: string; + /** + * Short description (body after frontmatter) for use in instruction tables + */ + description?: string; } -export interface ModelList { +export interface LogRequest { /** - * List of available models with full metadata + * Human-readable message */ - models: { - /** - * Model identifier (e.g., "claude-sonnet-4.5") - */ - id: string; - /** - * Display name - */ - name: string; - capabilities: ModelCapabilities1; - /** - * Policy state (if applicable) - */ - policy?: { - /** - * Current policy state for this model - */ - state: string; - /** - * Usage terms or conditions for this model - */ - terms: string; - }; - /** - * Billing information - */ - billing?: { - /** - * Billing cost multiplier relative to the base rate - */ - multiplier: number; - }; - /** - * Supported reasoning effort levels (only present if model supports reasoning effort) - */ - supportedReasoningEfforts?: string[]; - /** - * Default reasoning effort level (only present if model supports reasoning effort) - */ - defaultReasoningEffort?: string; - }[]; + message: string; + level?: SessionLogLevel; + /** + * When true, the message is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Optional URL the user can open in their browser for more details + */ + url?: string; } -/** - * Model capabilities and limits - */ -export interface ModelCapabilities1 { - /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; + +export interface LogResult { /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision; - }; + * The unique identifier of the emitted session event + */ + eventId: string; } -export interface ToolList { +export interface McpConfigAddRequest { /** - * List of available built-in tools with metadata + * Unique name for the MCP server */ - tools: { - /** - * Tool identifier (e.g., "bash", "grep", "str_replace_editor") - */ - name: string; - /** - * Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) - */ - namespacedName?: string; - /** - * Description of what the tool does - */ - description: string; - /** - * JSON Schema for the tool's input parameters - */ - parameters?: { - [k: string]: unknown; - }; - /** - * Optional instructions for how to use this tool effectively - */ - instructions?: string; - }[]; + name: string; + config: McpServerConfig; } -export interface ToolsListRequest { +export interface McpServerConfigLocal { /** - * Optional model ID — when provided, the returned tool list reflects model-specific overrides + * Tools to include. Defaults to all tools if not specified. */ - model?: string; + tools?: string[]; + type?: McpServerConfigLocalType; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + command: string; + args: string[]; + cwd?: string; + env?: { + [k: string]: string; + }; } -export interface AccountGetQuotaResult { +export interface McpServerConfigHttp { /** - * Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) + * Tools to include. Defaults to all tools if not specified. */ - quotaSnapshots: { - [k: string]: { - /** - * Number of requests included in the entitlement - */ - entitlementRequests: number; - /** - * Number of requests used so far this period - */ - usedRequests: number; - /** - * Percentage of entitlement remaining - */ - remainingPercentage: number; - /** - * Number of overage requests made this period - */ - overage: number; - /** - * Whether pay-per-request usage is allowed when quota is exhausted - */ - overageAllowedWithExhaustedQuota: boolean; - /** - * Date when the quota resets (ISO 8601) - */ - resetDate?: string; - }; + tools?: string[]; + type?: McpServerConfigHttpType; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + url: string; + headers?: { + [k: string]: string; }; + oauthClientId?: string; + oauthPublicClient?: boolean; } export interface McpConfigList { @@ -824,104 +542,15 @@ export interface McpConfigList { * All MCP servers from user config, keyed by name */ servers: { - /** - * MCP server configuration (local/stdio or remote/http) - */ - [k: string]: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; + [k: string]: McpServerConfig; }; } -export interface McpConfigAddRequest { +export interface McpConfigRemoveRequest { /** - * Unique name for the MCP server + * Name of the MCP server to remove */ name: string; - /** - * MCP server configuration (local/stdio or remote/http) - */ - config: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; } export interface McpConfigUpdateRequest { @@ -929,139 +558,232 @@ export interface McpConfigUpdateRequest { * Name of the MCP server to update */ name: string; - /** - * MCP server configuration (local/stdio or remote/http) - */ - config: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; + config: McpServerConfig; } -export interface McpConfigRemoveRequest { +/** @experimental */ +export interface McpDisableRequest { /** - * Name of the MCP server to remove + * Name of the MCP server to disable */ - name: string; + serverName: string; } -export interface McpDiscoverResult { +export interface McpDiscoverRequest { /** - * MCP servers discovered from all sources + * Working directory used as context for discovery (e.g., plugin resolution) */ - servers: DiscoveredMcpServer[]; + workingDirectory?: string; } -export interface McpDiscoverRequest { +export interface McpDiscoverResult { /** - * Working directory used as context for discovery (e.g., plugin resolution) + * MCP servers discovered from all sources */ - workingDirectory?: string; + servers: DiscoveredMcpServer[]; } -export interface SkillsConfigSetDisabledSkillsRequest { +/** @experimental */ +export interface McpEnableRequest { /** - * List of skill names to disable + * Name of the MCP server to enable */ - disabledSkills: string[]; + serverName: string; } -export interface SkillsDiscoverRequest { +export interface McpServer { /** - * Optional list of project directory paths to scan for project-scoped skills + * Server name (config key) */ - projectPaths?: string[]; + name: string; + status: McpServerStatus; + source?: McpServerSource; /** - * Optional list of additional skill directory paths to include + * Error message if the server failed to connect */ - skillDirectories?: string[]; + error?: string; } -export interface SessionFsSetProviderResult { +/** @experimental */ +export interface McpServerList { /** - * Whether the provider was set successfully + * Configured MCP servers */ - success: boolean; + servers: McpServer[]; } -export interface SessionFsSetProviderRequest { +export interface Model { /** - * Initial working directory for sessions + * Model identifier (e.g., "claude-sonnet-4.5") */ - initialCwd: string; + id: string; /** - * Path within each session's SessionFs where the runtime stores files for that session + * Display name */ - sessionStatePath: string; + name: string; + capabilities: ModelCapabilities; + policy?: ModelPolicy; + billing?: ModelBilling; /** - * Path conventions used by this filesystem + * Supported reasoning effort levels (only present if model supports reasoning effort) */ - conventions: "windows" | "posix"; -} - -/** @experimental */ -export interface SessionsForkResult { + supportedReasoningEfforts?: string[]; /** - * The new forked session's ID + * Default reasoning effort level (only present if model supports reasoning effort) */ - sessionId: string; + defaultReasoningEffort?: string; } - -/** @experimental */ -export interface SessionsForkRequest { +/** + * Model capabilities and limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilities". + */ +export interface ModelCapabilities { + supports?: ModelCapabilitiesSupports; + limits?: ModelCapabilitiesLimits; +} +/** + * Feature flags indicating what the model supports + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesSupports". + */ +export interface ModelCapabilitiesSupports { /** - * Source session ID to fork from + * Whether this model supports vision/image input */ - sessionId: string; + vision?: boolean; /** - * Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. + * Whether this model supports reasoning effort configuration */ - toEventId?: string; + reasoningEffort?: boolean; +} +/** + * Token limits for prompts, outputs, and context window + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesLimits". + */ +export interface ModelCapabilitiesLimits { + /** + * Maximum number of prompt/input tokens + */ + max_prompt_tokens?: number; + /** + * Maximum number of output/completion tokens + */ + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesLimitsVision; +} +/** + * Vision-specific limits + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesLimitsVision". + */ +export interface ModelCapabilitiesLimitsVision { + /** + * MIME types the model accepts + */ + supported_media_types: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size: number; +} +/** + * Policy state (if applicable) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelPolicy". + */ +export interface ModelPolicy { + /** + * Current policy state for this model + */ + state: string; + /** + * Usage terms or conditions for this model + */ + terms: string; +} +/** + * Billing information + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelBilling". + */ +export interface ModelBilling { + /** + * Billing cost multiplier relative to the base rate + */ + multiplier: number; +} +/** + * Override individual model capabilities resolved by the runtime + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverride". + */ +export interface ModelCapabilitiesOverride { + supports?: ModelCapabilitiesOverrideSupports; + limits?: ModelCapabilitiesOverrideLimits; +} +/** + * Feature flags indicating what the model supports + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverrideSupports". + */ +export interface ModelCapabilitiesOverrideSupports { + vision?: boolean; + reasoningEffort?: boolean; +} +/** + * Token limits for prompts, outputs, and context window + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverrideLimits". + */ +export interface ModelCapabilitiesOverrideLimits { + max_prompt_tokens?: number; + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesOverrideLimitsVision; } -export interface ModelSwitchToResult { +export interface ModelCapabilitiesOverrideLimitsVision { /** - * Currently active model identifier after the switch + * MIME types the model accepts */ - modelId?: string; + supported_media_types?: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images?: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size?: number; +} + +export interface ModelList { + /** + * List of available models with full metadata + */ + models: Model[]; } export interface ModelSwitchToRequest { @@ -1073,51 +795,18 @@ export interface ModelSwitchToRequest { * Reasoning effort level to use for the model */ reasoningEffort?: string; - modelCapabilities?: ModelCapabilitiesOverride1; + modelCapabilities?: ModelCapabilitiesOverride; } -/** - * Override individual model capabilities resolved by the runtime - */ -export interface ModelCapabilitiesOverride1 { + +export interface ModelSwitchToResult { /** - * Feature flags indicating what the model supports + * Currently active model identifier after the switch */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; - }; + modelId?: string; } export interface ModeSetRequest { - /** - * The agent mode. Valid values: "interactive", "plan", "autopilot". - */ - mode: "interactive" | "plan" | "autopilot"; + mode: SessionMode; } export interface NameGetResult { @@ -1134,642 +823,384 @@ export interface NameSetRequest { name: string; } -export interface PlanReadResult { +export interface PermissionDecisionApproved { /** - * Whether the plan file exists in the workspace + * The permission request was approved */ - exists: boolean; + kind: "approved"; +} + +export interface PermissionDecisionDeniedByRules { /** - * The content of the plan file, or null if it does not exist + * Denied because approval rules explicitly blocked it */ - content: string | null; + kind: "denied-by-rules"; /** - * Absolute file path of the plan file, or null if workspace is not enabled + * Rules that denied the request */ - path: string | null; + rules: unknown[]; } -export interface PlanUpdateRequest { +export interface PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { /** - * The new content for the plan file + * Denied because no approval rule matched and user confirmation was unavailable */ - content: string; + kind: "denied-no-approval-rule-and-could-not-request-from-user"; } -export interface WorkspacesGetWorkspaceResult { +export interface PermissionDecisionDeniedInteractivelyByUser { /** - * Current workspace metadata, or null if not available + * Denied by the user during an interactive prompt */ - workspace: { - id: string; - cwd?: string; - git_root?: string; - repository?: string; - host_type?: "github" | "ado"; - branch?: string; - summary?: string; - name?: string; - summary_count?: number; - created_at?: string; - updated_at?: string; - mc_task_id?: string; - mc_session_id?: string; - mc_last_event_id?: string; - session_sync_level?: "local" | "user" | "repo_and_user"; - pr_create_sync_dismissed?: boolean; - chronicle_sync_dismissed?: boolean; - } | null; -} - -export interface WorkspacesListFilesResult { + kind: "denied-interactively-by-user"; /** - * Relative file paths in the workspace files directory + * Optional feedback from the user explaining the denial */ - files: string[]; + feedback?: string; } -export interface WorkspacesReadFileResult { +export interface PermissionDecisionDeniedByContentExclusionPolicy { /** - * File content as a UTF-8 string + * Denied by the organization's content exclusion policy */ - content: string; -} - -export interface WorkspacesReadFileRequest { + kind: "denied-by-content-exclusion-policy"; /** - * Relative path within the workspace files directory + * File path that triggered the exclusion */ path: string; + /** + * Human-readable explanation of why the path was excluded + */ + message: string; } -export interface WorkspacesCreateFileRequest { +export interface PermissionDecisionDeniedByPermissionRequestHook { /** - * Relative path within the workspace files directory + * Denied by a permission request hook registered by an extension or plugin */ - path: string; + kind: "denied-by-permission-request-hook"; /** - * File content to write as a UTF-8 string + * Optional message from the hook explaining the denial */ - content: string; -} - -export interface InstructionsGetSourcesResult { + message?: string; /** - * Instruction sources for the session + * Whether to interrupt the current agent turn */ - sources: { - /** - * Unique identifier for this source (used for toggling) - */ - id: string; - /** - * Human-readable label - */ - label: string; - /** - * File path relative to repo or absolute for home - */ - sourcePath: string; - /** - * Raw content of the instruction file - */ - content: string; - /** - * Category of instruction source — used for merge logic - */ - type: "home" | "repo" | "model" | "vscode" | "nested-agents" | "child-instructions"; - /** - * Where this source lives — used for UI grouping - */ - location: "user" | "repository" | "working-directory"; - /** - * Glob pattern from frontmatter — when set, this instruction applies only to matching files - */ - applyTo?: string; - /** - * Short description (body after frontmatter) for use in instruction tables - */ - description?: string; - }[]; + interrupt?: boolean; } -/** @experimental */ -export interface FleetStartResult { +export interface PermissionDecisionRequest { /** - * Whether fleet mode was successfully activated + * Request ID of the pending permission request */ - started: boolean; + requestId: string; + result: PermissionDecision; } -/** @experimental */ -export interface FleetStartRequest { +export interface PermissionRequestResult { /** - * Optional user prompt to combine with fleet instructions + * Whether the permission request was handled successfully */ - prompt?: string; + success: boolean; } -/** @experimental */ -export interface AgentList { +export interface PingRequest { /** - * Available custom agents + * Optional message to echo back */ - agents: AgentInfo[]; + message?: string; } -/** @experimental */ -export interface AgentGetCurrentResult { +export interface PingResult { /** - * Currently selected custom agent, or null if using the default agent + * Echoed message (or default greeting) */ - agent?: AgentInfo | null; + message: string; + /** + * Server timestamp in milliseconds + */ + timestamp: number; + /** + * Server protocol version number + */ + protocolVersion: number; } -/** @experimental */ -export interface AgentSelectResult { - agent: AgentInfo1; -} -/** - * The newly selected custom agent - */ -export interface AgentInfo1 { +export interface PlanReadResult { /** - * Unique identifier of the custom agent + * Whether the plan file exists in the workspace */ - name: string; + exists: boolean; /** - * Human-readable display name + * The content of the plan file, or null if it does not exist */ - displayName: string; + content: string | null; /** - * Description of the agent's purpose + * Absolute file path of the plan file, or null if workspace is not enabled */ - description: string; + path: string | null; } -/** @experimental */ -export interface AgentSelectRequest { +export interface PlanUpdateRequest { /** - * Name of the custom agent to select + * The new content for the plan file */ - name: string; + content: string; } -/** @experimental */ -export interface AgentReloadResult { +export interface Plugin { /** - * Reloaded custom agents + * Plugin name */ - agents: AgentInfo[]; -} - -/** @experimental */ -export interface SkillList { + name: string; /** - * Available skills + * Marketplace the plugin came from + */ + marketplace: string; + /** + * Installed version + */ + version?: string; + /** + * Whether the plugin is currently enabled */ - skills: { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type (e.g., project, personal, plugin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled - */ - enabled: boolean; - /** - * Absolute path to the skill file - */ - path?: string; - }[]; + enabled: boolean; } /** @experimental */ -export interface SkillsEnableRequest { +export interface PluginList { /** - * Name of the skill to enable + * Installed plugins */ - name: string; + plugins: Plugin[]; } -/** @experimental */ -export interface SkillsDisableRequest { +export interface ServerSkill { /** - * Name of the skill to disable + * Unique identifier for the skill */ name: string; -} - -/** @experimental */ -export interface McpEnableRequest { /** - * Name of the MCP server to enable + * Description of what the skill does */ - serverName: string; -} - -/** @experimental */ -export interface McpDisableRequest { + description: string; /** - * Name of the MCP server to disable + * Source location type (e.g., project, personal-copilot, plugin, builtin) */ - serverName: string; -} - -/** @experimental */ -export interface PluginList { + source: string; /** - * Installed plugins + * Whether the skill can be invoked by the user as a slash command */ - plugins: { - /** - * Plugin name - */ - name: string; - /** - * Marketplace the plugin came from - */ - marketplace: string; - /** - * Installed version - */ - version?: string; - /** - * Whether the plugin is currently enabled - */ - enabled: boolean; - }[]; -} - -/** @experimental */ -export interface ExtensionList { + userInvocable: boolean; /** - * Discovered extensions and their current status + * Whether the skill is currently enabled (based on global config) */ - extensions: { - /** - * Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') - */ - id: string; - /** - * Extension name (directory name) - */ - name: string; - /** - * Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) - */ - source: "project" | "user"; - /** - * Current status: running, disabled, failed, or starting - */ - status: "running" | "disabled" | "failed" | "starting"; - /** - * Process ID if the extension is running - */ - pid?: number; - }[]; -} - -/** @experimental */ -export interface ExtensionsEnableRequest { + enabled: boolean; /** - * Source-qualified extension ID to enable + * Absolute path to the skill file */ - id: string; + path?: string; + /** + * The project path this skill belongs to (only for project/inherited skills) + */ + projectPath?: string; } -/** @experimental */ -export interface ExtensionsDisableRequest { +export interface ServerSkillList { /** - * Source-qualified extension ID to disable + * All discovered skills across all sources */ - id: string; + skills: ServerSkill[]; } -export interface ToolsHandlePendingToolCallRequest { +export interface SessionFsAppendFileRequest { /** - * Request ID of the pending tool call + * Target session identifier */ - requestId: string; + sessionId: string; /** - * Tool call result (string or expanded result object) + * Path using SessionFs conventions */ - result?: string | ToolCallResult; + path: string; /** - * Error message if the tool call failed + * Content to append */ - error?: string; -} - -export interface CommandsHandlePendingCommandResult { + content: string; /** - * Whether the command was handled successfully + * Optional POSIX-style mode for newly created files */ - success: boolean; + mode?: number; } - -export interface CommandsHandlePendingCommandRequest { - /** - * Request ID from the command invocation event - */ - requestId: string; +/** + * Describes a filesystem error. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsError". + */ +export interface SessionFsError { + code: SessionFsErrorCode; /** - * Error message if the command handler failed + * Free-form detail about the error, for logging/diagnostics */ - error?: string; + message?: string; } -export interface UIElicitationRequest { +export interface SessionFsExistsRequest { /** - * Message describing what information is needed from the user + * Target session identifier */ - message: string; + sessionId: string; /** - * JSON Schema describing the form fields to present to the user - */ - requestedSchema: { - /** - * Schema type indicator (always 'object') - */ - type: "object"; - /** - * Form field definitions, keyed by field name - */ - properties: { - [k: string]: - | UIElicitationStringEnumField - | UIElicitationStringOneOfField - | UIElicitationArrayEnumField - | UIElicitationArrayAnyOfField - | { - type: "boolean"; - description?: string; - default?: boolean; - } - | { - type: "string"; - description?: string; - minLength?: number; - maxLength?: number; - format?: "email" | "uri" | "date" | "date-time"; - default?: string; - } - | { - type: "number" | "integer"; - description?: string; - minimum?: number; - maximum?: number; - default?: number; - }; - }; - /** - * List of required field names - */ - required?: string[]; - }; + * Path using SessionFs conventions + */ + path: string; } -export interface LogResult { +export interface SessionFsExistsResult { /** - * The unique identifier of the emitted session event + * Whether the path exists */ - eventId: string; + exists: boolean; } -export interface LogRequest { - /** - * Human-readable message - */ - message: string; +export interface SessionFsMkdirRequest { /** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". + * Target session identifier */ - level?: "info" | "warning" | "error"; + sessionId: string; /** - * When true, the message is transient and not persisted to the session event log on disk + * Path using SessionFs conventions */ - ephemeral?: boolean; + path: string; /** - * Optional URL the user can open in their browser for more details + * Create parent directories as needed */ - url?: string; -} - -export interface ShellExecResult { + recursive?: boolean; /** - * Unique identifier for tracking streamed output + * Optional POSIX-style mode for newly created directories */ - processId: string; + mode?: number; } -export interface ShellExecRequest { +export interface SessionFsReaddirRequest { /** - * Shell command to execute + * Target session identifier */ - command: string; + sessionId: string; /** - * Working directory (defaults to session working directory) + * Path using SessionFs conventions */ - cwd?: string; + path: string; +} + +export interface SessionFsReaddirResult { /** - * Timeout in milliseconds (default: 30000) + * Entry names in the directory */ - timeout?: number; + entries: string[]; + error?: SessionFsError; } -export interface ShellKillResult { +export interface SessionFsReaddirWithTypesEntry { /** - * Whether the signal was sent successfully + * Entry name */ - killed: boolean; + name: string; + type: SessionFsReaddirWithTypesEntryType; } -export interface ShellKillRequest { +export interface SessionFsReaddirWithTypesRequest { /** - * Process identifier returned by shell.exec + * Target session identifier */ - processId: string; + sessionId: string; /** - * Signal to send (default: SIGTERM) + * Path using SessionFs conventions */ - signal?: "SIGTERM" | "SIGKILL" | "SIGINT"; + path: string; } -/** @experimental */ -export interface HistoryCompactResult { +export interface SessionFsReaddirWithTypesResult { /** - * Whether compaction completed successfully + * Directory entries with type information */ - success: boolean; + entries: SessionFsReaddirWithTypesEntry[]; + error?: SessionFsError; +} + +export interface SessionFsReadFileRequest { /** - * Number of tokens freed by compaction + * Target session identifier */ - tokensRemoved: number; + sessionId: string; /** - * Number of messages removed during compaction + * Path using SessionFs conventions */ - messagesRemoved: number; - /** - * Post-compaction context window usage breakdown - */ - contextWindow?: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Current total tokens in the context window (system + conversation + tool definitions) - */ - currentTokens: number; - /** - * Current number of messages in the conversation - */ - messagesLength: number; - /** - * Token count from system message(s) - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) - */ - conversationTokens?: number; - /** - * Token count from tool definitions - */ - toolDefinitionsTokens?: number; - }; + path: string; } -/** @experimental */ -export interface HistoryTruncateResult { +export interface SessionFsReadFileResult { /** - * Number of events that were removed + * File content as UTF-8 string */ - eventsRemoved: number; + content: string; + error?: SessionFsError; } -/** @experimental */ -export interface HistoryTruncateRequest { +export interface SessionFsRenameRequest { /** - * Event ID to truncate to. This event and all events after it are removed from the session. + * Target session identifier */ - eventId: string; -} - -/** @experimental */ -export interface UsageGetMetricsResult { + sessionId: string; /** - * Total user-initiated premium request cost across all models (may be fractional due to multipliers) + * Source path using SessionFs conventions */ - totalPremiumRequestCost: number; + src: string; /** - * Raw count of user-initiated API requests + * Destination path using SessionFs conventions */ - totalUserRequests: number; + dest: string; +} + +export interface SessionFsRmRequest { /** - * Total time spent in model API calls (milliseconds) + * Target session identifier */ - totalApiDurationMs: number; + sessionId: string; /** - * Session start timestamp (epoch milliseconds) + * Path using SessionFs conventions */ - sessionStartTime: number; - /** - * Aggregated code change metrics - */ - codeChanges: { - /** - * Total lines of code added - */ - linesAdded: number; - /** - * Total lines of code removed - */ - linesRemoved: number; - /** - * Number of distinct files modified - */ - filesModifiedCount: number; - }; + path: string; /** - * Per-model token and request metrics, keyed by model identifier + * Remove directories and their contents recursively */ - modelMetrics: { - [k: string]: { - /** - * Request count and cost metrics for this model - */ - requests: { - /** - * Number of API requests made with this model - */ - count: number; - /** - * User-initiated premium request cost (with multiplier applied) - */ - cost: number; - }; - /** - * Token usage metrics for this model - */ - usage: { - /** - * Total input tokens consumed - */ - inputTokens: number; - /** - * Total output tokens produced - */ - outputTokens: number; - /** - * Total tokens read from prompt cache - */ - cacheReadTokens: number; - /** - * Total tokens written to prompt cache - */ - cacheWriteTokens: number; - /** - * Total output tokens used for reasoning - */ - reasoningTokens?: number; - }; - }; - }; + recursive?: boolean; /** - * Currently active model identifier + * Ignore errors if the path does not exist */ - currentModel?: string; + force?: boolean; +} + +export interface SessionFsSetProviderRequest { /** - * Input tokens from the most recent main-agent API call + * Initial working directory for sessions */ - lastCallInputTokens: number; + initialCwd: string; /** - * Output tokens from the most recent main-agent API call + * Path within each session's SessionFs where the runtime stores files for that session */ - lastCallOutputTokens: number; + sessionStatePath: string; + conventions: SessionFsSetProviderConventions; } -export interface SessionFsReadFileResult { +export interface SessionFsSetProviderResult { /** - * File content as UTF-8 string + * Whether the provider was set successfully */ - content: string; + success: boolean; } -export interface SessionFsReadFileRequest { +export interface SessionFsStatRequest { /** * Target session identifier */ @@ -1780,26 +1211,31 @@ export interface SessionFsReadFileRequest { path: string; } -export interface SessionFsWriteFileRequest { +export interface SessionFsStatResult { /** - * Target session identifier + * Whether the path is a file */ - sessionId: string; + isFile: boolean; /** - * Path using SessionFs conventions + * Whether the path is a directory */ - path: string; + isDirectory: boolean; /** - * Content to write + * File size in bytes */ - content: string; + size: number; /** - * Optional POSIX-style mode for newly created files + * ISO 8601 timestamp of last modification */ - mode?: number; + mtime: string; + /** + * ISO 8601 timestamp of creation + */ + birthtime: string; + error?: SessionFsError; } -export interface SessionFsAppendFileRequest { +export interface SessionFsWriteFileRequest { /** * Target session identifier */ @@ -1809,7 +1245,7 @@ export interface SessionFsAppendFileRequest { */ path: string; /** - * Content to append + * Content to write */ content: string; /** @@ -1818,511 +1254,513 @@ export interface SessionFsAppendFileRequest { mode?: number; } -export interface SessionFsExistsResult { - /** - * Whether the path exists - */ - exists: boolean; -} - -export interface SessionFsExistsRequest { +/** @experimental */ +export interface SessionsForkRequest { /** - * Target session identifier + * Source session ID to fork from */ sessionId: string; /** - * Path using SessionFs conventions - */ - path: string; -} - -export interface SessionFsStatResult { - /** - * Whether the path is a file - */ - isFile: boolean; - /** - * Whether the path is a directory - */ - isDirectory: boolean; - /** - * File size in bytes - */ - size: number; - /** - * ISO 8601 timestamp of last modification - */ - mtime: string; - /** - * ISO 8601 timestamp of creation + * Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included. */ - birthtime: string; + toEventId?: string; } -export interface SessionFsStatRequest { +/** @experimental */ +export interface SessionsForkResult { /** - * Target session identifier + * The new forked session's ID */ sessionId: string; - /** - * Path using SessionFs conventions - */ - path: string; } -export interface SessionFsMkdirRequest { - /** - * Target session identifier - */ - sessionId: string; +export interface ShellExecRequest { /** - * Path using SessionFs conventions + * Shell command to execute */ - path: string; + command: string; /** - * Create parent directories as needed + * Working directory (defaults to session working directory) */ - recursive?: boolean; + cwd?: string; /** - * Optional POSIX-style mode for newly created directories + * Timeout in milliseconds (default: 30000) */ - mode?: number; + timeout?: number; } -export interface SessionFsReaddirResult { +export interface ShellExecResult { /** - * Entry names in the directory + * Unique identifier for tracking streamed output */ - entries: string[]; + processId: string; } -export interface SessionFsReaddirRequest { - /** - * Target session identifier - */ - sessionId: string; +export interface ShellKillRequest { /** - * Path using SessionFs conventions + * Process identifier returned by shell.exec */ - path: string; + processId: string; + signal?: ShellKillSignal; } -export interface SessionFsReaddirWithTypesResult { +export interface ShellKillResult { /** - * Directory entries with type information + * Whether the signal was sent successfully */ - entries: { - /** - * Entry name - */ - name: string; - /** - * Entry type - */ - type: "file" | "directory"; - }[]; + killed: boolean; } -export interface SessionFsReaddirWithTypesRequest { +export interface Skill { /** - * Target session identifier + * Unique identifier for the skill */ - sessionId: string; + name: string; /** - * Path using SessionFs conventions + * Description of what the skill does */ - path: string; -} - -export interface SessionFsRmRequest { + description: string; /** - * Target session identifier + * Source location type (e.g., project, personal, plugin) */ - sessionId: string; + source: string; /** - * Path using SessionFs conventions + * Whether the skill can be invoked by the user as a slash command */ - path: string; + userInvocable: boolean; /** - * Remove directories and their contents recursively + * Whether the skill is currently enabled */ - recursive?: boolean; + enabled: boolean; /** - * Ignore errors if the path does not exist + * Absolute path to the skill file */ - force?: boolean; + path?: string; } -export interface SessionFsRenameRequest { - /** - * Target session identifier - */ - sessionId: string; - /** - * Source path using SessionFs conventions - */ - src: string; - /** - * Destination path using SessionFs conventions - */ - dest: string; -} -/** - * Model capabilities and limits - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilities". - */ -export interface $Defs_ModelCapabilities { - /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision2; - }; -} -/** - * Vision-specific limits - */ -export interface ModelCapabilitiesLimitsVision2 { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; +/** @experimental */ +export interface SkillList { /** - * Maximum image size in bytes + * Available skills */ - max_prompt_image_size: number; + skills: Skill[]; } -/** - * Vision-specific limits - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilitiesLimitsVision". - */ -export interface $Defs_ModelCapabilitiesLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; + +export interface SkillsConfigSetDisabledSkillsRequest { /** - * Maximum image size in bytes + * List of skill names to disable */ - max_prompt_image_size: number; + disabledSkills: string[]; } -export interface $Defs_DiscoveredMcpServer { +/** @experimental */ +export interface SkillsDisableRequest { /** - * Server name (config key) + * Name of the skill to disable */ name: string; +} + +export interface SkillsDiscoverRequest { /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) - */ - type?: "stdio" | "http" | "sse" | "memory"; - /** - * Configuration source + * Optional list of project directory paths to scan for project-scoped skills */ - source: "user" | "workspace" | "plugin" | "builtin"; + projectPaths?: string[]; /** - * Whether the server is enabled (not in the disabled list) + * Optional list of additional skill directory paths to include */ - enabled: boolean; + skillDirectories?: string[]; } -export interface $Defs_ServerSkillList { +/** @experimental */ +export interface SkillsEnableRequest { /** - * All discovered skills across all sources + * Name of the skill to enable */ - skills: ServerSkill[]; + name: string; } -export interface $Defs_ServerSkill { +export interface Tool { /** - * Unique identifier for the skill + * Tool identifier (e.g., "bash", "grep", "str_replace_editor") */ name: string; /** - * Description of what the skill does + * Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) */ - description: string; + namespacedName?: string; /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Description of what the tool does */ - source: string; + description: string; /** - * Whether the skill can be invoked by the user as a slash command + * JSON Schema for the tool's input parameters */ - userInvocable: boolean; + parameters?: { + [k: string]: unknown; + }; /** - * Whether the skill is currently enabled (based on global config) + * Optional instructions for how to use this tool effectively */ - enabled: boolean; + instructions?: string; +} + +export interface ToolCallResult { /** - * Absolute path to the skill file + * Text result to send back to the LLM */ - path?: string; + textResultForLlm: string; /** - * The project path this skill belongs to (only for project/inherited skills) + * Type of the tool result */ - projectPath?: string; -} - -export interface $Defs_CurrentModel { + resultType?: string; /** - * Currently active model identifier + * Error message if the tool call failed */ - modelId?: string; -} -/** - * Override individual model capabilities resolved by the runtime - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilitiesOverride". - */ -export interface $Defs_ModelCapabilitiesOverride { + error?: string; /** - * Feature flags indicating what the model supports + * Telemetry data from tool execution */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; + toolTelemetry?: { + [k: string]: unknown; }; } -export interface $Defs_AgentInfo { +export interface ToolList { /** - * Unique identifier of the custom agent + * List of available built-in tools with metadata */ - name: string; + tools: Tool[]; +} + +export interface ToolsHandlePendingToolCallRequest { /** - * Human-readable display name + * Request ID of the pending tool call */ - displayName: string; + requestId: string; + result?: ToolsHandlePendingToolCall; /** - * Description of the agent's purpose + * Error message if the tool call failed */ - description: string; + error?: string; } -export interface $Defs_McpServerList { - /** - * Configured MCP servers - */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: "user" | "workspace" | "plugin" | "builtin"; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; -} - -export interface $Defs_ToolCallResult { +export interface ToolsListRequest { /** - * Text result to send back to the LLM + * Optional model ID — when provided, the returned tool list reflects model-specific overrides */ - textResultForLlm: string; + model?: string; +} + +export interface UIElicitationArrayAnyOfField { + type: "array"; + title?: string; + description?: string; + minItems?: number; + maxItems?: number; + items: UIElicitationArrayAnyOfFieldItems; + default?: string[]; +} + +export interface UIElicitationArrayAnyOfFieldItems { + anyOf: UIElicitationArrayAnyOfFieldItemsAnyOf[]; +} + +export interface UIElicitationArrayAnyOfFieldItemsAnyOf { + const: string; + title: string; +} + +export interface UIElicitationArrayEnumField { + type: "array"; + title?: string; + description?: string; + minItems?: number; + maxItems?: number; + items: UIElicitationArrayEnumFieldItems; + default?: string[]; +} + +export interface UIElicitationArrayEnumFieldItems { + type: "string"; + enum: string[]; +} + +export interface UIElicitationRequest { /** - * Type of the tool result + * Message describing what information is needed from the user */ - resultType?: string; + message: string; + requestedSchema: UIElicitationSchema; +} +/** + * JSON Schema describing the form fields to present to the user + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationSchema". + */ +export interface UIElicitationSchema { /** - * Error message if the tool call failed + * Schema type indicator (always 'object') */ - error?: string; + type: "object"; /** - * Telemetry data from tool execution + * Form field definitions, keyed by field name */ - toolTelemetry?: { - [k: string]: unknown; + properties: { + [k: string]: UIElicitationSchemaProperty; }; -} - -export interface $Defs_HandleToolCallResult { /** - * Whether the tool call result was handled successfully + * List of required field names */ - success: boolean; + required?: string[]; } -export interface $Defs_UIElicitationStringEnumField { +export interface UIElicitationStringEnumField { type: "string"; + title?: string; description?: string; enum: string[]; enumNames?: string[]; default?: string; } -export interface $Defs_UIElicitationStringOneOfField { +export interface UIElicitationStringOneOfField { type: "string"; + title?: string; description?: string; - oneOf: { - const: string; - }[]; + oneOf: UIElicitationStringOneOfFieldOneOf[]; default?: string; } -export interface $Defs_UIElicitationArrayEnumField { - type: "array"; +export interface UIElicitationStringOneOfFieldOneOf { + const: string; + title: string; +} + +export interface UIElicitationSchemaPropertyBoolean { + type: "boolean"; + title?: string; description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; + default?: boolean; } -export interface $Defs_UIElicitationArrayAnyOfField { - type: "array"; +export interface UIElicitationSchemaPropertyString { + type: "string"; + title?: string; description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - }[]; - }; - default?: string[]; + minLength?: number; + maxLength?: number; + format?: UIElicitationSchemaPropertyStringFormat; + default?: string; +} + +export interface UIElicitationSchemaPropertyNumber { + type: UIElicitationSchemaPropertyNumberType; + title?: string; + description?: string; + minimum?: number; + maximum?: number; + default?: number; } /** * The elicitation response (accept with form values, decline, or cancel) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponse". + * via the `definition` "UIElicitationResponse". */ -export interface $Defs_UIElicitationResponse { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent2; +export interface UIElicitationResponse { + action: UIElicitationResponseAction; + content?: UIElicitationResponseContent; } /** * The form values submitted by the user (present when action is 'accept') + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponseContent". */ -export interface UIElicitationResponseContent2 { +export interface UIElicitationResponseContent { [k: string]: UIElicitationFieldValue; } + +export interface UIElicitationResult { + /** + * Whether the response was accepted. False if the request was already resolved by another client. + */ + success: boolean; +} + +export interface UIHandlePendingElicitationRequest { + /** + * The unique request ID from the elicitation.requested event + */ + requestId: string; + result: UIElicitationResponse; +} + +/** @experimental */ +export interface UsageGetMetricsResult { + /** + * Total user-initiated premium request cost across all models (may be fractional due to multipliers) + */ + totalPremiumRequestCost: number; + /** + * Raw count of user-initiated API requests + */ + totalUserRequests: number; + /** + * Total time spent in model API calls (milliseconds) + */ + totalApiDurationMs: number; + /** + * Session start timestamp (epoch milliseconds) + */ + sessionStartTime: number; + codeChanges: UsageMetricsCodeChanges; + /** + * Per-model token and request metrics, keyed by model identifier + */ + modelMetrics: { + [k: string]: UsageMetricsModelMetric; + }; + /** + * Currently active model identifier + */ + currentModel?: string; + /** + * Input tokens from the most recent main-agent API call + */ + lastCallInputTokens: number; + /** + * Output tokens from the most recent main-agent API call + */ + lastCallOutputTokens: number; +} /** - * The form values submitted by the user (present when action is 'accept') + * Aggregated code change metrics * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponseContent". + * via the `definition` "UsageMetricsCodeChanges". */ -export interface $Defs_UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; +export interface UsageMetricsCodeChanges { + /** + * Total lines of code added + */ + linesAdded: number; + /** + * Total lines of code removed + */ + linesRemoved: number; + /** + * Number of distinct files modified + */ + filesModifiedCount: number; } -export interface $Defs_UIHandlePendingElicitationRequest { +export interface UsageMetricsModelMetric { + requests: UsageMetricsModelMetricRequests; + usage: UsageMetricsModelMetricUsage; +} +/** + * Request count and cost metrics for this model + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsModelMetricRequests". + */ +export interface UsageMetricsModelMetricRequests { /** - * The unique request ID from the elicitation.requested event + * Number of API requests made with this model */ - requestId: string; - result: UIElicitationResponse2; + count: number; + /** + * User-initiated premium request cost (with multiplier applied) + */ + cost: number; } /** - * The elicitation response (accept with form values, decline, or cancel) + * Token usage metrics for this model + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsModelMetricUsage". */ -export interface UIElicitationResponse2 { +export interface UsageMetricsModelMetricUsage { /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + * Total input tokens consumed */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent; + inputTokens: number; + /** + * Total output tokens produced + */ + outputTokens: number; + /** + * Total tokens read from prompt cache + */ + cacheReadTokens: number; + /** + * Total tokens written to prompt cache + */ + cacheWriteTokens: number; + /** + * Total output tokens used for reasoning + */ + reasoningTokens?: number; } -export interface $Defs_UIElicitationResult { +export interface WorkspacesCreateFileRequest { /** - * Whether the response was accepted. False if the request was already resolved by another client. + * Relative path within the workspace files directory */ - success: boolean; + path: string; + /** + * File content to write as a UTF-8 string + */ + content: string; } -export interface $Defs_PermissionDecisionRequest { +export interface WorkspacesGetWorkspaceResult { /** - * Request ID of the pending permission request + * Current workspace metadata, or null if not available */ - requestId: string; - result: PermissionDecision; + workspace: { + id: string; + cwd?: string; + git_root?: string; + repository?: string; + host_type?: "github" | "ado"; + branch?: string; + summary?: string; + name?: string; + summary_count?: number; + created_at?: string; + updated_at?: string; + remote_steerable?: boolean; + mc_task_id?: string; + mc_session_id?: string; + mc_last_event_id?: string; + session_sync_level?: "local" | "user" | "repo_and_user"; + chronicle_sync_dismissed?: boolean; + } | null; } -export interface $Defs_PermissionRequestResult { +export interface WorkspacesListFilesResult { /** - * Whether the permission request was handled successfully + * Relative file paths in the workspace files directory */ - success: boolean; + files: string[]; +} + +export interface WorkspacesReadFileRequest { + /** + * Relative path within the workspace files directory + */ + path: string; +} + +export interface WorkspacesReadFileResult { + /** + * File content as a UTF-8 string + */ + content: string; } /** Create typed server-scoped RPC methods (no session required). */ @@ -2519,15 +1957,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** Handler for `sessionFs` client session API methods. */ export interface SessionFsHandler { readFile(params: SessionFsReadFileRequest): Promise; - writeFile(params: SessionFsWriteFileRequest): Promise; - appendFile(params: SessionFsAppendFileRequest): Promise; + writeFile(params: SessionFsWriteFileRequest): Promise; + appendFile(params: SessionFsAppendFileRequest): Promise; exists(params: SessionFsExistsRequest): Promise; stat(params: SessionFsStatRequest): Promise; - mkdir(params: SessionFsMkdirRequest): Promise; + mkdir(params: SessionFsMkdirRequest): Promise; readdir(params: SessionFsReaddirRequest): Promise; readdirWithTypes(params: SessionFsReaddirWithTypesRequest): Promise; - rm(params: SessionFsRmRequest): Promise; - rename(params: SessionFsRenameRequest): Promise; + rm(params: SessionFsRmRequest): Promise; + rename(params: SessionFsRenameRequest): Promise; } /** All client session API handler groups. */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index d2de8d250..b35ab7c59 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4,3955 +4,302 @@ */ export type SessionEvent = - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.start"; - /** - * Session initialization metadata including context and configuration - */ - data: { - /** - * Unique identifier for the session - */ - sessionId: string; - /** - * Schema version number for the session event format - */ - version: number; - /** - * Identifier of the software producing the events (e.g., "copilot-agent") - */ - producer: string; - /** - * Version string of the Copilot application - */ - copilotVersion: string; - /** - * ISO 8601 timestamp when the session was created - */ - startTime: string; - /** - * Model selected at session creation time, if any - */ - selectedModel?: string; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - context?: WorkingDirectoryContext; - /** - * Whether the session was already in use by another client at start time - */ - alreadyInUse?: boolean; - /** - * Whether this session supports remote steering via Mission Control - */ - remoteSteerable?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.resume"; - /** - * Session resume metadata including current context and event count - */ - data: { - /** - * ISO 8601 timestamp when the session was resumed - */ - resumeTime: string; - /** - * Total number of persisted events in the session at the time of resume - */ - eventCount: number; - /** - * Model currently selected at resume time - */ - selectedModel?: string; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - context?: WorkingDirectoryContext1; - /** - * Whether the session was already in use by another client at resume time - */ - alreadyInUse?: boolean; - /** - * Whether this session supports remote steering via Mission Control - */ - remoteSteerable?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.remote_steerable_changed"; - /** - * Notifies Mission Control that the session's remote steering capability has changed - */ - data: { - /** - * Whether this session now supports remote steering via Mission Control - */ - remoteSteerable: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.error"; - /** - * Error details for timeline display including message and optional diagnostic information - */ - data: { - /** - * Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") - */ - errorType: string; - /** - * Human-readable error message - */ - message: string; - /** - * Error stack trace, when available - */ - stack?: string; - /** - * HTTP status code from the upstream request, if applicable - */ - statusCode?: number; - /** - * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - */ - providerCallId?: string; - /** - * Optional URL associated with this error that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.idle"; - /** - * Payload indicating the session is idle with no background agents in flight - */ - data: { - /** - * True when the preceding agentic loop was cancelled via abort signal - */ - aborted?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.title_changed"; - /** - * Session title change payload containing the new display title - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.info"; - /** - * Informational message for timeline display with categorization - */ - data: { - /** - * Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") - */ - infoType: string; - /** - * Human-readable informational message for display in the timeline - */ - message: string; - /** - * Optional URL associated with this message that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.warning"; - /** - * Warning message for timeline display with categorization - */ - data: { - /** - * Category of warning (e.g., "subscription", "policy", "mcp") - */ - warningType: string; - /** - * Human-readable warning message for display in the timeline - */ - message: string; - /** - * Optional URL associated with this warning that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.model_change"; - /** - * Model change details including previous and new model identifiers - */ - data: { - /** - * Model that was previously selected, if any - */ - previousModel?: string; - /** - * Newly selected model identifier - */ - newModel: string; - /** - * Reasoning effort level before the model change, if applicable - */ - previousReasoningEffort?: string; - /** - * Reasoning effort level after the model change, if applicable - */ - reasoningEffort?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mode_changed"; - /** - * Agent mode change details including previous and new modes - */ - data: { - /** - * Agent mode before the change (e.g., "interactive", "plan", "autopilot") - */ - previousMode: string; - /** - * Agent mode after the change (e.g., "interactive", "plan", "autopilot") - */ - newMode: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.plan_changed"; - /** - * Plan file operation details indicating what changed - */ - data: { - /** - * The type of operation performed on the plan file - */ - operation: "create" | "update" | "delete"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.workspace_file_changed"; - /** - * Workspace file change details including path and operation type - */ - data: { - /** - * Relative path within the session workspace files directory - */ - path: string; - /** - * Whether the file was newly created or updated - */ - operation: "create" | "update"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.handoff"; - /** - * Session handoff metadata including source, context, and repository information - */ - data: { - /** - * ISO 8601 timestamp when the handoff occurred - */ - handoffTime: string; - /** - * Origin type of the session being handed off - */ - sourceType: "remote" | "local"; - /** - * Repository context for the handed-off session - */ - repository?: { - /** - * Repository owner (user or organization) - */ - owner: string; - /** - * Repository name - */ - name: string; - /** - * Git branch name, if applicable - */ - branch?: string; - }; - /** - * Additional context information for the handoff - */ - context?: string; - /** - * Summary of the work done in the source session - */ - summary?: string; - /** - * Session ID of the remote session being handed off - */ - remoteSessionId?: string; - /** - * GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) - */ - host?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.truncation"; - /** - * Conversation truncation statistics including token counts and removed content metrics - */ - data: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Total tokens in conversation messages before truncation - */ - preTruncationTokensInMessages: number; - /** - * Number of conversation messages before truncation - */ - preTruncationMessagesLength: number; - /** - * Total tokens in conversation messages after truncation - */ - postTruncationTokensInMessages: number; - /** - * Number of conversation messages after truncation - */ - postTruncationMessagesLength: number; - /** - * Number of tokens removed by truncation - */ - tokensRemovedDuringTruncation: number; - /** - * Number of messages removed by truncation - */ - messagesRemovedDuringTruncation: number; - /** - * Identifier of the component that performed truncation (e.g., "BasicTruncator") - */ - performedBy: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.snapshot_rewind"; - /** - * Session rewind details including target event and count of removed events - */ - data: { - /** - * Event ID that was rewound to; this event and all after it were removed - */ - upToEventId: string; - /** - * Number of events that were removed by the rewind - */ - eventsRemoved: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.shutdown"; - /** - * Session termination metrics including usage statistics, code changes, and shutdown reason - */ - data: { - /** - * Whether the session ended normally ("routine") or due to a crash/fatal error ("error") - */ - shutdownType: "routine" | "error"; - /** - * Error description when shutdownType is "error" - */ - errorReason?: string; - /** - * Total number of premium API requests used during the session - */ - totalPremiumRequests: number; - /** - * Cumulative time spent in API calls during the session, in milliseconds - */ - totalApiDurationMs: number; - /** - * Unix timestamp (milliseconds) when the session started - */ - sessionStartTime: number; - /** - * Aggregate code change metrics for the session - */ - codeChanges: { - /** - * Total number of lines added during the session - */ - linesAdded: number; - /** - * Total number of lines removed during the session - */ - linesRemoved: number; - /** - * List of file paths that were modified during the session - */ - filesModified: string[]; - }; - /** - * Per-model usage breakdown, keyed by model identifier - */ - modelMetrics: { - [k: string]: { - /** - * Request count and cost metrics - */ - requests: { - /** - * Total number of API requests made to this model - */ - count: number; - /** - * Cumulative cost multiplier for requests to this model - */ - cost: number; - }; - /** - * Token usage breakdown - */ - usage: { - /** - * Total input tokens consumed across all requests to this model - */ - inputTokens: number; - /** - * Total output tokens produced across all requests to this model - */ - outputTokens: number; - /** - * Total tokens read from prompt cache across all requests - */ - cacheReadTokens: number; - /** - * Total tokens written to prompt cache across all requests - */ - cacheWriteTokens: number; - /** - * Total reasoning tokens produced across all requests to this model - */ - reasoningTokens?: number; - }; - }; - }; - /** - * Model that was selected at the time of shutdown - */ - currentModel?: string; - /** - * Total tokens in context window at shutdown - */ - currentTokens?: number; - /** - * System message token count at shutdown - */ - systemTokens?: number; - /** - * Non-system message token count at shutdown - */ - conversationTokens?: number; - /** - * Tool definitions token count at shutdown - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.context_changed"; - data: WorkingDirectoryContext2; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.usage_info"; - /** - * Current context window usage statistics including token and message counts - */ - data: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Current number of tokens in the context window - */ - currentTokens: number; - /** - * Current number of messages in the conversation - */ - messagesLength: number; - /** - * Token count from system message(s) - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) - */ - conversationTokens?: number; - /** - * Token count from tool definitions - */ - toolDefinitionsTokens?: number; - /** - * Whether this is the first usage_info event emitted in this session - */ - isInitial?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.compaction_start"; - /** - * Context window breakdown at the start of LLM-powered conversation compaction - */ - data: { - /** - * Token count from system message(s) at compaction start - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) at compaction start - */ - conversationTokens?: number; - /** - * Token count from tool definitions at compaction start - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.compaction_complete"; - /** - * Conversation compaction results including success status, metrics, and optional error details - */ - data: { - /** - * Whether compaction completed successfully - */ - success: boolean; - /** - * Error message if compaction failed - */ - error?: string; - /** - * Total tokens in conversation before compaction - */ - preCompactionTokens?: number; - /** - * Total tokens in conversation after compaction - */ - postCompactionTokens?: number; - /** - * Number of messages before compaction - */ - preCompactionMessagesLength?: number; - /** - * Number of messages removed during compaction - */ - messagesRemoved?: number; - /** - * Number of tokens removed during compaction - */ - tokensRemoved?: number; - /** - * LLM-generated summary of the compacted conversation history - */ - summaryContent?: string; - /** - * Checkpoint snapshot number created for recovery - */ - checkpointNumber?: number; - /** - * File path where the checkpoint was stored - */ - checkpointPath?: string; - /** - * Token usage breakdown for the compaction LLM call - */ - compactionTokensUsed?: { - /** - * Input tokens consumed by the compaction LLM call - */ - input: number; - /** - * Output tokens produced by the compaction LLM call - */ - output: number; - /** - * Cached input tokens reused in the compaction LLM call - */ - cachedInput: number; - }; - /** - * GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - */ - requestId?: string; - /** - * Token count from system message(s) after compaction - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) after compaction - */ - conversationTokens?: number; - /** - * Token count from tool definitions after compaction - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.task_complete"; - /** - * Task completion notification with summary from the agent - */ - data: { - /** - * Summary of the completed task, provided by the agent - */ - summary?: string; - /** - * Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - */ - success?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user.message"; - data: { - /** - * The user's message text as displayed in the timeline - */ - content: string; - /** - * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching - */ - transformedContent?: string; - /** - * Files, selections, or GitHub references attached to the message - */ - attachments?: ( - | { - /** - * Attachment type discriminator - */ - type: "file"; - /** - * Absolute file path - */ - path: string; - /** - * User-facing display name for the attachment - */ - displayName: string; - /** - * Optional line range to scope the attachment to a specific section of the file - */ - lineRange?: { - /** - * Start line number (1-based) - */ - start: number; - /** - * End line number (1-based, inclusive) - */ - end: number; - }; - } - | { - /** - * Attachment type discriminator - */ - type: "directory"; - /** - * Absolute directory path - */ - path: string; - /** - * User-facing display name for the attachment - */ - displayName: string; - } - | { - /** - * Attachment type discriminator - */ - type: "selection"; - /** - * Absolute path to the file containing the selection - */ - filePath: string; - /** - * User-facing display name for the selection - */ - displayName: string; - /** - * The selected text content - */ - text: string; - /** - * Position range of the selection within the file - */ - selection: { - /** - * Start position of the selection - */ - start: { - /** - * Start line number (0-based) - */ - line: number; - /** - * Start character offset within the line (0-based) - */ - character: number; - }; - /** - * End position of the selection - */ - end: { - /** - * End line number (0-based) - */ - line: number; - /** - * End character offset within the line (0-based) - */ - character: number; - }; - }; - } - | { - /** - * Attachment type discriminator - */ - type: "github_reference"; - /** - * Issue, pull request, or discussion number - */ - number: number; - /** - * Type of GitHub reference - */ - referenceType: "issue" | "pr" | "discussion"; - /** - * Current state of the referenced item (e.g., open, closed, merged) - */ - state: string; - /** - * URL to the referenced item on GitHub - */ - url: string; - } - | { - /** - * Attachment type discriminator - */ - type: "blob"; - /** - * Base64-encoded content - */ - data: string; - /** - * MIME type of the inline data - */ - mimeType: string; - /** - * User-facing display name for the attachment - */ - displayName?: string; - } - )[]; - /** - * Normalized document MIME types that were sent natively instead of through tagged_files XML - */ - supportedNativeDocumentMimeTypes?: string[]; - /** - * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit - */ - nativeDocumentPathFallbackPaths?: string[]; - /** - * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) - */ - source?: string; - /** - * The agent mode that was active when this message was sent - */ - agentMode?: "interactive" | "plan" | "autopilot" | "shell"; - /** - * CAPI interaction ID for correlating this user message with its turn - */ - interactionId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "pending_messages.modified"; - /** - * Empty payload; the event signals that the pending message queue has changed - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.turn_start"; - /** - * Turn initialization metadata including identifier and interaction tracking - */ - data: { - /** - * Identifier for this turn within the agentic loop, typically a stringified turn number - */ - turnId: string; - /** - * CAPI interaction ID for correlating this turn with upstream telemetry - */ - interactionId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.intent"; - /** - * Agent intent description for current activity or plan - */ - data: { - /** - * Short description of what the agent is currently doing or planning to do - */ - intent: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.reasoning"; - /** - * Assistant reasoning content for timeline display with complete thinking text - */ - data: { - /** - * Unique identifier for this reasoning block - */ - reasoningId: string; - /** - * The complete extended thinking text from the model - */ - content: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.reasoning_delta"; - /** - * Streaming reasoning delta for incremental extended thinking updates - */ - data: { - /** - * Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event - */ - reasoningId: string; - /** - * Incremental text chunk to append to the reasoning content - */ - deltaContent: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.streaming_delta"; - /** - * Streaming response progress with cumulative byte count - */ - data: { - /** - * Cumulative total bytes received from the streaming response so far - */ - totalResponseSizeBytes: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.message"; - /** - * Assistant response containing text content, optional tool requests, and interaction metadata - */ - data: { - /** - * Unique identifier for this assistant message - */ - messageId: string; - /** - * The assistant's text response content - */ - content: string; - /** - * Tool invocations requested by the assistant in this message - */ - toolRequests?: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool being invoked - */ - name: string; - /** - * Arguments to pass to the tool, format depends on the tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - */ - type?: "function" | "custom"; - /** - * Human-readable display title for the tool - */ - toolTitle?: string; - /** - * Name of the MCP server hosting this tool, when the tool is an MCP tool - */ - mcpServerName?: string; - /** - * Resolved intention summary describing what this specific call does - */ - intentionSummary?: string | null; - }[]; - /** - * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. - */ - reasoningOpaque?: string; - /** - * Readable reasoning text from the model's extended thinking - */ - reasoningText?: string; - /** - * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. - */ - encryptedContent?: string; - /** - * Generation phase for phased-output models (e.g., thinking vs. response phases) - */ - phase?: string; - /** - * Actual output token count from the API response (completion_tokens), used for accurate token accounting - */ - outputTokens?: number; - /** - * CAPI interaction ID for correlating this message with upstream telemetry - */ - interactionId?: string; - /** - * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - */ - requestId?: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.message_delta"; - /** - * Streaming assistant message delta for incremental response updates - */ - data: { - /** - * Message ID this delta belongs to, matching the corresponding assistant.message event - */ - messageId: string; - /** - * Incremental text chunk to append to the message content - */ - deltaContent: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.turn_end"; - /** - * Turn completion metadata including the turn identifier - */ - data: { - /** - * Identifier of the turn that has ended, matching the corresponding assistant.turn_start event - */ - turnId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.usage"; - /** - * LLM API call usage metrics including tokens, costs, quotas, and billing information - */ - data: { - /** - * Model identifier used for this API call - */ - model: string; - /** - * Number of input tokens consumed - */ - inputTokens?: number; - /** - * Number of output tokens produced - */ - outputTokens?: number; - /** - * Number of tokens read from prompt cache - */ - cacheReadTokens?: number; - /** - * Number of tokens written to prompt cache - */ - cacheWriteTokens?: number; - /** - * Number of output tokens used for reasoning (e.g., chain-of-thought) - */ - reasoningTokens?: number; - /** - * Model multiplier cost for billing purposes - */ - cost?: number; - /** - * Duration of the API call in milliseconds - */ - duration?: number; - /** - * Time to first token in milliseconds. Only available for streaming requests - */ - ttftMs?: number; - /** - * Average inter-token latency in milliseconds. Only available for streaming requests - */ - interTokenLatencyMs?: number; - /** - * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls - */ - initiator?: string; - /** - * Completion ID from the model provider (e.g., chatcmpl-abc123) - */ - apiCallId?: string; - /** - * GitHub request tracing ID (x-github-request-id header) for server-side log correlation - */ - providerCallId?: string; - /** - * @deprecated - * Parent tool call ID when this usage originates from a sub-agent - */ - parentToolCallId?: string; - /** - * Per-quota resource usage snapshots, keyed by quota identifier - */ - quotaSnapshots?: { - [k: string]: { - /** - * Whether the user has an unlimited usage entitlement - */ - isUnlimitedEntitlement: boolean; - /** - * Total requests allowed by the entitlement - */ - entitlementRequests: number; - /** - * Number of requests already consumed - */ - usedRequests: number; - /** - * Whether usage is still permitted after quota exhaustion - */ - usageAllowedWithExhaustedQuota: boolean; - /** - * Number of requests over the entitlement limit - */ - overage: number; - /** - * Whether overage is allowed when quota is exhausted - */ - overageAllowedWithExhaustedQuota: boolean; - /** - * Percentage of quota remaining (0.0 to 1.0) - */ - remainingPercentage: number; - /** - * Date when the quota resets - */ - resetDate?: string; - }; - }; - /** - * Per-request cost and usage data from the CAPI copilot_usage response field - */ - copilotUsage?: { - /** - * Itemized token usage breakdown - */ - tokenDetails: { - /** - * Number of tokens in this billing batch - */ - batchSize: number; - /** - * Cost per batch of tokens - */ - costPerBatch: number; - /** - * Total token count for this entry - */ - tokenCount: number; - /** - * Token category (e.g., "input", "output") - */ - tokenType: string; - }[]; - /** - * Total cost in nano-AIU (AI Units) for this request - */ - totalNanoAiu: number; - }; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "abort"; - /** - * Turn abort information including the reason for termination - */ - data: { - /** - * Reason the current turn was aborted (e.g., "user initiated") - */ - reason: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.user_requested"; - /** - * User-initiated tool invocation request with tool name and arguments - */ - data: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool the user wants to invoke - */ - toolName: string; - /** - * Arguments for the tool invocation - */ - arguments?: { - [k: string]: unknown; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_start"; - /** - * Tool execution startup details including MCP server information when applicable - */ - data: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool being executed - */ - toolName: string; - /** - * Arguments passed to the tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * Name of the MCP server hosting this tool, when the tool is an MCP tool - */ - mcpServerName?: string; - /** - * Original tool name on the MCP server, when the tool is an MCP tool - */ - mcpToolName?: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_partial_result"; - /** - * Streaming tool execution output for incremental result display - */ - data: { - /** - * Tool call ID this partial result belongs to - */ - toolCallId: string; - /** - * Incremental output chunk from the running tool - */ - partialOutput: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_progress"; - /** - * Tool execution progress notification with status message - */ - data: { - /** - * Tool call ID this progress notification belongs to - */ - toolCallId: string; - /** - * Human-readable progress status message (e.g., from an MCP server) - */ - progressMessage: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_complete"; - /** - * Tool execution completion results including success status, detailed output, and error information - */ - data: { - /** - * Unique identifier for the completed tool call - */ - toolCallId: string; - /** - * Whether the tool execution completed successfully - */ - success: boolean; - /** - * Model identifier that generated this tool call - */ - model?: string; - /** - * CAPI interaction ID for correlating this tool execution with upstream telemetry - */ - interactionId?: string; - /** - * Whether this tool call was explicitly requested by the user rather than the assistant - */ - isUserRequested?: boolean; - /** - * Tool execution result on success - */ - result?: { - /** - * Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency - */ - content: string; - /** - * Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. - */ - detailedContent?: string; - /** - * Structured content blocks (text, images, audio, resources) returned by the tool in their native format - */ - contents?: ( - | { - /** - * Content block type discriminator - */ - type: "text"; - /** - * The text content - */ - text: string; - } - | { - /** - * Content block type discriminator - */ - type: "terminal"; - /** - * Terminal/shell output text - */ - text: string; - /** - * Process exit code, if the command has completed - */ - exitCode?: number; - /** - * Working directory where the command was executed - */ - cwd?: string; - } - | { - /** - * Content block type discriminator - */ - type: "image"; - /** - * Base64-encoded image data - */ - data: string; - /** - * MIME type of the image (e.g., image/png, image/jpeg) - */ - mimeType: string; - } - | { - /** - * Content block type discriminator - */ - type: "audio"; - /** - * Base64-encoded audio data - */ - data: string; - /** - * MIME type of the audio (e.g., audio/wav, audio/mpeg) - */ - mimeType: string; - } - | { - /** - * Icons associated with this resource - */ - icons?: { - /** - * URL or path to the icon image - */ - src: string; - /** - * MIME type of the icon image - */ - mimeType?: string; - /** - * Available icon sizes (e.g., ['16x16', '32x32']) - */ - sizes?: string[]; - /** - * Theme variant this icon is intended for - */ - theme?: "light" | "dark"; - }[]; - /** - * Resource name identifier - */ - name: string; - /** - * URI identifying the resource - */ - uri: string; - /** - * Human-readable description of the resource - */ - description?: string; - /** - * MIME type of the resource content - */ - mimeType?: string; - /** - * Size of the resource in bytes - */ - size?: number; - /** - * Content block type discriminator - */ - type: "resource_link"; - } - | { - /** - * Content block type discriminator - */ - type: "resource"; - /** - * The embedded resource contents, either text or base64-encoded binary - */ - resource: EmbeddedTextResourceContents | EmbeddedBlobResourceContents; - } - )[]; - }; - /** - * Error details when the tool execution failed - */ - error?: { - /** - * Human-readable error message - */ - message: string; - /** - * Machine-readable error code - */ - code?: string; - }; - /** - * Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) - */ - toolTelemetry?: { - [k: string]: unknown; - }; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "skill.invoked"; - /** - * Skill invocation details including content, allowed tools, and plugin metadata - */ - data: { - /** - * Name of the invoked skill - */ - name: string; - /** - * File path to the SKILL.md definition - */ - path: string; - /** - * Full content of the skill file, injected into the conversation for the model - */ - content: string; - /** - * Tool names that should be auto-approved when this skill is active - */ - allowedTools?: string[]; - /** - * Name of the plugin this skill originated from, when applicable - */ - pluginName?: string; - /** - * Version of the plugin this skill originated from, when applicable - */ - pluginVersion?: string; - /** - * Description of the skill from its SKILL.md frontmatter - */ - description?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.started"; - /** - * Sub-agent startup details including parent tool call and agent information - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Description of what the sub-agent does - */ - agentDescription: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.completed"; - /** - * Sub-agent completion details for successful execution - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Model used by the sub-agent - */ - model?: string; - /** - * Total number of tool calls made by the sub-agent - */ - totalToolCalls?: number; - /** - * Total tokens (input + output) consumed by the sub-agent - */ - totalTokens?: number; - /** - * Wall-clock duration of the sub-agent execution in milliseconds - */ - durationMs?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.failed"; - /** - * Sub-agent failure details including error message and agent information - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Error message describing why the sub-agent failed - */ - error: string; - /** - * Model used by the sub-agent (if any model calls succeeded before failure) - */ - model?: string; - /** - * Total number of tool calls made before the sub-agent failed - */ - totalToolCalls?: number; - /** - * Total tokens (input + output) consumed before the sub-agent failed - */ - totalTokens?: number; - /** - * Wall-clock duration of the sub-agent execution in milliseconds - */ - durationMs?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.selected"; - /** - * Custom agent selection details including name and available tools - */ - data: { - /** - * Internal name of the selected custom agent - */ - agentName: string; - /** - * Human-readable display name of the selected custom agent - */ - agentDisplayName: string; - /** - * List of tool names available to this agent, or null for all tools - */ - tools: string[] | null; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.deselected"; - /** - * Empty payload; the event signals that the custom agent was deselected, returning to the default agent - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "hook.start"; - /** - * Hook invocation start details including type and input data - */ - data: { - /** - * Unique identifier for this hook invocation - */ - hookInvocationId: string; - /** - * Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - */ - hookType: string; - /** - * Input data passed to the hook - */ - input?: { - [k: string]: unknown; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "hook.end"; - /** - * Hook invocation completion details including output, success status, and error information - */ - data: { - /** - * Identifier matching the corresponding hook.start event - */ - hookInvocationId: string; - /** - * Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - */ - hookType: string; - /** - * Output data produced by the hook - */ - output?: { - [k: string]: unknown; - }; - /** - * Whether the hook completed successfully - */ - success: boolean; - /** - * Error details when the hook failed - */ - error?: { - /** - * Human-readable error message - */ - message: string; - /** - * Error stack trace, when available - */ - stack?: string; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "system.message"; - /** - * System/developer instruction content with role and optional template metadata - */ - data: { - /** - * The system or developer prompt text sent as model input - */ - content: string; - /** - * Message role: "system" for system prompts, "developer" for developer-injected instructions - */ - role: "system" | "developer"; - /** - * Optional name identifier for the message source - */ - name?: string; - /** - * Metadata about the prompt template and its construction - */ - metadata?: { - /** - * Version identifier of the prompt template used - */ - promptVersion?: string; - /** - * Template variables used when constructing the prompt - */ - variables?: { - [k: string]: unknown; - }; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "system.notification"; - /** - * System-generated notification for runtime events like background task completion - */ - data: { - /** - * The notification text, typically wrapped in XML tags - */ - content: string; - /** - * Structured metadata identifying what triggered this notification - */ - kind: - | { - type: "agent_completed"; - /** - * Unique identifier of the background agent - */ - agentId: string; - /** - * Type of the agent (e.g., explore, task, general-purpose) - */ - agentType: string; - /** - * Whether the agent completed successfully or failed - */ - status: "completed" | "failed"; - /** - * Human-readable description of the agent task - */ - description?: string; - /** - * The full prompt given to the background agent - */ - prompt?: string; - } - | { - type: "agent_idle"; - /** - * Unique identifier of the background agent - */ - agentId: string; - /** - * Type of the agent (e.g., explore, task, general-purpose) - */ - agentType: string; - /** - * Human-readable description of the agent task - */ - description?: string; - } - | { - type: "shell_completed"; - /** - * Unique identifier of the shell session - */ - shellId: string; - /** - * Exit code of the shell command, if available - */ - exitCode?: number; - /** - * Human-readable description of the command - */ - description?: string; - } - | { - type: "shell_detached_completed"; - /** - * Unique identifier of the detached shell session - */ - shellId: string; - /** - * Human-readable description of the command - */ - description?: string; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "permission.requested"; - /** - * Permission request notification requiring client approval with request details - */ - data: { - /** - * Unique identifier for this permission request; used to respond via session.respondToPermission() - */ - requestId: string; - /** - * Details of the permission being requested - */ - permissionRequest: - | { - /** - * Permission kind discriminator - */ - kind: "shell"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * The complete shell command text to be executed - */ - fullCommandText: string; - /** - * Human-readable description of what the command intends to do - */ - intention: string; - /** - * Parsed command identifiers found in the command text - */ - commands: { - /** - * Command identifier (e.g., executable name) - */ - identifier: string; - /** - * Whether this command is read-only (no side effects) - */ - readOnly: boolean; - }[]; - /** - * File paths that may be read or written by the command - */ - possiblePaths: string[]; - /** - * URLs that may be accessed by the command - */ - possibleUrls: { - /** - * URL that may be accessed by the command - */ - url: string; - }[]; - /** - * Whether the command includes a file write redirection (e.g., > or >>) - */ - hasWriteFileRedirection: boolean; - /** - * Whether the UI can offer session-wide approval for this command pattern - */ - canOfferSessionApproval: boolean; - /** - * Optional warning message about risks of running this command - */ - warning?: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "write"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of the intended file change - */ - intention: string; - /** - * Path of the file being written to - */ - fileName: string; - /** - * Unified diff showing the proposed changes - */ - diff: string; - /** - * Complete new file contents for newly created files - */ - newFileContents?: string; - /** - * Whether the UI can offer session-wide approval for file write operations - */ - canOfferSessionApproval: boolean; - } - | { - /** - * Permission kind discriminator - */ - kind: "read"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of why the file is being read - */ - intention: string; - /** - * Path of the file or directory being read - */ - path: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "mcp"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the MCP server providing the tool - */ - serverName: string; - /** - * Internal name of the MCP tool - */ - toolName: string; - /** - * Human-readable title of the MCP tool - */ - toolTitle: string; - /** - * Arguments to pass to the MCP tool - */ - args?: { - [k: string]: unknown; - }; - /** - * Whether this MCP tool is read-only (no side effects) - */ - readOnly: boolean; - } - | { - /** - * Permission kind discriminator - */ - kind: "url"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of why the URL is being accessed - */ - intention: string; - /** - * URL to be fetched - */ - url: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "memory"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Whether this is a store or vote memory operation - */ - action?: "store" | "vote"; - /** - * Topic or subject of the memory (store only) - */ - subject?: string; - /** - * The fact being stored or voted on - */ - fact: string; - /** - * Source references for the stored fact (store only) - */ - citations?: string; - /** - * Vote direction (vote only) - */ - direction?: "upvote" | "downvote"; - /** - * Reason for the vote (vote only) - */ - reason?: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "custom-tool"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the custom tool - */ - toolName: string; - /** - * Description of what the custom tool does - */ - toolDescription: string; - /** - * Arguments to pass to the custom tool - */ - args?: { - [k: string]: unknown; - }; - } - | { - /** - * Permission kind discriminator - */ - kind: "hook"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the tool the hook is gating - */ - toolName: string; - /** - * Arguments of the tool call being gated - */ - toolArgs?: { - [k: string]: unknown; - }; - /** - * Optional message from the hook explaining why confirmation is needed - */ - hookMessage?: string; - }; - /** - * When true, this permission was already resolved by a permissionRequest hook and requires no client action - */ - resolvedByHook?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "permission.completed"; - /** - * Permission request completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved permission request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The result of the permission request - */ - result: { - /** - * The outcome of the permission request - */ - kind: - | "approved" - | "denied-by-rules" - | "denied-no-approval-rule-and-could-not-request-from-user" - | "denied-interactively-by-user" - | "denied-by-content-exclusion-policy" - | "denied-by-permission-request-hook"; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user_input.requested"; - /** - * User input request notification with question and optional predefined choices - */ - data: { - /** - * Unique identifier for this input request; used to respond via session.respondToUserInput() - */ - requestId: string; - /** - * The question or prompt to present to the user - */ - question: string; - /** - * Predefined choices for the user to select from, if applicable - */ - choices?: string[]; - /** - * Whether the user can provide a free-form text response in addition to predefined choices - */ - allowFreeform?: boolean; - /** - * The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses - */ - toolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user_input.completed"; - /** - * User input request completion with the user's response - */ - data: { - /** - * Request ID of the resolved user input request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The user's answer to the input request - */ - answer?: string; - /** - * Whether the answer was typed as free-form text rather than selected from choices - */ - wasFreeform?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "elicitation.requested"; - /** - * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) - */ - data: { - /** - * Unique identifier for this elicitation request; used to respond via session.respondToElicitation() - */ - requestId: string; - /** - * Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs - */ - toolCallId?: string; - /** - * The source that initiated the request (MCP server name, or absent for agent-initiated) - */ - elicitationSource?: string; - /** - * Message describing what information is needed from the user - */ - message: string; - /** - * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. - */ - mode?: "form" | "url"; - /** - * JSON Schema describing the form fields to present to the user (form mode only) - */ - requestedSchema?: { - /** - * Schema type indicator (always 'object') - */ - type: "object"; - /** - * Form field definitions, keyed by field name - */ - properties: { - [k: string]: unknown; - }; - /** - * List of required field names - */ - required?: string[]; - }; - /** - * URL to open in the user's browser (url mode only) - */ - url?: string; - [k: string]: unknown; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "elicitation.completed"; - /** - * Elicitation request completion with the user's response - */ - data: { - /** - * Request ID of the resolved elicitation request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) - */ - action?: "accept" | "decline" | "cancel"; - /** - * The submitted form data when action is 'accept'; keys match the requested schema fields - */ - content?: { - [k: string]: string | number | boolean | string[]; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "sampling.requested"; - /** - * Sampling request from an MCP server; contains the server name and a requestId for correlation - */ - data: { - /** - * Unique identifier for this sampling request; used to respond via session.respondToSampling() - */ - requestId: string; - /** - * Name of the MCP server that initiated the sampling request - */ - serverName: string; - /** - * The JSON-RPC request ID from the MCP protocol - */ - mcpRequestId: string | number; - [k: string]: unknown; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "sampling.completed"; - /** - * Sampling request completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved sampling request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "mcp.oauth_required"; - /** - * OAuth authentication request for an MCP server - */ - data: { - /** - * Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() - */ - requestId: string; - /** - * Display name of the MCP server that requires OAuth - */ - serverName: string; - /** - * URL of the MCP server that requires OAuth - */ - serverUrl: string; - /** - * Static OAuth client configuration, if the server specifies one - */ - staticClientConfig?: { - /** - * OAuth client ID for the server - */ - clientId: string; - /** - * Whether this is a public OAuth client - */ - publicClient?: boolean; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "mcp.oauth_completed"; - /** - * MCP OAuth request completion notification - */ - data: { - /** - * Request ID of the resolved OAuth request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "external_tool.requested"; - /** - * External tool invocation request for client-side tool execution - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToExternalTool() - */ - requestId: string; - /** - * Session ID that this external tool request belongs to - */ - sessionId: string; - /** - * Tool call ID assigned to this external tool invocation - */ - toolCallId: string; - /** - * Name of the external tool to invoke - */ - toolName: string; - /** - * Arguments to pass to the external tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * W3C Trace Context traceparent header for the execute_tool span - */ - traceparent?: string; - /** - * W3C Trace Context tracestate header for the execute_tool span - */ - tracestate?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "external_tool.completed"; - /** - * External tool completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved external tool request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.queued"; - /** - * Queued slash command dispatch request for client execution - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToQueuedCommand() - */ - requestId: string; - /** - * The slash command text to be executed (e.g., /help, /clear) - */ - command: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.execute"; - /** - * Registered command dispatch request routed to the owning client - */ - data: { - /** - * Unique identifier; used to respond via session.commands.handlePendingCommand() - */ - requestId: string; - /** - * The full command text (e.g., /deploy production) - */ - command: string; - /** - * Command name without leading / - */ - commandName: string; - /** - * Raw argument string after the command name - */ - args: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.completed"; - /** - * Queued command completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved command request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "commands.changed"; - /** - * SDK command registration change notification - */ - data: { - /** - * Current list of registered SDK commands - */ - commands: { - name: string; - description?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "capabilities.changed"; - /** - * Session capability change notification - */ - data: { - /** - * UI capability changes - */ - ui?: { - /** - * Whether elicitation is now supported - */ - elicitation?: boolean; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "exit_plan_mode.requested"; - /** - * Plan approval request with plan content and available user actions - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToExitPlanMode() - */ - requestId: string; - /** - * Summary of the plan that was created - */ - summary: string; - /** - * Full content of the plan file - */ - planContent: string; - /** - * Available actions the user can take (e.g., approve, edit, reject) - */ - actions: string[]; - /** - * The recommended action for the user to take - */ - recommendedAction: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "exit_plan_mode.completed"; - /** - * Plan mode exit completion with the user's approval decision and optional feedback - */ - data: { - /** - * Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * Whether the plan was approved by the user - */ - approved?: boolean; - /** - * Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') - */ - selectedAction?: string; - /** - * Whether edits should be auto-approved without confirmation - */ - autoApproveEdits?: boolean; - /** - * Free-form feedback from the user if they requested changes to the plan - */ - feedback?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.tools_updated"; - data: { - model: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.background_tasks_changed"; - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.skills_loaded"; - data: { - /** - * Array of resolved skill metadata - */ - skills: { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type of the skill (e.g., project, personal, plugin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled - */ - enabled: boolean; - /** - * Absolute path to the skill file, if available - */ - path?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.custom_agents_updated"; - data: { - /** - * Array of loaded custom agent metadata - */ - agents: { - /** - * Unique identifier for the agent - */ - id: string; - /** - * Internal name of the agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of what the agent does - */ - description: string; - /** - * Source location: user, project, inherited, remote, or plugin - */ - source: string; - /** - * List of tool names available to this agent - */ - tools: string[]; - /** - * Whether the agent can be selected by the user - */ - userInvocable: boolean; - /** - * Model override for this agent, if set - */ - model?: string; - }[]; - /** - * Non-fatal warnings from agent loading - */ - warnings: string[]; - /** - * Fatal errors from agent loading - */ - errors: string[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mcp_servers_loaded"; - data: { - /** - * Array of MCP server status summaries - */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: string; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mcp_server_status_changed"; - data: { - /** - * Name of the MCP server whose status changed - */ - serverName: string; - /** - * New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.extensions_loaded"; - data: { - /** - * Array of discovered extensions and their status - */ - extensions: { - /** - * Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') - */ - id: string; - /** - * Extension name (directory name) - */ - name: string; - /** - * Discovery source - */ - source: "project" | "user"; - /** - * Current status: running, disabled, failed, or starting - */ - status: "running" | "disabled" | "failed" | "starting"; - }[]; - }; - }; + | StartEvent + | ResumeEvent + | RemoteSteerableChangedEvent + | ErrorEvent + | IdleEvent + | TitleChangedEvent + | InfoEvent + | WarningEvent + | ModelChangeEvent + | ModeChangedEvent + | PlanChangedEvent + | WorkspaceFileChangedEvent + | HandoffEvent + | TruncationEvent + | SnapshotRewindEvent + | ShutdownEvent + | ContextChangedEvent + | UsageInfoEvent + | CompactionStartEvent + | CompactionCompleteEvent + | TaskCompleteEvent + | UserMessageEvent + | PendingMessagesModifiedEvent + | AssistantTurnStartEvent + | AssistantIntentEvent + | AssistantReasoningEvent + | AssistantReasoningDeltaEvent + | AssistantStreamingDeltaEvent + | AssistantMessageEvent + | AssistantMessageDeltaEvent + | AssistantTurnEndEvent + | AssistantUsageEvent + | AbortEvent + | ToolUserRequestedEvent + | ToolExecutionStartEvent + | ToolExecutionPartialResultEvent + | ToolExecutionProgressEvent + | ToolExecutionCompleteEvent + | SkillInvokedEvent + | SubagentStartedEvent + | SubagentCompletedEvent + | SubagentFailedEvent + | SubagentSelectedEvent + | SubagentDeselectedEvent + | HookStartEvent + | HookEndEvent + | SystemMessageEvent + | SystemNotificationEvent + | PermissionRequestedEvent + | PermissionCompletedEvent + | UserInputRequestedEvent + | UserInputCompletedEvent + | ElicitationRequestedEvent + | ElicitationCompletedEvent + | SamplingRequestedEvent + | SamplingCompletedEvent + | McpOauthRequiredEvent + | McpOauthCompletedEvent + | ExternalToolRequestedEvent + | ExternalToolCompletedEvent + | CommandQueuedEvent + | CommandExecuteEvent + | CommandCompletedEvent + | CommandsChangedEvent + | CapabilitiesChangedEvent + | ExitPlanModeRequestedEvent + | ExitPlanModeCompletedEvent + | ToolsUpdatedEvent + | BackgroundTasksChangedEvent + | SkillsLoadedEvent + | CustomAgentsUpdatedEvent + | McpServersLoadedEvent + | McpServerStatusChangedEvent + | ExtensionsLoadedEvent; +/** + * Hosting platform type of the repository (github or ado) + */ +export type WorkingDirectoryContextHostType = "github" | "ado"; +/** + * The type of operation performed on the plan file + */ +export type PlanChangedOperation = "create" | "update" | "delete"; +/** + * Whether the file was newly created or updated + */ +export type WorkspaceFileChangedOperation = "create" | "update"; +/** + * Origin type of the session being handed off + */ +export type HandoffSourceType = "remote" | "local"; +/** + * Whether the session ended normally ("routine") or due to a crash/fatal error ("error") + */ +export type ShutdownType = "routine" | "error"; +/** + * The agent mode that was active when this message was sent + */ +export type UserMessageAgentMode = "interactive" | "plan" | "autopilot" | "shell"; +/** + * A user message attachment — a file, directory, code selection, blob, or GitHub reference + */ +export type UserMessageAttachment = + | UserMessageAttachmentFile + | UserMessageAttachmentDirectory + | UserMessageAttachmentSelection + | UserMessageAttachmentGithubReference + | UserMessageAttachmentBlob; +/** + * Type of GitHub reference + */ +export type UserMessageAttachmentGithubReferenceType = "issue" | "pr" | "discussion"; +/** + * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + */ +export type AssistantMessageToolRequestType = "function" | "custom"; +/** + * A content block within a tool result, which may be text, terminal output, image, audio, or a resource + */ +export type ToolExecutionCompleteContent = + | ToolExecutionCompleteContentText + | ToolExecutionCompleteContentTerminal + | ToolExecutionCompleteContentImage + | ToolExecutionCompleteContentAudio + | ToolExecutionCompleteContentResourceLink + | ToolExecutionCompleteContentResource; +/** + * Theme variant this icon is intended for + */ +export type ToolExecutionCompleteContentResourceLinkIconTheme = "light" | "dark"; +/** + * The embedded resource contents, either text or base64-encoded binary + */ +export type ToolExecutionCompleteContentResourceDetails = EmbeddedTextResourceContents | EmbeddedBlobResourceContents; +/** + * Message role: "system" for system prompts, "developer" for developer-injected instructions + */ +export type SystemMessageRole = "system" | "developer"; +/** + * Structured metadata identifying what triggered this notification + */ +export type SystemNotification = + | SystemNotificationAgentCompleted + | SystemNotificationAgentIdle + | SystemNotificationNewInboxMessage + | SystemNotificationShellCompleted + | SystemNotificationShellDetachedCompleted; +/** + * Whether the agent completed successfully or failed + */ +export type SystemNotificationAgentCompletedStatus = "completed" | "failed"; +/** + * Details of the permission being requested + */ +export type PermissionRequest = + | PermissionRequestShell + | PermissionRequestWrite + | PermissionRequestRead + | PermissionRequestMcp + | PermissionRequestUrl + | PermissionRequestMemory + | PermissionRequestCustomTool + | PermissionRequestHook; +/** + * Whether this is a store or vote memory operation + */ +export type PermissionRequestMemoryAction = "store" | "vote"; +/** + * Vote direction (vote only) + */ +export type PermissionRequestMemoryDirection = "upvote" | "downvote"; +/** + * The outcome of the permission request + */ +export type PermissionCompletedKind = + | "approved" + | "denied-by-rules" + | "denied-no-approval-rule-and-could-not-request-from-user" + | "denied-interactively-by-user" + | "denied-by-content-exclusion-policy" + | "denied-by-permission-request-hook"; +/** + * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. + */ +export type ElicitationRequestedMode = "form" | "url"; +/** + * The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) + */ +export type ElicitationCompletedAction = "accept" | "decline" | "cancel"; +export type ElicitationCompletedContent = string | number | boolean | string[]; +/** + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ +export type McpServersLoadedServerStatus = + | "connected" + | "failed" + | "needs-auth" + | "pending" + | "disabled" + | "not_configured"; +/** + * New connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ +export type McpServerStatusChangedStatus = + | "connected" + | "failed" + | "needs-auth" + | "pending" + | "disabled" + | "not_configured"; +/** + * Discovery source + */ +export type ExtensionsLoadedExtensionSource = "project" | "user"; +/** + * Current status: running, disabled, failed, or starting + */ +export type ExtensionsLoadedExtensionStatus = "running" | "disabled" | "failed" | "starting"; +export interface StartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: StartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.start"; +} +/** + * Session initialization metadata including context and configuration + */ +export interface StartData { + /** + * Whether the session was already in use by another client at start time + */ + alreadyInUse?: boolean; + context?: WorkingDirectoryContext; + /** + * Version string of the Copilot application + */ + copilotVersion: string; + /** + * Identifier of the software producing the events (e.g., "copilot-agent") + */ + producer: string; + /** + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + */ + reasoningEffort?: string; + /** + * Whether this session supports remote steering via Mission Control + */ + remoteSteerable?: boolean; + /** + * Model selected at session creation time, if any + */ + selectedModel?: string; + /** + * Unique identifier for the session + */ + sessionId: string; + /** + * ISO 8601 timestamp when the session was created + */ + startTime: string; + /** + * Schema version number for the session event format + */ + version: number; +} /** * Working directory and git context at session start */ export interface WorkingDirectoryContext { + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; + /** + * Current git branch name + */ + branch?: string; /** * Current working directory path */ @@ -3961,118 +308,4050 @@ export interface WorkingDirectoryContext { * Root directory of the git repository, resolved via git rev-parse */ gitRoot?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + hostType?: WorkingDirectoryContextHostType; /** * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) */ repository?: string; /** - * Hosting platform type of the repository (github or ado) + * Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") */ - hostType?: "github" | "ado"; + repositoryHost?: string; +} +export interface ResumeEvent { /** - * Current git branch name + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - branch?: string; + agentId?: string; + data: ResumeData; /** - * Head commit of current git branch at session start time + * When true, the event is transient and not persisted to the session event log on disk */ - headCommit?: string; + ephemeral?: boolean; /** - * Base commit of current git branch at session start time + * Unique event identifier (UUID v4), generated when the event is emitted */ - baseCommit?: string; + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.resume"; } /** - * Updated working directory and git context at resume time + * Session resume metadata including current context and event count */ -export interface WorkingDirectoryContext1 { +export interface ResumeData { /** - * Current working directory path + * Whether the session was already in use by another client at resume time */ - cwd: string; + alreadyInUse?: boolean; + context?: WorkingDirectoryContext; /** - * Root directory of the git repository, resolved via git rev-parse + * Total number of persisted events in the session at the time of resume */ - gitRoot?: string; + eventCount: number; /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ - repository?: string; + reasoningEffort?: string; /** - * Hosting platform type of the repository (github or ado) + * Whether this session supports remote steering via Mission Control */ - hostType?: "github" | "ado"; + remoteSteerable?: boolean; /** - * Current git branch name + * ISO 8601 timestamp when the session was resumed */ - branch?: string; + resumeTime: string; /** - * Head commit of current git branch at session start time + * Model currently selected at resume time */ - headCommit?: string; + selectedModel?: string; +} +export interface RemoteSteerableChangedEvent { /** - * Base commit of current git branch at session start time + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - baseCommit?: string; + agentId?: string; + data: RemoteSteerableChangedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.remote_steerable_changed"; } /** - * Updated working directory and git context after the change + * Notifies Mission Control that the session's remote steering capability has changed */ -export interface WorkingDirectoryContext2 { +export interface RemoteSteerableChangedData { /** - * Current working directory path + * Whether this session now supports remote steering via Mission Control */ - cwd: string; + remoteSteerable: boolean; +} +export interface ErrorEvent { /** - * Root directory of the git repository, resolved via git rev-parse + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - gitRoot?: string; + agentId?: string; + data: ErrorData; /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + * When true, the event is transient and not persisted to the session event log on disk */ - repository?: string; + ephemeral?: boolean; /** - * Hosting platform type of the repository (github or ado) + * Unique event identifier (UUID v4), generated when the event is emitted */ - hostType?: "github" | "ado"; + id: string; /** - * Current git branch name + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - branch?: string; + parentId: string | null; /** - * Head commit of current git branch at session start time + * ISO 8601 timestamp when the event was created */ - headCommit?: string; + timestamp: string; + type: "session.error"; +} +/** + * Error details for timeline display including message and optional diagnostic information + */ +export interface ErrorData { /** - * Base commit of current git branch at session start time + * Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") */ - baseCommit?: string; + errorType: string; + /** + * Human-readable error message + */ + message: string; + /** + * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + */ + providerCallId?: string; + /** + * Error stack trace, when available + */ + stack?: string; + /** + * HTTP status code from the upstream request, if applicable + */ + statusCode?: number; + /** + * Optional URL associated with this error that the user can open in a browser + */ + url?: string; } -export interface EmbeddedTextResourceContents { +export interface IdleEvent { /** - * URI identifying the resource + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - uri: string; + agentId?: string; + data: IdleData; + ephemeral: true; /** - * MIME type of the text content + * Unique event identifier (UUID v4), generated when the event is emitted */ - mimeType?: string; + id: string; /** - * Text content of the resource + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - text: string; + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.idle"; } -export interface EmbeddedBlobResourceContents { +/** + * Payload indicating the session is idle with no background agents in flight + */ +export interface IdleData { /** - * URI identifying the resource + * True when the preceding agentic loop was cancelled via abort signal */ - uri: string; + aborted?: boolean; +} +export interface TitleChangedEvent { /** - * MIME type of the blob content + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - mimeType?: string; + agentId?: string; + data: TitleChangedData; + ephemeral: true; /** - * Base64-encoded binary content of the resource + * Unique event identifier (UUID v4), generated when the event is emitted */ - blob: string; + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.title_changed"; +} +/** + * Session title change payload containing the new display title + */ +export interface TitleChangedData { + /** + * The new display title for the session + */ + title: string; +} +export interface InfoEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: InfoData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.info"; +} +/** + * Informational message for timeline display with categorization + */ +export interface InfoData { + /** + * Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") + */ + infoType: string; + /** + * Human-readable informational message for display in the timeline + */ + message: string; + /** + * Optional URL associated with this message that the user can open in a browser + */ + url?: string; +} +export interface WarningEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: WarningData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.warning"; +} +/** + * Warning message for timeline display with categorization + */ +export interface WarningData { + /** + * Human-readable warning message for display in the timeline + */ + message: string; + /** + * Optional URL associated with this warning that the user can open in a browser + */ + url?: string; + /** + * Category of warning (e.g., "subscription", "policy", "mcp") + */ + warningType: string; +} +export interface ModelChangeEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ModelChangeData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.model_change"; +} +/** + * Model change details including previous and new model identifiers + */ +export interface ModelChangeData { + /** + * Newly selected model identifier + */ + newModel: string; + /** + * Model that was previously selected, if any + */ + previousModel?: string; + /** + * Reasoning effort level before the model change, if applicable + */ + previousReasoningEffort?: string; + /** + * Reasoning effort level after the model change, if applicable + */ + reasoningEffort?: string; +} +export interface ModeChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ModeChangedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.mode_changed"; +} +/** + * Agent mode change details including previous and new modes + */ +export interface ModeChangedData { + /** + * Agent mode after the change (e.g., "interactive", "plan", "autopilot") + */ + newMode: string; + /** + * Agent mode before the change (e.g., "interactive", "plan", "autopilot") + */ + previousMode: string; +} +export interface PlanChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PlanChangedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.plan_changed"; +} +/** + * Plan file operation details indicating what changed + */ +export interface PlanChangedData { + operation: PlanChangedOperation; +} +export interface WorkspaceFileChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: WorkspaceFileChangedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.workspace_file_changed"; +} +/** + * Workspace file change details including path and operation type + */ +export interface WorkspaceFileChangedData { + operation: WorkspaceFileChangedOperation; + /** + * Relative path within the session workspace files directory + */ + path: string; +} +export interface HandoffEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: HandoffData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.handoff"; +} +/** + * Session handoff metadata including source, context, and repository information + */ +export interface HandoffData { + /** + * Additional context information for the handoff + */ + context?: string; + /** + * ISO 8601 timestamp when the handoff occurred + */ + handoffTime: string; + /** + * GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) + */ + host?: string; + /** + * Session ID of the remote session being handed off + */ + remoteSessionId?: string; + repository?: HandoffRepository; + sourceType: HandoffSourceType; + /** + * Summary of the work done in the source session + */ + summary?: string; +} +/** + * Repository context for the handed-off session + */ +export interface HandoffRepository { + /** + * Git branch name, if applicable + */ + branch?: string; + /** + * Repository name + */ + name: string; + /** + * Repository owner (user or organization) + */ + owner: string; +} +export interface TruncationEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: TruncationData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.truncation"; +} +/** + * Conversation truncation statistics including token counts and removed content metrics + */ +export interface TruncationData { + /** + * Number of messages removed by truncation + */ + messagesRemovedDuringTruncation: number; + /** + * Identifier of the component that performed truncation (e.g., "BasicTruncator") + */ + performedBy: string; + /** + * Number of conversation messages after truncation + */ + postTruncationMessagesLength: number; + /** + * Total tokens in conversation messages after truncation + */ + postTruncationTokensInMessages: number; + /** + * Number of conversation messages before truncation + */ + preTruncationMessagesLength: number; + /** + * Total tokens in conversation messages before truncation + */ + preTruncationTokensInMessages: number; + /** + * Maximum token count for the model's context window + */ + tokenLimit: number; + /** + * Number of tokens removed by truncation + */ + tokensRemovedDuringTruncation: number; +} +export interface SnapshotRewindEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SnapshotRewindData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.snapshot_rewind"; +} +/** + * Session rewind details including target event and count of removed events + */ +export interface SnapshotRewindData { + /** + * Number of events that were removed by the rewind + */ + eventsRemoved: number; + /** + * Event ID that was rewound to; this event and all after it were removed + */ + upToEventId: string; +} +export interface ShutdownEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ShutdownData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.shutdown"; +} +/** + * Session termination metrics including usage statistics, code changes, and shutdown reason + */ +export interface ShutdownData { + codeChanges: ShutdownCodeChanges; + /** + * Non-system message token count at shutdown + */ + conversationTokens?: number; + /** + * Model that was selected at the time of shutdown + */ + currentModel?: string; + /** + * Total tokens in context window at shutdown + */ + currentTokens?: number; + /** + * Error description when shutdownType is "error" + */ + errorReason?: string; + /** + * Per-model usage breakdown, keyed by model identifier + */ + modelMetrics: { + [k: string]: ShutdownModelMetric; + }; + /** + * Unix timestamp (milliseconds) when the session started + */ + sessionStartTime: number; + shutdownType: ShutdownType; + /** + * System message token count at shutdown + */ + systemTokens?: number; + /** + * Tool definitions token count at shutdown + */ + toolDefinitionsTokens?: number; + /** + * Cumulative time spent in API calls during the session, in milliseconds + */ + totalApiDurationMs: number; + /** + * Total number of premium API requests used during the session + */ + totalPremiumRequests: number; +} +/** + * Aggregate code change metrics for the session + */ +export interface ShutdownCodeChanges { + /** + * List of file paths that were modified during the session + */ + filesModified: string[]; + /** + * Total number of lines added during the session + */ + linesAdded: number; + /** + * Total number of lines removed during the session + */ + linesRemoved: number; +} +export interface ShutdownModelMetric { + requests: ShutdownModelMetricRequests; + usage: ShutdownModelMetricUsage; +} +/** + * Request count and cost metrics + */ +export interface ShutdownModelMetricRequests { + /** + * Cumulative cost multiplier for requests to this model + */ + cost: number; + /** + * Total number of API requests made to this model + */ + count: number; +} +/** + * Token usage breakdown + */ +export interface ShutdownModelMetricUsage { + /** + * Total tokens read from prompt cache across all requests + */ + cacheReadTokens: number; + /** + * Total tokens written to prompt cache across all requests + */ + cacheWriteTokens: number; + /** + * Total input tokens consumed across all requests to this model + */ + inputTokens: number; + /** + * Total output tokens produced across all requests to this model + */ + outputTokens: number; + /** + * Total reasoning tokens produced across all requests to this model + */ + reasoningTokens?: number; +} +export interface ContextChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: WorkingDirectoryContext; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.context_changed"; +} +export interface UsageInfoEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UsageInfoData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.usage_info"; +} +/** + * Current context window usage statistics including token and message counts + */ +export interface UsageInfoData { + /** + * Token count from non-system messages (user, assistant, tool) + */ + conversationTokens?: number; + /** + * Current number of tokens in the context window + */ + currentTokens: number; + /** + * Whether this is the first usage_info event emitted in this session + */ + isInitial?: boolean; + /** + * Current number of messages in the conversation + */ + messagesLength: number; + /** + * Token count from system message(s) + */ + systemTokens?: number; + /** + * Maximum token count for the model's context window + */ + tokenLimit: number; + /** + * Token count from tool definitions + */ + toolDefinitionsTokens?: number; +} +export interface CompactionStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CompactionStartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.compaction_start"; +} +/** + * Context window breakdown at the start of LLM-powered conversation compaction + */ +export interface CompactionStartData { + /** + * Token count from non-system messages (user, assistant, tool) at compaction start + */ + conversationTokens?: number; + /** + * Token count from system message(s) at compaction start + */ + systemTokens?: number; + /** + * Token count from tool definitions at compaction start + */ + toolDefinitionsTokens?: number; +} +export interface CompactionCompleteEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CompactionCompleteData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.compaction_complete"; +} +/** + * Conversation compaction results including success status, metrics, and optional error details + */ +export interface CompactionCompleteData { + /** + * Checkpoint snapshot number created for recovery + */ + checkpointNumber?: number; + /** + * File path where the checkpoint was stored + */ + checkpointPath?: string; + compactionTokensUsed?: CompactionCompleteCompactionTokensUsed; + /** + * Token count from non-system messages (user, assistant, tool) after compaction + */ + conversationTokens?: number; + /** + * Error message if compaction failed + */ + error?: string; + /** + * Number of messages removed during compaction + */ + messagesRemoved?: number; + /** + * Total tokens in conversation after compaction + */ + postCompactionTokens?: number; + /** + * Number of messages before compaction + */ + preCompactionMessagesLength?: number; + /** + * Total tokens in conversation before compaction + */ + preCompactionTokens?: number; + /** + * GitHub request tracing ID (x-github-request-id header) for the compaction LLM call + */ + requestId?: string; + /** + * Whether compaction completed successfully + */ + success: boolean; + /** + * LLM-generated summary of the compacted conversation history + */ + summaryContent?: string; + /** + * Token count from system message(s) after compaction + */ + systemTokens?: number; + /** + * Number of tokens removed during compaction + */ + tokensRemoved?: number; + /** + * Token count from tool definitions after compaction + */ + toolDefinitionsTokens?: number; +} +/** + * Token usage breakdown for the compaction LLM call + */ +export interface CompactionCompleteCompactionTokensUsed { + /** + * Cached input tokens reused in the compaction LLM call + */ + cachedInput: number; + /** + * Input tokens consumed by the compaction LLM call + */ + input: number; + /** + * Output tokens produced by the compaction LLM call + */ + output: number; +} +export interface TaskCompleteEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: TaskCompleteData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.task_complete"; +} +/** + * Task completion notification with summary from the agent + */ +export interface TaskCompleteData { + /** + * Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + */ + success?: boolean; + /** + * Summary of the completed task, provided by the agent + */ + summary?: string; +} +export interface UserMessageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UserMessageData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "user.message"; +} +export interface UserMessageData { + agentMode?: UserMessageAgentMode; + /** + * Files, selections, or GitHub references attached to the message + */ + attachments?: UserMessageAttachment[]; + /** + * The user's message text as displayed in the timeline + */ + content: string; + /** + * CAPI interaction ID for correlating this user message with its turn + */ + interactionId?: string; + /** + * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit + */ + nativeDocumentPathFallbackPaths?: string[]; + /** + * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + */ + source?: string; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + */ + transformedContent?: string; +} +/** + * File attachment + */ +export interface UserMessageAttachmentFile { + /** + * User-facing display name for the attachment + */ + displayName: string; + lineRange?: UserMessageAttachmentFileLineRange; + /** + * Absolute file path + */ + path: string; + /** + * Attachment type discriminator + */ + type: "file"; +} +/** + * Optional line range to scope the attachment to a specific section of the file + */ +export interface UserMessageAttachmentFileLineRange { + /** + * End line number (1-based, inclusive) + */ + end: number; + /** + * Start line number (1-based) + */ + start: number; +} +/** + * Directory attachment + */ +export interface UserMessageAttachmentDirectory { + /** + * User-facing display name for the attachment + */ + displayName: string; + /** + * Absolute directory path + */ + path: string; + /** + * Attachment type discriminator + */ + type: "directory"; +} +/** + * Code selection attachment from an editor + */ +export interface UserMessageAttachmentSelection { + /** + * User-facing display name for the selection + */ + displayName: string; + /** + * Absolute path to the file containing the selection + */ + filePath: string; + selection: UserMessageAttachmentSelectionDetails; + /** + * The selected text content + */ + text: string; + /** + * Attachment type discriminator + */ + type: "selection"; +} +/** + * Position range of the selection within the file + */ +export interface UserMessageAttachmentSelectionDetails { + end: UserMessageAttachmentSelectionDetailsEnd; + start: UserMessageAttachmentSelectionDetailsStart; +} +/** + * End position of the selection + */ +export interface UserMessageAttachmentSelectionDetailsEnd { + /** + * End character offset within the line (0-based) + */ + character: number; + /** + * End line number (0-based) + */ + line: number; +} +/** + * Start position of the selection + */ +export interface UserMessageAttachmentSelectionDetailsStart { + /** + * Start character offset within the line (0-based) + */ + character: number; + /** + * Start line number (0-based) + */ + line: number; +} +/** + * GitHub issue, pull request, or discussion reference + */ +export interface UserMessageAttachmentGithubReference { + /** + * Issue, pull request, or discussion number + */ + number: number; + referenceType: UserMessageAttachmentGithubReferenceType; + /** + * Current state of the referenced item (e.g., open, closed, merged) + */ + state: string; + /** + * Title of the referenced item + */ + title: string; + /** + * Attachment type discriminator + */ + type: "github_reference"; + /** + * URL to the referenced item on GitHub + */ + url: string; +} +/** + * Blob attachment with inline base64-encoded data + */ +export interface UserMessageAttachmentBlob { + /** + * Base64-encoded content + */ + data: string; + /** + * User-facing display name for the attachment + */ + displayName?: string; + /** + * MIME type of the inline data + */ + mimeType: string; + /** + * Attachment type discriminator + */ + type: "blob"; +} +export interface PendingMessagesModifiedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PendingMessagesModifiedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "pending_messages.modified"; +} +/** + * Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedData {} +export interface AssistantTurnStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantTurnStartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.turn_start"; +} +/** + * Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartData { + /** + * CAPI interaction ID for correlating this turn with upstream telemetry + */ + interactionId?: string; + /** + * Identifier for this turn within the agentic loop, typically a stringified turn number + */ + turnId: string; +} +export interface AssistantIntentEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantIntentData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.intent"; +} +/** + * Agent intent description for current activity or plan + */ +export interface AssistantIntentData { + /** + * Short description of what the agent is currently doing or planning to do + */ + intent: string; +} +export interface AssistantReasoningEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantReasoningData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.reasoning"; +} +/** + * Assistant reasoning content for timeline display with complete thinking text + */ +export interface AssistantReasoningData { + /** + * The complete extended thinking text from the model + */ + content: string; + /** + * Unique identifier for this reasoning block + */ + reasoningId: string; +} +export interface AssistantReasoningDeltaEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantReasoningDeltaData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.reasoning_delta"; +} +/** + * Streaming reasoning delta for incremental extended thinking updates + */ +export interface AssistantReasoningDeltaData { + /** + * Incremental text chunk to append to the reasoning content + */ + deltaContent: string; + /** + * Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event + */ + reasoningId: string; +} +export interface AssistantStreamingDeltaEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantStreamingDeltaData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.streaming_delta"; +} +/** + * Streaming response progress with cumulative byte count + */ +export interface AssistantStreamingDeltaData { + /** + * Cumulative total bytes received from the streaming response so far + */ + totalResponseSizeBytes: number; +} +export interface AssistantMessageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantMessageData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.message"; +} +/** + * Assistant response containing text content, optional tool requests, and interaction metadata + */ +export interface AssistantMessageData { + /** + * The assistant's text response content + */ + content: string; + /** + * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. + */ + encryptedContent?: string; + /** + * CAPI interaction ID for correlating this message with upstream telemetry + */ + interactionId?: string; + /** + * Unique identifier for this assistant message + */ + messageId: string; + /** + * Actual output token count from the API response (completion_tokens), used for accurate token accounting + */ + outputTokens?: number; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; + /** + * Generation phase for phased-output models (e.g., thinking vs. response phases) + */ + phase?: string; + /** + * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + */ + reasoningOpaque?: string; + /** + * Readable reasoning text from the model's extended thinking + */ + reasoningText?: string; + /** + * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + */ + requestId?: string; + /** + * Tool invocations requested by the assistant in this message + */ + toolRequests?: AssistantMessageToolRequest[]; +} +/** + * A tool invocation request from the assistant + */ +export interface AssistantMessageToolRequest { + /** + * Arguments to pass to the tool, format depends on the tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Resolved intention summary describing what this specific call does + */ + intentionSummary?: string | null; + /** + * Name of the MCP server hosting this tool, when the tool is an MCP tool + */ + mcpServerName?: string; + /** + * Name of the tool being invoked + */ + name: string; + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Human-readable display title for the tool + */ + toolTitle?: string; + type?: AssistantMessageToolRequestType; +} +export interface AssistantMessageDeltaEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantMessageDeltaData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.message_delta"; +} +/** + * Streaming assistant message delta for incremental response updates + */ +export interface AssistantMessageDeltaData { + /** + * Incremental text chunk to append to the message content + */ + deltaContent: string; + /** + * Message ID this delta belongs to, matching the corresponding assistant.message event + */ + messageId: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; +} +export interface AssistantTurnEndEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantTurnEndData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.turn_end"; +} +/** + * Turn completion metadata including the turn identifier + */ +export interface AssistantTurnEndData { + /** + * Identifier of the turn that has ended, matching the corresponding assistant.turn_start event + */ + turnId: string; +} +export interface AssistantUsageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AssistantUsageData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "assistant.usage"; +} +/** + * LLM API call usage metrics including tokens, costs, quotas, and billing information + */ +export interface AssistantUsageData { + /** + * Completion ID from the model provider (e.g., chatcmpl-abc123) + */ + apiCallId?: string; + /** + * Number of tokens read from prompt cache + */ + cacheReadTokens?: number; + /** + * Number of tokens written to prompt cache + */ + cacheWriteTokens?: number; + copilotUsage?: AssistantUsageCopilotUsage; + /** + * Model multiplier cost for billing purposes + */ + cost?: number; + /** + * Duration of the API call in milliseconds + */ + duration?: number; + /** + * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls + */ + initiator?: string; + /** + * Number of input tokens consumed + */ + inputTokens?: number; + /** + * Average inter-token latency in milliseconds. Only available for streaming requests + */ + interTokenLatencyMs?: number; + /** + * Model identifier used for this API call + */ + model: string; + /** + * Number of output tokens produced + */ + outputTokens?: number; + /** + * @deprecated + * Parent tool call ID when this usage originates from a sub-agent + */ + parentToolCallId?: string; + /** + * GitHub request tracing ID (x-github-request-id header) for server-side log correlation + */ + providerCallId?: string; + /** + * Per-quota resource usage snapshots, keyed by quota identifier + */ + quotaSnapshots?: { + [k: string]: AssistantUsageQuotaSnapshot; + }; + /** + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + */ + reasoningEffort?: string; + /** + * Number of output tokens used for reasoning (e.g., chain-of-thought) + */ + reasoningTokens?: number; + /** + * Time to first token in milliseconds. Only available for streaming requests + */ + ttftMs?: number; +} +/** + * Per-request cost and usage data from the CAPI copilot_usage response field + */ +export interface AssistantUsageCopilotUsage { + /** + * Itemized token usage breakdown + */ + tokenDetails: AssistantUsageCopilotUsageTokenDetail[]; + /** + * Total cost in nano-AIU (AI Units) for this request + */ + totalNanoAiu: number; +} +/** + * Token usage detail for a single billing category + */ +export interface AssistantUsageCopilotUsageTokenDetail { + /** + * Number of tokens in this billing batch + */ + batchSize: number; + /** + * Cost per batch of tokens + */ + costPerBatch: number; + /** + * Total token count for this entry + */ + tokenCount: number; + /** + * Token category (e.g., "input", "output") + */ + tokenType: string; +} +export interface AssistantUsageQuotaSnapshot { + /** + * Total requests allowed by the entitlement + */ + entitlementRequests: number; + /** + * Whether the user has an unlimited usage entitlement + */ + isUnlimitedEntitlement: boolean; + /** + * Number of requests over the entitlement limit + */ + overage: number; + /** + * Whether overage is allowed when quota is exhausted + */ + overageAllowedWithExhaustedQuota: boolean; + /** + * Percentage of quota remaining (0.0 to 1.0) + */ + remainingPercentage: number; + /** + * Date when the quota resets + */ + resetDate?: string; + /** + * Whether usage is still permitted after quota exhaustion + */ + usageAllowedWithExhaustedQuota: boolean; + /** + * Number of requests already consumed + */ + usedRequests: number; +} +export interface AbortEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AbortData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "abort"; +} +/** + * Turn abort information including the reason for termination + */ +export interface AbortData { + /** + * Reason the current turn was aborted (e.g., "user initiated") + */ + reason: string; +} +export interface ToolUserRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolUserRequestedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "tool.user_requested"; +} +/** + * User-initiated tool invocation request with tool name and arguments + */ +export interface ToolUserRequestedData { + /** + * Arguments for the tool invocation + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Name of the tool the user wants to invoke + */ + toolName: string; +} +export interface ToolExecutionStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolExecutionStartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "tool.execution_start"; +} +/** + * Tool execution startup details including MCP server information when applicable + */ +export interface ToolExecutionStartData { + /** + * Arguments passed to the tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Name of the MCP server hosting this tool, when the tool is an MCP tool + */ + mcpServerName?: string; + /** + * Original tool name on the MCP server, when the tool is an MCP tool + */ + mcpToolName?: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Name of the tool being executed + */ + toolName: string; +} +export interface ToolExecutionPartialResultEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolExecutionPartialData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "tool.execution_partial_result"; +} +/** + * Streaming tool execution output for incremental result display + */ +export interface ToolExecutionPartialData { + /** + * Incremental output chunk from the running tool + */ + partialOutput: string; + /** + * Tool call ID this partial result belongs to + */ + toolCallId: string; +} +export interface ToolExecutionProgressEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolExecutionProgressData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "tool.execution_progress"; +} +/** + * Tool execution progress notification with status message + */ +export interface ToolExecutionProgressData { + /** + * Human-readable progress status message (e.g., from an MCP server) + */ + progressMessage: string; + /** + * Tool call ID this progress notification belongs to + */ + toolCallId: string; +} +export interface ToolExecutionCompleteEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolExecutionCompleteData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "tool.execution_complete"; +} +/** + * Tool execution completion results including success status, detailed output, and error information + */ +export interface ToolExecutionCompleteData { + error?: ToolExecutionCompleteError; + /** + * CAPI interaction ID for correlating this tool execution with upstream telemetry + */ + interactionId?: string; + /** + * Whether this tool call was explicitly requested by the user rather than the assistant + */ + isUserRequested?: boolean; + /** + * Model identifier that generated this tool call + */ + model?: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; + result?: ToolExecutionCompleteResult; + /** + * Whether the tool execution completed successfully + */ + success: boolean; + /** + * Unique identifier for the completed tool call + */ + toolCallId: string; + /** + * Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) + */ + toolTelemetry?: { + [k: string]: unknown; + }; +} +/** + * Error details when the tool execution failed + */ +export interface ToolExecutionCompleteError { + /** + * Machine-readable error code + */ + code?: string; + /** + * Human-readable error message + */ + message: string; +} +/** + * Tool execution result on success + */ +export interface ToolExecutionCompleteResult { + /** + * Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency + */ + content: string; + /** + * Structured content blocks (text, images, audio, resources) returned by the tool in their native format + */ + contents?: ToolExecutionCompleteContent[]; + /** + * Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. + */ + detailedContent?: string; +} +/** + * Plain text content block + */ +export interface ToolExecutionCompleteContentText { + /** + * The text content + */ + text: string; + /** + * Content block type discriminator + */ + type: "text"; +} +/** + * Terminal/shell output content block with optional exit code and working directory + */ +export interface ToolExecutionCompleteContentTerminal { + /** + * Working directory where the command was executed + */ + cwd?: string; + /** + * Process exit code, if the command has completed + */ + exitCode?: number; + /** + * Terminal/shell output text + */ + text: string; + /** + * Content block type discriminator + */ + type: "terminal"; +} +/** + * Image content block with base64-encoded data + */ +export interface ToolExecutionCompleteContentImage { + /** + * Base64-encoded image data + */ + data: string; + /** + * MIME type of the image (e.g., image/png, image/jpeg) + */ + mimeType: string; + /** + * Content block type discriminator + */ + type: "image"; +} +/** + * Audio content block with base64-encoded data + */ +export interface ToolExecutionCompleteContentAudio { + /** + * Base64-encoded audio data + */ + data: string; + /** + * MIME type of the audio (e.g., audio/wav, audio/mpeg) + */ + mimeType: string; + /** + * Content block type discriminator + */ + type: "audio"; +} +/** + * Resource link content block referencing an external resource + */ +export interface ToolExecutionCompleteContentResourceLink { + /** + * Human-readable description of the resource + */ + description?: string; + /** + * Icons associated with this resource + */ + icons?: ToolExecutionCompleteContentResourceLinkIcon[]; + /** + * MIME type of the resource content + */ + mimeType?: string; + /** + * Resource name identifier + */ + name: string; + /** + * Size of the resource in bytes + */ + size?: number; + /** + * Human-readable display title for the resource + */ + title?: string; + /** + * Content block type discriminator + */ + type: "resource_link"; + /** + * URI identifying the resource + */ + uri: string; +} +/** + * Icon image for a resource + */ +export interface ToolExecutionCompleteContentResourceLinkIcon { + /** + * MIME type of the icon image + */ + mimeType?: string; + /** + * Available icon sizes (e.g., ['16x16', '32x32']) + */ + sizes?: string[]; + /** + * URL or path to the icon image + */ + src: string; + theme?: ToolExecutionCompleteContentResourceLinkIconTheme; +} +/** + * Embedded resource content block with inline text or binary data + */ +export interface ToolExecutionCompleteContentResource { + resource: ToolExecutionCompleteContentResourceDetails; + /** + * Content block type discriminator + */ + type: "resource"; +} +export interface EmbeddedTextResourceContents { + /** + * MIME type of the text content + */ + mimeType?: string; + /** + * Text content of the resource + */ + text: string; + /** + * URI identifying the resource + */ + uri: string; +} +export interface EmbeddedBlobResourceContents { + /** + * Base64-encoded binary content of the resource + */ + blob: string; + /** + * MIME type of the blob content + */ + mimeType?: string; + /** + * URI identifying the resource + */ + uri: string; +} +export interface SkillInvokedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SkillInvokedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "skill.invoked"; +} +/** + * Skill invocation details including content, allowed tools, and plugin metadata + */ +export interface SkillInvokedData { + /** + * Tool names that should be auto-approved when this skill is active + */ + allowedTools?: string[]; + /** + * Full content of the skill file, injected into the conversation for the model + */ + content: string; + /** + * Description of the skill from its SKILL.md frontmatter + */ + description?: string; + /** + * Name of the invoked skill + */ + name: string; + /** + * File path to the SKILL.md definition + */ + path: string; + /** + * Name of the plugin this skill originated from, when applicable + */ + pluginName?: string; + /** + * Version of the plugin this skill originated from, when applicable + */ + pluginVersion?: string; +} +export interface SubagentStartedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentStartedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "subagent.started"; +} +/** + * Sub-agent startup details including parent tool call and agent information + */ +export interface SubagentStartedData { + /** + * Description of what the sub-agent does + */ + agentDescription: string; + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; +} +export interface SubagentCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentCompletedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "subagent.completed"; +} +/** + * Sub-agent completion details for successful execution + */ +export interface SubagentCompletedData { + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Wall-clock duration of the sub-agent execution in milliseconds + */ + durationMs?: number; + /** + * Model used by the sub-agent + */ + model?: string; + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; + /** + * Total tokens (input + output) consumed by the sub-agent + */ + totalTokens?: number; + /** + * Total number of tool calls made by the sub-agent + */ + totalToolCalls?: number; +} +export interface SubagentFailedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentFailedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "subagent.failed"; +} +/** + * Sub-agent failure details including error message and agent information + */ +export interface SubagentFailedData { + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Wall-clock duration of the sub-agent execution in milliseconds + */ + durationMs?: number; + /** + * Error message describing why the sub-agent failed + */ + error: string; + /** + * Model used by the sub-agent (if any model calls succeeded before failure) + */ + model?: string; + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; + /** + * Total tokens (input + output) consumed before the sub-agent failed + */ + totalTokens?: number; + /** + * Total number of tool calls made before the sub-agent failed + */ + totalToolCalls?: number; +} +export interface SubagentSelectedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentSelectedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "subagent.selected"; +} +/** + * Custom agent selection details including name and available tools + */ +export interface SubagentSelectedData { + /** + * Human-readable display name of the selected custom agent + */ + agentDisplayName: string; + /** + * Internal name of the selected custom agent + */ + agentName: string; + /** + * List of tool names available to this agent, or null for all tools + */ + tools: string[] | null; +} +export interface SubagentDeselectedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SubagentDeselectedData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "subagent.deselected"; +} +/** + * Empty payload; the event signals that the custom agent was deselected, returning to the default agent + */ +export interface SubagentDeselectedData {} +export interface HookStartEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: HookStartData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "hook.start"; +} +/** + * Hook invocation start details including type and input data + */ +export interface HookStartData { + /** + * Unique identifier for this hook invocation + */ + hookInvocationId: string; + /** + * Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + */ + hookType: string; + /** + * Input data passed to the hook + */ + input?: { + [k: string]: unknown; + }; +} +export interface HookEndEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: HookEndData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "hook.end"; +} +/** + * Hook invocation completion details including output, success status, and error information + */ +export interface HookEndData { + error?: HookEndError; + /** + * Identifier matching the corresponding hook.start event + */ + hookInvocationId: string; + /** + * Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + */ + hookType: string; + /** + * Output data produced by the hook + */ + output?: { + [k: string]: unknown; + }; + /** + * Whether the hook completed successfully + */ + success: boolean; +} +/** + * Error details when the hook failed + */ +export interface HookEndError { + /** + * Human-readable error message + */ + message: string; + /** + * Error stack trace, when available + */ + stack?: string; +} +export interface SystemMessageEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SystemMessageData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "system.message"; +} +/** + * System/developer instruction content with role and optional template metadata + */ +export interface SystemMessageData { + /** + * The system or developer prompt text sent as model input + */ + content: string; + metadata?: SystemMessageMetadata; + /** + * Optional name identifier for the message source + */ + name?: string; + role: SystemMessageRole; +} +/** + * Metadata about the prompt template and its construction + */ +export interface SystemMessageMetadata { + /** + * Version identifier of the prompt template used + */ + promptVersion?: string; + /** + * Template variables used when constructing the prompt + */ + variables?: { + [k: string]: unknown; + }; +} +export interface SystemNotificationEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SystemNotificationData; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "system.notification"; +} +/** + * System-generated notification for runtime events like background task completion + */ +export interface SystemNotificationData { + /** + * The notification text, typically wrapped in XML tags + */ + content: string; + kind: SystemNotification; +} +export interface SystemNotificationAgentCompleted { + /** + * Unique identifier of the background agent + */ + agentId: string; + /** + * Type of the agent (e.g., explore, task, general-purpose) + */ + agentType: string; + /** + * Human-readable description of the agent task + */ + description?: string; + /** + * The full prompt given to the background agent + */ + prompt?: string; + status: SystemNotificationAgentCompletedStatus; + type: "agent_completed"; +} +export interface SystemNotificationAgentIdle { + /** + * Unique identifier of the background agent + */ + agentId: string; + /** + * Type of the agent (e.g., explore, task, general-purpose) + */ + agentType: string; + /** + * Human-readable description of the agent task + */ + description?: string; + type: "agent_idle"; +} +export interface SystemNotificationNewInboxMessage { + /** + * Unique identifier of the inbox entry + */ + entryId: string; + /** + * Human-readable name of the sender + */ + senderName: string; + /** + * Category of the sender (e.g., ambient-agent, plugin, hook) + */ + senderType: string; + /** + * Short summary shown before the agent decides whether to read the inbox + */ + summary: string; + type: "new_inbox_message"; +} +export interface SystemNotificationShellCompleted { + /** + * Human-readable description of the command + */ + description?: string; + /** + * Exit code of the shell command, if available + */ + exitCode?: number; + /** + * Unique identifier of the shell session + */ + shellId: string; + type: "shell_completed"; +} +export interface SystemNotificationShellDetachedCompleted { + /** + * Human-readable description of the command + */ + description?: string; + /** + * Unique identifier of the detached shell session + */ + shellId: string; + type: "shell_detached_completed"; +} +export interface PermissionRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PermissionRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "permission.requested"; +} +/** + * Permission request notification requiring client approval with request details + */ +export interface PermissionRequestedData { + permissionRequest: PermissionRequest; + /** + * Unique identifier for this permission request; used to respond via session.respondToPermission() + */ + requestId: string; + /** + * When true, this permission was already resolved by a permissionRequest hook and requires no client action + */ + resolvedByHook?: boolean; +} +/** + * Shell command permission request + */ +export interface PermissionRequestShell { + /** + * Whether the UI can offer session-wide approval for this command pattern + */ + canOfferSessionApproval: boolean; + /** + * Parsed command identifiers found in the command text + */ + commands: PermissionRequestShellCommand[]; + /** + * The complete shell command text to be executed + */ + fullCommandText: string; + /** + * Whether the command includes a file write redirection (e.g., > or >>) + */ + hasWriteFileRedirection: boolean; + /** + * Human-readable description of what the command intends to do + */ + intention: string; + /** + * Permission kind discriminator + */ + kind: "shell"; + /** + * File paths that may be read or written by the command + */ + possiblePaths: string[]; + /** + * URLs that may be accessed by the command + */ + possibleUrls: PermissionRequestShellPossibleUrl[]; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Optional warning message about risks of running this command + */ + warning?: string; +} +export interface PermissionRequestShellCommand { + /** + * Command identifier (e.g., executable name) + */ + identifier: string; + /** + * Whether this command is read-only (no side effects) + */ + readOnly: boolean; +} +export interface PermissionRequestShellPossibleUrl { + /** + * URL that may be accessed by the command + */ + url: string; +} +/** + * File write permission request + */ +export interface PermissionRequestWrite { + /** + * Whether the UI can offer session-wide approval for file write operations + */ + canOfferSessionApproval: boolean; + /** + * Unified diff showing the proposed changes + */ + diff: string; + /** + * Path of the file being written to + */ + fileName: string; + /** + * Human-readable description of the intended file change + */ + intention: string; + /** + * Permission kind discriminator + */ + kind: "write"; + /** + * Complete new file contents for newly created files + */ + newFileContents?: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * File or directory read permission request + */ +export interface PermissionRequestRead { + /** + * Human-readable description of why the file is being read + */ + intention: string; + /** + * Permission kind discriminator + */ + kind: "read"; + /** + * Path of the file or directory being read + */ + path: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * MCP tool invocation permission request + */ +export interface PermissionRequestMcp { + /** + * Arguments to pass to the MCP tool + */ + args?: { + [k: string]: unknown; + }; + /** + * Permission kind discriminator + */ + kind: "mcp"; + /** + * Whether this MCP tool is read-only (no side effects) + */ + readOnly: boolean; + /** + * Name of the MCP server providing the tool + */ + serverName: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Internal name of the MCP tool + */ + toolName: string; + /** + * Human-readable title of the MCP tool + */ + toolTitle: string; +} +/** + * URL access permission request + */ +export interface PermissionRequestUrl { + /** + * Human-readable description of why the URL is being accessed + */ + intention: string; + /** + * Permission kind discriminator + */ + kind: "url"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * URL to be fetched + */ + url: string; +} +/** + * Memory operation permission request + */ +export interface PermissionRequestMemory { + action?: PermissionRequestMemoryAction; + /** + * Source references for the stored fact (store only) + */ + citations?: string; + direction?: PermissionRequestMemoryDirection; + /** + * The fact being stored or voted on + */ + fact: string; + /** + * Permission kind discriminator + */ + kind: "memory"; + /** + * Reason for the vote (vote only) + */ + reason?: string; + /** + * Topic or subject of the memory (store only) + */ + subject?: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * Custom tool invocation permission request + */ +export interface PermissionRequestCustomTool { + /** + * Arguments to pass to the custom tool + */ + args?: { + [k: string]: unknown; + }; + /** + * Permission kind discriminator + */ + kind: "custom-tool"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Description of what the custom tool does + */ + toolDescription: string; + /** + * Name of the custom tool + */ + toolName: string; +} +/** + * Hook confirmation permission request + */ +export interface PermissionRequestHook { + /** + * Optional message from the hook explaining why confirmation is needed + */ + hookMessage?: string; + /** + * Permission kind discriminator + */ + kind: "hook"; + /** + * Arguments of the tool call being gated + */ + toolArgs?: { + [k: string]: unknown; + }; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Name of the tool the hook is gating + */ + toolName: string; +} +export interface PermissionCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: PermissionCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "permission.completed"; +} +/** + * Permission request completion notification signaling UI dismissal + */ +export interface PermissionCompletedData { + /** + * Request ID of the resolved permission request; clients should dismiss any UI for this request + */ + requestId: string; + result: PermissionCompletedResult; +} +/** + * The result of the permission request + */ +export interface PermissionCompletedResult { + kind: PermissionCompletedKind; +} +export interface UserInputRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UserInputRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "user_input.requested"; +} +/** + * User input request notification with question and optional predefined choices + */ +export interface UserInputRequestedData { + /** + * Whether the user can provide a free-form text response in addition to predefined choices + */ + allowFreeform?: boolean; + /** + * Predefined choices for the user to select from, if applicable + */ + choices?: string[]; + /** + * The question or prompt to present to the user + */ + question: string; + /** + * Unique identifier for this input request; used to respond via session.respondToUserInput() + */ + requestId: string; + /** + * The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses + */ + toolCallId?: string; +} +export interface UserInputCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: UserInputCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "user_input.completed"; +} +/** + * User input request completion with the user's response + */ +export interface UserInputCompletedData { + /** + * The user's answer to the input request + */ + answer?: string; + /** + * Request ID of the resolved user input request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * Whether the answer was typed as free-form text rather than selected from choices + */ + wasFreeform?: boolean; +} +export interface ElicitationRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ElicitationRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "elicitation.requested"; +} +/** + * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) + */ +export interface ElicitationRequestedData { + /** + * The source that initiated the request (MCP server name, or absent for agent-initiated) + */ + elicitationSource?: string; + /** + * Message describing what information is needed from the user + */ + message: string; + mode?: ElicitationRequestedMode; + requestedSchema?: ElicitationRequestedSchema; + /** + * Unique identifier for this elicitation request; used to respond via session.respondToElicitation() + */ + requestId: string; + /** + * Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs + */ + toolCallId?: string; + /** + * URL to open in the user's browser (url mode only) + */ + url?: string; + [k: string]: unknown; +} +/** + * JSON Schema describing the form fields to present to the user (form mode only) + */ +export interface ElicitationRequestedSchema { + /** + * Form field definitions, keyed by field name + */ + properties: { + [k: string]: unknown; + }; + /** + * List of required field names + */ + required?: string[]; + /** + * Schema type indicator (always 'object') + */ + type: "object"; +} +export interface ElicitationCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ElicitationCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "elicitation.completed"; +} +/** + * Elicitation request completion with the user's response + */ +export interface ElicitationCompletedData { + action?: ElicitationCompletedAction; + /** + * The submitted form data when action is 'accept'; keys match the requested schema fields + */ + content?: { + [k: string]: ElicitationCompletedContent; + }; + /** + * Request ID of the resolved elicitation request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface SamplingRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SamplingRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "sampling.requested"; +} +/** + * Sampling request from an MCP server; contains the server name and a requestId for correlation + */ +export interface SamplingRequestedData { + /** + * The JSON-RPC request ID from the MCP protocol + */ + mcpRequestId: string | number; + /** + * Unique identifier for this sampling request; used to respond via session.respondToSampling() + */ + requestId: string; + /** + * Name of the MCP server that initiated the sampling request + */ + serverName: string; + [k: string]: unknown; +} +export interface SamplingCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SamplingCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "sampling.completed"; +} +/** + * Sampling request completion notification signaling UI dismissal + */ +export interface SamplingCompletedData { + /** + * Request ID of the resolved sampling request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface McpOauthRequiredEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: McpOauthRequiredData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "mcp.oauth_required"; +} +/** + * OAuth authentication request for an MCP server + */ +export interface McpOauthRequiredData { + /** + * Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() + */ + requestId: string; + /** + * Display name of the MCP server that requires OAuth + */ + serverName: string; + /** + * URL of the MCP server that requires OAuth + */ + serverUrl: string; + staticClientConfig?: McpOauthRequiredStaticClientConfig; +} +/** + * Static OAuth client configuration, if the server specifies one + */ +export interface McpOauthRequiredStaticClientConfig { + /** + * OAuth client ID for the server + */ + clientId: string; + /** + * Whether this is a public OAuth client + */ + publicClient?: boolean; +} +export interface McpOauthCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: McpOauthCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "mcp.oauth_completed"; +} +/** + * MCP OAuth request completion notification + */ +export interface McpOauthCompletedData { + /** + * Request ID of the resolved OAuth request + */ + requestId: string; +} +export interface ExternalToolRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ExternalToolRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "external_tool.requested"; +} +/** + * External tool invocation request for client-side tool execution + */ +export interface ExternalToolRequestedData { + /** + * Arguments to pass to the external tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Unique identifier for this request; used to respond via session.respondToExternalTool() + */ + requestId: string; + /** + * Session ID that this external tool request belongs to + */ + sessionId: string; + /** + * Tool call ID assigned to this external tool invocation + */ + toolCallId: string; + /** + * Name of the external tool to invoke + */ + toolName: string; + /** + * W3C Trace Context traceparent header for the execute_tool span + */ + traceparent?: string; + /** + * W3C Trace Context tracestate header for the execute_tool span + */ + tracestate?: string; +} +export interface ExternalToolCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ExternalToolCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "external_tool.completed"; +} +/** + * External tool completion notification signaling UI dismissal + */ +export interface ExternalToolCompletedData { + /** + * Request ID of the resolved external tool request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface CommandQueuedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CommandQueuedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "command.queued"; +} +/** + * Queued slash command dispatch request for client execution + */ +export interface CommandQueuedData { + /** + * The slash command text to be executed (e.g., /help, /clear) + */ + command: string; + /** + * Unique identifier for this request; used to respond via session.respondToQueuedCommand() + */ + requestId: string; +} +export interface CommandExecuteEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CommandExecuteData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "command.execute"; +} +/** + * Registered command dispatch request routed to the owning client + */ +export interface CommandExecuteData { + /** + * Raw argument string after the command name + */ + args: string; + /** + * The full command text (e.g., /deploy production) + */ + command: string; + /** + * Command name without leading / + */ + commandName: string; + /** + * Unique identifier; used to respond via session.commands.handlePendingCommand() + */ + requestId: string; +} +export interface CommandCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CommandCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "command.completed"; +} +/** + * Queued command completion notification signaling UI dismissal + */ +export interface CommandCompletedData { + /** + * Request ID of the resolved command request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface CommandsChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CommandsChangedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "commands.changed"; +} +/** + * SDK command registration change notification + */ +export interface CommandsChangedData { + /** + * Current list of registered SDK commands + */ + commands: CommandsChangedCommand[]; +} +export interface CommandsChangedCommand { + description?: string; + name: string; +} +export interface CapabilitiesChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CapabilitiesChangedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "capabilities.changed"; +} +/** + * Session capability change notification + */ +export interface CapabilitiesChangedData { + ui?: CapabilitiesChangedUI; +} +/** + * UI capability changes + */ +export interface CapabilitiesChangedUI { + /** + * Whether elicitation is now supported + */ + elicitation?: boolean; +} +export interface ExitPlanModeRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ExitPlanModeRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "exit_plan_mode.requested"; +} +/** + * Plan approval request with plan content and available user actions + */ +export interface ExitPlanModeRequestedData { + /** + * Available actions the user can take (e.g., approve, edit, reject) + */ + actions: string[]; + /** + * Full content of the plan file + */ + planContent: string; + /** + * The recommended action for the user to take + */ + recommendedAction: string; + /** + * Unique identifier for this request; used to respond via session.respondToExitPlanMode() + */ + requestId: string; + /** + * Summary of the plan that was created + */ + summary: string; +} +export interface ExitPlanModeCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ExitPlanModeCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "exit_plan_mode.completed"; +} +/** + * Plan mode exit completion with the user's approval decision and optional feedback + */ +export interface ExitPlanModeCompletedData { + /** + * Whether the plan was approved by the user + */ + approved?: boolean; + /** + * Whether edits should be auto-approved without confirmation + */ + autoApproveEdits?: boolean; + /** + * Free-form feedback from the user if they requested changes to the plan + */ + feedback?: string; + /** + * Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') + */ + selectedAction?: string; +} +export interface ToolsUpdatedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ToolsUpdatedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.tools_updated"; +} +export interface ToolsUpdatedData { + model: string; +} +export interface BackgroundTasksChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: BackgroundTasksChangedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.background_tasks_changed"; +} +export interface BackgroundTasksChangedData {} +export interface SkillsLoadedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: SkillsLoadedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.skills_loaded"; +} +export interface SkillsLoadedData { + /** + * Array of resolved skill metadata + */ + skills: SkillsLoadedSkill[]; +} +export interface SkillsLoadedSkill { + /** + * Description of what the skill does + */ + description: string; + /** + * Whether the skill is currently enabled + */ + enabled: boolean; + /** + * Unique identifier for the skill + */ + name: string; + /** + * Absolute path to the skill file, if available + */ + path?: string; + /** + * Source location type of the skill (e.g., project, personal, plugin) + */ + source: string; + /** + * Whether the skill can be invoked by the user as a slash command + */ + userInvocable: boolean; +} +export interface CustomAgentsUpdatedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: CustomAgentsUpdatedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.custom_agents_updated"; +} +export interface CustomAgentsUpdatedData { + /** + * Array of loaded custom agent metadata + */ + agents: CustomAgentsUpdatedAgent[]; + /** + * Fatal errors from agent loading + */ + errors: string[]; + /** + * Non-fatal warnings from agent loading + */ + warnings: string[]; +} +export interface CustomAgentsUpdatedAgent { + /** + * Description of what the agent does + */ + description: string; + /** + * Human-readable display name + */ + displayName: string; + /** + * Unique identifier for the agent + */ + id: string; + /** + * Model override for this agent, if set + */ + model?: string; + /** + * Internal name of the agent + */ + name: string; + /** + * Source location: user, project, inherited, remote, or plugin + */ + source: string; + /** + * List of tool names available to this agent + */ + tools: string[]; + /** + * Whether the agent can be selected by the user + */ + userInvocable: boolean; +} +export interface McpServersLoadedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: McpServersLoadedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.mcp_servers_loaded"; +} +export interface McpServersLoadedData { + /** + * Array of MCP server status summaries + */ + servers: McpServersLoadedServer[]; +} +export interface McpServersLoadedServer { + /** + * Error message if the server failed to connect + */ + error?: string; + /** + * Server name (config key) + */ + name: string; + /** + * Configuration source: user, workspace, plugin, or builtin + */ + source?: string; + status: McpServersLoadedServerStatus; +} +export interface McpServerStatusChangedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: McpServerStatusChangedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.mcp_server_status_changed"; +} +export interface McpServerStatusChangedData { + /** + * Name of the MCP server whose status changed + */ + serverName: string; + status: McpServerStatusChangedStatus; +} +export interface ExtensionsLoadedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: ExtensionsLoadedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "session.extensions_loaded"; +} +export interface ExtensionsLoadedData { + /** + * Array of discovered extensions and their status + */ + extensions: ExtensionsLoadedExtension[]; +} +export interface ExtensionsLoadedExtension { + /** + * Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') + */ + id: string; + /** + * Extension name (directory name) + */ + name: string; + source: ExtensionsLoadedExtensionSource; + status: ExtensionsLoadedExtensionStatus; } diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 503d0942d..cc98cbcc8 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -14,6 +14,7 @@ export { defineTool, approveAll, convertMcpCallToolResult, + createSessionFsAdapter, SYSTEM_PROMPT_SECTIONS, } from "./types.js"; export type { @@ -67,7 +68,8 @@ export type { SessionMetadata, SessionUiApi, SessionFsConfig, - SessionFsHandler, + SessionFsProvider, + SessionFsFileInfo, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, diff --git a/nodejs/src/sessionFsProvider.ts b/nodejs/src/sessionFsProvider.ts new file mode 100644 index 000000000..721a990ec --- /dev/null +++ b/nodejs/src/sessionFsProvider.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import type { + SessionFsHandler, + SessionFsError, + SessionFsStatResult, + SessionFsReaddirWithTypesEntry, +} from "./generated/rpc.js"; + +/** + * File metadata returned by {@link SessionFsProvider.stat}. + * Same shape as the generated {@link SessionFsStatResult} but without the + * `error` field, since providers signal errors by throwing. + */ +export type SessionFsFileInfo = Omit; + +/** + * Interface for session filesystem providers. Implementors use idiomatic + * TypeScript patterns: throw on error, return values directly. Use + * {@link createSessionFsAdapter} to convert a provider into the + * {@link SessionFsHandler} expected by the SDK. + * + * Errors with a `code` property of `"ENOENT"` are mapped to the ENOENT + * error code; all others map to UNKNOWN. + */ +export interface SessionFsProvider { + /** Reads the full content of a file. Throw if the file does not exist. */ + readFile(path: string): Promise; + + /** Writes content to a file, creating parent directories if needed. */ + writeFile(path: string, content: string, mode?: number): Promise; + + /** Appends content to a file, creating parent directories if needed. */ + appendFile(path: string, content: string, mode?: number): Promise; + + /** Checks whether a path exists. */ + exists(path: string): Promise; + + /** Gets metadata about a file or directory. Throw if it does not exist. */ + stat(path: string): Promise; + + /** Creates a directory. If recursive is true, creates parents as needed. */ + mkdir(path: string, recursive: boolean, mode?: number): Promise; + + /** Lists entry names in a directory. Throw if it does not exist. */ + readdir(path: string): Promise; + + /** Lists entries with type info. Throw if the directory does not exist. */ + readdirWithTypes(path: string): Promise; + + /** Removes a file or directory. If force is true, do not throw on ENOENT. */ + rm(path: string, recursive: boolean, force: boolean): Promise; + + /** Renames/moves a file or directory. */ + rename(src: string, dest: string): Promise; +} + +/** + * Wraps a {@link SessionFsProvider} into the {@link SessionFsHandler} + * interface expected by the SDK, converting thrown errors into + * {@link SessionFsError} results. + */ +export function createSessionFsAdapter(provider: SessionFsProvider): SessionFsHandler { + return { + readFile: async ({ path }) => { + try { + const content = await provider.readFile(path); + return { content }; + } catch (err) { + return { content: "", error: toSessionFsError(err) }; + } + }, + writeFile: async ({ path, content, mode }) => { + try { + await provider.writeFile(path, content, mode); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + appendFile: async ({ path, content, mode }) => { + try { + await provider.appendFile(path, content, mode); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + exists: async ({ path }) => { + try { + return { exists: await provider.exists(path) }; + } catch { + return { exists: false }; + } + }, + stat: async ({ path }) => { + try { + return await provider.stat(path); + } catch (err) { + return { + isFile: false, + isDirectory: false, + size: 0, + mtime: new Date().toISOString(), + birthtime: new Date().toISOString(), + error: toSessionFsError(err), + }; + } + }, + mkdir: async ({ path, recursive, mode }) => { + try { + await provider.mkdir(path, recursive ?? false, mode); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + readdir: async ({ path }) => { + try { + const entries = await provider.readdir(path); + return { entries }; + } catch (err) { + return { entries: [], error: toSessionFsError(err) }; + } + }, + readdirWithTypes: async ({ path }) => { + try { + const entries = await provider.readdirWithTypes(path); + return { entries }; + } catch (err) { + return { entries: [], error: toSessionFsError(err) }; + } + }, + rm: async ({ path, recursive, force }) => { + try { + await provider.rm(path, recursive ?? false, force ?? false); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + rename: async ({ src, dest }) => { + try { + await provider.rename(src, dest); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + }; +} + +function toSessionFsError(err: unknown): SessionFsError { + const e = err as NodeJS.ErrnoException; + const code = e.code === "ENOENT" ? "ENOENT" : "UNKNOWN"; + return { code, message: e.message ?? String(err) }; +} diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index a8c644341..9f6eaf11d 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -7,11 +7,13 @@ */ // Import and re-export generated session event types -import type { SessionFsHandler } from "./generated/rpc.js"; +import type { SessionFsProvider } from "./sessionFsProvider.js"; import type { SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js"; import type { CopilotSession } from "./session.js"; export type SessionEvent = GeneratedSessionEvent; -export type { SessionFsHandler } from "./generated/rpc.js"; +export type { SessionFsProvider } from "./sessionFsProvider.js"; +export { createSessionFsAdapter } from "./sessionFsProvider.js"; +export type { SessionFsFileInfo } from "./sessionFsProvider.js"; /** * Options for creating a CopilotClient @@ -739,9 +741,8 @@ export type SystemMessageConfig = * Permission request types from the server */ export interface PermissionRequest { - kind: "shell" | "write" | "mcp" | "read" | "url" | "custom-tool"; + kind: "shell" | "write" | "mcp" | "read" | "url" | "custom-tool" | "memory" | "hook"; toolCallId?: string; - [key: string]: unknown; } import type { PermissionDecisionRequest } from "./generated/rpc.js"; @@ -1354,7 +1355,7 @@ export interface SessionConfig { * Supplies a handler for session filesystem operations. This takes effect * only if {@link CopilotClientOptions.sessionFs} is configured. */ - createSessionFsHandler?: (session: CopilotSession) => SessionFsHandler; + createSessionFsHandler?: (session: CopilotSession) => SessionFsProvider; } /** diff --git a/nodejs/test/e2e/session_fs.test.ts b/nodejs/test/e2e/session_fs.test.ts index 8185a55be..f455ffcd1 100644 --- a/nodejs/test/e2e/session_fs.test.ts +++ b/nodejs/test/e2e/session_fs.test.ts @@ -6,13 +6,15 @@ import { SessionCompactionCompleteEvent } from "@github/copilot/sdk"; import { MemoryProvider, VirtualProvider } from "@platformatic/vfs"; import { describe, expect, it, onTestFinished } from "vitest"; import { CopilotClient } from "../../src/client.js"; -import { SessionFsHandler } from "../../src/generated/rpc.js"; +import type { SessionFsReaddirWithTypesEntry } from "../../src/generated/rpc.js"; import { approveAll, CopilotSession, defineTool, SessionEvent, type SessionFsConfig, + type SessionFsProvider, + type SessionFsFileInfo, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -123,6 +125,46 @@ describe("Session Fs", async () => { expect(fileContent).toBe(suppliedFileContent); }); + it("should write workspace metadata via sessionFs", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + createSessionFsHandler, + }); + + const msg = await session.sendAndWait({ prompt: "What is 7 * 8?" }); + expect(msg?.data.content).toContain("56"); + + // WorkspaceManager should have created workspace.yaml via sessionFs + const workspaceYamlPath = p(session.sessionId, "/session-state/workspace.yaml"); + await expect.poll(() => provider.exists(workspaceYamlPath)).toBe(true); + const yaml = await provider.readFile(workspaceYamlPath, "utf8"); + expect(yaml).toContain("id:"); + + // Checkpoint index should also exist + const indexPath = p(session.sessionId, "/session-state/checkpoints/index.md"); + await expect.poll(() => provider.exists(indexPath)).toBe(true); + + await session.disconnect(); + }); + + it("should persist plan.md via sessionFs", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + createSessionFsHandler, + }); + + // Write a plan via the session RPC + await session.sendAndWait({ prompt: "What is 2 + 3?" }); + await session.rpc.plan.update({ content: "# Test Plan\n\nThis is a test." }); + + const planPath = p(session.sessionId, "/session-state/plan.md"); + await expect.poll(() => provider.exists(planPath)).toBe(true); + const content = await provider.readFile(planPath, "utf8"); + expect(content).toContain("# Test Plan"); + + await session.disconnect(); + }); + it("should succeed with compaction while using sessionFs", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, @@ -177,26 +219,24 @@ const sessionFsConfig: SessionFsConfig = { function createTestSessionFsHandler( session: CopilotSession, provider: VirtualProvider -): SessionFsHandler { - const sp = (sessionId: string, path: string) => - `/${sessionId}${path.startsWith("/") ? path : "/" + path}`; +): SessionFsProvider { + const sp = (path: string) => `/${session.sessionId}${path.startsWith("/") ? path : "/" + path}`; return { - readFile: async ({ path }) => { - const content = await provider.readFile(sp(session.sessionId, path), "utf8"); - return { content: content as string }; + async readFile(path: string): Promise { + return (await provider.readFile(sp(path), "utf8")) as string; }, - writeFile: async ({ path, content }) => { - await provider.writeFile(sp(session.sessionId, path), content); + async writeFile(path: string, content: string): Promise { + await provider.writeFile(sp(path), content); }, - appendFile: async ({ path, content }) => { - await provider.appendFile(sp(session.sessionId, path), content); + async appendFile(path: string, content: string): Promise { + await provider.appendFile(sp(path), content); }, - exists: async ({ path }) => { - return { exists: await provider.exists(sp(session.sessionId, path)) }; + async exists(path: string): Promise { + return provider.exists(sp(path)); }, - stat: async ({ path }) => { - const st = await provider.stat(sp(session.sessionId, path)); + async stat(path: string): Promise { + const st = await provider.stat(sp(path)); return { isFile: st.isFile(), isDirectory: st.isDirectory(), @@ -205,34 +245,29 @@ function createTestSessionFsHandler( birthtime: new Date(st.birthtimeMs).toISOString(), }; }, - mkdir: async ({ path, recursive, mode }) => { - await provider.mkdir(sp(session.sessionId, path), { - recursive: recursive ?? false, - mode, - }); + async mkdir(path: string, recursive: boolean, mode?: number): Promise { + await provider.mkdir(sp(path), { recursive, mode }); }, - readdir: async ({ path }) => { - const entries = await provider.readdir(sp(session.sessionId, path)); - return { entries: entries as string[] }; + async readdir(path: string): Promise { + return (await provider.readdir(sp(path))) as string[]; }, - readdirWithTypes: async ({ path }) => { - const names = (await provider.readdir(sp(session.sessionId, path))) as string[]; - const entries = await Promise.all( + async readdirWithTypes(path: string): Promise { + const names = (await provider.readdir(sp(path))) as string[]; + return Promise.all( names.map(async (name) => { - const st = await provider.stat(sp(session.sessionId, `${path}/${name}`)); + const st = await provider.stat(sp(`${path}/${name}`)); return { name, type: st.isDirectory() ? ("directory" as const) : ("file" as const), }; }) ); - return { entries }; }, - rm: async ({ path }) => { - await provider.unlink(sp(session.sessionId, path)); + async rm(path: string): Promise { + await provider.unlink(sp(path)); }, - rename: async ({ src, dest }) => { - await provider.rename(sp(session.sessionId, src), sp(session.sessionId, dest)); + async rename(src: string, dest: string): Promise { + await provider.rename(sp(src), sp(dest)); }, }; } diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 190c058a0..ad9e28803 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -26,10 +26,14 @@ ProviderConfig, SessionCapabilities, SessionFsConfig, - SessionFsHandler, SessionUiApi, SessionUiCapabilities, ) +from .session_fs_provider import ( + SessionFsFileInfo, + SessionFsProvider, + create_session_fs_adapter, +) from .tools import convert_mcp_call_tool_result, define_tool __version__ = "0.1.0" @@ -53,7 +57,9 @@ "ProviderConfig", "SessionCapabilities", "SessionFsConfig", - "SessionFsHandler", + "SessionFsFileInfo", + "SessionFsProvider", + "create_session_fs_adapter", "SessionUiApi", "SessionUiCapabilities", "SubprocessConfig", diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index 287f1b965..8a200cc8d 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -328,7 +328,8 @@ def _handle_message(self, message: dict): self._handle_request(message) def _handle_request(self, message: dict): - handler = self.request_handlers.get(message["method"]) + method = message.get("method", "") + handler = self.request_handlers.get(method) if not handler: if self._loop: asyncio.run_coroutine_threadsafe( @@ -351,17 +352,17 @@ async def _dispatch_request(self, message: dict, handler: RequestHandler): outcome = handler(params) if inspect.isawaitable(outcome): outcome = await outcome - if outcome is None: - outcome = {} - if not isinstance(outcome, dict): - raise ValueError("Request handler must return a dict") + if outcome is not None and not isinstance(outcome, dict): + raise ValueError( + f"Request handler must return a dict, got {type(outcome).__name__}" + ) await self._send_response(message["id"], outcome) except JsonRpcError as exc: await self._send_error_response(message["id"], exc.code, exc.message, exc.data) except Exception as exc: # pylint: disable=broad-except await self._send_error_response(message["id"], -32603, str(exc), None) - async def _send_response(self, request_id: str, result: dict): + async def _send_response(self, request_id: str, result: dict | None): response = { "jsonrpc": "2.0", "id": request_id, diff --git a/python/copilot/client.py b/python/copilot/client.py index 09d970f4b..a51940a96 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -60,6 +60,7 @@ UserInputHandler, _PermissionHandlerFn, ) +from .session_fs_provider import create_session_fs_adapter from .tools import Tool, ToolInvocation, ToolResult # ============================================================================ @@ -1435,7 +1436,9 @@ async def create_session( "create_session_fs_handler is required in session config when " "session_fs is enabled in client options." ) - session._client_session_apis.session_fs = create_session_fs_handler(session) + session._client_session_apis.session_fs = create_session_fs_adapter( + create_session_fs_handler(session) + ) session._register_tools(tools) session._register_commands(commands) session._register_permission_handler(on_permission_request) @@ -1697,7 +1700,9 @@ async def resume_session( "create_session_fs_handler is required in session config when " "session_fs is enabled in client options." ) - session._client_session_apis.session_fs = create_session_fs_handler(session) + session._client_session_apis.session_fs = create_session_fs_adapter( + create_session_fs_handler(session) + ) session._register_tools(tools) session._register_commands(commands) session._register_permission_handler(on_permission_request) diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 1aa658823..67c41fc96 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -25,9 +25,13 @@ def from_int(x: Any) -> int: assert isinstance(x, int) and not isinstance(x, bool) return x -def from_list(f: Callable[[Any], T], x: Any) -> list[T]: - assert isinstance(x, list) - return [f(y) for y in x] +def from_bool(x: Any) -> bool: + assert isinstance(x, bool) + return x + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) def from_str(x: Any) -> str: assert isinstance(x, str) @@ -45,126 +49,180 @@ def from_union(fs, x): pass assert False -def to_class(c: type[T], x: Any) -> dict: - assert isinstance(x, c) - return cast(Any, x).to_dict() - -def from_bool(x: Any) -> bool: - assert isinstance(x, bool) +def to_float(x: Any) -> float: + assert isinstance(x, (int, float)) return x def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: assert isinstance(x, dict) return { k: f(v) for (k, v) in x.items() } -def to_enum(c: type[EnumT], x: Any) -> EnumT: +def to_class(c: type[T], x: Any) -> dict: assert isinstance(x, c) - return x.value + return cast(Any, x).to_dict() -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) +def from_list(f: Callable[[Any], T], x: Any) -> list[T]: + assert isinstance(x, list) + return [f(y) for y in x] -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) - return x +def to_enum(c: type[EnumT], x: Any) -> EnumT: + assert isinstance(x, c) + return x.value def from_datetime(x: Any) -> datetime: return dateutil.parser.parse(x) @dataclass -class PurpleModelCapabilitiesLimitsVision: - """Vision-specific limits""" +class AccountQuotaSnapshot: + entitlement_requests: int + """Number of requests included in the entitlement""" - max_prompt_image_size: int - """Maximum image size in bytes""" + is_unlimited_entitlement: bool + """Whether the user has an unlimited usage entitlement""" - max_prompt_images: int - """Maximum number of images per prompt""" + overage: float + """Number of overage requests made this period""" - supported_media_types: list[str] - """MIME types the model accepts""" + overage_allowed_with_exhausted_quota: bool + """Whether overage is allowed when quota is exhausted""" + + remaining_percentage: float + """Percentage of entitlement remaining""" + + usage_allowed_with_exhausted_quota: bool + """Whether usage is still permitted after quota exhaustion""" + + used_requests: int + """Number of requests used so far this period""" + + reset_date: str | None = None + """Date when the quota resets (ISO 8601 string)""" @staticmethod - def from_dict(obj: Any) -> 'PurpleModelCapabilitiesLimitsVision': + def from_dict(obj: Any) -> 'AccountQuotaSnapshot': assert isinstance(obj, dict) - max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) - max_prompt_images = from_int(obj.get("max_prompt_images")) - supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return PurpleModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + entitlement_requests = from_int(obj.get("entitlementRequests")) + is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) + overage = from_float(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) + used_requests = from_int(obj.get("usedRequests")) + reset_date = from_union([from_str, from_none], obj.get("resetDate")) + return AccountQuotaSnapshot(entitlement_requests, is_unlimited_entitlement, overage, overage_allowed_with_exhausted_quota, remaining_percentage, usage_allowed_with_exhausted_quota, used_requests, reset_date) def to_dict(self) -> dict: result: dict = {} - result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) - result["max_prompt_images"] = from_int(self.max_prompt_images) - result["supported_media_types"] = from_list(from_str, self.supported_media_types) + result["entitlementRequests"] = from_int(self.entitlement_requests) + result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) + result["overage"] = to_float(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) + result["usedRequests"] = from_int(self.used_requests) + if self.reset_date is not None: + result["resetDate"] = from_union([from_str, from_none], self.reset_date) return result @dataclass -class ModelCapabilitiesSupports: - """Feature flags indicating what the model supports""" +class AgentInfo: + """The newly selected custom agent""" - reasoning_effort: bool | None = None - """Whether this model supports reasoning effort configuration""" + description: str + """Description of the agent's purpose""" - vision: bool | None = None - """Whether this model supports vision/image input""" + display_name: str + """Human-readable display name""" + + name: str + """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesSupports': + def from_dict(obj: Any) -> 'AgentInfo': assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return ModelCapabilitiesSupports(reasoning_effort, vision) + description = from_str(obj.get("description")) + display_name = from_str(obj.get("displayName")) + name = from_str(obj.get("name")) + return AgentInfo(description, display_name, name) def to_dict(self) -> dict: result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) + result["description"] = from_str(self.description) + result["displayName"] = from_str(self.display_name) + 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 ModelCapabilitiesLimitsVision: - """Vision-specific limits""" +class AgentSelectRequest: + name: str + """Name of the custom agent to select""" - max_prompt_image_size: int - """Maximum image size in bytes""" + @staticmethod + def from_dict(obj: Any) -> 'AgentSelectRequest': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + return AgentSelectRequest(name) - max_prompt_images: int - """Maximum number of images per prompt""" + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + return result - supported_media_types: list[str] - """MIME types the model accepts""" +@dataclass +class CommandsHandlePendingCommandRequest: + request_id: str + """Request ID from the command invocation event""" + + error: str | None = None + """Error message if the command handler failed""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': assert isinstance(obj, dict) - max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) - max_prompt_images = from_int(obj.get("max_prompt_images")) - supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + return CommandsHandlePendingCommandRequest(request_id, error) def to_dict(self) -> dict: result: dict = {} - result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) - result["max_prompt_images"] = from_int(self.max_prompt_images) - result["supported_media_types"] = from_list(from_str, self.supported_media_types) + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) return result -class FilterMappingString(Enum): - HIDDEN_CHARACTERS = "hidden_characters" - MARKDOWN = "markdown" - NONE = "none" +@dataclass +class CommandsHandlePendingCommandResult: + success: bool + """Whether the command was handled successfully""" -class MCPServerConfigType(Enum): - """Remote transport type. Defaults to "http" when omitted.""" + @staticmethod + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return CommandsHandlePendingCommandResult(success) - HTTP = "http" - LOCAL = "local" - SSE = "sse" - STDIO = "stdio" + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +@dataclass +class CurrentModel: + model_id: str | None = None + """Currently active model identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'CurrentModel': + assert isinstance(obj, dict) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return CurrentModel(model_id) + + def to_dict(self) -> dict: + result: dict = {} + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) + return result class MCPServerSource(Enum): """Configuration source @@ -184,390 +242,331 @@ class DiscoveredMCPServerType(Enum): SSE = "sse" STDIO = "stdio" -@dataclass -class SkillElement: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled (based on global config)""" - - name: str - """Unique identifier for the skill""" +class ExtensionSource(Enum): + """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" - source: str - """Source location type (e.g., project, personal-copilot, plugin, builtin)""" + PROJECT = "project" + USER = "user" - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" +class ExtensionStatus(Enum): + """Current status: running, disabled, failed, or starting""" - path: str | None = None - """Absolute path to the skill file""" + DISABLED = "disabled" + FAILED = "failed" + RUNNING = "running" + STARTING = "starting" - project_path: str | None = None - """The project path this skill belongs to (only for project/inherited skills)""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class ExtensionsDisableRequest: + id: str + """Source-qualified extension ID to disable""" @staticmethod - def from_dict(obj: Any) -> 'SkillElement': + def from_dict(obj: Any) -> 'ExtensionsDisableRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return SkillElement(description, enabled, name, source, user_invocable, path, project_path) + id = from_str(obj.get("id")) + return ExtensionsDisableRequest(id) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) + result["id"] = from_str(self.id) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ServerSkill: - description: str - """Description of what the skill does""" +class ExtensionsEnableRequest: + id: str + """Source-qualified extension ID to enable""" - enabled: bool - """Whether the skill is currently enabled (based on global config)""" + @staticmethod + def from_dict(obj: Any) -> 'ExtensionsEnableRequest': + assert isinstance(obj, dict) + id = from_str(obj.get("id")) + return ExtensionsEnableRequest(id) - name: str - """Unique identifier for the skill""" + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + return result - source: str - """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" - - path: str | None = None - """Absolute path to the skill file""" +class FilterMappingString(Enum): + HIDDEN_CHARACTERS = "hidden_characters" + MARKDOWN = "markdown" + NONE = "none" - project_path: str | None = None - """The project path this skill belongs to (only for project/inherited skills)""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class FleetStartRequest: + prompt: str | None = None + """Optional user prompt to combine with fleet instructions""" @staticmethod - def from_dict(obj: Any) -> 'ServerSkill': + def from_dict(obj: Any) -> 'FleetStartRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) + prompt = from_union([from_str, from_none], obj.get("prompt")) + return FleetStartRequest(prompt) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) + if self.prompt is not None: + result["prompt"] = from_union([from_str, from_none], self.prompt) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CurrentModel: - model_id: str | None = None - """Currently active model identifier""" +class FleetStartResult: + started: bool + """Whether fleet mode was successfully activated""" @staticmethod - def from_dict(obj: Any) -> 'CurrentModel': + def from_dict(obj: Any) -> 'FleetStartResult': assert isinstance(obj, dict) - model_id = from_union([from_str, from_none], obj.get("modelId")) - return CurrentModel(model_id) + started = from_bool(obj.get("started")) + return FleetStartResult(started) def to_dict(self) -> dict: result: dict = {} - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) + result["started"] = from_bool(self.started) return result @dataclass -class PurpleModelCapabilitiesOverrideLimitsVision: - max_prompt_image_size: int | None = None - """Maximum image size in bytes""" - - max_prompt_images: int | None = None - """Maximum number of images per prompt""" - - supported_media_types: list[str] | None = None - """MIME types the model accepts""" +class HandleToolCallResult: + success: bool + """Whether the tool call result was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'PurpleModelCapabilitiesOverrideLimitsVision': + def from_dict(obj: Any) -> 'HandleToolCallResult': assert isinstance(obj, dict) - max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) - max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) - supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) - return PurpleModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + success = from_bool(obj.get("success")) + return HandleToolCallResult(success) def to_dict(self) -> dict: result: dict = {} - if self.max_prompt_image_size is not None: - result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) - if self.max_prompt_images is not None: - result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) - if self.supported_media_types is not None: - result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) + result["success"] = from_bool(self.success) return result @dataclass -class ModelCapabilitiesOverrideSupports: - """Feature flags indicating what the model supports""" +class HistoryCompactContextWindow: + """Post-compaction context window usage breakdown""" - reasoning_effort: bool | None = None - vision: bool | None = None + current_tokens: int + """Current total tokens in the context window (system + conversation + tool definitions)""" - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': - assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) + messages_length: int + """Current number of messages in the conversation""" - def to_dict(self) -> dict: - result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) - return result + token_limit: int + """Maximum token count for the model's context window""" -@dataclass -class AgentInfo: - description: str - """Description of the agent's purpose""" + conversation_tokens: int | None = None + """Token count from non-system messages (user, assistant, tool)""" - display_name: str - """Human-readable display name""" + system_tokens: int | None = None + """Token count from system message(s)""" - name: str - """Unique identifier of the custom agent""" + tool_definitions_tokens: int | None = None + """Token count from tool definitions""" @staticmethod - def from_dict(obj: Any) -> 'AgentInfo': + def from_dict(obj: Any) -> 'HistoryCompactContextWindow': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentInfo(description, display_name, name) + current_tokens = from_int(obj.get("currentTokens")) + messages_length = from_int(obj.get("messagesLength")) + token_limit = from_int(obj.get("tokenLimit")) + conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) + return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) + result["currentTokens"] = from_int(self.current_tokens) + result["messagesLength"] = from_int(self.messages_length) + result["tokenLimit"] = from_int(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) return result -class MCPServerStatus(Enum): - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - CONNECTED = "connected" - DISABLED = "disabled" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - NOT_CONFIGURED = "not_configured" - PENDING = "pending" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ToolCallResult: - text_result_for_llm: str - """Text result to send back to the LLM""" - - error: str | None = None - """Error message if the tool call failed""" - - result_type: str | None = None - """Type of the tool result""" - - tool_telemetry: dict[str, Any] | None = None - """Telemetry data from tool execution""" +class HistoryTruncateRequest: + event_id: str + """Event ID to truncate to. This event and all events after it are removed from the session.""" @staticmethod - def from_dict(obj: Any) -> 'ToolCallResult': + def from_dict(obj: Any) -> 'HistoryTruncateRequest': assert isinstance(obj, dict) - text_result_for_llm = from_str(obj.get("textResultForLlm")) - error = from_union([from_str, from_none], obj.get("error")) - result_type = from_union([from_str, from_none], obj.get("resultType")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) + event_id = from_str(obj.get("eventId")) + return HistoryTruncateRequest(event_id) def to_dict(self) -> dict: result: dict = {} - result["textResultForLlm"] = from_str(self.text_result_for_llm) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result_type is not None: - result["resultType"] = from_union([from_str, from_none], self.result_type) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) + result["eventId"] = from_str(self.event_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HandleToolCallResult: - success: bool - """Whether the tool call result was handled successfully""" +class HistoryTruncateResult: + events_removed: int + """Number of events that were removed""" @staticmethod - def from_dict(obj: Any) -> 'HandleToolCallResult': + def from_dict(obj: Any) -> 'HistoryTruncateResult': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return HandleToolCallResult(success) + events_removed = from_int(obj.get("eventsRemoved")) + return HistoryTruncateResult(events_removed) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["eventsRemoved"] = from_int(self.events_removed) return result -class UIElicitationStringEnumFieldType(Enum): - STRING = "string" +class InstructionsSourcesLocation(Enum): + """Where this source lives — used for UI grouping""" -@dataclass -class UIElicitationStringOneOfFieldOneOf: - const: str - title: str + REPOSITORY = "repository" + USER = "user" + WORKING_DIRECTORY = "working-directory" - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': - assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationStringOneOfFieldOneOf(const, title) +class InstructionsSourcesType(Enum): + """Category of instruction source — used for merge logic""" - def to_dict(self) -> dict: - result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) - return result + CHILD_INSTRUCTIONS = "child-instructions" + HOME = "home" + MODEL = "model" + NESTED_AGENTS = "nested-agents" + REPO = "repo" + VSCODE = "vscode" -class UIElicitationArrayEnumFieldType(Enum): - ARRAY = "array" +class SessionLogLevel(Enum): + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". + """ + ERROR = "error" + INFO = "info" + WARNING = "warning" @dataclass -class PurpleUIElicitationArrayAnyOfFieldItemsAnyOf: - const: str - title: str +class LogResult: + event_id: UUID + """The unique identifier of the emitted session event""" @staticmethod - def from_dict(obj: Any) -> 'PurpleUIElicitationArrayAnyOfFieldItemsAnyOf': + def from_dict(obj: Any) -> 'LogResult': assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return PurpleUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) + event_id = UUID(obj.get("eventId")) + return LogResult(event_id) def to_dict(self) -> dict: result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) + result["eventId"] = str(self.event_id) return result -class UIElicitationResponseAction(Enum): - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" +class MCPServerConfigType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" - ACCEPT = "accept" - CANCEL = "cancel" - DECLINE = "decline" + HTTP = "http" + LOCAL = "local" + SSE = "sse" + STDIO = "stdio" @dataclass -class UIElicitationResult: - success: bool - """Whether the response was accepted. False if the request was already resolved by another - client. - """ +class MCPConfigRemoveRequest: + name: str + """Name of the MCP server to remove""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResult': + def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return UIElicitationResult(success) + name = from_str(obj.get("name")) + return MCPConfigRemoveRequest(name) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["name"] = from_str(self.name) return result -class Kind(Enum): - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - @dataclass -class PermissionRequestResult: - success: bool - """Whether the permission request was handled successfully""" +class MCPDisableRequest: + server_name: str + """Name of the MCP server to disable""" @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestResult': + def from_dict(obj: Any) -> 'MCPDisableRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return PermissionRequestResult(success) + server_name = from_str(obj.get("serverName")) + return MCPDisableRequest(server_name) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["serverName"] = from_str(self.server_name) return result @dataclass -class PingResult: - message: str - """Echoed message (or default greeting)""" - - protocol_version: int - """Server protocol version number""" - - timestamp: int - """Server timestamp in milliseconds""" +class MCPDiscoverRequest: + working_directory: str | None = None + """Working directory used as context for discovery (e.g., plugin resolution)""" @staticmethod - def from_dict(obj: Any) -> 'PingResult': + def from_dict(obj: Any) -> 'MCPDiscoverRequest': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - protocol_version = from_int(obj.get("protocolVersion")) - timestamp = from_int(obj.get("timestamp")) - return PingResult(message, protocol_version, timestamp) + working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) + return MCPDiscoverRequest(working_directory) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - result["protocolVersion"] = from_int(self.protocol_version) - result["timestamp"] = from_int(self.timestamp) + if self.working_directory is not None: + result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) return result @dataclass -class PingRequest: - message: str | None = None - """Optional message to echo back""" +class MCPEnableRequest: + server_name: str + """Name of the MCP server to enable""" @staticmethod - def from_dict(obj: Any) -> 'PingRequest': + def from_dict(obj: Any) -> 'MCPEnableRequest': assert isinstance(obj, dict) - message = from_union([from_str, from_none], obj.get("message")) - return PingRequest(message) + server_name = from_str(obj.get("serverName")) + return MCPEnableRequest(server_name) def to_dict(self) -> dict: result: dict = {} - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) + result["serverName"] = from_str(self.server_name) return result +class MCPServerStatus(Enum): + """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" + + CONNECTED = "connected" + DISABLED = "disabled" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + NOT_CONFIGURED = "not_configured" + PENDING = "pending" + +class MCPServerConfigHTTPType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" + + HTTP = "http" + SSE = "sse" + +class MCPServerConfigLocalType(Enum): + LOCAL = "local" + STDIO = "stdio" + +class SessionMode(Enum): + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" + + AUTOPILOT = "autopilot" + INTERACTIVE = "interactive" + PLAN = "plan" + @dataclass class ModelBilling: """Billing information""" @@ -587,7 +586,7 @@ def to_dict(self) -> dict: return result @dataclass -class FluffyModelCapabilitiesLimitsVision: +class ModelCapabilitiesLimitsVision: """Vision-specific limits""" max_prompt_image_size: int @@ -600,12 +599,12 @@ class FluffyModelCapabilitiesLimitsVision: """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'FluffyModelCapabilitiesLimitsVision': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': assert isinstance(obj, dict) max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) max_prompt_images = from_int(obj.get("max_prompt_images")) supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return FluffyModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} @@ -615,7 +614,7 @@ def to_dict(self) -> dict: return result @dataclass -class CapabilitiesSupports: +class ModelCapabilitiesSupports: """Feature flags indicating what the model supports""" reasoning_effort: bool | None = None @@ -625,11 +624,11 @@ class CapabilitiesSupports: """Whether this model supports vision/image input""" @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesSupports': + def from_dict(obj: Any) -> 'ModelCapabilitiesSupports': assert isinstance(obj, dict) reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) vision = from_union([from_bool, from_none], obj.get("vision")) - return CapabilitiesSupports(reasoning_effort, vision) + return ModelCapabilitiesSupports(reasoning_effort, vision) def to_dict(self) -> dict: result: dict = {} @@ -663,325 +662,188 @@ def to_dict(self) -> dict: return result @dataclass -class Tool: - description: str - """Description of what the tool does""" - - name: str - """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" +class ModelCapabilitiesOverrideLimitsVision: + max_prompt_image_size: int | None = None + """Maximum image size in bytes""" - instructions: str | None = None - """Optional instructions for how to use this tool effectively""" + max_prompt_images: int | None = None + """Maximum number of images per prompt""" - namespaced_name: str | None = None - """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP - tools) - """ - parameters: dict[str, Any] | None = None - """JSON Schema for the tool's input parameters""" + supported_media_types: list[str] | None = None + """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'Tool': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimitsVision': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - name = from_str(obj.get("name")) - instructions = from_union([from_str, from_none], obj.get("instructions")) - namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) - parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) - return Tool(description, name, instructions, namespaced_name, parameters) + max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) + max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) + supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) + return ModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["name"] = from_str(self.name) - if self.instructions is not None: - result["instructions"] = from_union([from_str, from_none], self.instructions) - if self.namespaced_name is not None: - result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) - if self.parameters is not None: - result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) + if self.max_prompt_image_size is not None: + result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) + if self.max_prompt_images is not None: + result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) + if self.supported_media_types is not None: + result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) return result @dataclass -class ToolsListRequest: - model: str | None = None - """Optional model ID — when provided, the returned tool list reflects model-specific - overrides - """ +class ModelCapabilitiesOverrideSupports: + """Feature flags indicating what the model supports""" + + reasoning_effort: bool | None = None + vision: bool | None = None @staticmethod - def from_dict(obj: Any) -> 'ToolsListRequest': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': assert isinstance(obj, dict) - model = from_union([from_str, from_none], obj.get("model")) - return ToolsListRequest(model) + reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) + vision = from_union([from_bool, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) def to_dict(self) -> dict: result: dict = {} - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) + if self.vision is not None: + result["vision"] = from_union([from_bool, from_none], self.vision) return result @dataclass -class AccountQuotaSnapshot: - entitlement_requests: int - """Number of requests included in the entitlement""" - - overage: int - """Number of overage requests made this period""" - - overage_allowed_with_exhausted_quota: bool - """Whether pay-per-request usage is allowed when quota is exhausted""" - - remaining_percentage: float - """Percentage of entitlement remaining""" - - used_requests: int - """Number of requests used so far this period""" - - reset_date: datetime | None = None - """Date when the quota resets (ISO 8601)""" +class ModelSwitchToResult: + model_id: str | None = None + """Currently active model identifier after the switch""" @staticmethod - def from_dict(obj: Any) -> 'AccountQuotaSnapshot': + def from_dict(obj: Any) -> 'ModelSwitchToResult': assert isinstance(obj, dict) - entitlement_requests = from_int(obj.get("entitlementRequests")) - overage = from_int(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - used_requests = from_int(obj.get("usedRequests")) - reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return ModelSwitchToResult(model_id) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = from_int(self.entitlement_requests) - result["overage"] = from_int(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usedRequests"] = from_int(self.used_requests) - if self.reset_date is not None: - result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) return result @dataclass -class MCPConfigRemoveRequest: - name: str - """Name of the MCP server to remove""" +class NameGetResult: + name: str | None = None + """The session name, falling back to the auto-generated summary, or null if neither exists""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': + def from_dict(obj: Any) -> 'NameGetResult': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return MCPConfigRemoveRequest(name) + name = from_union([from_none, from_str], obj.get("name")) + return NameGetResult(name) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["name"] = from_union([from_none, from_str], self.name) return result @dataclass -class MCPDiscoverRequest: - working_directory: str | None = None - """Working directory used as context for discovery (e.g., plugin resolution)""" +class NameSetRequest: + name: str + """New session name (1–100 characters, trimmed of leading/trailing whitespace)""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverRequest': + def from_dict(obj: Any) -> 'NameSetRequest': assert isinstance(obj, dict) - working_directory = from_union([from_str, from_none], obj.get("workingDirectory")) - return MCPDiscoverRequest(working_directory) + name = from_str(obj.get("name")) + return NameSetRequest(name) def to_dict(self) -> dict: result: dict = {} - if self.working_directory is not None: - result["workingDirectory"] = from_union([from_str, from_none], self.working_directory) + result["name"] = from_str(self.name) return result -@dataclass -class SkillsConfigSetDisabledSkillsRequest: - disabled_skills: list[str] - """List of skill names to disable""" +class PermissionDecisionKind(Enum): + APPROVED = "approved" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + DENIED_BY_RULES = "denied-by-rules" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - @staticmethod - def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': - assert isinstance(obj, dict) - disabled_skills = from_list(from_str, obj.get("disabledSkills")) - return SkillsConfigSetDisabledSkillsRequest(disabled_skills) +class PermissionDecisionApprovedKind(Enum): + APPROVED = "approved" - def to_dict(self) -> dict: - result: dict = {} - result["disabledSkills"] = from_list(from_str, self.disabled_skills) - return result +class PermissionDecisionDeniedByContentExclusionPolicyKind(Enum): + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" -@dataclass -class SkillsDiscoverRequest: - project_paths: list[str] | None = None - """Optional list of project directory paths to scan for project-scoped skills""" +class PermissionDecisionDeniedByPermissionRequestHookKind(Enum): + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - skill_directories: list[str] | None = None - """Optional list of additional skill directory paths to include""" +class PermissionDecisionDeniedByRulesKind(Enum): + DENIED_BY_RULES = "denied-by-rules" - @staticmethod - def from_dict(obj: Any) -> 'SkillsDiscoverRequest': - assert isinstance(obj, dict) - project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) - return SkillsDiscoverRequest(project_paths, skill_directories) +class PermissionDecisionDeniedInteractivelyByUserKind(Enum): + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - def to_dict(self) -> dict: - result: dict = {} - if self.project_paths is not None: - result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) - if self.skill_directories is not None: - result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) - return result +class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(Enum): + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" @dataclass -class SessionFSSetProviderResult: +class PermissionRequestResult: success: bool - """Whether the provider was set successfully""" + """Whether the permission request was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderResult': + def from_dict(obj: Any) -> 'PermissionRequestResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return SessionFSSetProviderResult(success) + return PermissionRequestResult(success) def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) return result -class SessionFSSetProviderConventions(Enum): - """Path conventions used by this filesystem""" - - POSIX = "posix" - WINDOWS = "windows" - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsForkResult: - session_id: str - """The new forked session's ID""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionsForkResult': - assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - return SessionsForkResult(session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["sessionId"] = from_str(self.session_id) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsForkRequest: - session_id: str - """Source session ID to fork from""" - - to_event_id: str | None = None - """Optional event ID boundary. When provided, the fork includes only events before this ID - (exclusive). When omitted, all events are included. - """ - - @staticmethod - def from_dict(obj: Any) -> 'SessionsForkRequest': - assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - to_event_id = from_union([from_str, from_none], obj.get("toEventId")) - return SessionsForkRequest(session_id, to_event_id) - - def to_dict(self) -> dict: - result: dict = {} - result["sessionId"] = from_str(self.session_id) - if self.to_event_id is not None: - result["toEventId"] = from_union([from_str, from_none], self.to_event_id) - return result - -@dataclass -class ModelSwitchToResult: - model_id: str | None = None - """Currently active model identifier after the switch""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelSwitchToResult': - assert isinstance(obj, dict) - model_id = from_union([from_str, from_none], obj.get("modelId")) - return ModelSwitchToResult(model_id) - - def to_dict(self) -> dict: - result: dict = {} - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) - return result - @dataclass -class FluffyModelCapabilitiesOverrideLimitsVision: - max_prompt_image_size: int | None = None - """Maximum image size in bytes""" - - max_prompt_images: int | None = None - """Maximum number of images per prompt""" - - supported_media_types: list[str] | None = None - """MIME types the model accepts""" +class PingRequest: + message: str | None = None + """Optional message to echo back""" @staticmethod - def from_dict(obj: Any) -> 'FluffyModelCapabilitiesOverrideLimitsVision': + def from_dict(obj: Any) -> 'PingRequest': assert isinstance(obj, dict) - max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) - max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) - supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) - return FluffyModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + message = from_union([from_str, from_none], obj.get("message")) + return PingRequest(message) def to_dict(self) -> dict: result: dict = {} - if self.max_prompt_image_size is not None: - result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) - if self.max_prompt_images is not None: - result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) - if self.supported_media_types is not None: - result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result -class SessionMode(Enum): - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" - - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" - @dataclass -class NameGetResult: - name: str | None = None - """The session name, falling back to the auto-generated summary, or null if neither exists""" - - @staticmethod - def from_dict(obj: Any) -> 'NameGetResult': - assert isinstance(obj, dict) - name = from_union([from_none, from_str], obj.get("name")) - return NameGetResult(name) +class PingResult: + message: str + """Echoed message (or default greeting)""" - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_union([from_none, from_str], self.name) - return result + protocol_version: int + """Server protocol version number""" -@dataclass -class NameSetRequest: - name: str - """New session name (1–100 characters, trimmed of leading/trailing whitespace)""" + timestamp: int + """Server timestamp in milliseconds""" @staticmethod - def from_dict(obj: Any) -> 'NameSetRequest': + def from_dict(obj: Any) -> 'PingResult': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return NameSetRequest(name) + message = from_str(obj.get("message")) + protocol_version = from_int(obj.get("protocolVersion")) + timestamp = from_int(obj.get("timestamp")) + return PingResult(message, protocol_version, timestamp) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["message"] = from_str(self.message) + result["protocolVersion"] = from_int(self.protocol_version) + result["timestamp"] = from_int(self.timestamp) return result @dataclass @@ -1026,697 +888,819 @@ def to_dict(self) -> dict: result["content"] = from_str(self.content) return result -class HostType(Enum): - ADO = "ado" - GITHUB = "github" - -class SessionSyncLevel(Enum): - LOCAL = "local" - REPO_AND_USER = "repo_and_user" - USER = "user" - @dataclass -class WorkspacesListFilesResult: - files: list[str] - """Relative file paths in the workspace files directory""" +class Plugin: + enabled: bool + """Whether the plugin is currently enabled""" - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesListFilesResult': - assert isinstance(obj, dict) - files = from_list(from_str, obj.get("files")) - return WorkspacesListFilesResult(files) + marketplace: str + """Marketplace the plugin came from""" - def to_dict(self) -> dict: - result: dict = {} - result["files"] = from_list(from_str, self.files) - return result + name: str + """Plugin name""" -@dataclass -class WorkspacesReadFileResult: - content: str - """File content as a UTF-8 string""" + version: str | None = None + """Installed version""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesReadFileResult': + def from_dict(obj: Any) -> 'Plugin': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - return WorkspacesReadFileResult(content) + enabled = from_bool(obj.get("enabled")) + marketplace = from_str(obj.get("marketplace")) + name = from_str(obj.get("name")) + version = from_union([from_str, from_none], obj.get("version")) + return Plugin(enabled, marketplace, name, version) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) + result["enabled"] = from_bool(self.enabled) + result["marketplace"] = from_str(self.marketplace) + result["name"] = from_str(self.name) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) return result @dataclass -class WorkspacesReadFileRequest: - path: str - """Relative path within the workspace files directory""" +class ServerSkill: + description: str + """Description of what the skill does""" - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesReadFileRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - return WorkspacesReadFileRequest(path) + enabled: bool + """Whether the skill is currently enabled (based on global config)""" - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - return result + name: str + """Unique identifier for the skill""" -@dataclass -class WorkspacesCreateFileRequest: - content: str - """File content to write as a UTF-8 string""" + source: str + """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - path: str - """Relative path within the workspace files directory""" + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': - assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - return WorkspacesCreateFileRequest(content, path) + path: str | None = None + """Absolute path to the skill file""" + + project_path: str | None = None + """The project path this skill belongs to (only for project/inherited skills)""" + + @staticmethod + def from_dict(obj: Any) -> 'ServerSkill': + assert isinstance(obj, dict) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) return result -class InstructionsSourcesLocation(Enum): - """Where this source lives — used for UI grouping""" - - REPOSITORY = "repository" - USER = "user" - WORKING_DIRECTORY = "working-directory" +@dataclass +class SessionFSAppendFileRequest: + content: str + """Content to append""" -class InstructionsSourcesType(Enum): - """Category of instruction source — used for merge logic""" + path: str + """Path using SessionFs conventions""" - CHILD_INSTRUCTIONS = "child-instructions" - HOME = "home" - MODEL = "model" - NESTED_AGENTS = "nested-agents" - REPO = "repo" - VSCODE = "vscode" + session_id: str + """Target session identifier""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class FleetStartResult: - started: bool - """Whether fleet mode was successfully activated""" + mode: int | None = None + """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartResult': + def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': assert isinstance(obj, dict) - started = from_bool(obj.get("started")) - return FleetStartResult(started) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSAppendFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} - result["started"] = from_bool(self.started) + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) return result -# Experimental: this type is part of an experimental API and may change or be removed. +class SessionFSErrorCode(Enum): + """Error classification""" + + ENOENT = "ENOENT" + UNKNOWN = "UNKNOWN" + @dataclass -class FleetStartRequest: - prompt: str | None = None - """Optional user prompt to combine with fleet instructions""" +class SessionFSExistsRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartRequest': + def from_dict(obj: Any) -> 'SessionFSExistsRequest': assert isinstance(obj, dict) - prompt = from_union([from_str, from_none], obj.get("prompt")) - return FleetStartRequest(prompt) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSExistsRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result @dataclass -class AgentListAgent: - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" - - name: str - """Unique identifier of the custom agent""" +class SessionFSExistsResult: + exists: bool + """Whether the path exists""" @staticmethod - def from_dict(obj: Any) -> 'AgentListAgent': + def from_dict(obj: Any) -> 'SessionFSExistsResult': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentListAgent(description, display_name, name) + exists = from_bool(obj.get("exists")) + return SessionFSExistsResult(exists) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) + result["exists"] = from_bool(self.exists) return result @dataclass -class AgentSelectResultAgent: - """The newly selected custom agent""" +class SessionFSMkdirRequest: + path: str + """Path using SessionFs conventions""" - description: str - """Description of the agent's purpose""" + session_id: str + """Target session identifier""" - display_name: str - """Human-readable display name""" + mode: int | None = None + """Optional POSIX-style mode for newly created directories""" - name: str - """Unique identifier of the custom agent""" + recursive: bool | None = None + """Create parent directories as needed""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectResultAgent': + def from_dict(obj: Any) -> 'SessionFSMkdirRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentSelectResultAgent(description, display_name, name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSMkdirRequest(path, session_id, mode, recursive) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentSelectRequest: - name: str - """Name of the custom agent to select""" +class SessionFSReadFileRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectRequest': + def from_dict(obj: Any) -> 'SessionFSReadFileRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return AgentSelectRequest(name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReadFileRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result @dataclass -class AgentReloadResultAgent: - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" +class SessionFSReaddirRequest: + path: str + """Path using SessionFs conventions""" - name: str - """Unique identifier of the custom agent""" + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResultAgent': + def from_dict(obj: Any) -> 'SessionFSReaddirRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentReloadResultAgent(description, display_name, name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -@dataclass -class Skill: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled""" - - name: str - """Unique identifier for the skill""" +class SessionFSReaddirWithTypesEntryType(Enum): + """Entry type""" - source: str - """Source location type (e.g., project, personal, plugin)""" + DIRECTORY = "directory" + FILE = "file" - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" +@dataclass +class SessionFSReaddirWithTypesRequest: + path: str + """Path using SessionFs conventions""" - path: str | None = None - """Absolute path to the skill file""" + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'Skill': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - return Skill(description, enabled, name, source, user_invocable, path) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirWithTypesRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillsEnableRequest: - name: str - """Name of the skill to enable""" +class SessionFSRenameRequest: + dest: str + """Destination path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + src: str + """Source path using SessionFs conventions""" @staticmethod - def from_dict(obj: Any) -> 'SkillsEnableRequest': + def from_dict(obj: Any) -> 'SessionFSRenameRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return SkillsEnableRequest(name) + dest = from_str(obj.get("dest")) + session_id = from_str(obj.get("sessionId")) + src = from_str(obj.get("src")) + return SessionFSRenameRequest(dest, session_id, src) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["dest"] = from_str(self.dest) + result["sessionId"] = from_str(self.session_id) + result["src"] = from_str(self.src) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillsDisableRequest: - name: str - """Name of the skill to disable""" +class SessionFSRmRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + force: bool | None = None + """Ignore errors if the path does not exist""" + + recursive: bool | None = None + """Remove directories and their contents recursively""" @staticmethod - def from_dict(obj: Any) -> 'SkillsDisableRequest': + def from_dict(obj: Any) -> 'SessionFSRmRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return SkillsDisableRequest(name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + force = from_union([from_bool, from_none], obj.get("force")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSRmRequest(path, session_id, force, recursive) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.force is not None: + result["force"] = from_union([from_bool, from_none], self.force) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) return result +class SessionFSSetProviderConventions(Enum): + """Path conventions used by this filesystem""" + + POSIX = "posix" + WINDOWS = "windows" + @dataclass -class MCPEnableRequest: - server_name: str - """Name of the MCP server to enable""" +class SessionFSSetProviderResult: + success: bool + """Whether the provider was set successfully""" @staticmethod - def from_dict(obj: Any) -> 'MCPEnableRequest': + def from_dict(obj: Any) -> 'SessionFSSetProviderResult': assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - return MCPEnableRequest(server_name) + success = from_bool(obj.get("success")) + return SessionFSSetProviderResult(success) def to_dict(self) -> dict: result: dict = {} - result["serverName"] = from_str(self.server_name) + result["success"] = from_bool(self.success) return result @dataclass -class MCPDisableRequest: - server_name: str - """Name of the MCP server to disable""" +class SessionFSStatRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'MCPDisableRequest': + def from_dict(obj: Any) -> 'SessionFSStatRequest': assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - return MCPDisableRequest(server_name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSStatRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["serverName"] = from_str(self.server_name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result @dataclass -class Plugin: - enabled: bool - """Whether the plugin is currently enabled""" +class SessionFSWriteFileRequest: + content: str + """Content to write""" - marketplace: str - """Marketplace the plugin came from""" + path: str + """Path using SessionFs conventions""" - name: str - """Plugin name""" + session_id: str + """Target session identifier""" - version: str | None = None - """Installed version""" + mode: int | None = None + """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'Plugin': + def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - marketplace = from_str(obj.get("marketplace")) - name = from_str(obj.get("name")) - version = from_union([from_str, from_none], obj.get("version")) - return Plugin(enabled, marketplace, name, version) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSWriteFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["marketplace"] = from_str(self.marketplace) - result["name"] = from_str(self.name) - if self.version is not None: - result["version"] = from_union([from_str, from_none], self.version) + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) return result -class ExtensionSource(Enum): - """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" - - PROJECT = "project" - USER = "user" - -class ExtensionStatus(Enum): - """Current status: running, disabled, failed, or starting""" - - DISABLED = "disabled" - FAILED = "failed" - RUNNING = "running" - STARTING = "starting" - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExtensionsEnableRequest: - id: str - """Source-qualified extension ID to enable""" +class SessionsForkRequest: + session_id: str + """Source session ID to fork from""" + + to_event_id: str | None = None + """Optional event ID boundary. When provided, the fork includes only events before this ID + (exclusive). When omitted, all events are included. + """ @staticmethod - def from_dict(obj: Any) -> 'ExtensionsEnableRequest': + def from_dict(obj: Any) -> 'SessionsForkRequest': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return ExtensionsEnableRequest(id) + session_id = from_str(obj.get("sessionId")) + to_event_id = from_union([from_str, from_none], obj.get("toEventId")) + return SessionsForkRequest(session_id, to_event_id) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["sessionId"] = from_str(self.session_id) + if self.to_event_id is not None: + result["toEventId"] = from_union([from_str, from_none], self.to_event_id) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExtensionsDisableRequest: - id: str - """Source-qualified extension ID to disable""" +class SessionsForkResult: + session_id: str + """The new forked session's ID""" @staticmethod - def from_dict(obj: Any) -> 'ExtensionsDisableRequest': + def from_dict(obj: Any) -> 'SessionsForkResult': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return ExtensionsDisableRequest(id) + session_id = from_str(obj.get("sessionId")) + return SessionsForkResult(session_id) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["sessionId"] = from_str(self.session_id) return result @dataclass -class CommandsHandlePendingCommandResult: - success: bool - """Whether the command was handled successfully""" +class ShellExecRequest: + command: str + """Shell command to execute""" + + cwd: str | None = None + """Working directory (defaults to session working directory)""" + + timeout: int | None = None + """Timeout in milliseconds (default: 30000)""" @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': + def from_dict(obj: Any) -> 'ShellExecRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return CommandsHandlePendingCommandResult(success) + command = from_str(obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + return ShellExecRequest(command, cwd, timeout) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["command"] = from_str(self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) return result @dataclass -class CommandsHandlePendingCommandRequest: - request_id: str - """Request ID from the command invocation event""" - - error: str | None = None - """Error message if the command handler failed""" +class ShellExecResult: + process_id: str + """Unique identifier for tracking streamed output""" @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': + def from_dict(obj: Any) -> 'ShellExecResult': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - return CommandsHandlePendingCommandRequest(request_id, error) + process_id = from_str(obj.get("processId")) + return ShellExecResult(process_id) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) + result["processId"] = from_str(self.process_id) return result -class UIElicitationSchemaPropertyStringFormat(Enum): - DATE = "date" - DATE_TIME = "date-time" - EMAIL = "email" - URI = "uri" +class ShellKillSignal(Enum): + """Signal to send (default: SIGTERM)""" + + SIGINT = "SIGINT" + SIGKILL = "SIGKILL" + SIGTERM = "SIGTERM" @dataclass -class FluffyUIElicitationArrayAnyOfFieldItemsAnyOf: - const: str - title: str +class ShellKillResult: + killed: bool + """Whether the signal was sent successfully""" @staticmethod - def from_dict(obj: Any) -> 'FluffyUIElicitationArrayAnyOfFieldItemsAnyOf': + def from_dict(obj: Any) -> 'ShellKillResult': assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return FluffyUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) + killed = from_bool(obj.get("killed")) + return ShellKillResult(killed) def to_dict(self) -> dict: result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) + result["killed"] = from_bool(self.killed) return result @dataclass -class UIElicitationSchemaPropertyOneOf: - const: str - title: str +class Skill: + description: str + """Description of what the skill does""" - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyOneOf': - assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationSchemaPropertyOneOf(const, title) + enabled: bool + """Whether the skill is currently enabled""" - def to_dict(self) -> dict: - result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) - return result + name: str + """Unique identifier for the skill""" -class UIElicitationSchemaPropertyNumberType(Enum): - ARRAY = "array" - BOOLEAN = "boolean" - INTEGER = "integer" - NUMBER = "number" - STRING = "string" + source: str + """Source location type (e.g., project, personal, plugin)""" -class RequestedSchemaType(Enum): - OBJECT = "object" + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" -@dataclass -class LogResult: - event_id: UUID - """The unique identifier of the emitted session event""" + path: str | None = None + """Absolute path to the skill file""" @staticmethod - def from_dict(obj: Any) -> 'LogResult': + def from_dict(obj: Any) -> 'Skill': assert isinstance(obj, dict) - event_id = UUID(obj.get("eventId")) - return LogResult(event_id) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + return Skill(description, enabled, name, source, user_invocable, path) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = str(self.event_id) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) return result -class SessionLogLevel(Enum): - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - ERROR = "error" - INFO = "info" - WARNING = "warning" - @dataclass -class ShellExecResult: - process_id: str - """Unique identifier for tracking streamed output""" +class SkillsConfigSetDisabledSkillsRequest: + disabled_skills: list[str] + """List of skill names to disable""" @staticmethod - def from_dict(obj: Any) -> 'ShellExecResult': + def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - return ShellExecResult(process_id) + disabled_skills = from_list(from_str, obj.get("disabledSkills")) + return SkillsConfigSetDisabledSkillsRequest(disabled_skills) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) + result["disabledSkills"] = from_list(from_str, self.disabled_skills) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ShellExecRequest: - command: str - """Shell command to execute""" - - cwd: str | None = None - """Working directory (defaults to session working directory)""" - - timeout: int | None = None - """Timeout in milliseconds (default: 30000)""" +class SkillsDisableRequest: + name: str + """Name of the skill to disable""" @staticmethod - def from_dict(obj: Any) -> 'ShellExecRequest': + def from_dict(obj: Any) -> 'SkillsDisableRequest': assert isinstance(obj, dict) - command = from_str(obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - return ShellExecRequest(command, cwd, timeout) + name = from_str(obj.get("name")) + return SkillsDisableRequest(name) def to_dict(self) -> dict: result: dict = {} - result["command"] = from_str(self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) + result["name"] = from_str(self.name) return result @dataclass -class ShellKillResult: - killed: bool - """Whether the signal was sent successfully""" +class SkillsDiscoverRequest: + project_paths: list[str] | None = None + """Optional list of project directory paths to scan for project-scoped skills""" + + skill_directories: list[str] | None = None + """Optional list of additional skill directory paths to include""" @staticmethod - def from_dict(obj: Any) -> 'ShellKillResult': + def from_dict(obj: Any) -> 'SkillsDiscoverRequest': assert isinstance(obj, dict) - killed = from_bool(obj.get("killed")) - return ShellKillResult(killed) + project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) + skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) + return SkillsDiscoverRequest(project_paths, skill_directories) def to_dict(self) -> dict: result: dict = {} - result["killed"] = from_bool(self.killed) + if self.project_paths is not None: + result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) + if self.skill_directories is not None: + result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) return result -class ShellKillSignal(Enum): - """Signal to send (default: SIGTERM)""" - - SIGINT = "SIGINT" - SIGKILL = "SIGKILL" - SIGTERM = "SIGTERM" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactContextWindow: - """Post-compaction context window usage breakdown""" +class SkillsEnableRequest: + name: str + """Name of the skill to enable""" - current_tokens: int - """Current total tokens in the context window (system + conversation + tool definitions)""" + @staticmethod + def from_dict(obj: Any) -> 'SkillsEnableRequest': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + return SkillsEnableRequest(name) - messages_length: int - """Current number of messages in the conversation""" + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + return result - token_limit: int - """Maximum token count for the model's context window""" +@dataclass +class Tool: + description: str + """Description of what the tool does""" - conversation_tokens: int | None = None - """Token count from non-system messages (user, assistant, tool)""" + name: str + """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" - system_tokens: int | None = None - """Token count from system message(s)""" + instructions: str | None = None + """Optional instructions for how to use this tool effectively""" - tool_definitions_tokens: int | None = None - """Token count from tool definitions""" + namespaced_name: str | None = None + """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP + tools) + """ + parameters: dict[str, Any] | None = None + """JSON Schema for the tool's input parameters""" @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactContextWindow': + def from_dict(obj: Any) -> 'Tool': assert isinstance(obj, dict) - current_tokens = from_int(obj.get("currentTokens")) - messages_length = from_int(obj.get("messagesLength")) - token_limit = from_int(obj.get("tokenLimit")) - conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) - system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) - return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + instructions = from_union([from_str, from_none], obj.get("instructions")) + namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) + parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) + return Tool(description, name, instructions, namespaced_name, parameters) def to_dict(self) -> dict: result: dict = {} - result["currentTokens"] = from_int(self.current_tokens) - result["messagesLength"] = from_int(self.messages_length) - result["tokenLimit"] = from_int(self.token_limit) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.instructions is not None: + result["instructions"] = from_union([from_str, from_none], self.instructions) + if self.namespaced_name is not None: + result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) + if self.parameters is not None: + result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateResult: - events_removed: int - """Number of events that were removed""" +class ToolCallResult: + text_result_for_llm: str + """Text result to send back to the LLM""" + + error: str | None = None + """Error message if the tool call failed""" + + result_type: str | None = None + """Type of the tool result""" + + tool_telemetry: dict[str, Any] | None = None + """Telemetry data from tool execution""" @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateResult': + def from_dict(obj: Any) -> 'ToolCallResult': assert isinstance(obj, dict) - events_removed = from_int(obj.get("eventsRemoved")) - return HistoryTruncateResult(events_removed) + text_result_for_llm = from_str(obj.get("textResultForLlm")) + error = from_union([from_str, from_none], obj.get("error")) + result_type = from_union([from_str, from_none], obj.get("resultType")) + tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) + return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) def to_dict(self) -> dict: result: dict = {} - result["eventsRemoved"] = from_int(self.events_removed) + result["textResultForLlm"] = from_str(self.text_result_for_llm) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result_type is not None: + result["resultType"] = from_union([from_str, from_none], self.result_type) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateRequest: - event_id: str - """Event ID to truncate to. This event and all events after it are removed from the session.""" +class ToolsListRequest: + model: str | None = None + """Optional model ID — when provided, the returned tool list reflects model-specific + overrides + """ @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateRequest': + def from_dict(obj: Any) -> 'ToolsListRequest': assert isinstance(obj, dict) - event_id = from_str(obj.get("eventId")) - return HistoryTruncateRequest(event_id) + model = from_union([from_str, from_none], obj.get("model")) + return ToolsListRequest(model) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = from_str(self.event_id) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) return result @dataclass -class UsageMetricsCodeChanges: - """Aggregated code change metrics""" - - files_modified_count: int - """Number of distinct files modified""" - - lines_added: int - """Total lines of code added""" - - lines_removed: int - """Total lines of code removed""" +class UIElicitationArrayAnyOfFieldItemsAnyOf: + const: str + title: str @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItemsAnyOf': + assert isinstance(obj, dict) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationArrayAnyOfFieldItemsAnyOf(const, title) + + def to_dict(self) -> dict: + result: dict = {} + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) + return result + +class UIElicitationArrayAnyOfFieldType(Enum): + ARRAY = "array" + +class UIElicitationArrayEnumFieldItemsType(Enum): + STRING = "string" + +class UIElicitationSchemaPropertyStringFormat(Enum): + DATE = "date" + DATE_TIME = "date-time" + EMAIL = "email" + URI = "uri" + +@dataclass +class UIElicitationStringOneOfFieldOneOf: + const: str + title: str + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': + assert isinstance(obj, dict) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationStringOneOfFieldOneOf(const, title) + + def to_dict(self) -> dict: + result: dict = {} + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) + return result + +class UIElicitationSchemaPropertyType(Enum): + ARRAY = "array" + BOOLEAN = "boolean" + INTEGER = "integer" + NUMBER = "number" + STRING = "string" + +class UIElicitationSchemaType(Enum): + OBJECT = "object" + +class UIElicitationResponseAction(Enum): + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + + ACCEPT = "accept" + CANCEL = "cancel" + DECLINE = "decline" + +@dataclass +class UIElicitationResult: + success: bool + """Whether the response was accepted. False if the request was already resolved by another + client. + """ + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return UIElicitationResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +class UIElicitationSchemaPropertyBooleanType(Enum): + BOOLEAN = "boolean" + +class UIElicitationSchemaPropertyNumberType(Enum): + INTEGER = "integer" + NUMBER = "number" + +@dataclass +class UsageMetricsCodeChanges: + """Aggregated code change metrics""" + + files_modified_count: int + """Number of distinct files modified""" + + lines_added: int + """Total lines of code added""" + + lines_removed: int + """Total lines of code removed""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': assert isinstance(obj, dict) files_modified_count = from_int(obj.get("filesModifiedCount")) lines_added = from_int(obj.get("linesAdded")) @@ -1793,611 +1777,360 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSReadFileResult: +class WorkspacesCreateFileRequest: content: str - """File content as UTF-8 string""" + """File content to write as a UTF-8 string""" + + path: str + """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileResult': + def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': assert isinstance(obj, dict) content = from_str(obj.get("content")) - return SessionFSReadFileResult(content) + path = from_str(obj.get("path")) + return WorkspacesCreateFileRequest(content, path) def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) + result["path"] = from_str(self.path) return result -@dataclass -class SessionFSReadFileRequest: - path: str - """Path using SessionFs conventions""" +class HostType(Enum): + ADO = "ado" + GITHUB = "github" - session_id: str - """Target session identifier""" +class SessionSyncLevel(Enum): + LOCAL = "local" + REPO_AND_USER = "repo_and_user" + USER = "user" + +@dataclass +class WorkspacesListFilesResult: + files: list[str] + """Relative file paths in the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileRequest': + def from_dict(obj: Any) -> 'WorkspacesListFilesResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReadFileRequest(path, session_id) + files = from_list(from_str, obj.get("files")) + return WorkspacesListFilesResult(files) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["files"] = from_list(from_str, self.files) return result @dataclass -class SessionFSWriteFileRequest: - content: str - """Content to write""" - +class WorkspacesReadFileRequest: path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - mode: int | None = None - """Optional POSIX-style mode for newly created files""" + """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': + def from_dict(obj: Any) -> 'WorkspacesReadFileRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSWriteFileRequest(content, path, session_id, mode) + return WorkspacesReadFileRequest(path) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) return result @dataclass -class SessionFSAppendFileRequest: +class WorkspacesReadFileResult: content: str - """Content to append""" - - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - mode: int | None = None - """Optional POSIX-style mode for newly created files""" + """File content as a UTF-8 string""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': + def from_dict(obj: Any) -> 'WorkspacesReadFileResult': assert isinstance(obj, dict) content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSAppendFileRequest(content, path, session_id, mode) + return WorkspacesReadFileResult(content) def to_dict(self) -> dict: result: dict = {} result["content"] = from_str(self.content) - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) return result @dataclass -class SessionFSExistsResult: - exists: bool - """Whether the path exists""" +class AccountGetQuotaResult: + quota_snapshots: dict[str, AccountQuotaSnapshot] + """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsResult': + def from_dict(obj: Any) -> 'AccountGetQuotaResult': assert isinstance(obj, dict) - exists = from_bool(obj.get("exists")) - return SessionFSExistsResult(exists) + quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) + return AccountGetQuotaResult(quota_snapshots) def to_dict(self) -> dict: result: dict = {} - result["exists"] = from_bool(self.exists) + result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSExistsRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" +class AgentGetCurrentResult: + agent: AgentInfo | None = None + """Currently selected custom agent, or null if using the default agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsRequest': + def from_dict(obj: Any) -> 'AgentGetCurrentResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSExistsRequest(path, session_id) + agent = from_union([AgentInfo.from_dict, from_none], obj.get("agent")) + return AgentGetCurrentResult(agent) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + if self.agent is not None: + result["agent"] = from_union([lambda x: to_class(AgentInfo, x), from_none], self.agent) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSStatResult: - birthtime: datetime - """ISO 8601 timestamp of creation""" - - is_directory: bool - """Whether the path is a directory""" - - is_file: bool - """Whether the path is a file""" - - mtime: datetime - """ISO 8601 timestamp of last modification""" - - size: int - """File size in bytes""" +class AgentList: + agents: list[AgentInfo] + """Available custom agents""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatResult': + def from_dict(obj: Any) -> 'AgentList': assert isinstance(obj, dict) - birthtime = from_datetime(obj.get("birthtime")) - is_directory = from_bool(obj.get("isDirectory")) - is_file = from_bool(obj.get("isFile")) - mtime = from_datetime(obj.get("mtime")) - size = from_int(obj.get("size")) - return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentList(agents) def to_dict(self) -> dict: result: dict = {} - result["birthtime"] = self.birthtime.isoformat() - result["isDirectory"] = from_bool(self.is_directory) - result["isFile"] = from_bool(self.is_file) - result["mtime"] = self.mtime.isoformat() - result["size"] = from_int(self.size) + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSStatRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" +class AgentReloadResult: + agents: list[AgentInfo] + """Reloaded custom agents""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatRequest': + def from_dict(obj: Any) -> 'AgentReloadResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSStatRequest(path, session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - return result - -@dataclass -class SessionFSMkdirRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - mode: int | None = None - """Optional POSIX-style mode for newly created directories""" - - recursive: bool | None = None - """Create parent directories as needed""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSMkdirRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSMkdirRequest(path, session_id, mode, recursive) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentReloadResult(agents) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionFSReaddirResult: - entries: list[str] - """Entry names in the directory""" +class AgentSelectResult: + agent: AgentInfo + """The newly selected custom agent""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirResult': + def from_dict(obj: Any) -> 'AgentSelectResult': assert isinstance(obj, dict) - entries = from_list(from_str, obj.get("entries")) - return SessionFSReaddirResult(entries) + agent = AgentInfo.from_dict(obj.get("agent")) + return AgentSelectResult(agent) def to_dict(self) -> dict: result: dict = {} - result["entries"] = from_list(from_str, self.entries) + result["agent"] = to_class(AgentInfo, self.agent) return result @dataclass -class SessionFSReaddirRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirRequest(path, session_id) - - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - return result - -class SessionFSReaddirWithTypesEntryType(Enum): - """Entry type""" +class DiscoveredMCPServer: + enabled: bool + """Whether the server is enabled (not in the disabled list)""" - DIRECTORY = "directory" - FILE = "file" + name: str + """Server name (config key)""" -@dataclass -class SessionFSReaddirWithTypesRequest: - path: str - """Path using SessionFs conventions""" + source: MCPServerSource + """Configuration source""" - session_id: str - """Target session identifier""" + type: DiscoveredMCPServerType | None = None + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': + def from_dict(obj: Any) -> 'DiscoveredMCPServer': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirWithTypesRequest(path, session_id) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = MCPServerSource(obj.get("source")) + type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) + return DiscoveredMCPServer(enabled, name, source, type) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = to_enum(MCPServerSource, self.source) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) return result @dataclass -class SessionFSRmRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - force: bool | None = None - """Ignore errors if the path does not exist""" - - recursive: bool | None = None - """Remove directories and their contents recursively""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSRmRequest': - assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - force = from_union([from_bool, from_none], obj.get("force")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSRmRequest(path, session_id, force, recursive) +class Extension: + id: str + """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.force is not None: - result["force"] = from_union([from_bool, from_none], self.force) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) - return result + name: str + """Extension name (directory name)""" -@dataclass -class SessionFSRenameRequest: - dest: str - """Destination path using SessionFs conventions""" + source: ExtensionSource + """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" - session_id: str - """Target session identifier""" + status: ExtensionStatus + """Current status: running, disabled, failed, or starting""" - src: str - """Source path using SessionFs conventions""" + pid: int | None = None + """Process ID if the extension is running""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSRenameRequest': + def from_dict(obj: Any) -> 'Extension': assert isinstance(obj, dict) - dest = from_str(obj.get("dest")) - session_id = from_str(obj.get("sessionId")) - src = from_str(obj.get("src")) - return SessionFSRenameRequest(dest, session_id, src) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = ExtensionSource(obj.get("source")) + status = ExtensionStatus(obj.get("status")) + pid = from_union([from_int, from_none], obj.get("pid")) + return Extension(id, name, source, status, pid) def to_dict(self) -> dict: result: dict = {} - result["dest"] = from_str(self.dest) - result["sessionId"] = from_str(self.session_id) - result["src"] = from_str(self.src) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionSource, self.source) + result["status"] = to_enum(ExtensionStatus, self.status) + if self.pid is not None: + result["pid"] = from_union([from_int, from_none], self.pid) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModelCapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" +class HistoryCompactResult: + messages_removed: int + """Number of messages removed during compaction""" - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" + success: bool + """Whether compaction completed successfully""" - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" + tokens_removed: int + """Number of tokens freed by compaction""" - vision: PurpleModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" + context_window: HistoryCompactContextWindow | None = None + """Post-compaction context window usage breakdown""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': + def from_dict(obj: Any) -> 'HistoryCompactResult': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([PurpleModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + messages_removed = from_int(obj.get("messagesRemoved")) + success = from_bool(obj.get("success")) + tokens_removed = from_int(obj.get("tokensRemoved")) + context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) + return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesLimitsVision, x), from_none], self.vision) + result["messagesRemoved"] = from_int(self.messages_removed) + result["success"] = from_bool(self.success) + result["tokensRemoved"] = from_int(self.tokens_removed) + if self.context_window is not None: + result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) return result @dataclass -class MCPServerConfig: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" - - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None +class InstructionsSources: + content: str + """Raw content of the instruction file""" - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfig': - assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + id: str + """Unique identifier for this source (used for toggling)""" - def to_dict(self) -> dict: - result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - return result + label: str + """Human-readable label""" -@dataclass -class MCPServerConfigValue: - """MCP server configuration (local/stdio or remote/http)""" + location: InstructionsSourcesLocation + """Where this source lives — used for UI grouping""" - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" + source_path: str + """File path relative to repo or absolute for home""" - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + type: InstructionsSourcesType + """Category of instruction source — used for merge logic""" - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" + apply_to: str | None = None + """Glob pattern from frontmatter — when set, this instruction applies only to matching files""" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None + description: str | None = None + """Short description (body after frontmatter) for use in instruction tables""" @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigValue': + def from_dict(obj: Any) -> 'InstructionsSources': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfigValue(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + content = from_str(obj.get("content")) + id = from_str(obj.get("id")) + label = from_str(obj.get("label")) + location = InstructionsSourcesLocation(obj.get("location")) + source_path = from_str(obj.get("sourcePath")) + type = InstructionsSourcesType(obj.get("type")) + apply_to = from_union([from_str, from_none], obj.get("applyTo")) + description = from_union([from_str, from_none], obj.get("description")) + return InstructionsSources(content, id, label, location, source_path, type, apply_to, description) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["content"] = from_str(self.content) + result["id"] = from_str(self.id) + result["label"] = from_str(self.label) + result["location"] = to_enum(InstructionsSourcesLocation, self.location) + result["sourcePath"] = from_str(self.source_path) + result["type"] = to_enum(InstructionsSourcesType, self.type) + if self.apply_to is not None: + result["applyTo"] = from_union([from_str, from_none], self.apply_to) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) return result @dataclass -class MCPConfigAddRequestMCPServerConfig: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" +class LogRequest: + message: str + """Human-readable message""" - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" + ephemeral: bool | None = None + """When true, the message is transient and not persisted to the session event log on disk""" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None + level: SessionLogLevel | None = None + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". + """ url: str | None = None + """Optional URL the user can open in their browser for more details""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddRequestMCPServerConfig': + def from_dict(obj: Any) -> 'LogRequest': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + message = from_str(obj.get("message")) + ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) + level = from_union([SessionLogLevel, from_none], obj.get("level")) url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigAddRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return LogRequest(message, ephemeral, level, url) def to_dict(self) -> dict: - result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + result: dict = {} + result["message"] = from_str(self.message) + if self.ephemeral is not None: + result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) + if self.level is not None: + result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) if self.url is not None: result["url"] = from_union([from_str, from_none], self.url) return result @dataclass -class MCPConfigUpdateRequestMCPServerConfig: +class MCPServerConfig: """MCP server configuration (local/stdio or remote/http)""" args: list[str] | None = None @@ -2421,7 +2154,7 @@ class MCPConfigUpdateRequestMCPServerConfig: url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateRequestMCPServerConfig': + def from_dict(obj: Any) -> 'MCPServerConfig': assert isinstance(obj, dict) args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) command = from_union([from_str, from_none], obj.get("command")) @@ -2436,7 +2169,7 @@ def from_dict(obj: Any) -> 'MCPConfigUpdateRequestMCPServerConfig': oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigUpdateRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} @@ -2468,118 +2201,6 @@ def to_dict(self) -> dict: result["url"] = from_union([from_str, from_none], self.url) return result -@dataclass -class DiscoveredMCPServer: - enabled: bool - """Whether the server is enabled (not in the disabled list)""" - - name: str - """Server name (config key)""" - - source: MCPServerSource - """Configuration source""" - - type: DiscoveredMCPServerType | None = None - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" - - @staticmethod - def from_dict(obj: Any) -> 'DiscoveredMCPServer': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = MCPServerSource(obj.get("source")) - type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) - return DiscoveredMCPServer(enabled, name, source, type) - - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = to_enum(MCPServerSource, self.source) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) - return result - -@dataclass -class ServerElement: - enabled: bool - """Whether the server is enabled (not in the disabled list)""" - - name: str - """Server name (config key)""" - - source: MCPServerSource - """Configuration source""" - - type: DiscoveredMCPServerType | None = None - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" - - @staticmethod - def from_dict(obj: Any) -> 'ServerElement': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = MCPServerSource(obj.get("source")) - type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) - return ServerElement(enabled, name, source, type) - - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = to_enum(MCPServerSource, self.source) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) - return result - -@dataclass -class ServerSkillList: - skills: list[SkillElement] - """All discovered skills across all sources""" - - @staticmethod - def from_dict(obj: Any) -> 'ServerSkillList': - assert isinstance(obj, dict) - skills = from_list(SkillElement.from_dict, obj.get("skills")) - return ServerSkillList(skills) - - def to_dict(self) -> dict: - result: dict = {} - result["skills"] = from_list(lambda x: to_class(SkillElement, x), self.skills) - return result - -@dataclass -class ModelCapabilitiesOverrideLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) - return result - @dataclass class MCPServer: name: str @@ -2614,129 +2235,195 @@ def to_dict(self) -> dict: return result @dataclass -class UIElicitationStringEnumField: - enum: list[str] - type: UIElicitationStringEnumFieldType - default: str | None = None - description: str | None = None - enum_names: list[str] | None = None - title: str | None = None +class MCPServerConfigHTTP: + url: str + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + headers: dict[str, str] | None = None + is_default_server: bool | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigHTTPType | None = None + """Remote transport type. Defaults to "http" when omitted.""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringEnumField': + def from_dict(obj: Any) -> 'MCPServerConfigHTTP': assert isinstance(obj, dict) - enum = from_list(from_str, obj.get("enum")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - default = from_union([from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationStringEnumField(enum, type, default, description, enum_names, title) + url = from_str(obj.get("url")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) + return MCPServerConfigHTTP(url, filter_mapping, headers, is_default_server, oauth_client_id, oauth_public_client, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} - result["enum"] = from_list(from_str, self.enum) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([from_str, from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.enum_names is not None: - result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["url"] = from_str(self.url) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) return result @dataclass -class UIElicitationArrayEnumFieldItems: - enum: list[str] - type: UIElicitationStringEnumFieldType +class MCPServerConfigLocal: + args: list[str] + command: str + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigLocalType | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayEnumFieldItems': + def from_dict(obj: Any) -> 'MCPServerConfigLocal': assert isinstance(obj, dict) - enum = from_list(from_str, obj.get("enum")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - return UIElicitationArrayEnumFieldItems(enum, type) + args = from_list(from_str, obj.get("args")) + command = from_str(obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigLocalType, from_none], obj.get("type")) + return MCPServerConfigLocal(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} - result["enum"] = from_list(from_str, self.enum) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) + result["args"] = from_list(from_str, self.args) + result["command"] = from_str(self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigLocalType, x), from_none], self.type) return result @dataclass -class UIElicitationStringOneOfField: - one_of: list[UIElicitationStringOneOfFieldOneOf] - type: UIElicitationStringEnumFieldType - default: str | None = None - description: str | None = None - title: str | None = None +class ModeSetRequest: + mode: SessionMode + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringOneOfField': + def from_dict(obj: Any) -> 'ModeSetRequest': assert isinstance(obj, dict) - one_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("oneOf")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - default = from_union([from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationStringOneOfField(one_of, type, default, description, title) + mode = SessionMode(obj.get("mode")) + return ModeSetRequest(mode) def to_dict(self) -> dict: result: dict = {} - result["oneOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.one_of) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([from_str, from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["mode"] = to_enum(SessionMode, self.mode) return result @dataclass -class UIElicitationArrayAnyOfFieldItems: - any_of: list[PurpleUIElicitationArrayAnyOfFieldItemsAnyOf] +class ModelCapabilitiesLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + """Maximum number of output/completion tokens""" + + max_prompt_tokens: int | None = None + """Maximum number of prompt/input tokens""" + + vision: ModelCapabilitiesLimitsVision | None = None + """Vision-specific limits""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': assert isinstance(obj, dict) - any_of = from_list(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, obj.get("anyOf")) - return UIElicitationArrayAnyOfFieldItems(any_of) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - result["anyOf"] = from_list(lambda x: to_class(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf, x), self.any_of) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result @dataclass -class UIElicitationResponse: - """The elicitation response (accept with form values, decline, or cancel)""" +class ModelCapabilitiesOverrideLimits: + """Token limits for prompts, outputs, and context window""" - action: UIElicitationResponseAction - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" - content: dict[str, float | bool | list[str] | str] | None = None - """The form values submitted by the user (present when action is 'accept')""" + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None + vision: ModelCapabilitiesOverrideLimitsVision | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResponse': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': assert isinstance(obj, dict) - action = UIElicitationResponseAction(obj.get("action")) - content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return UIElicitationResponse(action, content) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([ModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(UIElicitationResponseAction, self.action) - if self.content is not None: - result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) return result @dataclass class PermissionDecision: - kind: Kind + kind: PermissionDecisionKind """The permission request was approved Denied because approval rules explicitly blocked it @@ -2769,7 +2456,7 @@ class PermissionDecision: @staticmethod def from_dict(obj: Any) -> 'PermissionDecision': assert isinstance(obj, dict) - kind = Kind(obj.get("kind")) + kind = PermissionDecisionKind(obj.get("kind")) rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) feedback = from_union([from_str, from_none], obj.get("feedback")) message = from_union([from_str, from_none], obj.get("message")) @@ -2779,7 +2466,7 @@ def from_dict(obj: Any) -> 'PermissionDecision': def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(Kind, self.kind) + result["kind"] = to_enum(PermissionDecisionKind, self.kind) if self.rules is not None: result["rules"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.rules) if self.feedback is not None: @@ -2793,375 +2480,258 @@ def to_dict(self) -> dict: return result @dataclass -class CapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" - - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" - - vision: FluffyModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" - - @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesLimits': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([FluffyModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return CapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesLimitsVision, x), from_none], self.vision) - return result - -@dataclass -class ToolList: - tools: list[Tool] - """List of available built-in tools with metadata""" +class PermissionDecisionApproved: + kind: PermissionDecisionApprovedKind + """The permission request was approved""" @staticmethod - def from_dict(obj: Any) -> 'ToolList': + def from_dict(obj: Any) -> 'PermissionDecisionApproved': assert isinstance(obj, dict) - tools = from_list(Tool.from_dict, obj.get("tools")) - return ToolList(tools) + kind = PermissionDecisionApprovedKind(obj.get("kind")) + return PermissionDecisionApproved(kind) def to_dict(self) -> dict: result: dict = {} - result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) + result["kind"] = to_enum(PermissionDecisionApprovedKind, self.kind) return result @dataclass -class ToolsHandlePendingToolCallRequest: - request_id: str - """Request ID of the pending tool call""" +class PermissionDecisionDeniedByContentExclusionPolicy: + kind: PermissionDecisionDeniedByContentExclusionPolicyKind + """Denied by the organization's content exclusion policy""" - error: str | None = None - """Error message if the tool call failed""" + message: str + """Human-readable explanation of why the path was excluded""" - result: ToolCallResult | str | None = None - """Tool call result (string or expanded result object)""" + path: str + """File path that triggered the exclusion""" @staticmethod - def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByContentExclusionPolicy': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) - return ToolsHandlePendingToolCallRequest(request_id, error, result) + kind = PermissionDecisionDeniedByContentExclusionPolicyKind(obj.get("kind")) + message = from_str(obj.get("message")) + path = from_str(obj.get("path")) + return PermissionDecisionDeniedByContentExclusionPolicy(kind, message, path) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + result["kind"] = to_enum(PermissionDecisionDeniedByContentExclusionPolicyKind, self.kind) + result["message"] = from_str(self.message) + result["path"] = from_str(self.path) return result @dataclass -class AccountGetQuotaResult: - quota_snapshots: dict[str, AccountQuotaSnapshot] - """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" +class PermissionDecisionDeniedByPermissionRequestHook: + kind: PermissionDecisionDeniedByPermissionRequestHookKind + """Denied by a permission request hook registered by an extension or plugin""" + + interrupt: bool | None = None + """Whether to interrupt the current agent turn""" + + message: str | None = None + """Optional message from the hook explaining the denial""" @staticmethod - def from_dict(obj: Any) -> 'AccountGetQuotaResult': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByPermissionRequestHook': assert isinstance(obj, dict) - quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) - return AccountGetQuotaResult(quota_snapshots) + kind = PermissionDecisionDeniedByPermissionRequestHookKind(obj.get("kind")) + interrupt = from_union([from_bool, from_none], obj.get("interrupt")) + message = from_union([from_str, from_none], obj.get("message")) + return PermissionDecisionDeniedByPermissionRequestHook(kind, interrupt, message) def to_dict(self) -> dict: result: dict = {} - result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) + result["kind"] = to_enum(PermissionDecisionDeniedByPermissionRequestHookKind, self.kind) + if self.interrupt is not None: + result["interrupt"] = from_union([from_bool, from_none], self.interrupt) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result @dataclass -class SessionFSSetProviderRequest: - conventions: SessionFSSetProviderConventions - """Path conventions used by this filesystem""" - - initial_cwd: str - """Initial working directory for sessions""" +class PermissionDecisionDeniedByRules: + kind: PermissionDecisionDeniedByRulesKind + """Denied because approval rules explicitly blocked it""" - session_state_path: str - """Path within each session's SessionFs where the runtime stores files for that session""" + rules: list[Any] + """Rules that denied the request""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByRules': assert isinstance(obj, dict) - conventions = SessionFSSetProviderConventions(obj.get("conventions")) - initial_cwd = from_str(obj.get("initialCwd")) - session_state_path = from_str(obj.get("sessionStatePath")) - return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) + kind = PermissionDecisionDeniedByRulesKind(obj.get("kind")) + rules = from_list(lambda x: x, obj.get("rules")) + return PermissionDecisionDeniedByRules(kind, rules) def to_dict(self) -> dict: result: dict = {} - result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) - result["initialCwd"] = from_str(self.initial_cwd) - result["sessionStatePath"] = from_str(self.session_state_path) + result["kind"] = to_enum(PermissionDecisionDeniedByRulesKind, self.kind) + result["rules"] = from_list(lambda x: x, self.rules) return result @dataclass -class ModelCapabilitiesLimitsClass: - """Token limits for prompts, outputs, and context window""" +class PermissionDecisionDeniedInteractivelyByUser: + kind: PermissionDecisionDeniedInteractivelyByUserKind + """Denied by the user during an interactive prompt""" - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: FluffyModelCapabilitiesOverrideLimitsVision | None = None + feedback: str | None = None + """Optional feedback from the user explaining the denial""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsClass': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedInteractivelyByUser': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([FluffyModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimitsClass(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + kind = PermissionDecisionDeniedInteractivelyByUserKind(obj.get("kind")) + feedback = from_union([from_str, from_none], obj.get("feedback")) + return PermissionDecisionDeniedInteractivelyByUser(kind, feedback) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) + result["kind"] = to_enum(PermissionDecisionDeniedInteractivelyByUserKind, self.kind) + if self.feedback is not None: + result["feedback"] = from_union([from_str, from_none], self.feedback) return result @dataclass -class ModeSetRequest: - mode: SessionMode - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" +class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser: + kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind + """Denied because no approval rule matched and user confirmation was unavailable""" @staticmethod - def from_dict(obj: Any) -> 'ModeSetRequest': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser': assert isinstance(obj, dict) - mode = SessionMode(obj.get("mode")) - return ModeSetRequest(mode) + kind = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(obj.get("kind")) + return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser(kind) def to_dict(self) -> dict: result: dict = {} - result["mode"] = to_enum(SessionMode, self.mode) + result["kind"] = to_enum(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, self.kind) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class Workspace: - id: UUID - branch: str | None = None - chronicle_sync_dismissed: bool | None = None - created_at: datetime | None = None - cwd: str | None = None - git_root: str | None = None - host_type: HostType | None = None - mc_last_event_id: str | None = None - mc_session_id: str | None = None - mc_task_id: str | None = None - name: str | None = None - pr_create_sync_dismissed: bool | None = None - repository: str | None = None - session_sync_level: SessionSyncLevel | None = None - summary: str | None = None - summary_count: int | None = None - updated_at: datetime | None = None +class PluginList: + plugins: list[Plugin] + """Installed plugins""" @staticmethod - def from_dict(obj: Any) -> 'Workspace': + def from_dict(obj: Any) -> 'PluginList': assert isinstance(obj, dict) - id = UUID(obj.get("id")) - branch = from_union([from_str, from_none], obj.get("branch")) - chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) - created_at = from_union([from_datetime, from_none], obj.get("created_at")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - git_root = from_union([from_str, from_none], obj.get("git_root")) - host_type = from_union([HostType, from_none], obj.get("host_type")) - mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) - mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) - mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) - name = from_union([from_str, from_none], obj.get("name")) - pr_create_sync_dismissed = from_union([from_bool, from_none], obj.get("pr_create_sync_dismissed")) - repository = from_union([from_str, from_none], obj.get("repository")) - session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) - summary = from_union([from_str, from_none], obj.get("summary")) - summary_count = from_union([from_int, from_none], obj.get("summary_count")) - updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, pr_create_sync_dismissed, repository, session_sync_level, summary, summary_count, updated_at) + plugins = from_list(Plugin.from_dict, obj.get("plugins")) + return PluginList(plugins) def to_dict(self) -> dict: result: dict = {} - result["id"] = str(self.id) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.chronicle_sync_dismissed is not None: - result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) - if self.created_at is not None: - result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.git_root is not None: - result["git_root"] = from_union([from_str, from_none], self.git_root) - if self.host_type is not None: - result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) - if self.mc_last_event_id is not None: - result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) - if self.mc_session_id is not None: - result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) - if self.mc_task_id is not None: - result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.pr_create_sync_dismissed is not None: - result["pr_create_sync_dismissed"] = from_union([from_bool, from_none], self.pr_create_sync_dismissed) - if self.repository is not None: - result["repository"] = from_union([from_str, from_none], self.repository) - if self.session_sync_level is not None: - result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) - if self.summary_count is not None: - result["summary_count"] = from_union([from_int, from_none], self.summary_count) - if self.updated_at is not None: - result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) return result @dataclass -class InstructionsSources: - content: str - """Raw content of the instruction file""" - - id: str - """Unique identifier for this source (used for toggling)""" - - label: str - """Human-readable label""" - - location: InstructionsSourcesLocation - """Where this source lives — used for UI grouping""" - - source_path: str - """File path relative to repo or absolute for home""" - - type: InstructionsSourcesType - """Category of instruction source — used for merge logic""" - - apply_to: str | None = None - """Glob pattern from frontmatter — when set, this instruction applies only to matching files""" - - description: str | None = None - """Short description (body after frontmatter) for use in instruction tables""" +class ServerSkillList: + skills: list[ServerSkill] + """All discovered skills across all sources""" @staticmethod - def from_dict(obj: Any) -> 'InstructionsSources': + def from_dict(obj: Any) -> 'ServerSkillList': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - id = from_str(obj.get("id")) - label = from_str(obj.get("label")) - location = InstructionsSourcesLocation(obj.get("location")) - source_path = from_str(obj.get("sourcePath")) - type = InstructionsSourcesType(obj.get("type")) - apply_to = from_union([from_str, from_none], obj.get("applyTo")) - description = from_union([from_str, from_none], obj.get("description")) - return InstructionsSources(content, id, label, location, source_path, type, apply_to, description) + skills = from_list(ServerSkill.from_dict, obj.get("skills")) + return ServerSkillList(skills) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["id"] = from_str(self.id) - result["label"] = from_str(self.label) - result["location"] = to_enum(InstructionsSourcesLocation, self.location) - result["sourcePath"] = from_str(self.source_path) - result["type"] = to_enum(InstructionsSourcesType, self.type) - if self.apply_to is not None: - result["applyTo"] = from_union([from_str, from_none], self.apply_to) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) + result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentList: - agents: list[AgentListAgent] - """Available custom agents""" +class SessionFSError: + """Describes a filesystem error.""" + + code: SessionFSErrorCode + """Error classification""" + + message: str | None = None + """Free-form detail about the error, for logging/diagnostics""" @staticmethod - def from_dict(obj: Any) -> 'AgentList': + def from_dict(obj: Any) -> 'SessionFSError': assert isinstance(obj, dict) - agents = from_list(AgentListAgent.from_dict, obj.get("agents")) - return AgentList(agents) + code = SessionFSErrorCode(obj.get("code")) + message = from_union([from_str, from_none], obj.get("message")) + return SessionFSError(code, message) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentListAgent, x), self.agents) + result["code"] = to_enum(SessionFSErrorCode, self.code) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentSelectResult: - agent: AgentSelectResultAgent - """The newly selected custom agent""" +class SessionFSReaddirWithTypesEntry: + name: str + """Entry name""" + + type: SessionFSReaddirWithTypesEntryType + """Entry type""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectResult': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': assert isinstance(obj, dict) - agent = AgentSelectResultAgent.from_dict(obj.get("agent")) - return AgentSelectResult(agent) + name = from_str(obj.get("name")) + type = SessionFSReaddirWithTypesEntryType(obj.get("type")) + return SessionFSReaddirWithTypesEntry(name, type) def to_dict(self) -> dict: result: dict = {} - result["agent"] = to_class(AgentSelectResultAgent, self.agent) + result["name"] = from_str(self.name) + result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentGetCurrentResult: - agent: AgentReloadResultAgent | None = None - """Currently selected custom agent, or null if using the default agent""" +class SessionFSSetProviderRequest: + conventions: SessionFSSetProviderConventions + """Path conventions used by this filesystem""" + + initial_cwd: str + """Initial working directory for sessions""" + + session_state_path: str + """Path within each session's SessionFs where the runtime stores files for that session""" @staticmethod - def from_dict(obj: Any) -> 'AgentGetCurrentResult': + def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': assert isinstance(obj, dict) - agent = from_union([AgentReloadResultAgent.from_dict, from_none], obj.get("agent")) - return AgentGetCurrentResult(agent) + conventions = SessionFSSetProviderConventions(obj.get("conventions")) + initial_cwd = from_str(obj.get("initialCwd")) + session_state_path = from_str(obj.get("sessionStatePath")) + return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) def to_dict(self) -> dict: result: dict = {} - if self.agent is not None: - result["agent"] = from_union([lambda x: to_class(AgentReloadResultAgent, x), from_none], self.agent) + result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) + result["initialCwd"] = from_str(self.initial_cwd) + result["sessionStatePath"] = from_str(self.session_state_path) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentReloadResult: - agents: list[AgentReloadResultAgent] - """Reloaded custom agents""" +class ShellKillRequest: + process_id: str + """Process identifier returned by shell.exec""" + + signal: ShellKillSignal | None = None + """Signal to send (default: SIGTERM)""" @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResult': + def from_dict(obj: Any) -> 'ShellKillRequest': assert isinstance(obj, dict) - agents = from_list(AgentReloadResultAgent.from_dict, obj.get("agents")) - return AgentReloadResult(agents) + process_id = from_str(obj.get("processId")) + signal = from_union([ShellKillSignal, from_none], obj.get("signal")) + return ShellKillRequest(process_id, signal) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentReloadResultAgent, x), self.agents) + result["processId"] = from_str(self.process_id) + if self.signal is not None: + result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -3181,172 +2751,294 @@ def to_dict(self) -> dict: result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PluginList: - plugins: list[Plugin] - """Installed plugins""" +class ToolList: + tools: list[Tool] + """List of available built-in tools with metadata""" @staticmethod - def from_dict(obj: Any) -> 'PluginList': + def from_dict(obj: Any) -> 'ToolList': assert isinstance(obj, dict) - plugins = from_list(Plugin.from_dict, obj.get("plugins")) - return PluginList(plugins) + tools = from_list(Tool.from_dict, obj.get("tools")) + return ToolList(tools) def to_dict(self) -> dict: result: dict = {} - result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) + result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) return result @dataclass -class Extension: - id: str - """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" +class ToolsHandlePendingToolCallRequest: + request_id: str + """Request ID of the pending tool call""" - name: str - """Extension name (directory name)""" + error: str | None = None + """Error message if the tool call failed""" - source: ExtensionSource - """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" + result: ToolCallResult | str | None = None + """Tool call result (string or expanded result object)""" - status: ExtensionStatus - """Current status: running, disabled, failed, or starting""" + @staticmethod + def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) + return ToolsHandlePendingToolCallRequest(request_id, error, result) - pid: int | None = None - """Process ID if the extension is running""" + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result is not None: + result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + return result + +@dataclass +class UIElicitationArrayAnyOfFieldItems: + any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] @staticmethod - def from_dict(obj: Any) -> 'Extension': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = ExtensionSource(obj.get("source")) - status = ExtensionStatus(obj.get("status")) - pid = from_union([from_int, from_none], obj.get("pid")) - return Extension(id, name, source, status, pid) + any_of = from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, obj.get("anyOf")) + return UIElicitationArrayAnyOfFieldItems(any_of) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionSource, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) - if self.pid is not None: - result["pid"] = from_union([from_int, from_none], self.pid) + result["anyOf"] = from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), self.any_of) + return result + +@dataclass +class UIElicitationArrayEnumFieldItems: + enum: list[str] + type: UIElicitationArrayEnumFieldItemsType + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayEnumFieldItems': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + return UIElicitationArrayEnumFieldItems(enum, type) + + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) return result @dataclass class UIElicitationArrayFieldItems: enum: list[str] | None = None - type: UIElicitationStringEnumFieldType | None = None - any_of: list[FluffyUIElicitationArrayAnyOfFieldItemsAnyOf] | None = None + type: UIElicitationArrayEnumFieldItemsType | None = None + any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': + assert isinstance(obj, dict) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + type = from_union([UIElicitationArrayEnumFieldItemsType, from_none], obj.get("type")) + any_of = from_union([lambda x: from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) + return UIElicitationArrayFieldItems(enum, type, any_of) + + def to_dict(self) -> dict: + result: dict = {} + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(UIElicitationArrayEnumFieldItemsType, x), from_none], self.type) + if self.any_of is not None: + result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) + return result + +@dataclass +class UIElicitationStringEnumField: + enum: list[str] + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + enum_names: list[str] | None = None + title: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringEnumField': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringEnumField(enum, type, default, description, enum_names, title) + + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.enum_names is not None: + result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationSchemaPropertyString: + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + format: UIElicitationSchemaPropertyStringFormat | None = None + max_length: float | None = None + min_length: float | None = None + title: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyString': + assert isinstance(obj, dict) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) + max_length = from_union([from_float, from_none], obj.get("maxLength")) + min_length = from_union([from_float, from_none], obj.get("minLength")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyString(type, default, description, format, max_length, min_length, title) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.format is not None: + result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) + if self.max_length is not None: + result["maxLength"] = from_union([to_float, from_none], self.max_length) + if self.min_length is not None: + result["minLength"] = from_union([to_float, from_none], self.min_length) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationStringOneOfField: + one_of: list[UIElicitationStringOneOfFieldOneOf] + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': + def from_dict(obj: Any) -> 'UIElicitationStringOneOfField': assert isinstance(obj, dict) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - type = from_union([UIElicitationStringEnumFieldType, from_none], obj.get("type")) - any_of = from_union([lambda x: from_list(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) - return UIElicitationArrayFieldItems(enum, type, any_of) + one_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("oneOf")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringOneOfField(one_of, type, default, description, title) def to_dict(self) -> dict: result: dict = {} - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(UIElicitationStringEnumFieldType, x), from_none], self.type) - if self.any_of is not None: - result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) + result["oneOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.one_of) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result @dataclass -class LogRequest: - message: str - """Human-readable message""" +class UIElicitationResponse: + """The elicitation response (accept with form values, decline, or cancel)""" - ephemeral: bool | None = None - """When true, the message is transient and not persisted to the session event log on disk""" + action: UIElicitationResponseAction + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" - level: SessionLogLevel | None = None - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - url: str | None = None - """Optional URL the user can open in their browser for more details""" + content: dict[str, float | bool | list[str] | str] | None = None + """The form values submitted by the user (present when action is 'accept')""" @staticmethod - def from_dict(obj: Any) -> 'LogRequest': + def from_dict(obj: Any) -> 'UIElicitationResponse': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - level = from_union([SessionLogLevel, from_none], obj.get("level")) - url = from_union([from_str, from_none], obj.get("url")) - return LogRequest(message, ephemeral, level, url) + action = UIElicitationResponseAction(obj.get("action")) + content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) + return UIElicitationResponse(action, content) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.ephemeral is not None: - result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) - if self.level is not None: - result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["action"] = to_enum(UIElicitationResponseAction, self.action) + if self.content is not None: + result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) return result @dataclass -class ShellKillRequest: - process_id: str - """Process identifier returned by shell.exec""" - - signal: ShellKillSignal | None = None - """Signal to send (default: SIGTERM)""" +class UIElicitationSchemaPropertyBoolean: + type: UIElicitationSchemaPropertyBooleanType + default: bool | None = None + description: str | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ShellKillRequest': + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyBoolean': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - signal = from_union([ShellKillSignal, from_none], obj.get("signal")) - return ShellKillRequest(process_id, signal) + type = UIElicitationSchemaPropertyBooleanType(obj.get("type")) + default = from_union([from_bool, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyBoolean(type, default, description, title) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) - if self.signal is not None: - result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) + result["type"] = to_enum(UIElicitationSchemaPropertyBooleanType, self.type) + if self.default is not None: + result["default"] = from_union([from_bool, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactResult: - messages_removed: int - """Number of messages removed during compaction""" - - success: bool - """Whether compaction completed successfully""" - - tokens_removed: int - """Number of tokens freed by compaction""" - - context_window: HistoryCompactContextWindow | None = None - """Post-compaction context window usage breakdown""" +class UIElicitationSchemaPropertyNumber: + type: UIElicitationSchemaPropertyNumberType + default: float | None = None + description: str | None = None + maximum: float | None = None + minimum: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactResult': + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyNumber': assert isinstance(obj, dict) - messages_removed = from_int(obj.get("messagesRemoved")) - success = from_bool(obj.get("success")) - tokens_removed = from_int(obj.get("tokensRemoved")) - context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) - return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) + type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + default = from_union([from_float, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + maximum = from_union([from_float, from_none], obj.get("maximum")) + minimum = from_union([from_float, from_none], obj.get("minimum")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyNumber(type, default, description, maximum, minimum, title) def to_dict(self) -> dict: result: dict = {} - result["messagesRemoved"] = from_int(self.messages_removed) - result["success"] = from_bool(self.success) - result["tokensRemoved"] = from_int(self.tokens_removed) - if self.context_window is not None: - result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) + result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + if self.default is not None: + result["default"] = from_union([to_float, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.maximum is not None: + result["maximum"] = from_union([to_float, from_none], self.maximum) + if self.minimum is not None: + result["minimum"] = from_union([to_float, from_none], self.minimum) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result @dataclass @@ -3371,66 +3063,173 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSReaddirWithTypesEntry: - name: str - """Entry name""" +class Workspace: + id: UUID + branch: str | None = None + chronicle_sync_dismissed: bool | None = None + created_at: datetime | None = None + cwd: str | None = None + git_root: str | None = None + host_type: HostType | None = None + mc_last_event_id: str | None = None + mc_session_id: str | None = None + mc_task_id: str | None = None + name: str | None = None + remote_steerable: bool | None = None + repository: str | None = None + session_sync_level: SessionSyncLevel | None = None + summary: str | None = None + summary_count: int | None = None + updated_at: datetime | None = None - type: SessionFSReaddirWithTypesEntryType - """Entry type""" + @staticmethod + def from_dict(obj: Any) -> 'Workspace': + assert isinstance(obj, dict) + id = UUID(obj.get("id")) + branch = from_union([from_str, from_none], obj.get("branch")) + chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) + created_at = from_union([from_datetime, from_none], obj.get("created_at")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + git_root = from_union([from_str, from_none], obj.get("git_root")) + host_type = from_union([HostType, from_none], obj.get("host_type")) + mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) + mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) + mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) + name = from_union([from_str, from_none], obj.get("name")) + remote_steerable = from_union([from_bool, from_none], obj.get("remote_steerable")) + repository = from_union([from_str, from_none], obj.get("repository")) + session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) + summary = from_union([from_str, from_none], obj.get("summary")) + summary_count = from_union([from_int, from_none], obj.get("summary_count")) + updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = str(self.id) + if self.branch is not None: + result["branch"] = from_union([from_str, from_none], self.branch) + if self.chronicle_sync_dismissed is not None: + result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) + if self.created_at is not None: + result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.git_root is not None: + result["git_root"] = from_union([from_str, from_none], self.git_root) + if self.host_type is not None: + result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + if self.mc_last_event_id is not None: + result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) + if self.mc_session_id is not None: + result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) + if self.mc_task_id is not None: + result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.remote_steerable is not None: + result["remote_steerable"] = from_union([from_bool, from_none], self.remote_steerable) + if self.repository is not None: + result["repository"] = from_union([from_str, from_none], self.repository) + if self.session_sync_level is not None: + result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) + if self.summary is not None: + result["summary"] = from_union([from_str, from_none], self.summary) + if self.summary_count is not None: + result["summary_count"] = from_union([from_int, from_none], self.summary_count) + if self.updated_at is not None: + result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + return result + +@dataclass +class MCPDiscoverResult: + servers: list[DiscoveredMCPServer] + """MCP servers discovered from all sources""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': + def from_dict(obj: Any) -> 'MCPDiscoverResult': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - type = SessionFSReaddirWithTypesEntryType(obj.get("type")) - return SessionFSReaddirWithTypesEntry(name, type) + servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) + return MCPDiscoverResult(servers) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) + result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPConfigList: - servers: dict[str, MCPServerConfigValue] - """All MCP servers from user config, keyed by name""" +class ExtensionList: + extensions: list[Extension] + """Discovered extensions and their current status""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigList': + def from_dict(obj: Any) -> 'ExtensionList': assert isinstance(obj, dict) - servers = from_dict(MCPServerConfigValue.from_dict, obj.get("servers")) - return MCPConfigList(servers) + extensions = from_list(Extension.from_dict, obj.get("extensions")) + return ExtensionList(extensions) + + def to_dict(self) -> dict: + result: dict = {} + result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) + return result + +@dataclass +class InstructionsGetSourcesResult: + sources: list[InstructionsSources] + """Instruction sources for the session""" + + @staticmethod + def from_dict(obj: Any) -> 'InstructionsGetSourcesResult': + assert isinstance(obj, dict) + sources = from_list(InstructionsSources.from_dict, obj.get("sources")) + return InstructionsGetSourcesResult(sources) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_dict(lambda x: to_class(MCPServerConfigValue, x), self.servers) + result["sources"] = from_list(lambda x: to_class(InstructionsSources, x), self.sources) return result @dataclass class MCPConfigAddRequest: - config: MCPConfigAddRequestMCPServerConfig + config: MCPServerConfig """MCP server configuration (local/stdio or remote/http)""" name: str """Unique name for the MCP server""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddRequest': + def from_dict(obj: Any) -> 'MCPConfigAddRequest': + assert isinstance(obj, dict) + config = MCPServerConfig.from_dict(obj.get("config")) + name = from_str(obj.get("name")) + return MCPConfigAddRequest(config, name) + + def to_dict(self) -> dict: + result: dict = {} + result["config"] = to_class(MCPServerConfig, self.config) + result["name"] = from_str(self.name) + return result + +@dataclass +class MCPConfigList: + servers: dict[str, MCPServerConfig] + """All MCP servers from user config, keyed by name""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigList': assert isinstance(obj, dict) - config = MCPConfigAddRequestMCPServerConfig.from_dict(obj.get("config")) - name = from_str(obj.get("name")) - return MCPConfigAddRequest(config, name) + servers = from_dict(MCPServerConfig.from_dict, obj.get("servers")) + return MCPConfigList(servers) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigAddRequestMCPServerConfig, self.config) - result["name"] = from_str(self.name) + result["servers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.servers) return result @dataclass class MCPConfigUpdateRequest: - config: MCPConfigUpdateRequestMCPServerConfig + config: MCPServerConfig """MCP server configuration (local/stdio or remote/http)""" name: str @@ -3439,30 +3238,30 @@ class MCPConfigUpdateRequest: @staticmethod def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': assert isinstance(obj, dict) - config = MCPConfigUpdateRequestMCPServerConfig.from_dict(obj.get("config")) + config = MCPServerConfig.from_dict(obj.get("config")) name = from_str(obj.get("name")) return MCPConfigUpdateRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigUpdateRequestMCPServerConfig, self.config) + result["config"] = to_class(MCPServerConfig, self.config) result["name"] = from_str(self.name) return result @dataclass -class MCPDiscoverResult: - servers: list[ServerElement] - """MCP servers discovered from all sources""" +class MCPServerList: + servers: list[MCPServer] + """Configured MCP servers""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverResult': + def from_dict(obj: Any) -> 'MCPServerList': assert isinstance(obj, dict) - servers = from_list(ServerElement.from_dict, obj.get("servers")) - return MCPDiscoverResult(servers) + servers = from_list(MCPServer.from_dict, obj.get("servers")) + return MCPServerList(servers) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(ServerElement, x), self.servers) + result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) return result @dataclass @@ -3491,221 +3290,218 @@ def to_dict(self) -> dict: return result @dataclass -class MCPServerList: - servers: list[MCPServer] - """Configured MCP servers""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerList': - assert isinstance(obj, dict) - servers = from_list(MCPServer.from_dict, obj.get("servers")) - return MCPServerList(servers) - - def to_dict(self) -> dict: - result: dict = {} - result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) - return result +class PermissionDecisionRequest: + request_id: str + """Request ID of the pending permission request""" -@dataclass -class UIElicitationArrayEnumField: - items: UIElicitationArrayEnumFieldItems - type: UIElicitationArrayEnumFieldType - default: list[str] | None = None - description: str | None = None - max_items: float | None = None - min_items: float | None = None - title: str | None = None + result: PermissionDecision @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayEnumField': + def from_dict(obj: Any) -> 'PermissionDecisionRequest': assert isinstance(obj, dict) - items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("items")) - type = UIElicitationArrayEnumFieldType(obj.get("type")) - default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - max_items = from_union([from_float, from_none], obj.get("maxItems")) - min_items = from_union([from_float, from_none], obj.get("minItems")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationArrayEnumField(items, type, default, description, max_items, min_items, title) + request_id = from_str(obj.get("requestId")) + result = PermissionDecision.from_dict(obj.get("result")) + return PermissionDecisionRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} - result["items"] = to_class(UIElicitationArrayEnumFieldItems, self.items) - result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.max_items is not None: - result["maxItems"] = from_union([to_float, from_none], self.max_items) - if self.min_items is not None: - result["minItems"] = from_union([to_float, from_none], self.min_items) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionDecision, self.result) return result @dataclass -class UIElicitationArrayAnyOfField: - items: UIElicitationArrayAnyOfFieldItems - type: UIElicitationArrayEnumFieldType - default: list[str] | None = None - description: str | None = None - max_items: float | None = None - min_items: float | None = None - title: str | None = None +class SessionFSReadFileResult: + content: str + """File content as UTF-8 string""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfField': + def from_dict(obj: Any) -> 'SessionFSReadFileResult': assert isinstance(obj, dict) - items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("items")) - type = UIElicitationArrayEnumFieldType(obj.get("type")) - default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - max_items = from_union([from_float, from_none], obj.get("maxItems")) - min_items = from_union([from_float, from_none], obj.get("minItems")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationArrayAnyOfField(items, type, default, description, max_items, min_items, title) + content = from_str(obj.get("content")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReadFileResult(content, error) def to_dict(self) -> dict: result: dict = {} - result["items"] = to_class(UIElicitationArrayAnyOfFieldItems, self.items) - result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.max_items is not None: - result["maxItems"] = from_union([to_float, from_none], self.max_items) - if self.min_items is not None: - result["minItems"] = from_union([to_float, from_none], self.min_items) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["content"] = from_str(self.content) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class UIHandlePendingElicitationRequest: - request_id: str - """The unique request ID from the elicitation.requested event""" +class SessionFSReaddirResult: + entries: list[str] + """Entry names in the directory""" - result: UIElicitationResponse - """The elicitation response (accept with form values, decline, or cancel)""" + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': + def from_dict(obj: Any) -> 'SessionFSReaddirResult': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = UIElicitationResponse.from_dict(obj.get("result")) - return UIHandlePendingElicitationRequest(request_id, result) + entries = from_list(from_str, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirResult(entries, error) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(UIElicitationResponse, self.result) + result["entries"] = from_list(from_str, self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class PermissionDecisionRequest: - request_id: str - """Request ID of the pending permission request""" - - result: PermissionDecision +class SessionFSStatResult: + birthtime: datetime + """ISO 8601 timestamp of creation""" - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = PermissionDecision.from_dict(obj.get("result")) - return PermissionDecisionRequest(request_id, result) + is_directory: bool + """Whether the path is a directory""" - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionDecision, self.result) - return result + is_file: bool + """Whether the path is a file""" -@dataclass -class ModelCapabilitiesClass: - """Override individual model capabilities resolved by the runtime""" + mtime: datetime + """ISO 8601 timestamp of last modification""" - limits: ModelCapabilitiesLimitsClass | None = None - """Token limits for prompts, outputs, and context window""" + size: int + """File size in bytes""" - supports: ModelCapabilitiesOverrideSupports | None = None - """Feature flags indicating what the model supports""" + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesClass': + def from_dict(obj: Any) -> 'SessionFSStatResult': assert isinstance(obj, dict) - limits = from_union([ModelCapabilitiesLimitsClass.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) - return ModelCapabilitiesClass(limits, supports) + birthtime = from_datetime(obj.get("birthtime")) + is_directory = from_bool(obj.get("isDirectory")) + is_file = from_bool(obj.get("isFile")) + mtime = from_datetime(obj.get("mtime")) + size = from_int(obj.get("size")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size, error) def to_dict(self) -> dict: result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsClass, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + result["birthtime"] = self.birthtime.isoformat() + result["isDirectory"] = from_bool(self.is_directory) + result["isFile"] = from_bool(self.is_file) + result["mtime"] = self.mtime.isoformat() + result["size"] = from_int(self.size) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class WorkspacesGetWorkspaceResult: - workspace: Workspace | None = None - """Current workspace metadata, or null if not available""" +class SessionFSReaddirWithTypesResult: + entries: list[SessionFSReaddirWithTypesEntry] + """Directory entries with type information""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': assert isinstance(obj, dict) - workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) - return WorkspacesGetWorkspaceResult(workspace) + entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirWithTypesResult(entries, error) def to_dict(self) -> dict: result: dict = {} - result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) + result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class InstructionsGetSourcesResult: - sources: list[InstructionsSources] - """Instruction sources for the session""" +class UIElicitationArrayAnyOfField: + items: UIElicitationArrayAnyOfFieldItems + type: UIElicitationArrayAnyOfFieldType + default: list[str] | None = None + description: str | None = None + max_items: float | None = None + min_items: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'InstructionsGetSourcesResult': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfField': assert isinstance(obj, dict) - sources = from_list(InstructionsSources.from_dict, obj.get("sources")) - return InstructionsGetSourcesResult(sources) + items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayAnyOfFieldType(obj.get("type")) + default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationArrayAnyOfField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["sources"] = from_list(lambda x: to_class(InstructionsSources, x), self.sources) + result["items"] = to_class(UIElicitationArrayAnyOfFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayAnyOfFieldType, self.type) + if self.default is not None: + result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExtensionList: - extensions: list[Extension] - """Discovered extensions and their current status""" +class UIElicitationArrayEnumField: + items: UIElicitationArrayEnumFieldItems + type: UIElicitationArrayAnyOfFieldType + default: list[str] | None = None + description: str | None = None + max_items: float | None = None + min_items: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ExtensionList': + def from_dict(obj: Any) -> 'UIElicitationArrayEnumField': assert isinstance(obj, dict) - extensions = from_list(Extension.from_dict, obj.get("extensions")) - return ExtensionList(extensions) + items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayAnyOfFieldType(obj.get("type")) + default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationArrayEnumField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) + result["items"] = to_class(UIElicitationArrayEnumFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayAnyOfFieldType, self.type) + if self.default is not None: + result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result @dataclass class UIElicitationSchemaProperty: - type: UIElicitationSchemaPropertyNumberType + type: UIElicitationSchemaPropertyType default: float | bool | list[str] | str | None = None description: str | None = None enum: list[str] | None = None enum_names: list[str] | None = None title: str | None = None - one_of: list[UIElicitationSchemaPropertyOneOf] | None = None + one_of: list[UIElicitationStringOneOfFieldOneOf] | None = None items: UIElicitationArrayFieldItems | None = None max_items: float | None = None min_items: float | None = None @@ -3718,13 +3514,13 @@ class UIElicitationSchemaProperty: @staticmethod def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': assert isinstance(obj, dict) - type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + type = UIElicitationSchemaPropertyType(obj.get("type")) default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) description = from_union([from_str, from_none], obj.get("description")) enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) title = from_union([from_str, from_none], obj.get("title")) - one_of = from_union([lambda x: from_list(UIElicitationSchemaPropertyOneOf.from_dict, x), from_none], obj.get("oneOf")) + one_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("oneOf")) items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) max_items = from_union([from_float, from_none], obj.get("maxItems")) min_items = from_union([from_float, from_none], obj.get("minItems")) @@ -3737,7 +3533,7 @@ def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + result["type"] = to_enum(UIElicitationSchemaPropertyType, self.type) if self.default is not None: result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) if self.description is not None: @@ -3749,7 +3545,7 @@ def to_dict(self) -> dict: if self.title is not None: result["title"] = from_union([from_str, from_none], self.title) if self.one_of is not None: - result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationSchemaPropertyOneOf, x), x), from_none], self.one_of) + result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.one_of) if self.items is not None: result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) if self.max_items is not None: @@ -3768,6 +3564,27 @@ def to_dict(self) -> dict: result["minimum"] = from_union([to_float, from_none], self.minimum) return result +@dataclass +class UIHandlePendingElicitationRequest: + request_id: str + """The unique request ID from the elicitation.requested event""" + + result: UIElicitationResponse + """The elicitation response (accept with form values, decline, or cancel)""" + + @staticmethod + def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = UIElicitationResponse.from_dict(obj.get("result")) + return UIHandlePendingElicitationRequest(request_id, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(UIElicitationResponse, self.result) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageGetMetricsResult: @@ -3828,19 +3645,19 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSReaddirWithTypesResult: - entries: list[SessionFSReaddirWithTypesEntry] - """Directory entries with type information""" +class WorkspacesGetWorkspaceResult: + workspace: Workspace | None = None + """Current workspace metadata, or null if not available""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': + def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': assert isinstance(obj, dict) - entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) - return SessionFSReaddirWithTypesResult(entries) + workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) + return WorkspacesGetWorkspaceResult(workspace) def to_dict(self) -> dict: result: dict = {} - result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result @dataclass @@ -3850,7 +3667,7 @@ class UIElicitationSchema: properties: dict[str, UIElicitationSchemaProperty] """Form field definitions, keyed by field name""" - type: RequestedSchemaType + type: UIElicitationSchemaType """Schema type indicator (always 'object')""" required: list[str] | None = None @@ -3860,14 +3677,14 @@ class UIElicitationSchema: def from_dict(obj: Any) -> 'UIElicitationSchema': assert isinstance(obj, dict) properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) - type = RequestedSchemaType(obj.get("type")) + type = UIElicitationSchemaType(obj.get("type")) required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) return UIElicitationSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) - result["type"] = to_enum(RequestedSchemaType, self.type) + result["type"] = to_enum(UIElicitationSchemaType, self.type) if self.required is not None: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result @@ -3918,34 +3735,9 @@ def to_dict(self) -> dict: result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) return result -@dataclass -class CapabilitiesClass: - """Model capabilities and limits""" - - limits: CapabilitiesLimits | None = None - """Token limits for prompts, outputs, and context window""" - - supports: CapabilitiesSupports | None = None - """Feature flags indicating what the model supports""" - - @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesClass': - assert isinstance(obj, dict) - limits = from_union([CapabilitiesLimits.from_dict, from_none], obj.get("limits")) - supports = from_union([CapabilitiesSupports.from_dict, from_none], obj.get("supports")) - return CapabilitiesClass(limits, supports) - - def to_dict(self) -> dict: - result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(CapabilitiesLimits, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(CapabilitiesSupports, x), from_none], self.supports) - return result - @dataclass class Model: - capabilities: CapabilitiesClass + capabilities: ModelCapabilities """Model capabilities and limits""" id: str @@ -3969,7 +3761,7 @@ class Model: @staticmethod def from_dict(obj: Any) -> 'Model': assert isinstance(obj, dict) - capabilities = CapabilitiesClass.from_dict(obj.get("capabilities")) + capabilities = ModelCapabilities.from_dict(obj.get("capabilities")) id = from_str(obj.get("id")) name = from_str(obj.get("name")) billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) @@ -3980,7 +3772,7 @@ def from_dict(obj: Any) -> 'Model': def to_dict(self) -> dict: result: dict = {} - result["capabilities"] = to_class(CapabilitiesClass, self.capabilities) + result["capabilities"] = to_class(ModelCapabilities, self.capabilities) result["id"] = from_str(self.id) result["name"] = from_str(self.name) if self.billing is not None: @@ -4014,7 +3806,7 @@ class ModelSwitchToRequest: model_id: str """Model identifier to switch to""" - model_capabilities: ModelCapabilitiesClass | None = None + model_capabilities: ModelCapabilitiesOverride | None = None """Override individual model capabilities resolved by the runtime""" reasoning_effort: str | None = None @@ -4024,7 +3816,7 @@ class ModelSwitchToRequest: def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) model_id = from_str(obj.get("modelId")) - model_capabilities = from_union([ModelCapabilitiesClass.from_dict, from_none], obj.get("modelCapabilities")) + model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort) @@ -4032,634 +3824,514 @@ def to_dict(self) -> dict: result: dict = {} result["modelId"] = from_str(self.model_id) if self.model_capabilities is not None: - result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesClass, x), from_none], self.model_capabilities) + result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesOverride, x), from_none], self.model_capabilities) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) return result -def model_capabilities_from_dict(s: Any) -> ModelCapabilities: - return ModelCapabilities.from_dict(s) - -def model_capabilities_to_dict(x: ModelCapabilities) -> Any: - return to_class(ModelCapabilities, x) - -def model_capabilities_limits_vision_from_dict(s: Any) -> ModelCapabilitiesLimitsVision: - return ModelCapabilitiesLimitsVision.from_dict(s) - -def model_capabilities_limits_vision_to_dict(x: ModelCapabilitiesLimitsVision) -> Any: - return to_class(ModelCapabilitiesLimitsVision, x) - -def mcp_server_config_from_dict(s: Any) -> MCPServerConfig: - return MCPServerConfig.from_dict(s) - -def mcp_server_config_to_dict(x: MCPServerConfig) -> Any: - return to_class(MCPServerConfig, x) - -def filter_mapping_from_dict(s: Any) -> dict[str, FilterMappingString] | FilterMappingString: - return from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], s) - -def filter_mapping_to_dict(x: dict[str, FilterMappingString] | FilterMappingString) -> Any: - return from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x)], x) - -def discovered_mcp_server_from_dict(s: Any) -> DiscoveredMCPServer: - return DiscoveredMCPServer.from_dict(s) - -def discovered_mcp_server_to_dict(x: DiscoveredMCPServer) -> Any: - return to_class(DiscoveredMCPServer, x) - -def server_skill_list_from_dict(s: Any) -> ServerSkillList: - return ServerSkillList.from_dict(s) - -def server_skill_list_to_dict(x: ServerSkillList) -> Any: - return to_class(ServerSkillList, x) - -def server_skill_from_dict(s: Any) -> ServerSkill: - return ServerSkill.from_dict(s) - -def server_skill_to_dict(x: ServerSkill) -> Any: - return to_class(ServerSkill, x) - -def current_model_from_dict(s: Any) -> CurrentModel: - return CurrentModel.from_dict(s) - -def current_model_to_dict(x: CurrentModel) -> Any: - return to_class(CurrentModel, x) - -def model_capabilities_override_from_dict(s: Any) -> ModelCapabilitiesOverride: - return ModelCapabilitiesOverride.from_dict(s) - -def model_capabilities_override_to_dict(x: ModelCapabilitiesOverride) -> Any: - return to_class(ModelCapabilitiesOverride, x) - -def session_mode_from_dict(s: Any) -> SessionMode: - return SessionMode(s) - -def session_mode_to_dict(x: SessionMode) -> Any: - return to_enum(SessionMode, x) - -def agent_info_from_dict(s: Any) -> AgentInfo: - return AgentInfo.from_dict(s) - -def agent_info_to_dict(x: AgentInfo) -> Any: - return to_class(AgentInfo, x) - -def mcp_server_list_from_dict(s: Any) -> MCPServerList: - return MCPServerList.from_dict(s) - -def mcp_server_list_to_dict(x: MCPServerList) -> Any: - return to_class(MCPServerList, x) - -def tool_call_result_from_dict(s: Any) -> ToolCallResult: - return ToolCallResult.from_dict(s) - -def tool_call_result_to_dict(x: ToolCallResult) -> Any: - return to_class(ToolCallResult, x) - -def handle_tool_call_result_from_dict(s: Any) -> HandleToolCallResult: - return HandleToolCallResult.from_dict(s) - -def handle_tool_call_result_to_dict(x: HandleToolCallResult) -> Any: - return to_class(HandleToolCallResult, x) - -def ui_elicitation_string_enum_field_from_dict(s: Any) -> UIElicitationStringEnumField: - return UIElicitationStringEnumField.from_dict(s) - -def ui_elicitation_string_enum_field_to_dict(x: UIElicitationStringEnumField) -> Any: - return to_class(UIElicitationStringEnumField, x) - -def ui_elicitation_string_one_of_field_from_dict(s: Any) -> UIElicitationStringOneOfField: - return UIElicitationStringOneOfField.from_dict(s) - -def ui_elicitation_string_one_of_field_to_dict(x: UIElicitationStringOneOfField) -> Any: - return to_class(UIElicitationStringOneOfField, x) - -def ui_elicitation_array_enum_field_from_dict(s: Any) -> UIElicitationArrayEnumField: - return UIElicitationArrayEnumField.from_dict(s) - -def ui_elicitation_array_enum_field_to_dict(x: UIElicitationArrayEnumField) -> Any: - return to_class(UIElicitationArrayEnumField, x) - -def ui_elicitation_array_any_of_field_from_dict(s: Any) -> UIElicitationArrayAnyOfField: - return UIElicitationArrayAnyOfField.from_dict(s) - -def ui_elicitation_array_any_of_field_to_dict(x: UIElicitationArrayAnyOfField) -> Any: - return to_class(UIElicitationArrayAnyOfField, x) - -def ui_elicitation_response_from_dict(s: Any) -> UIElicitationResponse: - return UIElicitationResponse.from_dict(s) - -def ui_elicitation_response_to_dict(x: UIElicitationResponse) -> Any: - return to_class(UIElicitationResponse, x) - -def ui_elicitation_response_action_from_dict(s: Any) -> UIElicitationResponseAction: - return UIElicitationResponseAction(s) - -def ui_elicitation_response_action_to_dict(x: UIElicitationResponseAction) -> Any: - return to_enum(UIElicitationResponseAction, x) - -def ui_elicitation_response_content_from_dict(s: Any) -> dict[str, float | bool | list[str] | str]: - return from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), s) - -def ui_elicitation_response_content_to_dict(x: dict[str, float | bool | list[str] | str]) -> Any: - return from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x) - -def ui_elicitation_field_value_from_dict(s: Any) -> float | bool | list[str] | str: - return from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], s) - -def ui_elicitation_field_value_to_dict(x: float | bool | list[str] | str) -> Any: - return from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x) - -def ui_handle_pending_elicitation_request_from_dict(s: Any) -> UIHandlePendingElicitationRequest: - return UIHandlePendingElicitationRequest.from_dict(s) - -def ui_handle_pending_elicitation_request_to_dict(x: UIHandlePendingElicitationRequest) -> Any: - return to_class(UIHandlePendingElicitationRequest, x) - -def ui_elicitation_result_from_dict(s: Any) -> UIElicitationResult: - return UIElicitationResult.from_dict(s) - -def ui_elicitation_result_to_dict(x: UIElicitationResult) -> Any: - return to_class(UIElicitationResult, x) - -def permission_decision_request_from_dict(s: Any) -> PermissionDecisionRequest: - return PermissionDecisionRequest.from_dict(s) - -def permission_decision_request_to_dict(x: PermissionDecisionRequest) -> Any: - return to_class(PermissionDecisionRequest, x) - -def permission_decision_from_dict(s: Any) -> PermissionDecision: - return PermissionDecision.from_dict(s) - -def permission_decision_to_dict(x: PermissionDecision) -> Any: - return to_class(PermissionDecision, x) - -def permission_request_result_from_dict(s: Any) -> PermissionRequestResult: - return PermissionRequestResult.from_dict(s) - -def permission_request_result_to_dict(x: PermissionRequestResult) -> Any: - return to_class(PermissionRequestResult, x) - -def session_log_level_from_dict(s: Any) -> SessionLogLevel: - return SessionLogLevel(s) - -def session_log_level_to_dict(x: SessionLogLevel) -> Any: - return to_enum(SessionLogLevel, x) - -def ping_result_from_dict(s: Any) -> PingResult: - return PingResult.from_dict(s) - -def ping_result_to_dict(x: PingResult) -> Any: - return to_class(PingResult, x) - -def ping_request_from_dict(s: Any) -> PingRequest: - return PingRequest.from_dict(s) - -def ping_request_to_dict(x: PingRequest) -> Any: - return to_class(PingRequest, x) - -def model_list_from_dict(s: Any) -> ModelList: - return ModelList.from_dict(s) - -def model_list_to_dict(x: ModelList) -> Any: - return to_class(ModelList, x) - -def tool_list_from_dict(s: Any) -> ToolList: - return ToolList.from_dict(s) - -def tool_list_to_dict(x: ToolList) -> Any: - return to_class(ToolList, x) - -def tools_list_request_from_dict(s: Any) -> ToolsListRequest: - return ToolsListRequest.from_dict(s) - -def tools_list_request_to_dict(x: ToolsListRequest) -> Any: - return to_class(ToolsListRequest, x) - -def account_get_quota_result_from_dict(s: Any) -> AccountGetQuotaResult: - return AccountGetQuotaResult.from_dict(s) - -def account_get_quota_result_to_dict(x: AccountGetQuotaResult) -> Any: - return to_class(AccountGetQuotaResult, x) - -def mcp_config_list_from_dict(s: Any) -> MCPConfigList: - return MCPConfigList.from_dict(s) - -def mcp_config_list_to_dict(x: MCPConfigList) -> Any: - return to_class(MCPConfigList, x) - -def mcp_config_add_request_from_dict(s: Any) -> MCPConfigAddRequest: - return MCPConfigAddRequest.from_dict(s) - -def mcp_config_add_request_to_dict(x: MCPConfigAddRequest) -> Any: - return to_class(MCPConfigAddRequest, x) - -def mcp_config_update_request_from_dict(s: Any) -> MCPConfigUpdateRequest: - return MCPConfigUpdateRequest.from_dict(s) - -def mcp_config_update_request_to_dict(x: MCPConfigUpdateRequest) -> Any: - return to_class(MCPConfigUpdateRequest, x) - -def mcp_config_remove_request_from_dict(s: Any) -> MCPConfigRemoveRequest: - return MCPConfigRemoveRequest.from_dict(s) - -def mcp_config_remove_request_to_dict(x: MCPConfigRemoveRequest) -> Any: - return to_class(MCPConfigRemoveRequest, x) - -def mcp_discover_result_from_dict(s: Any) -> MCPDiscoverResult: - return MCPDiscoverResult.from_dict(s) - -def mcp_discover_result_to_dict(x: MCPDiscoverResult) -> Any: - return to_class(MCPDiscoverResult, x) - -def mcp_discover_request_from_dict(s: Any) -> MCPDiscoverRequest: - return MCPDiscoverRequest.from_dict(s) - -def mcp_discover_request_to_dict(x: MCPDiscoverRequest) -> Any: - return to_class(MCPDiscoverRequest, x) - -def skills_config_set_disabled_skills_request_from_dict(s: Any) -> SkillsConfigSetDisabledSkillsRequest: - return SkillsConfigSetDisabledSkillsRequest.from_dict(s) - -def skills_config_set_disabled_skills_request_to_dict(x: SkillsConfigSetDisabledSkillsRequest) -> Any: - return to_class(SkillsConfigSetDisabledSkillsRequest, x) - -def skills_discover_request_from_dict(s: Any) -> SkillsDiscoverRequest: - return SkillsDiscoverRequest.from_dict(s) - -def skills_discover_request_to_dict(x: SkillsDiscoverRequest) -> Any: - return to_class(SkillsDiscoverRequest, x) - -def session_fs_set_provider_result_from_dict(s: Any) -> SessionFSSetProviderResult: - return SessionFSSetProviderResult.from_dict(s) - -def session_fs_set_provider_result_to_dict(x: SessionFSSetProviderResult) -> Any: - return to_class(SessionFSSetProviderResult, x) - -def session_fs_set_provider_request_from_dict(s: Any) -> SessionFSSetProviderRequest: - return SessionFSSetProviderRequest.from_dict(s) - -def session_fs_set_provider_request_to_dict(x: SessionFSSetProviderRequest) -> Any: - return to_class(SessionFSSetProviderRequest, x) - -def sessions_fork_result_from_dict(s: Any) -> SessionsForkResult: - return SessionsForkResult.from_dict(s) - -def sessions_fork_result_to_dict(x: SessionsForkResult) -> Any: - return to_class(SessionsForkResult, x) - -def sessions_fork_request_from_dict(s: Any) -> SessionsForkRequest: - return SessionsForkRequest.from_dict(s) - -def sessions_fork_request_to_dict(x: SessionsForkRequest) -> Any: - return to_class(SessionsForkRequest, x) - -def model_switch_to_result_from_dict(s: Any) -> ModelSwitchToResult: - return ModelSwitchToResult.from_dict(s) - -def model_switch_to_result_to_dict(x: ModelSwitchToResult) -> Any: - return to_class(ModelSwitchToResult, x) - -def model_switch_to_request_from_dict(s: Any) -> ModelSwitchToRequest: - return ModelSwitchToRequest.from_dict(s) - -def model_switch_to_request_to_dict(x: ModelSwitchToRequest) -> Any: - return to_class(ModelSwitchToRequest, x) - -def mode_set_request_from_dict(s: Any) -> ModeSetRequest: - return ModeSetRequest.from_dict(s) - -def mode_set_request_to_dict(x: ModeSetRequest) -> Any: - return to_class(ModeSetRequest, x) - -def name_get_result_from_dict(s: Any) -> NameGetResult: - return NameGetResult.from_dict(s) - -def name_get_result_to_dict(x: NameGetResult) -> Any: - return to_class(NameGetResult, x) - -def name_set_request_from_dict(s: Any) -> NameSetRequest: - return NameSetRequest.from_dict(s) - -def name_set_request_to_dict(x: NameSetRequest) -> Any: - return to_class(NameSetRequest, x) - -def plan_read_result_from_dict(s: Any) -> PlanReadResult: - return PlanReadResult.from_dict(s) - -def plan_read_result_to_dict(x: PlanReadResult) -> Any: - return to_class(PlanReadResult, x) - -def plan_update_request_from_dict(s: Any) -> PlanUpdateRequest: - return PlanUpdateRequest.from_dict(s) - -def plan_update_request_to_dict(x: PlanUpdateRequest) -> Any: - return to_class(PlanUpdateRequest, x) - -def workspaces_get_workspace_result_from_dict(s: Any) -> WorkspacesGetWorkspaceResult: - return WorkspacesGetWorkspaceResult.from_dict(s) - -def workspaces_get_workspace_result_to_dict(x: WorkspacesGetWorkspaceResult) -> Any: - return to_class(WorkspacesGetWorkspaceResult, x) - -def workspaces_list_files_result_from_dict(s: Any) -> WorkspacesListFilesResult: - return WorkspacesListFilesResult.from_dict(s) - -def workspaces_list_files_result_to_dict(x: WorkspacesListFilesResult) -> Any: - return to_class(WorkspacesListFilesResult, x) - -def workspaces_read_file_result_from_dict(s: Any) -> WorkspacesReadFileResult: - return WorkspacesReadFileResult.from_dict(s) - -def workspaces_read_file_result_to_dict(x: WorkspacesReadFileResult) -> Any: - return to_class(WorkspacesReadFileResult, x) - -def workspaces_read_file_request_from_dict(s: Any) -> WorkspacesReadFileRequest: - return WorkspacesReadFileRequest.from_dict(s) - -def workspaces_read_file_request_to_dict(x: WorkspacesReadFileRequest) -> Any: - return to_class(WorkspacesReadFileRequest, x) - -def workspaces_create_file_request_from_dict(s: Any) -> WorkspacesCreateFileRequest: - return WorkspacesCreateFileRequest.from_dict(s) - -def workspaces_create_file_request_to_dict(x: WorkspacesCreateFileRequest) -> Any: - return to_class(WorkspacesCreateFileRequest, x) - -def instructions_get_sources_result_from_dict(s: Any) -> InstructionsGetSourcesResult: - return InstructionsGetSourcesResult.from_dict(s) - -def instructions_get_sources_result_to_dict(x: InstructionsGetSourcesResult) -> Any: - return to_class(InstructionsGetSourcesResult, x) - -def fleet_start_result_from_dict(s: Any) -> FleetStartResult: - return FleetStartResult.from_dict(s) - -def fleet_start_result_to_dict(x: FleetStartResult) -> Any: - return to_class(FleetStartResult, x) - -def fleet_start_request_from_dict(s: Any) -> FleetStartRequest: - return FleetStartRequest.from_dict(s) - -def fleet_start_request_to_dict(x: FleetStartRequest) -> Any: - return to_class(FleetStartRequest, x) - -def agent_list_from_dict(s: Any) -> AgentList: - return AgentList.from_dict(s) - -def agent_list_to_dict(x: AgentList) -> Any: - return to_class(AgentList, x) - -def agent_get_current_result_from_dict(s: Any) -> AgentGetCurrentResult: - return AgentGetCurrentResult.from_dict(s) - -def agent_get_current_result_to_dict(x: AgentGetCurrentResult) -> Any: - return to_class(AgentGetCurrentResult, x) - -def agent_select_result_from_dict(s: Any) -> AgentSelectResult: - return AgentSelectResult.from_dict(s) - -def agent_select_result_to_dict(x: AgentSelectResult) -> Any: - return to_class(AgentSelectResult, x) - -def agent_select_request_from_dict(s: Any) -> AgentSelectRequest: - return AgentSelectRequest.from_dict(s) - -def agent_select_request_to_dict(x: AgentSelectRequest) -> Any: - return to_class(AgentSelectRequest, x) - -def agent_reload_result_from_dict(s: Any) -> AgentReloadResult: - return AgentReloadResult.from_dict(s) - -def agent_reload_result_to_dict(x: AgentReloadResult) -> Any: - return to_class(AgentReloadResult, x) - -def skill_list_from_dict(s: Any) -> SkillList: - return SkillList.from_dict(s) - -def skill_list_to_dict(x: SkillList) -> Any: - return to_class(SkillList, x) - -def skills_enable_request_from_dict(s: Any) -> SkillsEnableRequest: - return SkillsEnableRequest.from_dict(s) - -def skills_enable_request_to_dict(x: SkillsEnableRequest) -> Any: - return to_class(SkillsEnableRequest, x) - -def skills_disable_request_from_dict(s: Any) -> SkillsDisableRequest: - return SkillsDisableRequest.from_dict(s) - -def skills_disable_request_to_dict(x: SkillsDisableRequest) -> Any: - return to_class(SkillsDisableRequest, x) - -def mcp_enable_request_from_dict(s: Any) -> MCPEnableRequest: - return MCPEnableRequest.from_dict(s) - -def mcp_enable_request_to_dict(x: MCPEnableRequest) -> Any: - return to_class(MCPEnableRequest, x) - -def mcp_disable_request_from_dict(s: Any) -> MCPDisableRequest: - return MCPDisableRequest.from_dict(s) - -def mcp_disable_request_to_dict(x: MCPDisableRequest) -> Any: - return to_class(MCPDisableRequest, x) - -def plugin_list_from_dict(s: Any) -> PluginList: - return PluginList.from_dict(s) - -def plugin_list_to_dict(x: PluginList) -> Any: - return to_class(PluginList, x) - -def extension_list_from_dict(s: Any) -> ExtensionList: - return ExtensionList.from_dict(s) - -def extension_list_to_dict(x: ExtensionList) -> Any: - return to_class(ExtensionList, x) - -def extensions_enable_request_from_dict(s: Any) -> ExtensionsEnableRequest: - return ExtensionsEnableRequest.from_dict(s) - -def extensions_enable_request_to_dict(x: ExtensionsEnableRequest) -> Any: - return to_class(ExtensionsEnableRequest, x) - -def extensions_disable_request_from_dict(s: Any) -> ExtensionsDisableRequest: - return ExtensionsDisableRequest.from_dict(s) - -def extensions_disable_request_to_dict(x: ExtensionsDisableRequest) -> Any: - return to_class(ExtensionsDisableRequest, x) - -def tools_handle_pending_tool_call_request_from_dict(s: Any) -> ToolsHandlePendingToolCallRequest: - return ToolsHandlePendingToolCallRequest.from_dict(s) - -def tools_handle_pending_tool_call_request_to_dict(x: ToolsHandlePendingToolCallRequest) -> Any: - return to_class(ToolsHandlePendingToolCallRequest, x) - -def commands_handle_pending_command_result_from_dict(s: Any) -> CommandsHandlePendingCommandResult: - return CommandsHandlePendingCommandResult.from_dict(s) - -def commands_handle_pending_command_result_to_dict(x: CommandsHandlePendingCommandResult) -> Any: - return to_class(CommandsHandlePendingCommandResult, x) - -def commands_handle_pending_command_request_from_dict(s: Any) -> CommandsHandlePendingCommandRequest: - return CommandsHandlePendingCommandRequest.from_dict(s) - -def commands_handle_pending_command_request_to_dict(x: CommandsHandlePendingCommandRequest) -> Any: - return to_class(CommandsHandlePendingCommandRequest, x) - -def ui_elicitation_request_from_dict(s: Any) -> UIElicitationRequest: - return UIElicitationRequest.from_dict(s) - -def ui_elicitation_request_to_dict(x: UIElicitationRequest) -> Any: - return to_class(UIElicitationRequest, x) - -def log_result_from_dict(s: Any) -> LogResult: - return LogResult.from_dict(s) - -def log_result_to_dict(x: LogResult) -> Any: - return to_class(LogResult, x) - -def log_request_from_dict(s: Any) -> LogRequest: - return LogRequest.from_dict(s) - -def log_request_to_dict(x: LogRequest) -> Any: - return to_class(LogRequest, x) - -def shell_exec_result_from_dict(s: Any) -> ShellExecResult: - return ShellExecResult.from_dict(s) - -def shell_exec_result_to_dict(x: ShellExecResult) -> Any: - return to_class(ShellExecResult, x) - -def shell_exec_request_from_dict(s: Any) -> ShellExecRequest: - return ShellExecRequest.from_dict(s) - -def shell_exec_request_to_dict(x: ShellExecRequest) -> Any: - return to_class(ShellExecRequest, x) - -def shell_kill_result_from_dict(s: Any) -> ShellKillResult: - return ShellKillResult.from_dict(s) - -def shell_kill_result_to_dict(x: ShellKillResult) -> Any: - return to_class(ShellKillResult, x) - -def shell_kill_request_from_dict(s: Any) -> ShellKillRequest: - return ShellKillRequest.from_dict(s) - -def shell_kill_request_to_dict(x: ShellKillRequest) -> Any: - return to_class(ShellKillRequest, x) - -def history_compact_result_from_dict(s: Any) -> HistoryCompactResult: - return HistoryCompactResult.from_dict(s) - -def history_compact_result_to_dict(x: HistoryCompactResult) -> Any: - return to_class(HistoryCompactResult, x) - -def history_truncate_result_from_dict(s: Any) -> HistoryTruncateResult: - return HistoryTruncateResult.from_dict(s) - -def history_truncate_result_to_dict(x: HistoryTruncateResult) -> Any: - return to_class(HistoryTruncateResult, x) - -def history_truncate_request_from_dict(s: Any) -> HistoryTruncateRequest: - return HistoryTruncateRequest.from_dict(s) - -def history_truncate_request_to_dict(x: HistoryTruncateRequest) -> Any: - return to_class(HistoryTruncateRequest, x) - -def usage_get_metrics_result_from_dict(s: Any) -> UsageGetMetricsResult: - return UsageGetMetricsResult.from_dict(s) - -def usage_get_metrics_result_to_dict(x: UsageGetMetricsResult) -> Any: - return to_class(UsageGetMetricsResult, x) - -def session_fs_read_file_result_from_dict(s: Any) -> SessionFSReadFileResult: - return SessionFSReadFileResult.from_dict(s) - -def session_fs_read_file_result_to_dict(x: SessionFSReadFileResult) -> Any: - return to_class(SessionFSReadFileResult, x) - -def session_fs_read_file_request_from_dict(s: Any) -> SessionFSReadFileRequest: - return SessionFSReadFileRequest.from_dict(s) - -def session_fs_read_file_request_to_dict(x: SessionFSReadFileRequest) -> Any: - return to_class(SessionFSReadFileRequest, x) - -def session_fs_write_file_request_from_dict(s: Any) -> SessionFSWriteFileRequest: - return SessionFSWriteFileRequest.from_dict(s) - -def session_fs_write_file_request_to_dict(x: SessionFSWriteFileRequest) -> Any: - return to_class(SessionFSWriteFileRequest, x) - -def session_fs_append_file_request_from_dict(s: Any) -> SessionFSAppendFileRequest: - return SessionFSAppendFileRequest.from_dict(s) - -def session_fs_append_file_request_to_dict(x: SessionFSAppendFileRequest) -> Any: - return to_class(SessionFSAppendFileRequest, x) - -def session_fs_exists_result_from_dict(s: Any) -> SessionFSExistsResult: - return SessionFSExistsResult.from_dict(s) - -def session_fs_exists_result_to_dict(x: SessionFSExistsResult) -> Any: - return to_class(SessionFSExistsResult, x) - -def session_fs_exists_request_from_dict(s: Any) -> SessionFSExistsRequest: - return SessionFSExistsRequest.from_dict(s) - -def session_fs_exists_request_to_dict(x: SessionFSExistsRequest) -> Any: - return to_class(SessionFSExistsRequest, x) - -def session_fs_stat_result_from_dict(s: Any) -> SessionFSStatResult: - return SessionFSStatResult.from_dict(s) - -def session_fs_stat_result_to_dict(x: SessionFSStatResult) -> Any: - return to_class(SessionFSStatResult, x) - -def session_fs_stat_request_from_dict(s: Any) -> SessionFSStatRequest: - return SessionFSStatRequest.from_dict(s) - -def session_fs_stat_request_to_dict(x: SessionFSStatRequest) -> Any: - return to_class(SessionFSStatRequest, x) - -def session_fs_mkdir_request_from_dict(s: Any) -> SessionFSMkdirRequest: - return SessionFSMkdirRequest.from_dict(s) - -def session_fs_mkdir_request_to_dict(x: SessionFSMkdirRequest) -> Any: - return to_class(SessionFSMkdirRequest, x) - -def session_fs_readdir_result_from_dict(s: Any) -> SessionFSReaddirResult: - return SessionFSReaddirResult.from_dict(s) - -def session_fs_readdir_result_to_dict(x: SessionFSReaddirResult) -> Any: - return to_class(SessionFSReaddirResult, x) - -def session_fs_readdir_request_from_dict(s: Any) -> SessionFSReaddirRequest: - return SessionFSReaddirRequest.from_dict(s) - -def session_fs_readdir_request_to_dict(x: SessionFSReaddirRequest) -> Any: - return to_class(SessionFSReaddirRequest, x) - -def session_fs_readdir_with_types_result_from_dict(s: Any) -> SessionFSReaddirWithTypesResult: - return SessionFSReaddirWithTypesResult.from_dict(s) - -def session_fs_readdir_with_types_result_to_dict(x: SessionFSReaddirWithTypesResult) -> Any: - return to_class(SessionFSReaddirWithTypesResult, x) - -def session_fs_readdir_with_types_request_from_dict(s: Any) -> SessionFSReaddirWithTypesRequest: - return SessionFSReaddirWithTypesRequest.from_dict(s) - -def session_fs_readdir_with_types_request_to_dict(x: SessionFSReaddirWithTypesRequest) -> Any: - return to_class(SessionFSReaddirWithTypesRequest, x) - -def session_fs_rm_request_from_dict(s: Any) -> SessionFSRmRequest: - return SessionFSRmRequest.from_dict(s) - -def session_fs_rm_request_to_dict(x: SessionFSRmRequest) -> Any: - return to_class(SessionFSRmRequest, x) - -def session_fs_rename_request_from_dict(s: Any) -> SessionFSRenameRequest: - return SessionFSRenameRequest.from_dict(s) - -def session_fs_rename_request_to_dict(x: SessionFSRenameRequest) -> Any: - return to_class(SessionFSRenameRequest, x) +@dataclass +class RPC: + account_get_quota_result: AccountGetQuotaResult + account_quota_snapshot: AccountQuotaSnapshot + agent_get_current_result: AgentGetCurrentResult + agent_info: AgentInfo + agent_list: AgentList + agent_reload_result: AgentReloadResult + agent_select_request: AgentSelectRequest + agent_select_result: AgentSelectResult + commands_handle_pending_command_request: CommandsHandlePendingCommandRequest + commands_handle_pending_command_result: CommandsHandlePendingCommandResult + current_model: CurrentModel + discovered_mcp_server: DiscoveredMCPServer + discovered_mcp_server_source: MCPServerSource + discovered_mcp_server_type: DiscoveredMCPServerType + extension: Extension + extension_list: ExtensionList + extensions_disable_request: ExtensionsDisableRequest + extensions_enable_request: ExtensionsEnableRequest + extension_source: ExtensionSource + extension_status: ExtensionStatus + filter_mapping: dict[str, FilterMappingString] | FilterMappingString + filter_mapping_string: FilterMappingString + filter_mapping_value: FilterMappingString + fleet_start_request: FleetStartRequest + fleet_start_result: FleetStartResult + handle_tool_call_result: HandleToolCallResult + history_compact_context_window: HistoryCompactContextWindow + history_compact_result: HistoryCompactResult + history_truncate_request: HistoryTruncateRequest + history_truncate_result: HistoryTruncateResult + instructions_get_sources_result: InstructionsGetSourcesResult + instructions_sources: InstructionsSources + instructions_sources_location: InstructionsSourcesLocation + instructions_sources_type: InstructionsSourcesType + log_request: LogRequest + log_result: LogResult + mcp_config_add_request: MCPConfigAddRequest + mcp_config_list: MCPConfigList + mcp_config_remove_request: MCPConfigRemoveRequest + mcp_config_update_request: MCPConfigUpdateRequest + mcp_disable_request: MCPDisableRequest + mcp_discover_request: MCPDiscoverRequest + mcp_discover_result: MCPDiscoverResult + mcp_enable_request: MCPEnableRequest + mcp_server: MCPServer + mcp_server_config: MCPServerConfig + mcp_server_config_http: MCPServerConfigHTTP + mcp_server_config_http_type: MCPServerConfigHTTPType + mcp_server_config_local: MCPServerConfigLocal + mcp_server_config_local_type: MCPServerConfigLocalType + mcp_server_list: MCPServerList + mcp_server_source: MCPServerSource + mcp_server_status: MCPServerStatus + model: Model + model_billing: ModelBilling + model_capabilities: ModelCapabilities + model_capabilities_limits: ModelCapabilitiesLimits + model_capabilities_limits_vision: ModelCapabilitiesLimitsVision + model_capabilities_override: ModelCapabilitiesOverride + model_capabilities_override_limits: ModelCapabilitiesOverrideLimits + model_capabilities_override_limits_vision: ModelCapabilitiesOverrideLimitsVision + model_capabilities_override_supports: ModelCapabilitiesOverrideSupports + model_capabilities_supports: ModelCapabilitiesSupports + model_list: ModelList + model_policy: ModelPolicy + model_switch_to_request: ModelSwitchToRequest + model_switch_to_result: ModelSwitchToResult + mode_set_request: ModeSetRequest + name_get_result: NameGetResult + name_set_request: NameSetRequest + permission_decision: PermissionDecision + permission_decision_approved: PermissionDecisionApproved + permission_decision_denied_by_content_exclusion_policy: PermissionDecisionDeniedByContentExclusionPolicy + permission_decision_denied_by_permission_request_hook: PermissionDecisionDeniedByPermissionRequestHook + permission_decision_denied_by_rules: PermissionDecisionDeniedByRules + permission_decision_denied_interactively_by_user: PermissionDecisionDeniedInteractivelyByUser + permission_decision_denied_no_approval_rule_and_could_not_request_from_user: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + permission_decision_request: PermissionDecisionRequest + permission_request_result: PermissionRequestResult + ping_request: PingRequest + ping_result: PingResult + plan_read_result: PlanReadResult + plan_update_request: PlanUpdateRequest + plugin: Plugin + plugin_list: PluginList + server_skill: ServerSkill + server_skill_list: ServerSkillList + session_fs_append_file_request: SessionFSAppendFileRequest + session_fs_error: SessionFSError + session_fs_error_code: SessionFSErrorCode + session_fs_exists_request: SessionFSExistsRequest + session_fs_exists_result: SessionFSExistsResult + session_fs_mkdir_request: SessionFSMkdirRequest + session_fs_readdir_request: SessionFSReaddirRequest + session_fs_readdir_result: SessionFSReaddirResult + session_fs_readdir_with_types_entry: SessionFSReaddirWithTypesEntry + session_fs_readdir_with_types_entry_type: SessionFSReaddirWithTypesEntryType + session_fs_readdir_with_types_request: SessionFSReaddirWithTypesRequest + session_fs_readdir_with_types_result: SessionFSReaddirWithTypesResult + session_fs_read_file_request: SessionFSReadFileRequest + session_fs_read_file_result: SessionFSReadFileResult + session_fs_rename_request: SessionFSRenameRequest + session_fs_rm_request: SessionFSRmRequest + session_fs_set_provider_conventions: SessionFSSetProviderConventions + session_fs_set_provider_request: SessionFSSetProviderRequest + session_fs_set_provider_result: SessionFSSetProviderResult + session_fs_stat_request: SessionFSStatRequest + session_fs_stat_result: SessionFSStatResult + session_fs_write_file_request: SessionFSWriteFileRequest + session_log_level: SessionLogLevel + session_mode: SessionMode + sessions_fork_request: SessionsForkRequest + sessions_fork_result: SessionsForkResult + shell_exec_request: ShellExecRequest + shell_exec_result: ShellExecResult + shell_kill_request: ShellKillRequest + shell_kill_result: ShellKillResult + shell_kill_signal: ShellKillSignal + skill: Skill + skill_list: SkillList + skills_config_set_disabled_skills_request: SkillsConfigSetDisabledSkillsRequest + skills_disable_request: SkillsDisableRequest + skills_discover_request: SkillsDiscoverRequest + skills_enable_request: SkillsEnableRequest + tool: Tool + tool_call_result: ToolCallResult + tool_list: ToolList + tools_handle_pending_tool_call: ToolCallResult | str + tools_handle_pending_tool_call_request: ToolsHandlePendingToolCallRequest + tools_list_request: ToolsListRequest + ui_elicitation_array_any_of_field: UIElicitationArrayAnyOfField + ui_elicitation_array_any_of_field_items: UIElicitationArrayAnyOfFieldItems + ui_elicitation_array_any_of_field_items_any_of: UIElicitationArrayAnyOfFieldItemsAnyOf + ui_elicitation_array_enum_field: UIElicitationArrayEnumField + ui_elicitation_array_enum_field_items: UIElicitationArrayEnumFieldItems + ui_elicitation_field_value: float | bool | list[str] | str + ui_elicitation_request: UIElicitationRequest + ui_elicitation_response: UIElicitationResponse + ui_elicitation_response_action: UIElicitationResponseAction + ui_elicitation_response_content: dict[str, float | bool | list[str] | str] + ui_elicitation_result: UIElicitationResult + ui_elicitation_schema: UIElicitationSchema + ui_elicitation_schema_property: UIElicitationSchemaProperty + ui_elicitation_schema_property_boolean: UIElicitationSchemaPropertyBoolean + ui_elicitation_schema_property_number: UIElicitationSchemaPropertyNumber + ui_elicitation_schema_property_number_type: UIElicitationSchemaPropertyNumberType + ui_elicitation_schema_property_string: UIElicitationSchemaPropertyString + ui_elicitation_schema_property_string_format: UIElicitationSchemaPropertyStringFormat + ui_elicitation_string_enum_field: UIElicitationStringEnumField + ui_elicitation_string_one_of_field: UIElicitationStringOneOfField + ui_elicitation_string_one_of_field_one_of: UIElicitationStringOneOfFieldOneOf + ui_handle_pending_elicitation_request: UIHandlePendingElicitationRequest + usage_get_metrics_result: UsageGetMetricsResult + usage_metrics_code_changes: UsageMetricsCodeChanges + usage_metrics_model_metric: UsageMetricsModelMetric + usage_metrics_model_metric_requests: UsageMetricsModelMetricRequests + usage_metrics_model_metric_usage: UsageMetricsModelMetricUsage + workspaces_create_file_request: WorkspacesCreateFileRequest + workspaces_get_workspace_result: WorkspacesGetWorkspaceResult + workspaces_list_files_result: WorkspacesListFilesResult + workspaces_read_file_request: WorkspacesReadFileRequest + workspaces_read_file_result: WorkspacesReadFileResult + + @staticmethod + def from_dict(obj: Any) -> 'RPC': + assert isinstance(obj, dict) + account_get_quota_result = AccountGetQuotaResult.from_dict(obj.get("AccountGetQuotaResult")) + account_quota_snapshot = AccountQuotaSnapshot.from_dict(obj.get("AccountQuotaSnapshot")) + agent_get_current_result = AgentGetCurrentResult.from_dict(obj.get("AgentGetCurrentResult")) + agent_info = AgentInfo.from_dict(obj.get("AgentInfo")) + agent_list = AgentList.from_dict(obj.get("AgentList")) + agent_reload_result = AgentReloadResult.from_dict(obj.get("AgentReloadResult")) + agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest")) + agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult")) + commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest")) + commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult")) + current_model = CurrentModel.from_dict(obj.get("CurrentModel")) + discovered_mcp_server = DiscoveredMCPServer.from_dict(obj.get("DiscoveredMcpServer")) + discovered_mcp_server_source = MCPServerSource(obj.get("DiscoveredMcpServerSource")) + discovered_mcp_server_type = DiscoveredMCPServerType(obj.get("DiscoveredMcpServerType")) + extension = Extension.from_dict(obj.get("Extension")) + extension_list = ExtensionList.from_dict(obj.get("ExtensionList")) + extensions_disable_request = ExtensionsDisableRequest.from_dict(obj.get("ExtensionsDisableRequest")) + extensions_enable_request = ExtensionsEnableRequest.from_dict(obj.get("ExtensionsEnableRequest")) + extension_source = ExtensionSource(obj.get("ExtensionSource")) + extension_status = ExtensionStatus(obj.get("ExtensionStatus")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], obj.get("FilterMapping")) + filter_mapping_string = FilterMappingString(obj.get("FilterMappingString")) + filter_mapping_value = FilterMappingString(obj.get("FilterMappingValue")) + fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) + fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) + handle_tool_call_result = HandleToolCallResult.from_dict(obj.get("HandleToolCallResult")) + history_compact_context_window = HistoryCompactContextWindow.from_dict(obj.get("HistoryCompactContextWindow")) + history_compact_result = HistoryCompactResult.from_dict(obj.get("HistoryCompactResult")) + history_truncate_request = HistoryTruncateRequest.from_dict(obj.get("HistoryTruncateRequest")) + history_truncate_result = HistoryTruncateResult.from_dict(obj.get("HistoryTruncateResult")) + instructions_get_sources_result = InstructionsGetSourcesResult.from_dict(obj.get("InstructionsGetSourcesResult")) + instructions_sources = InstructionsSources.from_dict(obj.get("InstructionsSources")) + instructions_sources_location = InstructionsSourcesLocation(obj.get("InstructionsSourcesLocation")) + instructions_sources_type = InstructionsSourcesType(obj.get("InstructionsSourcesType")) + log_request = LogRequest.from_dict(obj.get("LogRequest")) + log_result = LogResult.from_dict(obj.get("LogResult")) + mcp_config_add_request = MCPConfigAddRequest.from_dict(obj.get("McpConfigAddRequest")) + mcp_config_list = MCPConfigList.from_dict(obj.get("McpConfigList")) + mcp_config_remove_request = MCPConfigRemoveRequest.from_dict(obj.get("McpConfigRemoveRequest")) + mcp_config_update_request = MCPConfigUpdateRequest.from_dict(obj.get("McpConfigUpdateRequest")) + mcp_disable_request = MCPDisableRequest.from_dict(obj.get("McpDisableRequest")) + mcp_discover_request = MCPDiscoverRequest.from_dict(obj.get("McpDiscoverRequest")) + mcp_discover_result = MCPDiscoverResult.from_dict(obj.get("McpDiscoverResult")) + mcp_enable_request = MCPEnableRequest.from_dict(obj.get("McpEnableRequest")) + mcp_server = MCPServer.from_dict(obj.get("McpServer")) + mcp_server_config = MCPServerConfig.from_dict(obj.get("McpServerConfig")) + mcp_server_config_http = MCPServerConfigHTTP.from_dict(obj.get("McpServerConfigHttp")) + mcp_server_config_http_type = MCPServerConfigHTTPType(obj.get("McpServerConfigHttpType")) + mcp_server_config_local = MCPServerConfigLocal.from_dict(obj.get("McpServerConfigLocal")) + mcp_server_config_local_type = MCPServerConfigLocalType(obj.get("McpServerConfigLocalType")) + mcp_server_list = MCPServerList.from_dict(obj.get("McpServerList")) + mcp_server_source = MCPServerSource(obj.get("McpServerSource")) + mcp_server_status = MCPServerStatus(obj.get("McpServerStatus")) + model = Model.from_dict(obj.get("Model")) + model_billing = ModelBilling.from_dict(obj.get("ModelBilling")) + model_capabilities = ModelCapabilities.from_dict(obj.get("ModelCapabilities")) + model_capabilities_limits = ModelCapabilitiesLimits.from_dict(obj.get("ModelCapabilitiesLimits")) + model_capabilities_limits_vision = ModelCapabilitiesLimitsVision.from_dict(obj.get("ModelCapabilitiesLimitsVision")) + model_capabilities_override = ModelCapabilitiesOverride.from_dict(obj.get("ModelCapabilitiesOverride")) + model_capabilities_override_limits = ModelCapabilitiesOverrideLimits.from_dict(obj.get("ModelCapabilitiesOverrideLimits")) + model_capabilities_override_limits_vision = ModelCapabilitiesOverrideLimitsVision.from_dict(obj.get("ModelCapabilitiesOverrideLimitsVision")) + model_capabilities_override_supports = ModelCapabilitiesOverrideSupports.from_dict(obj.get("ModelCapabilitiesOverrideSupports")) + model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) + model_list = ModelList.from_dict(obj.get("ModelList")) + model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) + model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) + model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) + mode_set_request = ModeSetRequest.from_dict(obj.get("ModeSetRequest")) + name_get_result = NameGetResult.from_dict(obj.get("NameGetResult")) + name_set_request = NameSetRequest.from_dict(obj.get("NameSetRequest")) + permission_decision = PermissionDecision.from_dict(obj.get("PermissionDecision")) + permission_decision_approved = PermissionDecisionApproved.from_dict(obj.get("PermissionDecisionApproved")) + permission_decision_denied_by_content_exclusion_policy = PermissionDecisionDeniedByContentExclusionPolicy.from_dict(obj.get("PermissionDecisionDeniedByContentExclusionPolicy")) + permission_decision_denied_by_permission_request_hook = PermissionDecisionDeniedByPermissionRequestHook.from_dict(obj.get("PermissionDecisionDeniedByPermissionRequestHook")) + permission_decision_denied_by_rules = PermissionDecisionDeniedByRules.from_dict(obj.get("PermissionDecisionDeniedByRules")) + permission_decision_denied_interactively_by_user = PermissionDecisionDeniedInteractivelyByUser.from_dict(obj.get("PermissionDecisionDeniedInteractivelyByUser")) + permission_decision_denied_no_approval_rule_and_could_not_request_from_user = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj.get("PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser")) + permission_decision_request = PermissionDecisionRequest.from_dict(obj.get("PermissionDecisionRequest")) + permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) + ping_request = PingRequest.from_dict(obj.get("PingRequest")) + ping_result = PingResult.from_dict(obj.get("PingResult")) + plan_read_result = PlanReadResult.from_dict(obj.get("PlanReadResult")) + plan_update_request = PlanUpdateRequest.from_dict(obj.get("PlanUpdateRequest")) + plugin = Plugin.from_dict(obj.get("Plugin")) + plugin_list = PluginList.from_dict(obj.get("PluginList")) + server_skill = ServerSkill.from_dict(obj.get("ServerSkill")) + server_skill_list = ServerSkillList.from_dict(obj.get("ServerSkillList")) + session_fs_append_file_request = SessionFSAppendFileRequest.from_dict(obj.get("SessionFsAppendFileRequest")) + session_fs_error = SessionFSError.from_dict(obj.get("SessionFsError")) + session_fs_error_code = SessionFSErrorCode(obj.get("SessionFsErrorCode")) + session_fs_exists_request = SessionFSExistsRequest.from_dict(obj.get("SessionFsExistsRequest")) + session_fs_exists_result = SessionFSExistsResult.from_dict(obj.get("SessionFsExistsResult")) + session_fs_mkdir_request = SessionFSMkdirRequest.from_dict(obj.get("SessionFsMkdirRequest")) + session_fs_readdir_request = SessionFSReaddirRequest.from_dict(obj.get("SessionFsReaddirRequest")) + session_fs_readdir_result = SessionFSReaddirResult.from_dict(obj.get("SessionFsReaddirResult")) + session_fs_readdir_with_types_entry = SessionFSReaddirWithTypesEntry.from_dict(obj.get("SessionFsReaddirWithTypesEntry")) + session_fs_readdir_with_types_entry_type = SessionFSReaddirWithTypesEntryType(obj.get("SessionFsReaddirWithTypesEntryType")) + session_fs_readdir_with_types_request = SessionFSReaddirWithTypesRequest.from_dict(obj.get("SessionFsReaddirWithTypesRequest")) + session_fs_readdir_with_types_result = SessionFSReaddirWithTypesResult.from_dict(obj.get("SessionFsReaddirWithTypesResult")) + session_fs_read_file_request = SessionFSReadFileRequest.from_dict(obj.get("SessionFsReadFileRequest")) + session_fs_read_file_result = SessionFSReadFileResult.from_dict(obj.get("SessionFsReadFileResult")) + session_fs_rename_request = SessionFSRenameRequest.from_dict(obj.get("SessionFsRenameRequest")) + session_fs_rm_request = SessionFSRmRequest.from_dict(obj.get("SessionFsRmRequest")) + session_fs_set_provider_conventions = SessionFSSetProviderConventions(obj.get("SessionFsSetProviderConventions")) + session_fs_set_provider_request = SessionFSSetProviderRequest.from_dict(obj.get("SessionFsSetProviderRequest")) + session_fs_set_provider_result = SessionFSSetProviderResult.from_dict(obj.get("SessionFsSetProviderResult")) + session_fs_stat_request = SessionFSStatRequest.from_dict(obj.get("SessionFsStatRequest")) + session_fs_stat_result = SessionFSStatResult.from_dict(obj.get("SessionFsStatResult")) + session_fs_write_file_request = SessionFSWriteFileRequest.from_dict(obj.get("SessionFsWriteFileRequest")) + session_log_level = SessionLogLevel(obj.get("SessionLogLevel")) + session_mode = SessionMode(obj.get("SessionMode")) + sessions_fork_request = SessionsForkRequest.from_dict(obj.get("SessionsForkRequest")) + sessions_fork_result = SessionsForkResult.from_dict(obj.get("SessionsForkResult")) + shell_exec_request = ShellExecRequest.from_dict(obj.get("ShellExecRequest")) + shell_exec_result = ShellExecResult.from_dict(obj.get("ShellExecResult")) + shell_kill_request = ShellKillRequest.from_dict(obj.get("ShellKillRequest")) + shell_kill_result = ShellKillResult.from_dict(obj.get("ShellKillResult")) + shell_kill_signal = ShellKillSignal(obj.get("ShellKillSignal")) + skill = Skill.from_dict(obj.get("Skill")) + skill_list = SkillList.from_dict(obj.get("SkillList")) + skills_config_set_disabled_skills_request = SkillsConfigSetDisabledSkillsRequest.from_dict(obj.get("SkillsConfigSetDisabledSkillsRequest")) + skills_disable_request = SkillsDisableRequest.from_dict(obj.get("SkillsDisableRequest")) + skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest")) + skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest")) + tool = Tool.from_dict(obj.get("Tool")) + tool_call_result = ToolCallResult.from_dict(obj.get("ToolCallResult")) + tool_list = ToolList.from_dict(obj.get("ToolList")) + tools_handle_pending_tool_call = from_union([ToolCallResult.from_dict, from_str], obj.get("ToolsHandlePendingToolCall")) + tools_handle_pending_tool_call_request = ToolsHandlePendingToolCallRequest.from_dict(obj.get("ToolsHandlePendingToolCallRequest")) + tools_list_request = ToolsListRequest.from_dict(obj.get("ToolsListRequest")) + ui_elicitation_array_any_of_field = UIElicitationArrayAnyOfField.from_dict(obj.get("UIElicitationArrayAnyOfField")) + ui_elicitation_array_any_of_field_items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("UIElicitationArrayAnyOfFieldItems")) + ui_elicitation_array_any_of_field_items_any_of = UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict(obj.get("UIElicitationArrayAnyOfFieldItemsAnyOf")) + ui_elicitation_array_enum_field = UIElicitationArrayEnumField.from_dict(obj.get("UIElicitationArrayEnumField")) + ui_elicitation_array_enum_field_items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("UIElicitationArrayEnumFieldItems")) + ui_elicitation_field_value = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], obj.get("UIElicitationFieldValue")) + ui_elicitation_request = UIElicitationRequest.from_dict(obj.get("UIElicitationRequest")) + ui_elicitation_response = UIElicitationResponse.from_dict(obj.get("UIElicitationResponse")) + ui_elicitation_response_action = UIElicitationResponseAction(obj.get("UIElicitationResponseAction")) + ui_elicitation_response_content = from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), obj.get("UIElicitationResponseContent")) + ui_elicitation_result = UIElicitationResult.from_dict(obj.get("UIElicitationResult")) + ui_elicitation_schema = UIElicitationSchema.from_dict(obj.get("UIElicitationSchema")) + ui_elicitation_schema_property = UIElicitationSchemaProperty.from_dict(obj.get("UIElicitationSchemaProperty")) + ui_elicitation_schema_property_boolean = UIElicitationSchemaPropertyBoolean.from_dict(obj.get("UIElicitationSchemaPropertyBoolean")) + ui_elicitation_schema_property_number = UIElicitationSchemaPropertyNumber.from_dict(obj.get("UIElicitationSchemaPropertyNumber")) + ui_elicitation_schema_property_number_type = UIElicitationSchemaPropertyNumberType(obj.get("UIElicitationSchemaPropertyNumberType")) + ui_elicitation_schema_property_string = UIElicitationSchemaPropertyString.from_dict(obj.get("UIElicitationSchemaPropertyString")) + ui_elicitation_schema_property_string_format = UIElicitationSchemaPropertyStringFormat(obj.get("UIElicitationSchemaPropertyStringFormat")) + ui_elicitation_string_enum_field = UIElicitationStringEnumField.from_dict(obj.get("UIElicitationStringEnumField")) + ui_elicitation_string_one_of_field = UIElicitationStringOneOfField.from_dict(obj.get("UIElicitationStringOneOfField")) + ui_elicitation_string_one_of_field_one_of = UIElicitationStringOneOfFieldOneOf.from_dict(obj.get("UIElicitationStringOneOfFieldOneOf")) + ui_handle_pending_elicitation_request = UIHandlePendingElicitationRequest.from_dict(obj.get("UIHandlePendingElicitationRequest")) + usage_get_metrics_result = UsageGetMetricsResult.from_dict(obj.get("UsageGetMetricsResult")) + usage_metrics_code_changes = UsageMetricsCodeChanges.from_dict(obj.get("UsageMetricsCodeChanges")) + usage_metrics_model_metric = UsageMetricsModelMetric.from_dict(obj.get("UsageMetricsModelMetric")) + usage_metrics_model_metric_requests = UsageMetricsModelMetricRequests.from_dict(obj.get("UsageMetricsModelMetricRequests")) + usage_metrics_model_metric_usage = UsageMetricsModelMetricUsage.from_dict(obj.get("UsageMetricsModelMetricUsage")) + workspaces_create_file_request = WorkspacesCreateFileRequest.from_dict(obj.get("WorkspacesCreateFileRequest")) + workspaces_get_workspace_result = WorkspacesGetWorkspaceResult.from_dict(obj.get("WorkspacesGetWorkspaceResult")) + workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult")) + workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest")) + workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult")) + return RPC(account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approved, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_request, permission_request_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_fs_append_file_request, session_fs_error, session_fs_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, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) + + def to_dict(self) -> dict: + result: dict = {} + result["AccountGetQuotaResult"] = to_class(AccountGetQuotaResult, self.account_get_quota_result) + result["AccountQuotaSnapshot"] = to_class(AccountQuotaSnapshot, self.account_quota_snapshot) + result["AgentGetCurrentResult"] = to_class(AgentGetCurrentResult, self.agent_get_current_result) + result["AgentInfo"] = to_class(AgentInfo, self.agent_info) + result["AgentList"] = to_class(AgentList, self.agent_list) + result["AgentReloadResult"] = to_class(AgentReloadResult, self.agent_reload_result) + result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request) + result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result) + result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request) + result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result) + result["CurrentModel"] = to_class(CurrentModel, self.current_model) + result["DiscoveredMcpServer"] = to_class(DiscoveredMCPServer, self.discovered_mcp_server) + result["DiscoveredMcpServerSource"] = to_enum(MCPServerSource, self.discovered_mcp_server_source) + result["DiscoveredMcpServerType"] = to_enum(DiscoveredMCPServerType, self.discovered_mcp_server_type) + result["Extension"] = to_class(Extension, self.extension) + result["ExtensionList"] = to_class(ExtensionList, self.extension_list) + result["ExtensionsDisableRequest"] = to_class(ExtensionsDisableRequest, self.extensions_disable_request) + result["ExtensionsEnableRequest"] = to_class(ExtensionsEnableRequest, self.extensions_enable_request) + result["ExtensionSource"] = to_enum(ExtensionSource, self.extension_source) + result["ExtensionStatus"] = to_enum(ExtensionStatus, self.extension_status) + result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x)], self.filter_mapping) + result["FilterMappingString"] = to_enum(FilterMappingString, self.filter_mapping_string) + result["FilterMappingValue"] = to_enum(FilterMappingString, self.filter_mapping_value) + result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) + result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) + result["HandleToolCallResult"] = to_class(HandleToolCallResult, self.handle_tool_call_result) + result["HistoryCompactContextWindow"] = to_class(HistoryCompactContextWindow, self.history_compact_context_window) + result["HistoryCompactResult"] = to_class(HistoryCompactResult, self.history_compact_result) + result["HistoryTruncateRequest"] = to_class(HistoryTruncateRequest, self.history_truncate_request) + result["HistoryTruncateResult"] = to_class(HistoryTruncateResult, self.history_truncate_result) + result["InstructionsGetSourcesResult"] = to_class(InstructionsGetSourcesResult, self.instructions_get_sources_result) + result["InstructionsSources"] = to_class(InstructionsSources, self.instructions_sources) + result["InstructionsSourcesLocation"] = to_enum(InstructionsSourcesLocation, self.instructions_sources_location) + result["InstructionsSourcesType"] = to_enum(InstructionsSourcesType, self.instructions_sources_type) + result["LogRequest"] = to_class(LogRequest, self.log_request) + result["LogResult"] = to_class(LogResult, self.log_result) + result["McpConfigAddRequest"] = to_class(MCPConfigAddRequest, self.mcp_config_add_request) + result["McpConfigList"] = to_class(MCPConfigList, self.mcp_config_list) + result["McpConfigRemoveRequest"] = to_class(MCPConfigRemoveRequest, self.mcp_config_remove_request) + result["McpConfigUpdateRequest"] = to_class(MCPConfigUpdateRequest, self.mcp_config_update_request) + result["McpDisableRequest"] = to_class(MCPDisableRequest, self.mcp_disable_request) + result["McpDiscoverRequest"] = to_class(MCPDiscoverRequest, self.mcp_discover_request) + result["McpDiscoverResult"] = to_class(MCPDiscoverResult, self.mcp_discover_result) + result["McpEnableRequest"] = to_class(MCPEnableRequest, self.mcp_enable_request) + result["McpServer"] = to_class(MCPServer, self.mcp_server) + result["McpServerConfig"] = to_class(MCPServerConfig, self.mcp_server_config) + result["McpServerConfigHttp"] = to_class(MCPServerConfigHTTP, self.mcp_server_config_http) + result["McpServerConfigHttpType"] = to_enum(MCPServerConfigHTTPType, self.mcp_server_config_http_type) + result["McpServerConfigLocal"] = to_class(MCPServerConfigLocal, self.mcp_server_config_local) + result["McpServerConfigLocalType"] = to_enum(MCPServerConfigLocalType, self.mcp_server_config_local_type) + result["McpServerList"] = to_class(MCPServerList, self.mcp_server_list) + result["McpServerSource"] = to_enum(MCPServerSource, self.mcp_server_source) + result["McpServerStatus"] = to_enum(MCPServerStatus, self.mcp_server_status) + result["Model"] = to_class(Model, self.model) + result["ModelBilling"] = to_class(ModelBilling, self.model_billing) + result["ModelCapabilities"] = to_class(ModelCapabilities, self.model_capabilities) + result["ModelCapabilitiesLimits"] = to_class(ModelCapabilitiesLimits, self.model_capabilities_limits) + result["ModelCapabilitiesLimitsVision"] = to_class(ModelCapabilitiesLimitsVision, self.model_capabilities_limits_vision) + result["ModelCapabilitiesOverride"] = to_class(ModelCapabilitiesOverride, self.model_capabilities_override) + result["ModelCapabilitiesOverrideLimits"] = to_class(ModelCapabilitiesOverrideLimits, self.model_capabilities_override_limits) + result["ModelCapabilitiesOverrideLimitsVision"] = to_class(ModelCapabilitiesOverrideLimitsVision, self.model_capabilities_override_limits_vision) + result["ModelCapabilitiesOverrideSupports"] = to_class(ModelCapabilitiesOverrideSupports, self.model_capabilities_override_supports) + result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) + result["ModelList"] = to_class(ModelList, self.model_list) + result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) + result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) + result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) + result["ModeSetRequest"] = to_class(ModeSetRequest, self.mode_set_request) + result["NameGetResult"] = to_class(NameGetResult, self.name_get_result) + result["NameSetRequest"] = to_class(NameSetRequest, self.name_set_request) + result["PermissionDecision"] = to_class(PermissionDecision, self.permission_decision) + result["PermissionDecisionApproved"] = to_class(PermissionDecisionApproved, self.permission_decision_approved) + result["PermissionDecisionDeniedByContentExclusionPolicy"] = to_class(PermissionDecisionDeniedByContentExclusionPolicy, self.permission_decision_denied_by_content_exclusion_policy) + result["PermissionDecisionDeniedByPermissionRequestHook"] = to_class(PermissionDecisionDeniedByPermissionRequestHook, self.permission_decision_denied_by_permission_request_hook) + result["PermissionDecisionDeniedByRules"] = to_class(PermissionDecisionDeniedByRules, self.permission_decision_denied_by_rules) + result["PermissionDecisionDeniedInteractivelyByUser"] = to_class(PermissionDecisionDeniedInteractivelyByUser, self.permission_decision_denied_interactively_by_user) + result["PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"] = to_class(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, self.permission_decision_denied_no_approval_rule_and_could_not_request_from_user) + result["PermissionDecisionRequest"] = to_class(PermissionDecisionRequest, self.permission_decision_request) + result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) + result["PingRequest"] = to_class(PingRequest, self.ping_request) + result["PingResult"] = to_class(PingResult, self.ping_result) + result["PlanReadResult"] = to_class(PlanReadResult, self.plan_read_result) + result["PlanUpdateRequest"] = to_class(PlanUpdateRequest, self.plan_update_request) + result["Plugin"] = to_class(Plugin, self.plugin) + result["PluginList"] = to_class(PluginList, self.plugin_list) + result["ServerSkill"] = to_class(ServerSkill, self.server_skill) + result["ServerSkillList"] = to_class(ServerSkillList, self.server_skill_list) + result["SessionFsAppendFileRequest"] = to_class(SessionFSAppendFileRequest, self.session_fs_append_file_request) + result["SessionFsError"] = to_class(SessionFSError, self.session_fs_error) + result["SessionFsErrorCode"] = to_enum(SessionFSErrorCode, self.session_fs_error_code) + result["SessionFsExistsRequest"] = to_class(SessionFSExistsRequest, self.session_fs_exists_request) + result["SessionFsExistsResult"] = to_class(SessionFSExistsResult, self.session_fs_exists_result) + result["SessionFsMkdirRequest"] = to_class(SessionFSMkdirRequest, self.session_fs_mkdir_request) + result["SessionFsReaddirRequest"] = to_class(SessionFSReaddirRequest, self.session_fs_readdir_request) + result["SessionFsReaddirResult"] = to_class(SessionFSReaddirResult, self.session_fs_readdir_result) + result["SessionFsReaddirWithTypesEntry"] = to_class(SessionFSReaddirWithTypesEntry, self.session_fs_readdir_with_types_entry) + result["SessionFsReaddirWithTypesEntryType"] = to_enum(SessionFSReaddirWithTypesEntryType, self.session_fs_readdir_with_types_entry_type) + result["SessionFsReaddirWithTypesRequest"] = to_class(SessionFSReaddirWithTypesRequest, self.session_fs_readdir_with_types_request) + result["SessionFsReaddirWithTypesResult"] = to_class(SessionFSReaddirWithTypesResult, self.session_fs_readdir_with_types_result) + result["SessionFsReadFileRequest"] = to_class(SessionFSReadFileRequest, self.session_fs_read_file_request) + result["SessionFsReadFileResult"] = to_class(SessionFSReadFileResult, self.session_fs_read_file_result) + result["SessionFsRenameRequest"] = to_class(SessionFSRenameRequest, self.session_fs_rename_request) + result["SessionFsRmRequest"] = to_class(SessionFSRmRequest, self.session_fs_rm_request) + result["SessionFsSetProviderConventions"] = to_enum(SessionFSSetProviderConventions, self.session_fs_set_provider_conventions) + result["SessionFsSetProviderRequest"] = to_class(SessionFSSetProviderRequest, self.session_fs_set_provider_request) + result["SessionFsSetProviderResult"] = to_class(SessionFSSetProviderResult, self.session_fs_set_provider_result) + result["SessionFsStatRequest"] = to_class(SessionFSStatRequest, self.session_fs_stat_request) + result["SessionFsStatResult"] = to_class(SessionFSStatResult, self.session_fs_stat_result) + result["SessionFsWriteFileRequest"] = to_class(SessionFSWriteFileRequest, self.session_fs_write_file_request) + result["SessionLogLevel"] = to_enum(SessionLogLevel, self.session_log_level) + result["SessionMode"] = to_enum(SessionMode, self.session_mode) + result["SessionsForkRequest"] = to_class(SessionsForkRequest, self.sessions_fork_request) + result["SessionsForkResult"] = to_class(SessionsForkResult, self.sessions_fork_result) + result["ShellExecRequest"] = to_class(ShellExecRequest, self.shell_exec_request) + result["ShellExecResult"] = to_class(ShellExecResult, self.shell_exec_result) + result["ShellKillRequest"] = to_class(ShellKillRequest, self.shell_kill_request) + result["ShellKillResult"] = to_class(ShellKillResult, self.shell_kill_result) + result["ShellKillSignal"] = to_enum(ShellKillSignal, self.shell_kill_signal) + result["Skill"] = to_class(Skill, self.skill) + result["SkillList"] = to_class(SkillList, self.skill_list) + result["SkillsConfigSetDisabledSkillsRequest"] = to_class(SkillsConfigSetDisabledSkillsRequest, self.skills_config_set_disabled_skills_request) + result["SkillsDisableRequest"] = to_class(SkillsDisableRequest, self.skills_disable_request) + result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request) + result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request) + result["Tool"] = to_class(Tool, self.tool) + result["ToolCallResult"] = to_class(ToolCallResult, self.tool_call_result) + result["ToolList"] = to_class(ToolList, self.tool_list) + result["ToolsHandlePendingToolCall"] = from_union([lambda x: to_class(ToolCallResult, x), from_str], self.tools_handle_pending_tool_call) + result["ToolsHandlePendingToolCallRequest"] = to_class(ToolsHandlePendingToolCallRequest, self.tools_handle_pending_tool_call_request) + result["ToolsListRequest"] = to_class(ToolsListRequest, self.tools_list_request) + result["UIElicitationArrayAnyOfField"] = to_class(UIElicitationArrayAnyOfField, self.ui_elicitation_array_any_of_field) + result["UIElicitationArrayAnyOfFieldItems"] = to_class(UIElicitationArrayAnyOfFieldItems, self.ui_elicitation_array_any_of_field_items) + result["UIElicitationArrayAnyOfFieldItemsAnyOf"] = to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, self.ui_elicitation_array_any_of_field_items_any_of) + result["UIElicitationArrayEnumField"] = to_class(UIElicitationArrayEnumField, self.ui_elicitation_array_enum_field) + result["UIElicitationArrayEnumFieldItems"] = to_class(UIElicitationArrayEnumFieldItems, self.ui_elicitation_array_enum_field_items) + result["UIElicitationFieldValue"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], self.ui_elicitation_field_value) + result["UIElicitationRequest"] = to_class(UIElicitationRequest, self.ui_elicitation_request) + result["UIElicitationResponse"] = to_class(UIElicitationResponse, self.ui_elicitation_response) + result["UIElicitationResponseAction"] = to_enum(UIElicitationResponseAction, self.ui_elicitation_response_action) + result["UIElicitationResponseContent"] = from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), self.ui_elicitation_response_content) + result["UIElicitationResult"] = to_class(UIElicitationResult, self.ui_elicitation_result) + result["UIElicitationSchema"] = to_class(UIElicitationSchema, self.ui_elicitation_schema) + result["UIElicitationSchemaProperty"] = to_class(UIElicitationSchemaProperty, self.ui_elicitation_schema_property) + result["UIElicitationSchemaPropertyBoolean"] = to_class(UIElicitationSchemaPropertyBoolean, self.ui_elicitation_schema_property_boolean) + result["UIElicitationSchemaPropertyNumber"] = to_class(UIElicitationSchemaPropertyNumber, self.ui_elicitation_schema_property_number) + result["UIElicitationSchemaPropertyNumberType"] = to_enum(UIElicitationSchemaPropertyNumberType, self.ui_elicitation_schema_property_number_type) + result["UIElicitationSchemaPropertyString"] = to_class(UIElicitationSchemaPropertyString, self.ui_elicitation_schema_property_string) + result["UIElicitationSchemaPropertyStringFormat"] = to_enum(UIElicitationSchemaPropertyStringFormat, self.ui_elicitation_schema_property_string_format) + result["UIElicitationStringEnumField"] = to_class(UIElicitationStringEnumField, self.ui_elicitation_string_enum_field) + result["UIElicitationStringOneOfField"] = to_class(UIElicitationStringOneOfField, self.ui_elicitation_string_one_of_field) + result["UIElicitationStringOneOfFieldOneOf"] = to_class(UIElicitationStringOneOfFieldOneOf, self.ui_elicitation_string_one_of_field_one_of) + result["UIHandlePendingElicitationRequest"] = to_class(UIHandlePendingElicitationRequest, self.ui_handle_pending_elicitation_request) + result["UsageGetMetricsResult"] = to_class(UsageGetMetricsResult, self.usage_get_metrics_result) + result["UsageMetricsCodeChanges"] = to_class(UsageMetricsCodeChanges, self.usage_metrics_code_changes) + result["UsageMetricsModelMetric"] = to_class(UsageMetricsModelMetric, self.usage_metrics_model_metric) + result["UsageMetricsModelMetricRequests"] = to_class(UsageMetricsModelMetricRequests, self.usage_metrics_model_metric_requests) + result["UsageMetricsModelMetricUsage"] = to_class(UsageMetricsModelMetricUsage, self.usage_metrics_model_metric_usage) + result["WorkspacesCreateFileRequest"] = to_class(WorkspacesCreateFileRequest, self.workspaces_create_file_request) + result["WorkspacesGetWorkspaceResult"] = to_class(WorkspacesGetWorkspaceResult, self.workspaces_get_workspace_result) + result["WorkspacesListFilesResult"] = to_class(WorkspacesListFilesResult, self.workspaces_list_files_result) + result["WorkspacesReadFileRequest"] = to_class(WorkspacesReadFileRequest, self.workspaces_read_file_request) + result["WorkspacesReadFileResult"] = to_class(WorkspacesReadFileResult, self.workspaces_read_file_result) + return result + +def rpc_from_dict(s: Any) -> RPC: + return RPC.from_dict(s) + +def rpc_to_dict(x: RPC) -> Any: + return to_class(RPC, x) def _timeout_kwargs(timeout: float | None) -> dict: @@ -5129,23 +4801,23 @@ async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogR class SessionFsHandler(Protocol): async def read_file(self, params: SessionFSReadFileRequest) -> SessionFSReadFileResult: pass - async def write_file(self, params: SessionFSWriteFileRequest) -> None: + async def write_file(self, params: SessionFSWriteFileRequest) -> SessionFSError | None: pass - async def append_file(self, params: SessionFSAppendFileRequest) -> None: + async def append_file(self, params: SessionFSAppendFileRequest) -> SessionFSError | None: pass async def exists(self, params: SessionFSExistsRequest) -> SessionFSExistsResult: pass async def stat(self, params: SessionFSStatRequest) -> SessionFSStatResult: pass - async def mkdir(self, params: SessionFSMkdirRequest) -> None: + async def mkdir(self, params: SessionFSMkdirRequest) -> SessionFSError | None: pass async def readdir(self, params: SessionFSReaddirRequest) -> SessionFSReaddirResult: pass async def readdir_with_types(self, params: SessionFSReaddirWithTypesRequest) -> SessionFSReaddirWithTypesResult: pass - async def rm(self, params: SessionFSRmRequest) -> None: + async def rm(self, params: SessionFSRmRequest) -> SessionFSError | None: pass - async def rename(self, params: SessionFSRenameRequest) -> None: + async def rename(self, params: SessionFSRenameRequest) -> SessionFSError | None: pass @dataclass @@ -5168,15 +4840,15 @@ async def handle_session_fs_write_file(params: dict) -> dict | None: request = SessionFSWriteFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.write_file(request) - return None + result = await handler.write_file(request) + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.writeFile", handle_session_fs_write_file) async def handle_session_fs_append_file(params: dict) -> dict | None: request = SessionFSAppendFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.append_file(request) - return None + result = await handler.append_file(request) + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.appendFile", handle_session_fs_append_file) async def handle_session_fs_exists(params: dict) -> dict | None: request = SessionFSExistsRequest.from_dict(params) @@ -5196,8 +4868,8 @@ async def handle_session_fs_mkdir(params: dict) -> dict | None: request = SessionFSMkdirRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.mkdir(request) - return None + result = await handler.mkdir(request) + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.mkdir", handle_session_fs_mkdir) async def handle_session_fs_readdir(params: dict) -> dict | None: request = SessionFSReaddirRequest.from_dict(params) @@ -5217,13 +4889,13 @@ async def handle_session_fs_rm(params: dict) -> dict | None: request = SessionFSRmRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.rm(request) - return None + result = await handler.rm(request) + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.rm", handle_session_fs_rm) async def handle_session_fs_rename(params: dict) -> dict | None: request = SessionFSRenameRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.rename(request) - return None + result = await handler.rename(request) + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.rename", handle_session_fs_rename) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 7cbff3039..1b3452bd4 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -252,3676 +252,3720 @@ def to_dict(self) -> dict: @dataclass -class WorkingDirectoryContext: - "Working directory and git context at session start" - cwd: str - git_root: str | None = None - repository: str | None = None - host_type: WorkingDirectoryContextHostType | None = None - branch: str | None = None - head_commit: str | None = None - base_commit: str | None = None +class AbortData: + "Turn abort information including the reason for termination" + reason: str @staticmethod - def from_dict(obj: Any) -> "WorkingDirectoryContext": + def from_dict(obj: Any) -> "AbortData": assert isinstance(obj, dict) - cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, from_str], obj.get("gitRoot")) - repository = from_union([from_none, from_str], obj.get("repository")) - host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) - branch = from_union([from_none, from_str], obj.get("branch")) - head_commit = from_union([from_none, from_str], obj.get("headCommit")) - base_commit = from_union([from_none, from_str], obj.get("baseCommit")) - return WorkingDirectoryContext( - cwd=cwd, - git_root=git_root, - repository=repository, - host_type=host_type, - branch=branch, - head_commit=head_commit, - base_commit=base_commit, + reason = from_str(obj.get("reason")) + return AbortData( + reason=reason, ) def to_dict(self) -> dict: result: dict = {} - result["cwd"] = from_str(self.cwd) - if self.git_root is not None: - result["gitRoot"] = from_union([from_none, from_str], self.git_root) - if self.repository is not None: - result["repository"] = from_union([from_none, from_str], self.repository) - if self.host_type is not None: - result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) - if self.branch is not None: - result["branch"] = from_union([from_none, from_str], self.branch) - if self.head_commit is not None: - result["headCommit"] = from_union([from_none, from_str], self.head_commit) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, from_str], self.base_commit) + result["reason"] = from_str(self.reason) return result @dataclass -class SessionStartData: - "Session initialization metadata including context and configuration" - session_id: str - version: float - producer: str - copilot_version: str - start_time: datetime - selected_model: str | None = None - reasoning_effort: str | None = None - context: WorkingDirectoryContext | None = None - already_in_use: bool | None = None - remote_steerable: bool | None = None +class AssistantIntentData: + "Agent intent description for current activity or plan" + intent: str @staticmethod - def from_dict(obj: Any) -> "SessionStartData": + def from_dict(obj: Any) -> "AssistantIntentData": assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - version = from_float(obj.get("version")) - producer = from_str(obj.get("producer")) - copilot_version = from_str(obj.get("copilotVersion")) - start_time = from_datetime(obj.get("startTime")) - selected_model = from_union([from_none, from_str], obj.get("selectedModel")) - reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) - already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) - remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) - return SessionStartData( - session_id=session_id, - version=version, - producer=producer, - copilot_version=copilot_version, - start_time=start_time, - selected_model=selected_model, - reasoning_effort=reasoning_effort, - context=context, - already_in_use=already_in_use, - remote_steerable=remote_steerable, + intent = from_str(obj.get("intent")) + return AssistantIntentData( + intent=intent, ) def to_dict(self) -> dict: result: dict = {} - result["sessionId"] = from_str(self.session_id) - result["version"] = to_float(self.version) - result["producer"] = from_str(self.producer) - result["copilotVersion"] = from_str(self.copilot_version) - result["startTime"] = to_datetime(self.start_time) - if self.selected_model is not None: - result["selectedModel"] = from_union([from_none, from_str], self.selected_model) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) - if self.context is not None: - result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) - if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) - if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) + result["intent"] = from_str(self.intent) return result @dataclass -class SessionResumeData: - "Session resume metadata including current context and event count" - resume_time: datetime - event_count: float - selected_model: str | None = None - reasoning_effort: str | None = None - context: WorkingDirectoryContext | None = None - already_in_use: bool | None = None - remote_steerable: bool | None = None +class AssistantMessageData: + "Assistant response containing text content, optional tool requests, and interaction metadata" + content: str + message_id: str + encrypted_content: str | None = None + interaction_id: str | None = None + output_tokens: float | None = None + # Deprecated: this field is deprecated. + parent_tool_call_id: str | None = None + phase: str | None = None + reasoning_opaque: str | None = None + reasoning_text: str | None = None + request_id: str | None = None + tool_requests: list[AssistantMessageToolRequest] | None = None @staticmethod - def from_dict(obj: Any) -> "SessionResumeData": + def from_dict(obj: Any) -> "AssistantMessageData": assert isinstance(obj, dict) - resume_time = from_datetime(obj.get("resumeTime")) - event_count = from_float(obj.get("eventCount")) - selected_model = from_union([from_none, from_str], obj.get("selectedModel")) - reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) - already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) - remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) - return SessionResumeData( - resume_time=resume_time, - event_count=event_count, - selected_model=selected_model, - reasoning_effort=reasoning_effort, - context=context, - already_in_use=already_in_use, - remote_steerable=remote_steerable, + content = from_str(obj.get("content")) + message_id = from_str(obj.get("messageId")) + encrypted_content = from_union([from_none, from_str], obj.get("encryptedContent")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + phase = from_union([from_none, from_str], obj.get("phase")) + reasoning_opaque = from_union([from_none, from_str], obj.get("reasoningOpaque")) + reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) + request_id = from_union([from_none, from_str], obj.get("requestId")) + tool_requests = from_union([from_none, lambda x: from_list(AssistantMessageToolRequest.from_dict, x)], obj.get("toolRequests")) + return AssistantMessageData( + content=content, + message_id=message_id, + encrypted_content=encrypted_content, + interaction_id=interaction_id, + output_tokens=output_tokens, + parent_tool_call_id=parent_tool_call_id, + phase=phase, + reasoning_opaque=reasoning_opaque, + reasoning_text=reasoning_text, + request_id=request_id, + tool_requests=tool_requests, ) def to_dict(self) -> dict: result: dict = {} - result["resumeTime"] = to_datetime(self.resume_time) - result["eventCount"] = to_float(self.event_count) - if self.selected_model is not None: - result["selectedModel"] = from_union([from_none, from_str], self.selected_model) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) - if self.context is not None: - result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) - if self.already_in_use is not None: - result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) - if self.remote_steerable is not None: - result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) + result["content"] = from_str(self.content) + result["messageId"] = from_str(self.message_id) + if self.encrypted_content is not None: + result["encryptedContent"] = from_union([from_none, from_str], self.encrypted_content) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.phase is not None: + result["phase"] = from_union([from_none, from_str], self.phase) + if self.reasoning_opaque is not None: + result["reasoningOpaque"] = from_union([from_none, from_str], self.reasoning_opaque) + if self.reasoning_text is not None: + result["reasoningText"] = from_union([from_none, from_str], self.reasoning_text) + if self.request_id is not None: + result["requestId"] = from_union([from_none, from_str], self.request_id) + if self.tool_requests is not None: + result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x)], self.tool_requests) return result @dataclass -class SessionRemoteSteerableChangedData: - "Notifies Mission Control that the session's remote steering capability has changed" - remote_steerable: bool +class AssistantMessageDeltaData: + "Streaming assistant message delta for incremental response updates" + delta_content: str + message_id: str + # Deprecated: this field is deprecated. + parent_tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionRemoteSteerableChangedData": + def from_dict(obj: Any) -> "AssistantMessageDeltaData": assert isinstance(obj, dict) - remote_steerable = from_bool(obj.get("remoteSteerable")) - return SessionRemoteSteerableChangedData( - remote_steerable=remote_steerable, + delta_content = from_str(obj.get("deltaContent")) + message_id = from_str(obj.get("messageId")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + return AssistantMessageDeltaData( + delta_content=delta_content, + message_id=message_id, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - result["remoteSteerable"] = from_bool(self.remote_steerable) + result["deltaContent"] = from_str(self.delta_content) + result["messageId"] = from_str(self.message_id) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @dataclass -class SessionErrorData: - "Error details for timeline display including message and optional diagnostic information" - error_type: str - message: str - stack: str | None = None - status_code: int | None = None - provider_call_id: str | None = None - url: str | None = None +class AssistantMessageToolRequest: + "A tool invocation request from the assistant" + name: str + tool_call_id: str + arguments: Any = None + intention_summary: str | None = None + mcp_server_name: str | None = None + tool_title: str | None = None + type: AssistantMessageToolRequestType | None = None @staticmethod - def from_dict(obj: Any) -> "SessionErrorData": + def from_dict(obj: Any) -> "AssistantMessageToolRequest": assert isinstance(obj, dict) - error_type = from_str(obj.get("errorType")) - message = from_str(obj.get("message")) - stack = from_union([from_none, from_str], obj.get("stack")) - status_code = from_union([from_none, from_int], obj.get("statusCode")) - provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) - url = from_union([from_none, from_str], obj.get("url")) - return SessionErrorData( - error_type=error_type, - message=message, - stack=stack, - status_code=status_code, - provider_call_id=provider_call_id, - url=url, + name = from_str(obj.get("name")) + tool_call_id = from_str(obj.get("toolCallId")) + arguments = obj.get("arguments") + intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) + mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) + tool_title = from_union([from_none, from_str], obj.get("toolTitle")) + type = from_union([from_none, lambda x: parse_enum(AssistantMessageToolRequestType, x)], obj.get("type")) + return AssistantMessageToolRequest( + name=name, + tool_call_id=tool_call_id, + arguments=arguments, + intention_summary=intention_summary, + mcp_server_name=mcp_server_name, + tool_title=tool_title, + type=type, ) def to_dict(self) -> dict: result: dict = {} - result["errorType"] = from_str(self.error_type) - result["message"] = from_str(self.message) - if self.stack is not None: - result["stack"] = from_union([from_none, from_str], self.stack) - if self.status_code is not None: - result["statusCode"] = from_union([from_none, to_int], self.status_code) - if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) - if self.url is not None: - result["url"] = from_union([from_none, from_str], self.url) + result["name"] = from_str(self.name) + result["toolCallId"] = from_str(self.tool_call_id) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.intention_summary is not None: + result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, from_str], self.tool_title) + if self.type is not None: + result["type"] = from_union([from_none, lambda x: to_enum(AssistantMessageToolRequestType, x)], self.type) return result @dataclass -class SessionIdleData: - "Payload indicating the session is idle with no background agents in flight" - aborted: bool | None = None +class AssistantReasoningData: + "Assistant reasoning content for timeline display with complete thinking text" + content: str + reasoning_id: str @staticmethod - def from_dict(obj: Any) -> "SessionIdleData": + def from_dict(obj: Any) -> "AssistantReasoningData": assert isinstance(obj, dict) - aborted = from_union([from_none, from_bool], obj.get("aborted")) - return SessionIdleData( - aborted=aborted, + content = from_str(obj.get("content")) + reasoning_id = from_str(obj.get("reasoningId")) + return AssistantReasoningData( + content=content, + reasoning_id=reasoning_id, ) def to_dict(self) -> dict: result: dict = {} - if self.aborted is not None: - result["aborted"] = from_union([from_none, from_bool], self.aborted) + result["content"] = from_str(self.content) + result["reasoningId"] = from_str(self.reasoning_id) return result @dataclass -class SessionTitleChangedData: - "Session title change payload containing the new display title" - title: str +class AssistantReasoningDeltaData: + "Streaming reasoning delta for incremental extended thinking updates" + delta_content: str + reasoning_id: str @staticmethod - def from_dict(obj: Any) -> "SessionTitleChangedData": + def from_dict(obj: Any) -> "AssistantReasoningDeltaData": assert isinstance(obj, dict) - title = from_str(obj.get("title")) - return SessionTitleChangedData( - title=title, + delta_content = from_str(obj.get("deltaContent")) + reasoning_id = from_str(obj.get("reasoningId")) + return AssistantReasoningDeltaData( + delta_content=delta_content, + reasoning_id=reasoning_id, ) def to_dict(self) -> dict: result: dict = {} - result["title"] = from_str(self.title) + result["deltaContent"] = from_str(self.delta_content) + result["reasoningId"] = from_str(self.reasoning_id) return result @dataclass -class SessionInfoData: - "Informational message for timeline display with categorization" - info_type: str - message: str - url: str | None = None +class AssistantStreamingDeltaData: + "Streaming response progress with cumulative byte count" + total_response_size_bytes: float @staticmethod - def from_dict(obj: Any) -> "SessionInfoData": + def from_dict(obj: Any) -> "AssistantStreamingDeltaData": assert isinstance(obj, dict) - info_type = from_str(obj.get("infoType")) - message = from_str(obj.get("message")) - url = from_union([from_none, from_str], obj.get("url")) - return SessionInfoData( - info_type=info_type, - message=message, - url=url, + total_response_size_bytes = from_float(obj.get("totalResponseSizeBytes")) + return AssistantStreamingDeltaData( + total_response_size_bytes=total_response_size_bytes, ) def to_dict(self) -> dict: result: dict = {} - result["infoType"] = from_str(self.info_type) - result["message"] = from_str(self.message) - if self.url is not None: - result["url"] = from_union([from_none, from_str], self.url) + result["totalResponseSizeBytes"] = to_float(self.total_response_size_bytes) return result @dataclass -class SessionWarningData: - "Warning message for timeline display with categorization" - warning_type: str - message: str - url: str | None = None +class AssistantTurnEndData: + "Turn completion metadata including the turn identifier" + turn_id: str @staticmethod - def from_dict(obj: Any) -> "SessionWarningData": + def from_dict(obj: Any) -> "AssistantTurnEndData": assert isinstance(obj, dict) - warning_type = from_str(obj.get("warningType")) - message = from_str(obj.get("message")) - url = from_union([from_none, from_str], obj.get("url")) - return SessionWarningData( - warning_type=warning_type, - message=message, - url=url, + turn_id = from_str(obj.get("turnId")) + return AssistantTurnEndData( + turn_id=turn_id, ) def to_dict(self) -> dict: result: dict = {} - result["warningType"] = from_str(self.warning_type) - result["message"] = from_str(self.message) - if self.url is not None: - result["url"] = from_union([from_none, from_str], self.url) + result["turnId"] = from_str(self.turn_id) return result @dataclass -class SessionModelChangeData: - "Model change details including previous and new model identifiers" - new_model: str - previous_model: str | None = None - previous_reasoning_effort: str | None = None - reasoning_effort: str | None = None +class AssistantTurnStartData: + "Turn initialization metadata including identifier and interaction tracking" + turn_id: str + interaction_id: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionModelChangeData": + def from_dict(obj: Any) -> "AssistantTurnStartData": assert isinstance(obj, dict) - new_model = from_str(obj.get("newModel")) - previous_model = from_union([from_none, from_str], obj.get("previousModel")) - previous_reasoning_effort = from_union([from_none, from_str], obj.get("previousReasoningEffort")) - reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - return SessionModelChangeData( - new_model=new_model, - previous_model=previous_model, - previous_reasoning_effort=previous_reasoning_effort, - reasoning_effort=reasoning_effort, + turn_id = from_str(obj.get("turnId")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + return AssistantTurnStartData( + turn_id=turn_id, + interaction_id=interaction_id, ) def to_dict(self) -> dict: result: dict = {} - result["newModel"] = from_str(self.new_model) - if self.previous_model is not None: - result["previousModel"] = from_union([from_none, from_str], self.previous_model) - if self.previous_reasoning_effort is not None: - result["previousReasoningEffort"] = from_union([from_none, from_str], self.previous_reasoning_effort) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + result["turnId"] = from_str(self.turn_id) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) return result @dataclass -class SessionModeChangedData: - "Agent mode change details including previous and new modes" - previous_mode: str - new_mode: str +class AssistantUsageCopilotUsage: + "Per-request cost and usage data from the CAPI copilot_usage response field" + token_details: list[AssistantUsageCopilotUsageTokenDetail] + total_nano_aiu: float @staticmethod - def from_dict(obj: Any) -> "SessionModeChangedData": + def from_dict(obj: Any) -> "AssistantUsageCopilotUsage": assert isinstance(obj, dict) - previous_mode = from_str(obj.get("previousMode")) - new_mode = from_str(obj.get("newMode")) - return SessionModeChangedData( - previous_mode=previous_mode, - new_mode=new_mode, + token_details = from_list(AssistantUsageCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + return AssistantUsageCopilotUsage( + token_details=token_details, + total_nano_aiu=total_nano_aiu, ) def to_dict(self) -> dict: result: dict = {} - result["previousMode"] = from_str(self.previous_mode) - result["newMode"] = from_str(self.new_mode) + result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageCopilotUsageTokenDetail, x), self.token_details) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) return result @dataclass -class SessionPlanChangedData: - "Plan file operation details indicating what changed" - operation: SessionPlanChangedDataOperation +class AssistantUsageCopilotUsageTokenDetail: + "Token usage detail for a single billing category" + batch_size: float + cost_per_batch: float + token_count: float + token_type: str @staticmethod - def from_dict(obj: Any) -> "SessionPlanChangedData": + def from_dict(obj: Any) -> "AssistantUsageCopilotUsageTokenDetail": assert isinstance(obj, dict) - operation = parse_enum(SessionPlanChangedDataOperation, obj.get("operation")) - return SessionPlanChangedData( - operation=operation, + batch_size = from_float(obj.get("batchSize")) + cost_per_batch = from_float(obj.get("costPerBatch")) + token_count = from_float(obj.get("tokenCount")) + token_type = from_str(obj.get("tokenType")) + return AssistantUsageCopilotUsageTokenDetail( + batch_size=batch_size, + cost_per_batch=cost_per_batch, + token_count=token_count, + token_type=token_type, ) def to_dict(self) -> dict: result: dict = {} - result["operation"] = to_enum(SessionPlanChangedDataOperation, self.operation) + result["batchSize"] = to_float(self.batch_size) + result["costPerBatch"] = to_float(self.cost_per_batch) + result["tokenCount"] = to_float(self.token_count) + result["tokenType"] = from_str(self.token_type) return result @dataclass -class SessionWorkspaceFileChangedData: - "Workspace file change details including path and operation type" - path: str - operation: SessionWorkspaceFileChangedDataOperation +class AssistantUsageData: + "LLM API call usage metrics including tokens, costs, quotas, and billing information" + model: str + api_call_id: str | None = None + cache_read_tokens: float | None = None + cache_write_tokens: float | None = None + copilot_usage: AssistantUsageCopilotUsage | None = None + cost: float | None = None + duration: float | None = None + initiator: str | None = None + input_tokens: float | None = None + inter_token_latency_ms: float | None = None + output_tokens: float | None = None + # Deprecated: this field is deprecated. + parent_tool_call_id: str | None = None + provider_call_id: str | None = None + quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None + reasoning_effort: str | None = None + reasoning_tokens: float | None = None + ttft_ms: float | None = None @staticmethod - def from_dict(obj: Any) -> "SessionWorkspaceFileChangedData": + def from_dict(obj: Any) -> "AssistantUsageData": assert isinstance(obj, dict) - path = from_str(obj.get("path")) - operation = parse_enum(SessionWorkspaceFileChangedDataOperation, obj.get("operation")) - return SessionWorkspaceFileChangedData( - path=path, - operation=operation, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["path"] = from_str(self.path) - result["operation"] = to_enum(SessionWorkspaceFileChangedDataOperation, self.operation) - return result - - -@dataclass -class HandoffRepository: - "Repository context for the handed-off session" - owner: str - name: str - branch: str | None = None - - @staticmethod - def from_dict(obj: Any) -> "HandoffRepository": - assert isinstance(obj, dict) - owner = from_str(obj.get("owner")) - name = from_str(obj.get("name")) - branch = from_union([from_none, from_str], obj.get("branch")) - return HandoffRepository( - owner=owner, - name=name, - branch=branch, + model = from_str(obj.get("model")) + api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) + 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")) + cost = from_union([from_none, from_float], obj.get("cost")) + duration = from_union([from_none, from_float], obj.get("duration")) + initiator = from_union([from_none, from_str], obj.get("initiator")) + input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) + inter_token_latency_ms = from_union([from_none, from_float], obj.get("interTokenLatencyMs")) + output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) + quota_snapshots = from_union([from_none, lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) + ttft_ms = from_union([from_none, from_float], obj.get("ttftMs")) + return AssistantUsageData( + model=model, + api_call_id=api_call_id, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + copilot_usage=copilot_usage, + cost=cost, + duration=duration, + initiator=initiator, + input_tokens=input_tokens, + inter_token_latency_ms=inter_token_latency_ms, + output_tokens=output_tokens, + parent_tool_call_id=parent_tool_call_id, + provider_call_id=provider_call_id, + quota_snapshots=quota_snapshots, + reasoning_effort=reasoning_effort, + reasoning_tokens=reasoning_tokens, + ttft_ms=ttft_ms, ) def to_dict(self) -> dict: result: dict = {} - result["owner"] = from_str(self.owner) - result["name"] = from_str(self.name) - if self.branch is not None: - result["branch"] = from_union([from_none, from_str], self.branch) + 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.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: + result["cacheWriteTokens"] = from_union([from_none, to_float], self.cache_write_tokens) + if self.copilot_usage is not None: + result["copilotUsage"] = from_union([from_none, lambda x: to_class(AssistantUsageCopilotUsage, x)], self.copilot_usage) + if self.cost is not None: + result["cost"] = from_union([from_none, to_float], self.cost) + if self.duration is not None: + result["duration"] = from_union([from_none, to_float], self.duration) + if self.initiator is not None: + result["initiator"] = from_union([from_none, from_str], self.initiator) + if self.input_tokens is not None: + result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) + if self.inter_token_latency_ms is not None: + result["interTokenLatencyMs"] = from_union([from_none, to_float], self.inter_token_latency_ms) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) + if self.quota_snapshots is not None: + result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x)], self.quota_snapshots) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) + if self.ttft_ms is not None: + result["ttftMs"] = from_union([from_none, to_float], self.ttft_ms) return result @dataclass -class SessionHandoffData: - "Session handoff metadata including source, context, and repository information" - handoff_time: datetime - source_type: HandoffSourceType - repository: HandoffRepository | None = None - context: str | None = None - summary: str | None = None - remote_session_id: str | None = None - host: str | None = None +class AssistantUsageQuotaSnapshot: + entitlement_requests: float + is_unlimited_entitlement: bool + overage: float + overage_allowed_with_exhausted_quota: bool + remaining_percentage: float + usage_allowed_with_exhausted_quota: bool + used_requests: float + reset_date: datetime | None = None @staticmethod - def from_dict(obj: Any) -> "SessionHandoffData": + def from_dict(obj: Any) -> "AssistantUsageQuotaSnapshot": assert isinstance(obj, dict) - handoff_time = from_datetime(obj.get("handoffTime")) - source_type = parse_enum(HandoffSourceType, obj.get("sourceType")) - repository = from_union([from_none, HandoffRepository.from_dict], obj.get("repository")) - context = from_union([from_none, from_str], obj.get("context")) - summary = from_union([from_none, from_str], obj.get("summary")) - remote_session_id = from_union([from_none, from_str], obj.get("remoteSessionId")) - host = from_union([from_none, from_str], obj.get("host")) - return SessionHandoffData( - handoff_time=handoff_time, - source_type=source_type, - repository=repository, - context=context, - summary=summary, - remote_session_id=remote_session_id, - host=host, + entitlement_requests = from_float(obj.get("entitlementRequests")) + is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) + overage = from_float(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) + used_requests = from_float(obj.get("usedRequests")) + reset_date = from_union([from_none, from_datetime], obj.get("resetDate")) + return AssistantUsageQuotaSnapshot( + entitlement_requests=entitlement_requests, + is_unlimited_entitlement=is_unlimited_entitlement, + overage=overage, + overage_allowed_with_exhausted_quota=overage_allowed_with_exhausted_quota, + remaining_percentage=remaining_percentage, + usage_allowed_with_exhausted_quota=usage_allowed_with_exhausted_quota, + used_requests=used_requests, + reset_date=reset_date, ) def to_dict(self) -> dict: result: dict = {} - result["handoffTime"] = to_datetime(self.handoff_time) - result["sourceType"] = to_enum(HandoffSourceType, self.source_type) - if self.repository is not None: - result["repository"] = from_union([from_none, lambda x: to_class(HandoffRepository, x)], self.repository) - if self.context is not None: - result["context"] = from_union([from_none, from_str], self.context) - if self.summary is not None: - result["summary"] = from_union([from_none, from_str], self.summary) - if self.remote_session_id is not None: - result["remoteSessionId"] = from_union([from_none, from_str], self.remote_session_id) - if self.host is not None: - result["host"] = from_union([from_none, from_str], self.host) + result["entitlementRequests"] = to_float(self.entitlement_requests) + result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) + result["overage"] = to_float(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) + result["usedRequests"] = to_float(self.used_requests) + if self.reset_date is not None: + result["resetDate"] = from_union([from_none, to_datetime], self.reset_date) return result @dataclass -class SessionTruncationData: - "Conversation truncation statistics including token counts and removed content metrics" - token_limit: float - pre_truncation_tokens_in_messages: float - pre_truncation_messages_length: float - post_truncation_tokens_in_messages: float - post_truncation_messages_length: float - tokens_removed_during_truncation: float - messages_removed_during_truncation: float - performed_by: str +class CapabilitiesChangedData: + "Session capability change notification" + ui: CapabilitiesChangedUI | None = None @staticmethod - def from_dict(obj: Any) -> "SessionTruncationData": + def from_dict(obj: Any) -> "CapabilitiesChangedData": assert isinstance(obj, dict) - token_limit = from_float(obj.get("tokenLimit")) - pre_truncation_tokens_in_messages = from_float(obj.get("preTruncationTokensInMessages")) - pre_truncation_messages_length = from_float(obj.get("preTruncationMessagesLength")) - post_truncation_tokens_in_messages = from_float(obj.get("postTruncationTokensInMessages")) - post_truncation_messages_length = from_float(obj.get("postTruncationMessagesLength")) - tokens_removed_during_truncation = from_float(obj.get("tokensRemovedDuringTruncation")) - messages_removed_during_truncation = from_float(obj.get("messagesRemovedDuringTruncation")) - performed_by = from_str(obj.get("performedBy")) - return SessionTruncationData( - token_limit=token_limit, - pre_truncation_tokens_in_messages=pre_truncation_tokens_in_messages, - pre_truncation_messages_length=pre_truncation_messages_length, - post_truncation_tokens_in_messages=post_truncation_tokens_in_messages, - post_truncation_messages_length=post_truncation_messages_length, - tokens_removed_during_truncation=tokens_removed_during_truncation, - messages_removed_during_truncation=messages_removed_during_truncation, - performed_by=performed_by, + ui = from_union([from_none, CapabilitiesChangedUI.from_dict], obj.get("ui")) + return CapabilitiesChangedData( + ui=ui, ) def to_dict(self) -> dict: result: dict = {} - result["tokenLimit"] = to_float(self.token_limit) - result["preTruncationTokensInMessages"] = to_float(self.pre_truncation_tokens_in_messages) - result["preTruncationMessagesLength"] = to_float(self.pre_truncation_messages_length) - result["postTruncationTokensInMessages"] = to_float(self.post_truncation_tokens_in_messages) - result["postTruncationMessagesLength"] = to_float(self.post_truncation_messages_length) - result["tokensRemovedDuringTruncation"] = to_float(self.tokens_removed_during_truncation) - result["messagesRemovedDuringTruncation"] = to_float(self.messages_removed_during_truncation) - result["performedBy"] = from_str(self.performed_by) + if self.ui is not None: + result["ui"] = from_union([from_none, lambda x: to_class(CapabilitiesChangedUI, x)], self.ui) return result @dataclass -class SessionSnapshotRewindData: - "Session rewind details including target event and count of removed events" - up_to_event_id: str - events_removed: float +class CapabilitiesChangedUI: + "UI capability changes" + elicitation: bool | None = None @staticmethod - def from_dict(obj: Any) -> "SessionSnapshotRewindData": + def from_dict(obj: Any) -> "CapabilitiesChangedUI": assert isinstance(obj, dict) - up_to_event_id = from_str(obj.get("upToEventId")) - events_removed = from_float(obj.get("eventsRemoved")) - return SessionSnapshotRewindData( - up_to_event_id=up_to_event_id, - events_removed=events_removed, + elicitation = from_union([from_none, from_bool], obj.get("elicitation")) + return CapabilitiesChangedUI( + elicitation=elicitation, ) def to_dict(self) -> dict: result: dict = {} - result["upToEventId"] = from_str(self.up_to_event_id) - result["eventsRemoved"] = to_float(self.events_removed) + if self.elicitation is not None: + result["elicitation"] = from_union([from_none, from_bool], self.elicitation) return result @dataclass -class ShutdownCodeChanges: - "Aggregate code change metrics for the session" - lines_added: float - lines_removed: float - files_modified: list[str] +class CommandCompletedData: + "Queued command completion notification signaling UI dismissal" + request_id: str @staticmethod - def from_dict(obj: Any) -> "ShutdownCodeChanges": + def from_dict(obj: Any) -> "CommandCompletedData": assert isinstance(obj, dict) - lines_added = from_float(obj.get("linesAdded")) - lines_removed = from_float(obj.get("linesRemoved")) - files_modified = from_list(from_str, obj.get("filesModified")) - return ShutdownCodeChanges( - lines_added=lines_added, - lines_removed=lines_removed, - files_modified=files_modified, + request_id = from_str(obj.get("requestId")) + return CommandCompletedData( + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["linesAdded"] = to_float(self.lines_added) - result["linesRemoved"] = to_float(self.lines_removed) - result["filesModified"] = from_list(from_str, self.files_modified) + result["requestId"] = from_str(self.request_id) return result @dataclass -class ShutdownModelMetricRequests: - "Request count and cost metrics" - count: float - cost: float +class CommandExecuteData: + "Registered command dispatch request routed to the owning client" + args: str + command: str + command_name: str + request_id: str @staticmethod - def from_dict(obj: Any) -> "ShutdownModelMetricRequests": + def from_dict(obj: Any) -> "CommandExecuteData": assert isinstance(obj, dict) - count = from_float(obj.get("count")) - cost = from_float(obj.get("cost")) - return ShutdownModelMetricRequests( - count=count, - cost=cost, + args = from_str(obj.get("args")) + command = from_str(obj.get("command")) + command_name = from_str(obj.get("commandName")) + request_id = from_str(obj.get("requestId")) + return CommandExecuteData( + args=args, + command=command, + command_name=command_name, + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["count"] = to_float(self.count) - result["cost"] = to_float(self.cost) + result["args"] = from_str(self.args) + result["command"] = from_str(self.command) + result["commandName"] = from_str(self.command_name) + result["requestId"] = from_str(self.request_id) return result @dataclass -class ShutdownModelMetricUsage: - "Token usage breakdown" - input_tokens: float - output_tokens: float - cache_read_tokens: float - cache_write_tokens: float - reasoning_tokens: float | None = None +class CommandQueuedData: + "Queued slash command dispatch request for client execution" + command: str + request_id: str @staticmethod - def from_dict(obj: Any) -> "ShutdownModelMetricUsage": + def from_dict(obj: Any) -> "CommandQueuedData": assert isinstance(obj, dict) - input_tokens = from_float(obj.get("inputTokens")) - output_tokens = from_float(obj.get("outputTokens")) - cache_read_tokens = from_float(obj.get("cacheReadTokens")) - cache_write_tokens = from_float(obj.get("cacheWriteTokens")) - reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) - return ShutdownModelMetricUsage( - input_tokens=input_tokens, - output_tokens=output_tokens, - cache_read_tokens=cache_read_tokens, - cache_write_tokens=cache_write_tokens, - reasoning_tokens=reasoning_tokens, + command = from_str(obj.get("command")) + request_id = from_str(obj.get("requestId")) + return CommandQueuedData( + command=command, + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["inputTokens"] = to_float(self.input_tokens) - result["outputTokens"] = to_float(self.output_tokens) - result["cacheReadTokens"] = to_float(self.cache_read_tokens) - result["cacheWriteTokens"] = to_float(self.cache_write_tokens) - if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) + result["command"] = from_str(self.command) + result["requestId"] = from_str(self.request_id) return result @dataclass -class ShutdownModelMetric: - requests: ShutdownModelMetricRequests - usage: ShutdownModelMetricUsage +class CommandsChangedCommand: + name: str + description: str | None = None @staticmethod - def from_dict(obj: Any) -> "ShutdownModelMetric": + def from_dict(obj: Any) -> "CommandsChangedCommand": assert isinstance(obj, dict) - requests = ShutdownModelMetricRequests.from_dict(obj.get("requests")) - usage = ShutdownModelMetricUsage.from_dict(obj.get("usage")) - return ShutdownModelMetric( - requests=requests, - usage=usage, + name = from_str(obj.get("name")) + description = from_union([from_none, from_str], obj.get("description")) + return CommandsChangedCommand( + name=name, + description=description, ) def to_dict(self) -> dict: result: dict = {} - result["requests"] = to_class(ShutdownModelMetricRequests, self.requests) - result["usage"] = to_class(ShutdownModelMetricUsage, self.usage) + result["name"] = from_str(self.name) + if self.description is not None: + result["description"] = from_union([from_none, from_str], self.description) return result @dataclass -class SessionShutdownData: - "Session termination metrics including usage statistics, code changes, and shutdown reason" - shutdown_type: ShutdownType - total_premium_requests: float - total_api_duration_ms: float - session_start_time: float - code_changes: ShutdownCodeChanges - model_metrics: dict[str, ShutdownModelMetric] - error_reason: str | None = None - current_model: str | None = None - current_tokens: float | None = None - system_tokens: float | None = None - conversation_tokens: float | None = None - tool_definitions_tokens: float | None = None +class CommandsChangedData: + "SDK command registration change notification" + commands: list[CommandsChangedCommand] @staticmethod - def from_dict(obj: Any) -> "SessionShutdownData": + def from_dict(obj: Any) -> "CommandsChangedData": assert isinstance(obj, dict) - shutdown_type = parse_enum(ShutdownType, obj.get("shutdownType")) - total_premium_requests = from_float(obj.get("totalPremiumRequests")) - total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) - session_start_time = from_float(obj.get("sessionStartTime")) - code_changes = ShutdownCodeChanges.from_dict(obj.get("codeChanges")) - model_metrics = from_dict(ShutdownModelMetric.from_dict, obj.get("modelMetrics")) - error_reason = from_union([from_none, from_str], obj.get("errorReason")) - current_model = from_union([from_none, from_str], obj.get("currentModel")) - current_tokens = from_union([from_none, from_float], obj.get("currentTokens")) - system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) - return SessionShutdownData( - shutdown_type=shutdown_type, - total_premium_requests=total_premium_requests, - total_api_duration_ms=total_api_duration_ms, - session_start_time=session_start_time, - code_changes=code_changes, - model_metrics=model_metrics, - error_reason=error_reason, - current_model=current_model, - current_tokens=current_tokens, - system_tokens=system_tokens, - conversation_tokens=conversation_tokens, - tool_definitions_tokens=tool_definitions_tokens, + commands = from_list(CommandsChangedCommand.from_dict, obj.get("commands")) + return CommandsChangedData( + commands=commands, ) def to_dict(self) -> dict: result: dict = {} - result["shutdownType"] = to_enum(ShutdownType, self.shutdown_type) - result["totalPremiumRequests"] = to_float(self.total_premium_requests) - result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) - result["sessionStartTime"] = to_float(self.session_start_time) - result["codeChanges"] = to_class(ShutdownCodeChanges, self.code_changes) - result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) - if self.error_reason is not None: - result["errorReason"] = from_union([from_none, from_str], self.error_reason) - if self.current_model is not None: - result["currentModel"] = from_union([from_none, from_str], self.current_model) - if self.current_tokens is not None: - result["currentTokens"] = from_union([from_none, to_float], self.current_tokens) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) + result["commands"] = from_list(lambda x: to_class(CommandsChangedCommand, x), self.commands) return result @dataclass -class SessionContextChangedData: - "Working directory and git context at session start" - cwd: str - git_root: str | None = None - repository: str | None = None - host_type: SessionContextChangedDataHostType | None = None - branch: str | None = None - head_commit: str | None = None - base_commit: str | None = None +class CompactionCompleteCompactionTokensUsed: + "Token usage breakdown for the compaction LLM call" + cached_input: float + input: float + output: float @staticmethod - def from_dict(obj: Any) -> "SessionContextChangedData": + def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": assert isinstance(obj, dict) - cwd = from_str(obj.get("cwd")) - git_root = from_union([from_none, from_str], obj.get("gitRoot")) - repository = from_union([from_none, from_str], obj.get("repository")) - host_type = from_union([from_none, lambda x: parse_enum(SessionContextChangedDataHostType, x)], obj.get("hostType")) - branch = from_union([from_none, from_str], obj.get("branch")) - head_commit = from_union([from_none, from_str], obj.get("headCommit")) - base_commit = from_union([from_none, from_str], obj.get("baseCommit")) - return SessionContextChangedData( - cwd=cwd, - git_root=git_root, - repository=repository, - host_type=host_type, - branch=branch, - head_commit=head_commit, - base_commit=base_commit, + cached_input = from_float(obj.get("cachedInput")) + input = from_float(obj.get("input")) + output = from_float(obj.get("output")) + return CompactionCompleteCompactionTokensUsed( + cached_input=cached_input, + input=input, + output=output, ) def to_dict(self) -> dict: result: dict = {} - result["cwd"] = from_str(self.cwd) - if self.git_root is not None: - result["gitRoot"] = from_union([from_none, from_str], self.git_root) - if self.repository is not None: - result["repository"] = from_union([from_none, from_str], self.repository) - if self.host_type is not None: - result["hostType"] = from_union([from_none, lambda x: to_enum(SessionContextChangedDataHostType, x)], self.host_type) - if self.branch is not None: - result["branch"] = from_union([from_none, from_str], self.branch) - if self.head_commit is not None: - result["headCommit"] = from_union([from_none, from_str], self.head_commit) - if self.base_commit is not None: - result["baseCommit"] = from_union([from_none, from_str], self.base_commit) + result["cachedInput"] = to_float(self.cached_input) + result["input"] = to_float(self.input) + result["output"] = to_float(self.output) return result @dataclass -class SessionUsageInfoData: - "Current context window usage statistics including token and message counts" - token_limit: float - current_tokens: float - messages_length: float - system_tokens: float | None = None - conversation_tokens: float | None = None - tool_definitions_tokens: float | None = None - is_initial: bool | None = None +class CustomAgentsUpdatedAgent: + description: str + display_name: str + id: str + name: str + source: str + tools: list[str] + user_invocable: bool + model: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionUsageInfoData": + def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": assert isinstance(obj, dict) - token_limit = from_float(obj.get("tokenLimit")) - current_tokens = from_float(obj.get("currentTokens")) - messages_length = from_float(obj.get("messagesLength")) - system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) - is_initial = from_union([from_none, from_bool], obj.get("isInitial")) - return SessionUsageInfoData( - token_limit=token_limit, - current_tokens=current_tokens, - messages_length=messages_length, - system_tokens=system_tokens, - conversation_tokens=conversation_tokens, - tool_definitions_tokens=tool_definitions_tokens, - is_initial=is_initial, + description = from_str(obj.get("description")) + display_name = from_str(obj.get("displayName")) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + tools = from_list(from_str, obj.get("tools")) + user_invocable = from_bool(obj.get("userInvocable")) + model = from_union([from_none, from_str], obj.get("model")) + return CustomAgentsUpdatedAgent( + description=description, + display_name=display_name, + id=id, + name=name, + source=source, + tools=tools, + user_invocable=user_invocable, + model=model, ) def to_dict(self) -> dict: result: dict = {} - result["tokenLimit"] = to_float(self.token_limit) - result["currentTokens"] = to_float(self.current_tokens) - result["messagesLength"] = to_float(self.messages_length) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) - if self.is_initial is not None: - result["isInitial"] = from_union([from_none, from_bool], self.is_initial) + result["description"] = from_str(self.description) + result["displayName"] = from_str(self.display_name) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["tools"] = from_list(from_str, self.tools) + result["userInvocable"] = from_bool(self.user_invocable) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) return result @dataclass -class SessionCompactionStartData: - "Context window breakdown at the start of LLM-powered conversation compaction" - system_tokens: float | None = None - conversation_tokens: float | None = None - tool_definitions_tokens: float | None = None +class ElicitationCompletedData: + "Elicitation request completion with the user's response" + request_id: str + action: ElicitationCompletedAction | None = None + content: dict[str, Any] | None = None @staticmethod - def from_dict(obj: Any) -> "SessionCompactionStartData": + def from_dict(obj: Any) -> "ElicitationCompletedData": assert isinstance(obj, dict) - system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) - return SessionCompactionStartData( - system_tokens=system_tokens, - conversation_tokens=conversation_tokens, - tool_definitions_tokens=tool_definitions_tokens, + request_id = from_str(obj.get("requestId")) + action = from_union([from_none, lambda x: parse_enum(ElicitationCompletedAction, x)], obj.get("action")) + content = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("content")) + return ElicitationCompletedData( + request_id=request_id, + action=action, + content=content, ) def to_dict(self) -> dict: result: dict = {} - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) + result["requestId"] = from_str(self.request_id) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(ElicitationCompletedAction, x)], self.action) + if self.content is not None: + result["content"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.content) return result @dataclass -class CompactionCompleteCompactionTokensUsed: - "Token usage breakdown for the compaction LLM call" - input: float - output: float - cached_input: float +class ElicitationRequestedData: + "Elicitation request; may be form-based (structured input) or URL-based (browser redirect)" + message: str + request_id: str + elicitation_source: str | None = None + mode: ElicitationRequestedMode | None = None + requested_schema: ElicitationRequestedSchema | None = None + tool_call_id: str | None = None + url: str | None = None @staticmethod - def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": + def from_dict(obj: Any) -> "ElicitationRequestedData": assert isinstance(obj, dict) - input = from_float(obj.get("input")) - output = from_float(obj.get("output")) - cached_input = from_float(obj.get("cachedInput")) - return CompactionCompleteCompactionTokensUsed( - input=input, - output=output, - cached_input=cached_input, + message = from_str(obj.get("message")) + request_id = from_str(obj.get("requestId")) + elicitation_source = from_union([from_none, from_str], obj.get("elicitationSource")) + mode = from_union([from_none, lambda x: parse_enum(ElicitationRequestedMode, x)], obj.get("mode")) + requested_schema = from_union([from_none, ElicitationRequestedSchema.from_dict], obj.get("requestedSchema")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + url = from_union([from_none, from_str], obj.get("url")) + return ElicitationRequestedData( + message=message, + request_id=request_id, + elicitation_source=elicitation_source, + mode=mode, + requested_schema=requested_schema, + tool_call_id=tool_call_id, + url=url, ) def to_dict(self) -> dict: result: dict = {} - result["input"] = to_float(self.input) - result["output"] = to_float(self.output) - result["cachedInput"] = to_float(self.cached_input) + result["message"] = from_str(self.message) + result["requestId"] = from_str(self.request_id) + if self.elicitation_source is not None: + result["elicitationSource"] = from_union([from_none, from_str], self.elicitation_source) + if self.mode is not None: + result["mode"] = from_union([from_none, lambda x: to_enum(ElicitationRequestedMode, x)], self.mode) + if self.requested_schema is not None: + result["requestedSchema"] = from_union([from_none, lambda x: to_class(ElicitationRequestedSchema, x)], self.requested_schema) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) return result @dataclass -class SessionCompactionCompleteData: - "Conversation compaction results including success status, metrics, and optional error details" - success: bool - error: str | None = None - pre_compaction_tokens: float | None = None - post_compaction_tokens: float | None = None - pre_compaction_messages_length: float | None = None - messages_removed: float | None = None - tokens_removed: float | None = None - summary_content: str | None = None - checkpoint_number: float | None = None - checkpoint_path: str | None = None - compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None - request_id: str | None = None - system_tokens: float | None = None - conversation_tokens: float | None = None - tool_definitions_tokens: float | None = None +class ElicitationRequestedSchema: + "JSON Schema describing the form fields to present to the user (form mode only)" + properties: dict[str, Any] + type: str + required: list[str] | None = None @staticmethod - def from_dict(obj: Any) -> "SessionCompactionCompleteData": + def from_dict(obj: Any) -> "ElicitationRequestedSchema": assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - error = from_union([from_none, from_str], obj.get("error")) - pre_compaction_tokens = from_union([from_none, from_float], obj.get("preCompactionTokens")) - post_compaction_tokens = from_union([from_none, from_float], obj.get("postCompactionTokens")) - pre_compaction_messages_length = from_union([from_none, from_float], obj.get("preCompactionMessagesLength")) - messages_removed = from_union([from_none, from_float], obj.get("messagesRemoved")) - tokens_removed = from_union([from_none, from_float], obj.get("tokensRemoved")) - summary_content = from_union([from_none, from_str], obj.get("summaryContent")) - checkpoint_number = from_union([from_none, from_float], obj.get("checkpointNumber")) - checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) - compaction_tokens_used = from_union([from_none, CompactionCompleteCompactionTokensUsed.from_dict], obj.get("compactionTokensUsed")) - request_id = from_union([from_none, from_str], obj.get("requestId")) - system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) - conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) - tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) - return SessionCompactionCompleteData( - success=success, - error=error, - pre_compaction_tokens=pre_compaction_tokens, - post_compaction_tokens=post_compaction_tokens, - pre_compaction_messages_length=pre_compaction_messages_length, - messages_removed=messages_removed, - tokens_removed=tokens_removed, - summary_content=summary_content, - checkpoint_number=checkpoint_number, - checkpoint_path=checkpoint_path, - compaction_tokens_used=compaction_tokens_used, - request_id=request_id, - system_tokens=system_tokens, - conversation_tokens=conversation_tokens, - tool_definitions_tokens=tool_definitions_tokens, + properties = from_dict(lambda x: x, obj.get("properties")) + type = from_str(obj.get("type")) + required = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("required")) + return ElicitationRequestedSchema( + properties=properties, + type=type, + required=required, ) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) - if self.error is not None: - result["error"] = from_union([from_none, from_str], self.error) - if self.pre_compaction_tokens is not None: - result["preCompactionTokens"] = from_union([from_none, to_float], self.pre_compaction_tokens) - if self.post_compaction_tokens is not None: - result["postCompactionTokens"] = from_union([from_none, to_float], self.post_compaction_tokens) - if self.pre_compaction_messages_length is not None: - result["preCompactionMessagesLength"] = from_union([from_none, to_float], self.pre_compaction_messages_length) - if self.messages_removed is not None: - result["messagesRemoved"] = from_union([from_none, to_float], self.messages_removed) - if self.tokens_removed is not None: - result["tokensRemoved"] = from_union([from_none, to_float], self.tokens_removed) - if self.summary_content is not None: - result["summaryContent"] = from_union([from_none, from_str], self.summary_content) - if self.checkpoint_number is not None: - result["checkpointNumber"] = from_union([from_none, to_float], self.checkpoint_number) - if self.checkpoint_path is not None: - result["checkpointPath"] = from_union([from_none, from_str], self.checkpoint_path) - if self.compaction_tokens_used is not None: - result["compactionTokensUsed"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsed, x)], self.compaction_tokens_used) - if self.request_id is not None: - result["requestId"] = from_union([from_none, from_str], self.request_id) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) + result["properties"] = from_dict(lambda x: x, self.properties) + result["type"] = from_str(self.type) + if self.required is not None: + result["required"] = from_union([from_none, lambda x: from_list(from_str, x)], self.required) return result @dataclass -class SessionTaskCompleteData: - "Task completion notification with summary from the agent" - summary: str | None = None - success: bool | None = None +class ExitPlanModeCompletedData: + "Plan mode exit completion with the user's approval decision and optional feedback" + request_id: str + approved: bool | None = None + auto_approve_edits: bool | None = None + feedback: str | None = None + selected_action: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionTaskCompleteData": + def from_dict(obj: Any) -> "ExitPlanModeCompletedData": assert isinstance(obj, dict) - summary = from_union([from_none, from_str], obj.get("summary", "")) - success = from_union([from_none, from_bool], obj.get("success")) - return SessionTaskCompleteData( - summary=summary, - success=success, + request_id = from_str(obj.get("requestId")) + approved = from_union([from_none, from_bool], obj.get("approved")) + auto_approve_edits = from_union([from_none, from_bool], obj.get("autoApproveEdits")) + feedback = from_union([from_none, from_str], obj.get("feedback")) + selected_action = from_union([from_none, from_str], obj.get("selectedAction")) + return ExitPlanModeCompletedData( + request_id=request_id, + approved=approved, + auto_approve_edits=auto_approve_edits, + feedback=feedback, + selected_action=selected_action, ) def to_dict(self) -> dict: result: dict = {} - if self.summary is not None: - result["summary"] = from_union([from_none, from_str], self.summary) - if self.success is not None: - result["success"] = from_union([from_none, from_bool], self.success) + result["requestId"] = from_str(self.request_id) + if self.approved is not None: + result["approved"] = from_union([from_none, from_bool], self.approved) + if self.auto_approve_edits is not None: + result["autoApproveEdits"] = from_union([from_none, from_bool], self.auto_approve_edits) + if self.feedback is not None: + result["feedback"] = from_union([from_none, from_str], self.feedback) + if self.selected_action is not None: + result["selectedAction"] = from_union([from_none, from_str], self.selected_action) return result @dataclass -class UserMessageAttachmentFileLineRange: - "Optional line range to scope the attachment to a specific section of the file" - start: float - end: float +class ExitPlanModeRequestedData: + "Plan approval request with plan content and available user actions" + actions: list[str] + plan_content: str + recommended_action: str + request_id: str + summary: str @staticmethod - def from_dict(obj: Any) -> "UserMessageAttachmentFileLineRange": + def from_dict(obj: Any) -> "ExitPlanModeRequestedData": assert isinstance(obj, dict) - start = from_float(obj.get("start")) - end = from_float(obj.get("end")) - return UserMessageAttachmentFileLineRange( - start=start, - end=end, + actions = from_list(from_str, obj.get("actions")) + plan_content = from_str(obj.get("planContent")) + recommended_action = from_str(obj.get("recommendedAction")) + request_id = from_str(obj.get("requestId")) + summary = from_str(obj.get("summary")) + return ExitPlanModeRequestedData( + actions=actions, + plan_content=plan_content, + recommended_action=recommended_action, + request_id=request_id, + summary=summary, ) def to_dict(self) -> dict: result: dict = {} - result["start"] = to_float(self.start) - result["end"] = to_float(self.end) + result["actions"] = from_list(from_str, self.actions) + result["planContent"] = from_str(self.plan_content) + result["recommendedAction"] = from_str(self.recommended_action) + result["requestId"] = from_str(self.request_id) + result["summary"] = from_str(self.summary) return result @dataclass -class UserMessageAttachmentSelectionDetailsStart: - "Start position of the selection" - line: float - character: float +class ExtensionsLoadedExtension: + id: str + name: str + source: ExtensionsLoadedExtensionSource + status: ExtensionsLoadedExtensionStatus @staticmethod - def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsStart": + def from_dict(obj: Any) -> "ExtensionsLoadedExtension": assert isinstance(obj, dict) - line = from_float(obj.get("line")) - character = from_float(obj.get("character")) - return UserMessageAttachmentSelectionDetailsStart( - line=line, - character=character, + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = parse_enum(ExtensionsLoadedExtensionSource, obj.get("source")) + status = parse_enum(ExtensionsLoadedExtensionStatus, obj.get("status")) + return ExtensionsLoadedExtension( + id=id, + name=name, + source=source, + status=status, ) def to_dict(self) -> dict: result: dict = {} - result["line"] = to_float(self.line) - result["character"] = to_float(self.character) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionsLoadedExtensionSource, self.source) + result["status"] = to_enum(ExtensionsLoadedExtensionStatus, self.status) return result @dataclass -class UserMessageAttachmentSelectionDetailsEnd: - "End position of the selection" - line: float - character: float +class ExternalToolCompletedData: + "External tool completion notification signaling UI dismissal" + request_id: str @staticmethod - def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsEnd": + def from_dict(obj: Any) -> "ExternalToolCompletedData": assert isinstance(obj, dict) - line = from_float(obj.get("line")) - character = from_float(obj.get("character")) - return UserMessageAttachmentSelectionDetailsEnd( - line=line, - character=character, + request_id = from_str(obj.get("requestId")) + return ExternalToolCompletedData( + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["line"] = to_float(self.line) - result["character"] = to_float(self.character) + result["requestId"] = from_str(self.request_id) return result @dataclass -class UserMessageAttachmentSelectionDetails: - "Position range of the selection within the file" - start: UserMessageAttachmentSelectionDetailsStart - end: UserMessageAttachmentSelectionDetailsEnd +class ExternalToolRequestedData: + "External tool invocation request for client-side tool execution" + request_id: str + session_id: str + tool_call_id: str + tool_name: str + arguments: Any = None + traceparent: str | None = None + tracestate: str | None = None @staticmethod - def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetails": + def from_dict(obj: Any) -> "ExternalToolRequestedData": assert isinstance(obj, dict) - start = UserMessageAttachmentSelectionDetailsStart.from_dict(obj.get("start")) - end = UserMessageAttachmentSelectionDetailsEnd.from_dict(obj.get("end")) - return UserMessageAttachmentSelectionDetails( - start=start, - end=end, + request_id = from_str(obj.get("requestId")) + session_id = from_str(obj.get("sessionId")) + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + traceparent = from_union([from_none, from_str], obj.get("traceparent")) + tracestate = from_union([from_none, from_str], obj.get("tracestate")) + return ExternalToolRequestedData( + request_id=request_id, + session_id=session_id, + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + traceparent=traceparent, + tracestate=tracestate, ) def to_dict(self) -> dict: result: dict = {} - result["start"] = to_class(UserMessageAttachmentSelectionDetailsStart, self.start) - result["end"] = to_class(UserMessageAttachmentSelectionDetailsEnd, self.end) + result["requestId"] = from_str(self.request_id) + result["sessionId"] = from_str(self.session_id) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.traceparent is not None: + result["traceparent"] = from_union([from_none, from_str], self.traceparent) + if self.tracestate is not None: + result["tracestate"] = from_union([from_none, from_str], self.tracestate) return result @dataclass -class UserMessageAttachment: - "A user message attachment — a file, directory, code selection, blob, or GitHub reference" - type: UserMessageAttachmentType - path: str | None = None - display_name: str | None = None - line_range: UserMessageAttachmentFileLineRange | None = None - file_path: str | None = None - text: str | None = None - selection: UserMessageAttachmentSelectionDetails | None = None - number: float | None = None - title: str | None = None - reference_type: UserMessageAttachmentGithubReferenceType | None = None - state: str | None = None - url: str | None = None - data: str | None = None - mime_type: str | None = None +class HandoffRepository: + "Repository context for the handed-off session" + name: str + owner: str + branch: str | None = None @staticmethod - def from_dict(obj: Any) -> "UserMessageAttachment": + def from_dict(obj: Any) -> "HandoffRepository": assert isinstance(obj, dict) - type = parse_enum(UserMessageAttachmentType, obj.get("type")) - path = from_union([from_none, from_str], obj.get("path")) - display_name = from_union([from_none, from_str], obj.get("displayName")) - line_range = from_union([from_none, UserMessageAttachmentFileLineRange.from_dict], obj.get("lineRange")) - file_path = from_union([from_none, from_str], obj.get("filePath")) - text = from_union([from_none, from_str], obj.get("text")) - selection = from_union([from_none, UserMessageAttachmentSelectionDetails.from_dict], obj.get("selection")) - number = from_union([from_none, from_float], obj.get("number")) - title = from_union([from_none, from_str], obj.get("title")) - reference_type = from_union([from_none, lambda x: parse_enum(UserMessageAttachmentGithubReferenceType, x)], obj.get("referenceType")) - state = from_union([from_none, from_str], obj.get("state")) - url = from_union([from_none, from_str], obj.get("url")) - data = from_union([from_none, from_str], obj.get("data")) - mime_type = from_union([from_none, from_str], obj.get("mimeType")) - return UserMessageAttachment( - type=type, - path=path, - display_name=display_name, - line_range=line_range, - file_path=file_path, - text=text, - selection=selection, - number=number, - title=title, - reference_type=reference_type, - state=state, - url=url, - data=data, - mime_type=mime_type, + name = from_str(obj.get("name")) + owner = from_str(obj.get("owner")) + branch = from_union([from_none, from_str], obj.get("branch")) + return HandoffRepository( + name=name, + owner=owner, + branch=branch, ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(UserMessageAttachmentType, self.type) - if self.path is not None: - result["path"] = from_union([from_none, from_str], self.path) - if self.display_name is not None: - result["displayName"] = from_union([from_none, from_str], self.display_name) - if self.line_range is not None: - result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentFileLineRange, x)], self.line_range) - if self.file_path is not None: - result["filePath"] = from_union([from_none, from_str], self.file_path) - if self.text is not None: - result["text"] = from_union([from_none, from_str], self.text) - if self.selection is not None: - result["selection"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentSelectionDetails, x)], self.selection) - if self.number is not None: - result["number"] = from_union([from_none, to_float], self.number) - if self.title is not None: - result["title"] = from_union([from_none, from_str], self.title) - if self.reference_type is not None: - result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x)], self.reference_type) - if self.state is not None: - result["state"] = from_union([from_none, from_str], self.state) - if self.url is not None: - result["url"] = from_union([from_none, from_str], self.url) - if self.data is not None: - result["data"] = from_union([from_none, from_str], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_none, from_str], self.mime_type) + result["name"] = from_str(self.name) + result["owner"] = from_str(self.owner) + if self.branch is not None: + result["branch"] = from_union([from_none, from_str], self.branch) return result @dataclass -class UserMessageData: - content: str - transformed_content: str | None = None - attachments: list[UserMessageAttachment] | None = None - supported_native_document_mime_types: list[str] | None = None - native_document_path_fallback_paths: list[str] | None = None - source: str | None = None - agent_mode: UserMessageAgentMode | None = None - interaction_id: str | None = None +class HookEndData: + "Hook invocation completion details including output, success status, and error information" + hook_invocation_id: str + hook_type: str + success: bool + error: HookEndError | None = None + output: Any = None @staticmethod - def from_dict(obj: Any) -> "UserMessageData": + def from_dict(obj: Any) -> "HookEndData": assert isinstance(obj, dict) - content = from_str(obj.get("content")) - transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) - attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments")) - supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) - native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths")) - source = from_union([from_none, from_str], obj.get("source")) - agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode")) - interaction_id = from_union([from_none, from_str], obj.get("interactionId")) - return UserMessageData( - content=content, - transformed_content=transformed_content, - attachments=attachments, - supported_native_document_mime_types=supported_native_document_mime_types, - native_document_path_fallback_paths=native_document_path_fallback_paths, - source=source, - agent_mode=agent_mode, - interaction_id=interaction_id, + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) + success = from_bool(obj.get("success")) + error = from_union([from_none, HookEndError.from_dict], obj.get("error")) + output = obj.get("output") + return HookEndData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + success=success, + error=error, + output=output, ) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - if self.transformed_content is not None: - result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) - if self.attachments is not None: - result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments) - if self.supported_native_document_mime_types is not None: - result["supportedNativeDocumentMimeTypes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.supported_native_document_mime_types) - 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.source is not None: - result["source"] = from_union([from_none, from_str], self.source) - if self.agent_mode is not None: - result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) + result["success"] = from_bool(self.success) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(HookEndError, x)], self.error) + if self.output is not None: + result["output"] = self.output return result @dataclass -class PendingMessagesModifiedData: - "Empty payload; the event signals that the pending message queue has changed" - @staticmethod - def from_dict(obj: Any) -> "PendingMessagesModifiedData": - assert isinstance(obj, dict) - return PendingMessagesModifiedData() - - def to_dict(self) -> dict: - return {} - - -@dataclass -class AssistantTurnStartData: - "Turn initialization metadata including identifier and interaction tracking" - turn_id: str - interaction_id: str | None = None +class HookEndError: + "Error details when the hook failed" + message: str + stack: str | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantTurnStartData": + def from_dict(obj: Any) -> "HookEndError": assert isinstance(obj, dict) - turn_id = from_str(obj.get("turnId")) - interaction_id = from_union([from_none, from_str], obj.get("interactionId")) - return AssistantTurnStartData( - turn_id=turn_id, - interaction_id=interaction_id, + message = from_str(obj.get("message")) + stack = from_union([from_none, from_str], obj.get("stack")) + return HookEndError( + message=message, + stack=stack, ) def to_dict(self) -> dict: result: dict = {} - result["turnId"] = from_str(self.turn_id) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + result["message"] = from_str(self.message) + if self.stack is not None: + result["stack"] = from_union([from_none, from_str], self.stack) return result @dataclass -class AssistantIntentData: - "Agent intent description for current activity or plan" - intent: str +class HookStartData: + "Hook invocation start details including type and input data" + hook_invocation_id: str + hook_type: str + input: Any = None @staticmethod - def from_dict(obj: Any) -> "AssistantIntentData": + def from_dict(obj: Any) -> "HookStartData": assert isinstance(obj, dict) - intent = from_str(obj.get("intent")) - return AssistantIntentData( - intent=intent, + hook_invocation_id = from_str(obj.get("hookInvocationId")) + hook_type = from_str(obj.get("hookType")) + input = obj.get("input") + return HookStartData( + hook_invocation_id=hook_invocation_id, + hook_type=hook_type, + input=input, ) def to_dict(self) -> dict: result: dict = {} - result["intent"] = from_str(self.intent) + result["hookInvocationId"] = from_str(self.hook_invocation_id) + result["hookType"] = from_str(self.hook_type) + if self.input is not None: + result["input"] = self.input return result @dataclass -class AssistantReasoningData: - "Assistant reasoning content for timeline display with complete thinking text" - reasoning_id: str - content: str +class McpOauthCompletedData: + "MCP OAuth request completion notification" + request_id: str @staticmethod - def from_dict(obj: Any) -> "AssistantReasoningData": + def from_dict(obj: Any) -> "McpOauthCompletedData": assert isinstance(obj, dict) - reasoning_id = from_str(obj.get("reasoningId")) - content = from_str(obj.get("content")) - return AssistantReasoningData( - reasoning_id=reasoning_id, - content=content, + request_id = from_str(obj.get("requestId")) + return McpOauthCompletedData( + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["reasoningId"] = from_str(self.reasoning_id) - result["content"] = from_str(self.content) + result["requestId"] = from_str(self.request_id) return result @dataclass -class AssistantReasoningDeltaData: - "Streaming reasoning delta for incremental extended thinking updates" - reasoning_id: str - delta_content: str +class McpOauthRequiredData: + "OAuth authentication request for an MCP server" + request_id: str + server_name: str + server_url: str + static_client_config: McpOauthRequiredStaticClientConfig | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantReasoningDeltaData": + def from_dict(obj: Any) -> "McpOauthRequiredData": assert isinstance(obj, dict) - reasoning_id = from_str(obj.get("reasoningId")) - delta_content = from_str(obj.get("deltaContent")) - return AssistantReasoningDeltaData( - reasoning_id=reasoning_id, - delta_content=delta_content, + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + server_url = from_str(obj.get("serverUrl")) + static_client_config = from_union([from_none, McpOauthRequiredStaticClientConfig.from_dict], obj.get("staticClientConfig")) + return McpOauthRequiredData( + request_id=request_id, + server_name=server_name, + server_url=server_url, + static_client_config=static_client_config, ) def to_dict(self) -> dict: result: dict = {} - result["reasoningId"] = from_str(self.reasoning_id) - result["deltaContent"] = from_str(self.delta_content) + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) + result["serverUrl"] = from_str(self.server_url) + if self.static_client_config is not None: + result["staticClientConfig"] = from_union([from_none, lambda x: to_class(McpOauthRequiredStaticClientConfig, x)], self.static_client_config) return result @dataclass -class AssistantStreamingDeltaData: - "Streaming response progress with cumulative byte count" - total_response_size_bytes: float +class McpOauthRequiredStaticClientConfig: + "Static OAuth client configuration, if the server specifies one" + client_id: str + public_client: bool | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantStreamingDeltaData": + def from_dict(obj: Any) -> "McpOauthRequiredStaticClientConfig": assert isinstance(obj, dict) - total_response_size_bytes = from_float(obj.get("totalResponseSizeBytes")) - return AssistantStreamingDeltaData( - total_response_size_bytes=total_response_size_bytes, + client_id = from_str(obj.get("clientId")) + public_client = from_union([from_none, from_bool], obj.get("publicClient")) + return McpOauthRequiredStaticClientConfig( + client_id=client_id, + public_client=public_client, ) def to_dict(self) -> dict: result: dict = {} - result["totalResponseSizeBytes"] = to_float(self.total_response_size_bytes) + result["clientId"] = from_str(self.client_id) + if self.public_client is not None: + result["publicClient"] = from_union([from_none, from_bool], self.public_client) return result @dataclass -class AssistantMessageToolRequest: - "A tool invocation request from the assistant" - tool_call_id: str +class McpServersLoadedServer: name: str - arguments: Any = None - type: AssistantMessageToolRequestType | None = None - tool_title: str | None = None - mcp_server_name: str | None = None - intention_summary: str | None = None + status: McpServersLoadedServerStatus + error: str | None = None + source: str | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantMessageToolRequest": + def from_dict(obj: Any) -> "McpServersLoadedServer": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) name = from_str(obj.get("name")) - arguments = obj.get("arguments") - type = from_union([from_none, lambda x: parse_enum(AssistantMessageToolRequestType, x)], obj.get("type")) - tool_title = from_union([from_none, from_str], obj.get("toolTitle")) - mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) - intention_summary = from_union([from_none, from_str], obj.get("intentionSummary")) - return AssistantMessageToolRequest( - tool_call_id=tool_call_id, + status = parse_enum(McpServersLoadedServerStatus, obj.get("status")) + error = from_union([from_none, from_str], obj.get("error")) + source = from_union([from_none, from_str], obj.get("source")) + return McpServersLoadedServer( name=name, - arguments=arguments, - type=type, - tool_title=tool_title, - mcp_server_name=mcp_server_name, - intention_summary=intention_summary, + status=status, + error=error, + source=source, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) result["name"] = from_str(self.name) - if self.arguments is not None: - result["arguments"] = self.arguments - if self.type is not None: - result["type"] = from_union([from_none, lambda x: to_enum(AssistantMessageToolRequestType, x)], self.type) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_none, from_str], self.tool_title) - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) - if self.intention_summary is not None: - result["intentionSummary"] = from_union([from_none, from_str], self.intention_summary) + result["status"] = to_enum(McpServersLoadedServerStatus, self.status) + if self.error is not None: + result["error"] = from_union([from_none, from_str], self.error) + if self.source is not None: + result["source"] = from_union([from_none, from_str], self.source) return result @dataclass -class AssistantMessageData: - "Assistant response containing text content, optional tool requests, and interaction metadata" - message_id: str - content: str - tool_requests: list[AssistantMessageToolRequest] | None = None - reasoning_opaque: str | None = None - reasoning_text: str | None = None - encrypted_content: str | None = None - phase: str | None = None - output_tokens: float | None = None - interaction_id: str | None = None - request_id: str | None = None - # Deprecated: this field is deprecated. - parent_tool_call_id: str | None = None +class PendingMessagesModifiedData: + "Empty payload; the event signals that the pending message queue has changed" + @staticmethod + def from_dict(obj: Any) -> "PendingMessagesModifiedData": + assert isinstance(obj, dict) + return PendingMessagesModifiedData() + + def to_dict(self) -> dict: + return {} + + +@dataclass +class PermissionCompletedData: + "Permission request completion notification signaling UI dismissal" + request_id: str + result: PermissionCompletedResult @staticmethod - def from_dict(obj: Any) -> "AssistantMessageData": + def from_dict(obj: Any) -> "PermissionCompletedData": assert isinstance(obj, dict) - message_id = from_str(obj.get("messageId")) - content = from_str(obj.get("content")) - tool_requests = from_union([from_none, lambda x: from_list(AssistantMessageToolRequest.from_dict, x)], obj.get("toolRequests")) - reasoning_opaque = from_union([from_none, from_str], obj.get("reasoningOpaque")) - reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) - encrypted_content = from_union([from_none, from_str], obj.get("encryptedContent")) - phase = from_union([from_none, from_str], obj.get("phase")) - output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) - interaction_id = from_union([from_none, from_str], obj.get("interactionId")) - request_id = from_union([from_none, from_str], obj.get("requestId")) - parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) - return AssistantMessageData( - message_id=message_id, - content=content, - tool_requests=tool_requests, - reasoning_opaque=reasoning_opaque, - reasoning_text=reasoning_text, - encrypted_content=encrypted_content, - phase=phase, - output_tokens=output_tokens, - interaction_id=interaction_id, + request_id = from_str(obj.get("requestId")) + result = PermissionCompletedResult.from_dict(obj.get("result")) + return PermissionCompletedData( request_id=request_id, - parent_tool_call_id=parent_tool_call_id, + result=result, ) def to_dict(self) -> dict: result: dict = {} - result["messageId"] = from_str(self.message_id) - result["content"] = from_str(self.content) - if self.tool_requests is not None: - result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x)], self.tool_requests) - if self.reasoning_opaque is not None: - result["reasoningOpaque"] = from_union([from_none, from_str], self.reasoning_opaque) - if self.reasoning_text is not None: - result["reasoningText"] = from_union([from_none, from_str], self.reasoning_text) - if self.encrypted_content is not None: - result["encryptedContent"] = from_union([from_none, from_str], self.encrypted_content) - if self.phase is not None: - result["phase"] = from_union([from_none, from_str], self.phase) - if self.output_tokens is not None: - result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, from_str], self.interaction_id) - if self.request_id is not None: - result["requestId"] = from_union([from_none, from_str], self.request_id) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionCompletedResult, self.result) return result @dataclass -class AssistantMessageDeltaData: - "Streaming assistant message delta for incremental response updates" - message_id: str - delta_content: str - # Deprecated: this field is deprecated. - parent_tool_call_id: str | None = None +class PermissionCompletedResult: + "The result of the permission request" + kind: PermissionCompletedKind @staticmethod - def from_dict(obj: Any) -> "AssistantMessageDeltaData": + def from_dict(obj: Any) -> "PermissionCompletedResult": assert isinstance(obj, dict) - message_id = from_str(obj.get("messageId")) - delta_content = from_str(obj.get("deltaContent")) - parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) - return AssistantMessageDeltaData( - message_id=message_id, - delta_content=delta_content, - parent_tool_call_id=parent_tool_call_id, + kind = parse_enum(PermissionCompletedKind, obj.get("kind")) + return PermissionCompletedResult( + kind=kind, ) def to_dict(self) -> dict: result: dict = {} - result["messageId"] = from_str(self.message_id) - result["deltaContent"] = from_str(self.delta_content) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + result["kind"] = to_enum(PermissionCompletedKind, self.kind) return result @dataclass -class AssistantTurnEndData: - "Turn completion metadata including the turn identifier" - turn_id: str +class PermissionRequest: + "Details of the permission being requested" + kind: PermissionRequestKind + action: PermissionRequestMemoryAction | None = None + args: Any = None + can_offer_session_approval: bool | None = None + citations: str | None = None + commands: list[PermissionRequestShellCommand] | None = None + diff: str | None = None + direction: PermissionRequestMemoryDirection | None = None + fact: str | None = None + file_name: str | None = None + full_command_text: str | None = None + has_write_file_redirection: bool | None = None + hook_message: str | None = None + intention: str | None = None + new_file_contents: str | None = None + path: str | None = None + possible_paths: list[str] | None = None + possible_urls: list[PermissionRequestShellPossibleUrl] | None = None + read_only: bool | None = None + reason: str | None = None + server_name: str | None = None + subject: str | None = None + tool_args: Any = None + tool_call_id: str | None = None + tool_description: str | None = None + tool_name: str | None = None + tool_title: str | None = None + url: str | None = None + warning: str | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantTurnEndData": + def from_dict(obj: Any) -> "PermissionRequest": assert isinstance(obj, dict) - turn_id = from_str(obj.get("turnId")) - return AssistantTurnEndData( - turn_id=turn_id, + kind = parse_enum(PermissionRequestKind, obj.get("kind")) + action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store")) + args = obj.get("args") + can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) + citations = from_union([from_none, from_str], obj.get("citations")) + commands = from_union([from_none, lambda x: from_list(PermissionRequestShellCommand.from_dict, x)], obj.get("commands")) + diff = from_union([from_none, from_str], obj.get("diff")) + direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) + fact = from_union([from_none, from_str], obj.get("fact")) + file_name = from_union([from_none, from_str], obj.get("fileName")) + full_command_text = from_union([from_none, from_str], obj.get("fullCommandText")) + has_write_file_redirection = from_union([from_none, from_bool], obj.get("hasWriteFileRedirection")) + hook_message = from_union([from_none, from_str], obj.get("hookMessage")) + intention = from_union([from_none, from_str], obj.get("intention")) + new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) + path = from_union([from_none, from_str], obj.get("path")) + possible_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("possiblePaths")) + possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleUrl.from_dict, x)], obj.get("possibleUrls")) + read_only = from_union([from_none, from_bool], obj.get("readOnly")) + reason = from_union([from_none, from_str], obj.get("reason")) + server_name = from_union([from_none, from_str], obj.get("serverName")) + subject = from_union([from_none, from_str], obj.get("subject")) + tool_args = obj.get("toolArgs") + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + tool_description = from_union([from_none, from_str], obj.get("toolDescription")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + tool_title = from_union([from_none, from_str], obj.get("toolTitle")) + url = from_union([from_none, from_str], obj.get("url")) + warning = from_union([from_none, from_str], obj.get("warning")) + return PermissionRequest( + kind=kind, + action=action, + args=args, + can_offer_session_approval=can_offer_session_approval, + citations=citations, + commands=commands, + diff=diff, + direction=direction, + fact=fact, + file_name=file_name, + full_command_text=full_command_text, + has_write_file_redirection=has_write_file_redirection, + hook_message=hook_message, + intention=intention, + new_file_contents=new_file_contents, + path=path, + possible_paths=possible_paths, + possible_urls=possible_urls, + read_only=read_only, + reason=reason, + server_name=server_name, + subject=subject, + tool_args=tool_args, + tool_call_id=tool_call_id, + tool_description=tool_description, + tool_name=tool_name, + tool_title=tool_title, + url=url, + warning=warning, ) def to_dict(self) -> dict: result: dict = {} - result["turnId"] = from_str(self.turn_id) + result["kind"] = to_enum(PermissionRequestKind, self.kind) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) + if self.args is not None: + result["args"] = self.args + if self.can_offer_session_approval is not None: + result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval) + if self.citations is not None: + result["citations"] = from_union([from_none, from_str], self.citations) + if self.commands is not None: + result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x)], self.commands) + if self.diff is not None: + result["diff"] = from_union([from_none, from_str], self.diff) + if self.direction is not None: + result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) + if self.fact is not None: + result["fact"] = from_union([from_none, from_str], self.fact) + if self.file_name is not None: + result["fileName"] = from_union([from_none, from_str], self.file_name) + if self.full_command_text is not None: + result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text) + if self.has_write_file_redirection is not None: + result["hasWriteFileRedirection"] = from_union([from_none, from_bool], self.has_write_file_redirection) + if self.hook_message is not None: + result["hookMessage"] = from_union([from_none, from_str], self.hook_message) + if self.intention is not None: + result["intention"] = from_union([from_none, from_str], self.intention) + if self.new_file_contents is not None: + result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) + if self.path is not None: + result["path"] = from_union([from_none, from_str], self.path) + if self.possible_paths is not None: + result["possiblePaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.possible_paths) + if self.possible_urls is not None: + result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleUrl, x), x)], self.possible_urls) + if self.read_only is not None: + result["readOnly"] = from_union([from_none, from_bool], self.read_only) + if self.reason is not None: + result["reason"] = from_union([from_none, from_str], self.reason) + if self.server_name is not None: + result["serverName"] = from_union([from_none, from_str], self.server_name) + if self.subject is not None: + result["subject"] = from_union([from_none, from_str], self.subject) + if self.tool_args is not None: + result["toolArgs"] = self.tool_args + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.tool_description is not None: + result["toolDescription"] = from_union([from_none, from_str], self.tool_description) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, from_str], self.tool_title) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) + if self.warning is not None: + result["warning"] = from_union([from_none, from_str], self.warning) return result @dataclass -class AssistantUsageQuotaSnapshot: - is_unlimited_entitlement: bool - entitlement_requests: float - used_requests: float - usage_allowed_with_exhausted_quota: bool - overage: float - overage_allowed_with_exhausted_quota: bool - remaining_percentage: float - reset_date: datetime | None = None +class PermissionRequestShellCommand: + identifier: str + read_only: bool @staticmethod - def from_dict(obj: Any) -> "AssistantUsageQuotaSnapshot": + def from_dict(obj: Any) -> "PermissionRequestShellCommand": assert isinstance(obj, dict) - is_unlimited_entitlement = from_bool(obj.get("isUnlimitedEntitlement")) - entitlement_requests = from_float(obj.get("entitlementRequests")) - used_requests = from_float(obj.get("usedRequests")) - usage_allowed_with_exhausted_quota = from_bool(obj.get("usageAllowedWithExhaustedQuota")) - overage = from_float(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - reset_date = from_union([from_none, from_datetime], obj.get("resetDate")) - return AssistantUsageQuotaSnapshot( - is_unlimited_entitlement=is_unlimited_entitlement, - entitlement_requests=entitlement_requests, - used_requests=used_requests, - usage_allowed_with_exhausted_quota=usage_allowed_with_exhausted_quota, - overage=overage, - overage_allowed_with_exhausted_quota=overage_allowed_with_exhausted_quota, - remaining_percentage=remaining_percentage, - reset_date=reset_date, + identifier = from_str(obj.get("identifier")) + read_only = from_bool(obj.get("readOnly")) + return PermissionRequestShellCommand( + identifier=identifier, + read_only=read_only, ) def to_dict(self) -> dict: result: dict = {} - result["isUnlimitedEntitlement"] = from_bool(self.is_unlimited_entitlement) - result["entitlementRequests"] = to_float(self.entitlement_requests) - result["usedRequests"] = to_float(self.used_requests) - result["usageAllowedWithExhaustedQuota"] = from_bool(self.usage_allowed_with_exhausted_quota) - result["overage"] = to_float(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - if self.reset_date is not None: - result["resetDate"] = from_union([from_none, to_datetime], self.reset_date) + result["identifier"] = from_str(self.identifier) + result["readOnly"] = from_bool(self.read_only) return result @dataclass -class AssistantUsageCopilotUsageTokenDetail: - "Token usage detail for a single billing category" - batch_size: float - cost_per_batch: float - token_count: float - token_type: str +class PermissionRequestShellPossibleUrl: + url: str @staticmethod - def from_dict(obj: Any) -> "AssistantUsageCopilotUsageTokenDetail": + def from_dict(obj: Any) -> "PermissionRequestShellPossibleUrl": assert isinstance(obj, dict) - batch_size = from_float(obj.get("batchSize")) - cost_per_batch = from_float(obj.get("costPerBatch")) - token_count = from_float(obj.get("tokenCount")) - token_type = from_str(obj.get("tokenType")) - return AssistantUsageCopilotUsageTokenDetail( - batch_size=batch_size, - cost_per_batch=cost_per_batch, - token_count=token_count, - token_type=token_type, + url = from_str(obj.get("url")) + return PermissionRequestShellPossibleUrl( + url=url, ) def to_dict(self) -> dict: result: dict = {} - result["batchSize"] = to_float(self.batch_size) - result["costPerBatch"] = to_float(self.cost_per_batch) - result["tokenCount"] = to_float(self.token_count) - result["tokenType"] = from_str(self.token_type) + result["url"] = from_str(self.url) return result @dataclass -class AssistantUsageCopilotUsage: - "Per-request cost and usage data from the CAPI copilot_usage response field" - token_details: list[AssistantUsageCopilotUsageTokenDetail] - total_nano_aiu: float +class PermissionRequestedData: + "Permission request notification requiring client approval with request details" + permission_request: PermissionRequest + request_id: str + resolved_by_hook: bool | None = None @staticmethod - def from_dict(obj: Any) -> "AssistantUsageCopilotUsage": + def from_dict(obj: Any) -> "PermissionRequestedData": assert isinstance(obj, dict) - token_details = from_list(AssistantUsageCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) - total_nano_aiu = from_float(obj.get("totalNanoAiu")) - return AssistantUsageCopilotUsage( - token_details=token_details, - total_nano_aiu=total_nano_aiu, + permission_request = PermissionRequest.from_dict(obj.get("permissionRequest")) + request_id = from_str(obj.get("requestId")) + resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook")) + return PermissionRequestedData( + permission_request=permission_request, + request_id=request_id, + resolved_by_hook=resolved_by_hook, ) def to_dict(self) -> dict: result: dict = {} - result["tokenDetails"] = from_list(lambda x: to_class(AssistantUsageCopilotUsageTokenDetail, x), self.token_details) - result["totalNanoAiu"] = to_float(self.total_nano_aiu) + result["permissionRequest"] = to_class(PermissionRequest, self.permission_request) + result["requestId"] = from_str(self.request_id) + if self.resolved_by_hook is not None: + result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook) return result @dataclass -class AssistantUsageData: - "LLM API call usage metrics including tokens, costs, quotas, and billing information" - model: str - input_tokens: float | None = None - output_tokens: float | None = None - cache_read_tokens: float | None = None - cache_write_tokens: float | None = None - reasoning_tokens: float | None = None - cost: float | None = None - duration: float | None = None - ttft_ms: float | None = None - inter_token_latency_ms: float | None = None - initiator: str | None = None - api_call_id: str | None = None - provider_call_id: str | None = None - # Deprecated: this field is deprecated. - parent_tool_call_id: str | None = None - quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None - copilot_usage: AssistantUsageCopilotUsage | None = None - reasoning_effort: str | None = None +class SamplingCompletedData: + "Sampling request completion notification signaling UI dismissal" + request_id: str @staticmethod - def from_dict(obj: Any) -> "AssistantUsageData": + def from_dict(obj: Any) -> "SamplingCompletedData": assert isinstance(obj, dict) - model = from_str(obj.get("model")) - input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) - output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) - cache_read_tokens = from_union([from_none, from_float], obj.get("cacheReadTokens")) - cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens")) - reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) - cost = from_union([from_none, from_float], obj.get("cost")) - duration = from_union([from_none, from_float], obj.get("duration")) - ttft_ms = from_union([from_none, from_float], obj.get("ttftMs")) - inter_token_latency_ms = from_union([from_none, from_float], obj.get("interTokenLatencyMs")) - initiator = from_union([from_none, from_str], obj.get("initiator")) - api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) - provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) - parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) - quota_snapshots = from_union([from_none, lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) - copilot_usage = from_union([from_none, AssistantUsageCopilotUsage.from_dict], obj.get("copilotUsage")) - reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) - return AssistantUsageData( - model=model, - input_tokens=input_tokens, - output_tokens=output_tokens, - cache_read_tokens=cache_read_tokens, - cache_write_tokens=cache_write_tokens, - reasoning_tokens=reasoning_tokens, - cost=cost, - duration=duration, - ttft_ms=ttft_ms, - inter_token_latency_ms=inter_token_latency_ms, - initiator=initiator, - api_call_id=api_call_id, - provider_call_id=provider_call_id, - parent_tool_call_id=parent_tool_call_id, - quota_snapshots=quota_snapshots, - copilot_usage=copilot_usage, - reasoning_effort=reasoning_effort, + request_id = from_str(obj.get("requestId")) + return SamplingCompletedData( + request_id=request_id, ) def to_dict(self) -> dict: result: dict = {} - result["model"] = from_str(self.model) - if self.input_tokens is not None: - result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) - if self.output_tokens is not None: - result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) - 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: - result["cacheWriteTokens"] = from_union([from_none, to_float], self.cache_write_tokens) - if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) - if self.cost is not None: - result["cost"] = from_union([from_none, to_float], self.cost) - if self.duration is not None: - result["duration"] = from_union([from_none, to_float], self.duration) - if self.ttft_ms is not None: - result["ttftMs"] = from_union([from_none, to_float], self.ttft_ms) - if self.inter_token_latency_ms is not None: - result["interTokenLatencyMs"] = from_union([from_none, to_float], self.inter_token_latency_ms) - if self.initiator is not None: - result["initiator"] = from_union([from_none, from_str], self.initiator) - if self.api_call_id is not None: - result["apiCallId"] = from_union([from_none, from_str], self.api_call_id) - if self.provider_call_id is not None: - result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) - if self.quota_snapshots is not None: - result["quotaSnapshots"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(AssistantUsageQuotaSnapshot, x), x)], self.quota_snapshots) - if self.copilot_usage is not None: - result["copilotUsage"] = from_union([from_none, lambda x: to_class(AssistantUsageCopilotUsage, x)], self.copilot_usage) - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + result["requestId"] = from_str(self.request_id) return result @dataclass -class AbortData: - "Turn abort information including the reason for termination" - reason: str +class SamplingRequestedData: + "Sampling request from an MCP server; contains the server name and a requestId for correlation" + mcp_request_id: Any + request_id: str + server_name: str @staticmethod - def from_dict(obj: Any) -> "AbortData": + def from_dict(obj: Any) -> "SamplingRequestedData": assert isinstance(obj, dict) - reason = from_str(obj.get("reason")) - return AbortData( - reason=reason, + mcp_request_id = obj.get("mcpRequestId") + request_id = from_str(obj.get("requestId")) + server_name = from_str(obj.get("serverName")) + return SamplingRequestedData( + mcp_request_id=mcp_request_id, + request_id=request_id, + server_name=server_name, ) def to_dict(self) -> dict: result: dict = {} - result["reason"] = from_str(self.reason) + result["mcpRequestId"] = self.mcp_request_id + result["requestId"] = from_str(self.request_id) + result["serverName"] = from_str(self.server_name) return result @dataclass -class ToolUserRequestedData: - "User-initiated tool invocation request with tool name and arguments" - tool_call_id: str - tool_name: str - arguments: Any = None - +class SessionBackgroundTasksChangedData: @staticmethod - def from_dict(obj: Any) -> "ToolUserRequestedData": + def from_dict(obj: Any) -> "SessionBackgroundTasksChangedData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - tool_name = from_str(obj.get("toolName")) - arguments = obj.get("arguments") - return ToolUserRequestedData( - tool_call_id=tool_call_id, - tool_name=tool_name, - arguments=arguments, - ) + return SessionBackgroundTasksChangedData() def to_dict(self) -> dict: - result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["toolName"] = from_str(self.tool_name) - if self.arguments is not None: - result["arguments"] = self.arguments - return result + return {} @dataclass -class ToolExecutionStartData: - "Tool execution startup details including MCP server information when applicable" - tool_call_id: str - tool_name: str - arguments: Any = None - mcp_server_name: str | None = None - mcp_tool_name: str | None = None - # Deprecated: this field is deprecated. - parent_tool_call_id: str | None = None +class SessionCompactionCompleteData: + "Conversation compaction results including success status, metrics, and optional error details" + success: bool + checkpoint_number: float | None = None + checkpoint_path: str | None = None + compaction_tokens_used: CompactionCompleteCompactionTokensUsed | None = None + conversation_tokens: float | None = None + error: str | None = None + messages_removed: float | None = None + post_compaction_tokens: float | None = None + pre_compaction_messages_length: float | None = None + pre_compaction_tokens: float | None = None + request_id: str | None = None + summary_content: str | None = None + system_tokens: float | None = None + tokens_removed: float | None = None + tool_definitions_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionStartData": + def from_dict(obj: Any) -> "SessionCompactionCompleteData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - tool_name = from_str(obj.get("toolName")) - arguments = obj.get("arguments") - mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) - mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) - parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) - return ToolExecutionStartData( - tool_call_id=tool_call_id, - tool_name=tool_name, - arguments=arguments, - mcp_server_name=mcp_server_name, - mcp_tool_name=mcp_tool_name, - parent_tool_call_id=parent_tool_call_id, + success = from_bool(obj.get("success")) + checkpoint_number = from_union([from_none, from_float], obj.get("checkpointNumber")) + checkpoint_path = from_union([from_none, from_str], obj.get("checkpointPath")) + compaction_tokens_used = from_union([from_none, CompactionCompleteCompactionTokensUsed.from_dict], obj.get("compactionTokensUsed")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + error = from_union([from_none, from_str], obj.get("error")) + messages_removed = from_union([from_none, from_float], obj.get("messagesRemoved")) + post_compaction_tokens = from_union([from_none, from_float], obj.get("postCompactionTokens")) + pre_compaction_messages_length = from_union([from_none, from_float], obj.get("preCompactionMessagesLength")) + pre_compaction_tokens = from_union([from_none, from_float], obj.get("preCompactionTokens")) + request_id = from_union([from_none, from_str], obj.get("requestId")) + summary_content = from_union([from_none, from_str], obj.get("summaryContent")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + tokens_removed = from_union([from_none, from_float], obj.get("tokensRemoved")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + return SessionCompactionCompleteData( + success=success, + checkpoint_number=checkpoint_number, + checkpoint_path=checkpoint_path, + compaction_tokens_used=compaction_tokens_used, + conversation_tokens=conversation_tokens, + error=error, + messages_removed=messages_removed, + post_compaction_tokens=post_compaction_tokens, + pre_compaction_messages_length=pre_compaction_messages_length, + pre_compaction_tokens=pre_compaction_tokens, + request_id=request_id, + summary_content=summary_content, + system_tokens=system_tokens, + tokens_removed=tokens_removed, + tool_definitions_tokens=tool_definitions_tokens, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["toolName"] = from_str(self.tool_name) - if self.arguments is not None: - result["arguments"] = self.arguments - if self.mcp_server_name is not None: - result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) - if self.mcp_tool_name is not None: - result["mcpToolName"] = from_union([from_none, from_str], self.mcp_tool_name) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + result["success"] = from_bool(self.success) + if self.checkpoint_number is not None: + result["checkpointNumber"] = from_union([from_none, to_float], self.checkpoint_number) + if self.checkpoint_path is not None: + result["checkpointPath"] = from_union([from_none, from_str], self.checkpoint_path) + if self.compaction_tokens_used is not None: + result["compactionTokensUsed"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsed, x)], self.compaction_tokens_used) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) + if self.error is not None: + result["error"] = from_union([from_none, from_str], self.error) + if self.messages_removed is not None: + result["messagesRemoved"] = from_union([from_none, to_float], self.messages_removed) + if self.post_compaction_tokens is not None: + result["postCompactionTokens"] = from_union([from_none, to_float], self.post_compaction_tokens) + if self.pre_compaction_messages_length is not None: + result["preCompactionMessagesLength"] = from_union([from_none, to_float], self.pre_compaction_messages_length) + if self.pre_compaction_tokens is not None: + result["preCompactionTokens"] = from_union([from_none, to_float], self.pre_compaction_tokens) + if self.request_id is not None: + result["requestId"] = from_union([from_none, from_str], self.request_id) + if self.summary_content is not None: + result["summaryContent"] = from_union([from_none, from_str], self.summary_content) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) + if self.tokens_removed is not None: + result["tokensRemoved"] = from_union([from_none, to_float], self.tokens_removed) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @dataclass -class ToolExecutionPartialResultData: - "Streaming tool execution output for incremental result display" - tool_call_id: str - partial_output: str +class SessionCompactionStartData: + "Context window breakdown at the start of LLM-powered conversation compaction" + conversation_tokens: float | None = None + system_tokens: float | None = None + tool_definitions_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionPartialResultData": + def from_dict(obj: Any) -> "SessionCompactionStartData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - partial_output = from_str(obj.get("partialOutput")) - return ToolExecutionPartialResultData( - tool_call_id=tool_call_id, - partial_output=partial_output, + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + return SessionCompactionStartData( + conversation_tokens=conversation_tokens, + system_tokens=system_tokens, + tool_definitions_tokens=tool_definitions_tokens, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["partialOutput"] = from_str(self.partial_output) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @dataclass -class ToolExecutionProgressData: - "Tool execution progress notification with status message" - tool_call_id: str - progress_message: str +class SessionContextChangedData: + "Working directory and git context at session start" + cwd: str + base_commit: str | None = None + branch: str | None = None + git_root: str | None = None + head_commit: str | None = None + host_type: WorkingDirectoryContextHostType | None = None + repository: str | None = None + repository_host: str | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionProgressData": + def from_dict(obj: Any) -> "SessionContextChangedData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - progress_message = from_str(obj.get("progressMessage")) - return ToolExecutionProgressData( - tool_call_id=tool_call_id, - progress_message=progress_message, + cwd = from_str(obj.get("cwd")) + base_commit = from_union([from_none, from_str], obj.get("baseCommit")) + branch = from_union([from_none, from_str], obj.get("branch")) + git_root = from_union([from_none, from_str], obj.get("gitRoot")) + head_commit = from_union([from_none, from_str], obj.get("headCommit")) + host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) + repository = from_union([from_none, from_str], obj.get("repository")) + repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) + return SessionContextChangedData( + cwd=cwd, + base_commit=base_commit, + branch=branch, + git_root=git_root, + head_commit=head_commit, + host_type=host_type, + repository=repository, + repository_host=repository_host, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["progressMessage"] = from_str(self.progress_message) + result["cwd"] = from_str(self.cwd) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, from_str], self.base_commit) + if self.branch is not None: + result["branch"] = from_union([from_none, from_str], self.branch) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, from_str], self.git_root) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, from_str], self.head_commit) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) + if self.repository is not None: + result["repository"] = from_union([from_none, from_str], self.repository) + if self.repository_host is not None: + result["repositoryHost"] = from_union([from_none, from_str], self.repository_host) return result @dataclass -class ToolExecutionCompleteDataResultContentsItemIconsItem: - "Icon image for a resource" - src: str - mime_type: str | None = None - sizes: list[str] | None = None - theme: ToolExecutionCompleteDataResultContentsItemIconsItemTheme | None = None +class SessionCustomAgentsUpdatedData: + agents: list[CustomAgentsUpdatedAgent] + errors: list[str] + warnings: list[str] @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItemIconsItem": + def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": assert isinstance(obj, dict) - src = from_str(obj.get("src")) - mime_type = from_union([from_none, from_str], obj.get("mimeType")) - sizes = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("sizes")) - theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], obj.get("theme")) - return ToolExecutionCompleteDataResultContentsItemIconsItem( - src=src, - mime_type=mime_type, - sizes=sizes, - theme=theme, + agents = from_list(CustomAgentsUpdatedAgent.from_dict, obj.get("agents")) + errors = from_list(from_str, obj.get("errors")) + warnings = from_list(from_str, obj.get("warnings")) + return SessionCustomAgentsUpdatedData( + agents=agents, + errors=errors, + warnings=warnings, ) def to_dict(self) -> dict: result: dict = {} - result["src"] = from_str(self.src) - if self.mime_type is not None: - result["mimeType"] = from_union([from_none, from_str], self.mime_type) - if self.sizes is not None: - result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes) - if self.theme is not None: - result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], self.theme) + result["agents"] = from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), self.agents) + result["errors"] = from_list(from_str, self.errors) + result["warnings"] = from_list(from_str, self.warnings) return result @dataclass -class ToolExecutionCompleteDataResultContentsItem: - "A content block within a tool result, which may be text, terminal output, image, audio, or a resource" - type: ToolExecutionCompleteDataResultContentsItemType - text: str | None = None - exit_code: float | None = None - cwd: str | None = None - data: str | None = None - mime_type: str | None = None - icons: list[ToolExecutionCompleteDataResultContentsItemIconsItem] | None = None - name: str | None = None - title: str | None = None - uri: str | None = None - description: str | None = None - size: float | None = None - resource: Any = None +class SessionErrorData: + "Error details for timeline display including message and optional diagnostic information" + error_type: str + message: str + provider_call_id: str | None = None + stack: str | None = None + status_code: int | None = None + url: str | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": + def from_dict(obj: Any) -> "SessionErrorData": assert isinstance(obj, dict) - type = parse_enum(ToolExecutionCompleteDataResultContentsItemType, obj.get("type")) - text = from_union([from_none, from_str], obj.get("text")) - exit_code = from_union([from_none, from_float], obj.get("exitCode")) - cwd = from_union([from_none, from_str], obj.get("cwd")) - data = from_union([from_none, from_str], obj.get("data")) - mime_type = from_union([from_none, from_str], obj.get("mimeType")) - icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict, x)], obj.get("icons")) - name = from_union([from_none, from_str], obj.get("name")) - title = from_union([from_none, from_str], obj.get("title")) - uri = from_union([from_none, from_str], obj.get("uri")) - description = from_union([from_none, from_str], obj.get("description")) - size = from_union([from_none, from_float], obj.get("size")) - resource = obj.get("resource") - return ToolExecutionCompleteDataResultContentsItem( - type=type, - text=text, - exit_code=exit_code, - cwd=cwd, - data=data, - mime_type=mime_type, - icons=icons, - name=name, - title=title, - uri=uri, - description=description, - size=size, - resource=resource, + error_type = from_str(obj.get("errorType")) + message = from_str(obj.get("message")) + provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) + stack = from_union([from_none, from_str], obj.get("stack")) + status_code = from_union([from_none, from_int], obj.get("statusCode")) + url = from_union([from_none, from_str], obj.get("url")) + return SessionErrorData( + error_type=error_type, + message=message, + provider_call_id=provider_call_id, + stack=stack, + status_code=status_code, + url=url, ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(ToolExecutionCompleteDataResultContentsItemType, self.type) - if self.text is not None: - result["text"] = from_union([from_none, from_str], self.text) - if self.exit_code is not None: - result["exitCode"] = from_union([from_none, to_float], self.exit_code) - if self.cwd is not None: - result["cwd"] = from_union([from_none, from_str], self.cwd) - if self.data is not None: - result["data"] = from_union([from_none, from_str], self.data) - if self.mime_type is not None: - result["mimeType"] = from_union([from_none, from_str], self.mime_type) - if self.icons is not None: - result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItemIconsItem, x), x)], self.icons) - if self.name is not None: - result["name"] = from_union([from_none, from_str], self.name) - if self.title is not None: - result["title"] = from_union([from_none, from_str], self.title) - if self.uri is not None: - result["uri"] = from_union([from_none, from_str], self.uri) - if self.description is not None: - result["description"] = from_union([from_none, from_str], self.description) - if self.size is not None: - result["size"] = from_union([from_none, to_float], self.size) - if self.resource is not None: - result["resource"] = self.resource + result["errorType"] = from_str(self.error_type) + result["message"] = from_str(self.message) + if self.provider_call_id is not None: + result["providerCallId"] = from_union([from_none, from_str], self.provider_call_id) + if self.stack is not None: + result["stack"] = from_union([from_none, from_str], self.stack) + if self.status_code is not None: + result["statusCode"] = from_union([from_none, to_int], self.status_code) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) return result @dataclass -class ToolExecutionCompleteDataResult: - "Tool execution result on success" - content: str - detailed_content: str | None = None - contents: list[ToolExecutionCompleteDataResultContentsItem] | None = None +class SessionExtensionsLoadedData: + extensions: list[ExtensionsLoadedExtension] @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResult": + def from_dict(obj: Any) -> "SessionExtensionsLoadedData": assert isinstance(obj, dict) - content = from_str(obj.get("content")) - detailed_content = from_union([from_none, from_str], obj.get("detailedContent")) - contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItem.from_dict, x)], obj.get("contents")) - return ToolExecutionCompleteDataResult( - content=content, - detailed_content=detailed_content, - contents=contents, + extensions = from_list(ExtensionsLoadedExtension.from_dict, obj.get("extensions")) + return SessionExtensionsLoadedData( + extensions=extensions, ) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - if self.detailed_content is not None: - result["detailedContent"] = from_union([from_none, from_str], self.detailed_content) - if self.contents is not None: - result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItem, x), x)], self.contents) + result["extensions"] = from_list(lambda x: to_class(ExtensionsLoadedExtension, x), self.extensions) return result @dataclass -class ToolExecutionCompleteDataError: - "Error details when the tool execution failed" - message: str - code: str | None = None +class SessionHandoffData: + "Session handoff metadata including source, context, and repository information" + handoff_time: datetime + source_type: HandoffSourceType + context: str | None = None + host: str | None = None + remote_session_id: str | None = None + repository: HandoffRepository | None = None + summary: str | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataError": + def from_dict(obj: Any) -> "SessionHandoffData": assert isinstance(obj, dict) - message = from_str(obj.get("message")) - code = from_union([from_none, from_str], obj.get("code")) - return ToolExecutionCompleteDataError( - message=message, - code=code, + handoff_time = from_datetime(obj.get("handoffTime")) + source_type = parse_enum(HandoffSourceType, obj.get("sourceType")) + context = from_union([from_none, from_str], obj.get("context")) + host = from_union([from_none, from_str], obj.get("host")) + remote_session_id = from_union([from_none, from_str], obj.get("remoteSessionId")) + repository = from_union([from_none, HandoffRepository.from_dict], obj.get("repository")) + summary = from_union([from_none, from_str], obj.get("summary")) + return SessionHandoffData( + handoff_time=handoff_time, + source_type=source_type, + context=context, + host=host, + remote_session_id=remote_session_id, + repository=repository, + summary=summary, ) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.code is not None: - result["code"] = from_union([from_none, from_str], self.code) + result["handoffTime"] = to_datetime(self.handoff_time) + result["sourceType"] = to_enum(HandoffSourceType, self.source_type) + if self.context is not None: + result["context"] = from_union([from_none, from_str], self.context) + if self.host is not None: + result["host"] = from_union([from_none, from_str], self.host) + if self.remote_session_id is not None: + result["remoteSessionId"] = from_union([from_none, from_str], self.remote_session_id) + if self.repository is not None: + result["repository"] = from_union([from_none, lambda x: to_class(HandoffRepository, x)], self.repository) + if self.summary is not None: + result["summary"] = from_union([from_none, from_str], self.summary) return result @dataclass -class ToolExecutionCompleteData: - "Tool execution completion results including success status, detailed output, and error information" - tool_call_id: str - success: bool - model: str | None = None - interaction_id: str | None = None - is_user_requested: bool | None = None - result: ToolExecutionCompleteDataResult | None = None - error: ToolExecutionCompleteDataError | None = None - tool_telemetry: dict[str, Any] | None = None - # Deprecated: this field is deprecated. - parent_tool_call_id: str | None = None +class SessionIdleData: + "Payload indicating the session is idle with no background agents in flight" + aborted: bool | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteData": + def from_dict(obj: Any) -> "SessionIdleData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - success = from_bool(obj.get("success")) - model = from_union([from_none, from_str], obj.get("model")) - interaction_id = from_union([from_none, from_str], obj.get("interactionId")) - is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) - result = from_union([from_none, ToolExecutionCompleteDataResult.from_dict], obj.get("result")) - error = from_union([from_none, ToolExecutionCompleteDataError.from_dict], obj.get("error")) - tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) - parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) - return ToolExecutionCompleteData( - tool_call_id=tool_call_id, - success=success, - model=model, - interaction_id=interaction_id, - is_user_requested=is_user_requested, - result=result, - error=error, - tool_telemetry=tool_telemetry, - parent_tool_call_id=parent_tool_call_id, + aborted = from_union([from_none, from_bool], obj.get("aborted")) + return SessionIdleData( + aborted=aborted, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["success"] = from_bool(self.success) - if self.model is not None: - result["model"] = from_union([from_none, from_str], self.model) - if self.interaction_id is not None: - result["interactionId"] = from_union([from_none, from_str], self.interaction_id) - if self.is_user_requested is not None: - result["isUserRequested"] = from_union([from_none, from_bool], self.is_user_requested) - if self.result is not None: - result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataResult, x)], self.result) - if self.error is not None: - result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataError, x)], self.error) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) - if self.parent_tool_call_id is not None: - result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.aborted is not None: + result["aborted"] = from_union([from_none, from_bool], self.aborted) return result @dataclass -class SkillInvokedData: - "Skill invocation details including content, allowed tools, and plugin metadata" - name: str - path: str - content: str - allowed_tools: list[str] | None = None - plugin_name: str | None = None - plugin_version: str | None = None - description: str | None = None +class SessionInfoData: + "Informational message for timeline display with categorization" + info_type: str + message: str + url: str | None = None @staticmethod - def from_dict(obj: Any) -> "SkillInvokedData": + def from_dict(obj: Any) -> "SessionInfoData": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - path = from_str(obj.get("path")) - content = from_str(obj.get("content")) - allowed_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("allowedTools")) - plugin_name = from_union([from_none, from_str], obj.get("pluginName")) - plugin_version = from_union([from_none, from_str], obj.get("pluginVersion")) - description = from_union([from_none, from_str], obj.get("description")) - return SkillInvokedData( - name=name, - path=path, - content=content, - allowed_tools=allowed_tools, - plugin_name=plugin_name, - plugin_version=plugin_version, - description=description, + info_type = from_str(obj.get("infoType")) + message = from_str(obj.get("message")) + url = from_union([from_none, from_str], obj.get("url")) + return SessionInfoData( + info_type=info_type, + message=message, + url=url, ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["path"] = from_str(self.path) - result["content"] = from_str(self.content) - if self.allowed_tools is not None: - result["allowedTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.allowed_tools) - if self.plugin_name is not None: - result["pluginName"] = from_union([from_none, from_str], self.plugin_name) - if self.plugin_version is not None: - result["pluginVersion"] = from_union([from_none, from_str], self.plugin_version) - if self.description is not None: - result["description"] = from_union([from_none, from_str], self.description) + result["infoType"] = from_str(self.info_type) + result["message"] = from_str(self.message) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) return result @dataclass -class SubagentStartedData: - "Sub-agent startup details including parent tool call and agent information" - tool_call_id: str - agent_name: str - agent_display_name: str - agent_description: str +class SessionMcpServerStatusChangedData: + server_name: str + status: McpServerStatusChangedStatus @staticmethod - def from_dict(obj: Any) -> "SubagentStartedData": + def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - agent_name = from_str(obj.get("agentName")) - agent_display_name = from_str(obj.get("agentDisplayName")) - agent_description = from_str(obj.get("agentDescription")) - return SubagentStartedData( - tool_call_id=tool_call_id, - agent_name=agent_name, - agent_display_name=agent_display_name, - agent_description=agent_description, + server_name = from_str(obj.get("serverName")) + status = parse_enum(McpServerStatusChangedStatus, obj.get("status")) + return SessionMcpServerStatusChangedData( + server_name=server_name, + status=status, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["agentName"] = from_str(self.agent_name) - result["agentDisplayName"] = from_str(self.agent_display_name) - result["agentDescription"] = from_str(self.agent_description) + result["serverName"] = from_str(self.server_name) + result["status"] = to_enum(McpServerStatusChangedStatus, self.status) return result @dataclass -class SubagentCompletedData: - "Sub-agent completion details for successful execution" - tool_call_id: str - agent_name: str - agent_display_name: str - model: str | None = None - total_tool_calls: float | None = None - total_tokens: float | None = None - duration_ms: float | None = None +class SessionMcpServersLoadedData: + servers: list[McpServersLoadedServer] @staticmethod - def from_dict(obj: Any) -> "SubagentCompletedData": + def from_dict(obj: Any) -> "SessionMcpServersLoadedData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - agent_name = from_str(obj.get("agentName")) - agent_display_name = from_str(obj.get("agentDisplayName")) - model = from_union([from_none, from_str], obj.get("model")) - total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) - total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) - duration_ms = from_union([from_none, from_float], obj.get("durationMs")) - return SubagentCompletedData( - tool_call_id=tool_call_id, - agent_name=agent_name, - agent_display_name=agent_display_name, - model=model, - total_tool_calls=total_tool_calls, - total_tokens=total_tokens, - duration_ms=duration_ms, + servers = from_list(McpServersLoadedServer.from_dict, obj.get("servers")) + return SessionMcpServersLoadedData( + servers=servers, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["agentName"] = from_str(self.agent_name) - result["agentDisplayName"] = from_str(self.agent_display_name) - if self.model is not None: - result["model"] = from_union([from_none, from_str], self.model) - if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) - if self.total_tokens is not None: - result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) - if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + result["servers"] = from_list(lambda x: to_class(McpServersLoadedServer, x), self.servers) return result @dataclass -class SubagentFailedData: - "Sub-agent failure details including error message and agent information" - tool_call_id: str - agent_name: str - agent_display_name: str - error: str - model: str | None = None - total_tool_calls: float | None = None - total_tokens: float | None = None - duration_ms: float | None = None +class SessionModeChangedData: + "Agent mode change details including previous and new modes" + new_mode: str + previous_mode: str @staticmethod - def from_dict(obj: Any) -> "SubagentFailedData": + def from_dict(obj: Any) -> "SessionModeChangedData": assert isinstance(obj, dict) - tool_call_id = from_str(obj.get("toolCallId")) - agent_name = from_str(obj.get("agentName")) - agent_display_name = from_str(obj.get("agentDisplayName")) - error = from_str(obj.get("error")) - model = from_union([from_none, from_str], obj.get("model")) - total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) - total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) - duration_ms = from_union([from_none, from_float], obj.get("durationMs")) - return SubagentFailedData( - tool_call_id=tool_call_id, - agent_name=agent_name, - agent_display_name=agent_display_name, - error=error, - model=model, - total_tool_calls=total_tool_calls, - total_tokens=total_tokens, - duration_ms=duration_ms, + new_mode = from_str(obj.get("newMode")) + previous_mode = from_str(obj.get("previousMode")) + return SessionModeChangedData( + new_mode=new_mode, + previous_mode=previous_mode, ) def to_dict(self) -> dict: result: dict = {} - result["toolCallId"] = from_str(self.tool_call_id) - result["agentName"] = from_str(self.agent_name) - result["agentDisplayName"] = from_str(self.agent_display_name) - result["error"] = from_str(self.error) - if self.model is not None: - result["model"] = from_union([from_none, from_str], self.model) - if self.total_tool_calls is not None: - result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) - if self.total_tokens is not None: - result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) - if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + result["newMode"] = from_str(self.new_mode) + result["previousMode"] = from_str(self.previous_mode) return result @dataclass -class SubagentSelectedData: - "Custom agent selection details including name and available tools" - agent_name: str - agent_display_name: str - tools: list[str] | None +class SessionModelChangeData: + "Model change details including previous and new model identifiers" + new_model: str + previous_model: str | None = None + previous_reasoning_effort: str | None = None + reasoning_effort: str | None = None @staticmethod - def from_dict(obj: Any) -> "SubagentSelectedData": + def from_dict(obj: Any) -> "SessionModelChangeData": assert isinstance(obj, dict) - agent_name = from_str(obj.get("agentName")) - agent_display_name = from_str(obj.get("agentDisplayName")) - tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tools")) - return SubagentSelectedData( - agent_name=agent_name, - agent_display_name=agent_display_name, - tools=tools, + new_model = from_str(obj.get("newModel")) + previous_model = from_union([from_none, from_str], obj.get("previousModel")) + previous_reasoning_effort = from_union([from_none, from_str], obj.get("previousReasoningEffort")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + return SessionModelChangeData( + new_model=new_model, + previous_model=previous_model, + previous_reasoning_effort=previous_reasoning_effort, + reasoning_effort=reasoning_effort, ) def to_dict(self) -> dict: result: dict = {} - result["agentName"] = from_str(self.agent_name) - result["agentDisplayName"] = from_str(self.agent_display_name) - result["tools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.tools) + result["newModel"] = from_str(self.new_model) + if self.previous_model is not None: + result["previousModel"] = from_union([from_none, from_str], self.previous_model) + if self.previous_reasoning_effort is not None: + result["previousReasoningEffort"] = from_union([from_none, from_str], self.previous_reasoning_effort) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) return result @dataclass -class SubagentDeselectedData: - "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" - @staticmethod - def from_dict(obj: Any) -> "SubagentDeselectedData": - assert isinstance(obj, dict) - return SubagentDeselectedData() - - def to_dict(self) -> dict: - return {} - - -@dataclass -class HookStartData: - "Hook invocation start details including type and input data" - hook_invocation_id: str - hook_type: str - input: Any = None +class SessionPlanChangedData: + "Plan file operation details indicating what changed" + operation: PlanChangedOperation @staticmethod - def from_dict(obj: Any) -> "HookStartData": + def from_dict(obj: Any) -> "SessionPlanChangedData": assert isinstance(obj, dict) - hook_invocation_id = from_str(obj.get("hookInvocationId")) - hook_type = from_str(obj.get("hookType")) - input = obj.get("input") - return HookStartData( - hook_invocation_id=hook_invocation_id, - hook_type=hook_type, - input=input, + operation = parse_enum(PlanChangedOperation, obj.get("operation")) + return SessionPlanChangedData( + operation=operation, ) def to_dict(self) -> dict: result: dict = {} - result["hookInvocationId"] = from_str(self.hook_invocation_id) - result["hookType"] = from_str(self.hook_type) - if self.input is not None: - result["input"] = self.input + result["operation"] = to_enum(PlanChangedOperation, self.operation) return result @dataclass -class HookEndDataError: - "Error details when the hook failed" - message: str - stack: str | None = None +class SessionRemoteSteerableChangedData: + "Notifies Mission Control that the session's remote steering capability has changed" + remote_steerable: bool @staticmethod - def from_dict(obj: Any) -> "HookEndDataError": + def from_dict(obj: Any) -> "SessionRemoteSteerableChangedData": assert isinstance(obj, dict) - message = from_str(obj.get("message")) - stack = from_union([from_none, from_str], obj.get("stack")) - return HookEndDataError( - message=message, - stack=stack, + remote_steerable = from_bool(obj.get("remoteSteerable")) + return SessionRemoteSteerableChangedData( + remote_steerable=remote_steerable, ) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.stack is not None: - result["stack"] = from_union([from_none, from_str], self.stack) + result["remoteSteerable"] = from_bool(self.remote_steerable) return result @dataclass -class HookEndData: - "Hook invocation completion details including output, success status, and error information" - hook_invocation_id: str - hook_type: str - success: bool - output: Any = None - error: HookEndDataError | None = None +class SessionResumeData: + "Session resume metadata including current context and event count" + event_count: float + resume_time: datetime + already_in_use: bool | None = None + context: WorkingDirectoryContext | None = None + reasoning_effort: str | None = None + remote_steerable: bool | None = None + selected_model: str | None = None @staticmethod - def from_dict(obj: Any) -> "HookEndData": + def from_dict(obj: Any) -> "SessionResumeData": assert isinstance(obj, dict) - hook_invocation_id = from_str(obj.get("hookInvocationId")) - hook_type = from_str(obj.get("hookType")) - success = from_bool(obj.get("success")) - output = obj.get("output") - error = from_union([from_none, HookEndDataError.from_dict], obj.get("error")) - return HookEndData( - hook_invocation_id=hook_invocation_id, - hook_type=hook_type, - success=success, - output=output, - error=error, + event_count = from_float(obj.get("eventCount")) + resume_time = from_datetime(obj.get("resumeTime")) + already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) + selected_model = from_union([from_none, from_str], obj.get("selectedModel")) + return SessionResumeData( + event_count=event_count, + resume_time=resume_time, + already_in_use=already_in_use, + context=context, + reasoning_effort=reasoning_effort, + remote_steerable=remote_steerable, + selected_model=selected_model, ) def to_dict(self) -> dict: result: dict = {} - result["hookInvocationId"] = from_str(self.hook_invocation_id) - result["hookType"] = from_str(self.hook_type) - result["success"] = from_bool(self.success) - if self.output is not None: - result["output"] = self.output - if self.error is not None: - result["error"] = from_union([from_none, lambda x: to_class(HookEndDataError, x)], self.error) + result["eventCount"] = to_float(self.event_count) + result["resumeTime"] = to_datetime(self.resume_time) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, from_str], self.selected_model) return result @dataclass -class SystemMessageDataMetadata: - "Metadata about the prompt template and its construction" - prompt_version: str | None = None - variables: dict[str, Any] | None = None +class SessionShutdownData: + "Session termination metrics including usage statistics, code changes, and shutdown reason" + code_changes: ShutdownCodeChanges + model_metrics: dict[str, ShutdownModelMetric] + session_start_time: float + shutdown_type: ShutdownType + total_api_duration_ms: float + total_premium_requests: float + conversation_tokens: float | None = None + current_model: str | None = None + current_tokens: float | None = None + error_reason: str | None = None + system_tokens: float | None = None + tool_definitions_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> "SystemMessageDataMetadata": + def from_dict(obj: Any) -> "SessionShutdownData": assert isinstance(obj, dict) - prompt_version = from_union([from_none, from_str], obj.get("promptVersion")) - variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) - return SystemMessageDataMetadata( - prompt_version=prompt_version, - variables=variables, + code_changes = ShutdownCodeChanges.from_dict(obj.get("codeChanges")) + model_metrics = from_dict(ShutdownModelMetric.from_dict, obj.get("modelMetrics")) + session_start_time = from_float(obj.get("sessionStartTime")) + shutdown_type = parse_enum(ShutdownType, obj.get("shutdownType")) + total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + total_premium_requests = from_float(obj.get("totalPremiumRequests")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + current_model = from_union([from_none, from_str], obj.get("currentModel")) + current_tokens = from_union([from_none, from_float], obj.get("currentTokens")) + error_reason = from_union([from_none, from_str], obj.get("errorReason")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + return SessionShutdownData( + code_changes=code_changes, + model_metrics=model_metrics, + session_start_time=session_start_time, + shutdown_type=shutdown_type, + total_api_duration_ms=total_api_duration_ms, + total_premium_requests=total_premium_requests, + conversation_tokens=conversation_tokens, + current_model=current_model, + current_tokens=current_tokens, + error_reason=error_reason, + system_tokens=system_tokens, + tool_definitions_tokens=tool_definitions_tokens, ) def to_dict(self) -> dict: result: dict = {} - if self.prompt_version is not None: - result["promptVersion"] = from_union([from_none, from_str], self.prompt_version) - if self.variables is not None: - result["variables"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.variables) + result["codeChanges"] = to_class(ShutdownCodeChanges, self.code_changes) + result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) + result["sessionStartTime"] = to_float(self.session_start_time) + result["shutdownType"] = to_enum(ShutdownType, self.shutdown_type) + result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["totalPremiumRequests"] = to_float(self.total_premium_requests) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) + if self.current_model is not None: + result["currentModel"] = from_union([from_none, from_str], self.current_model) + if self.current_tokens is not None: + result["currentTokens"] = from_union([from_none, to_float], self.current_tokens) + if self.error_reason is not None: + result["errorReason"] = from_union([from_none, from_str], self.error_reason) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) return result @dataclass -class SystemMessageData: - "System/developer instruction content with role and optional template metadata" - content: str - role: SystemMessageDataRole - name: str | None = None - metadata: SystemMessageDataMetadata | None = None +class SessionSkillsLoadedData: + skills: list[SkillsLoadedSkill] @staticmethod - def from_dict(obj: Any) -> "SystemMessageData": + def from_dict(obj: Any) -> "SessionSkillsLoadedData": assert isinstance(obj, dict) - content = from_str(obj.get("content")) - role = parse_enum(SystemMessageDataRole, obj.get("role")) - name = from_union([from_none, from_str], obj.get("name")) - metadata = from_union([from_none, SystemMessageDataMetadata.from_dict], obj.get("metadata")) - return SystemMessageData( - content=content, - role=role, - name=name, - metadata=metadata, + skills = from_list(SkillsLoadedSkill.from_dict, obj.get("skills")) + return SessionSkillsLoadedData( + skills=skills, ) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["role"] = to_enum(SystemMessageDataRole, self.role) - if self.name is not None: - result["name"] = from_union([from_none, from_str], self.name) - if self.metadata is not None: - result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageDataMetadata, x)], self.metadata) + result["skills"] = from_list(lambda x: to_class(SkillsLoadedSkill, x), self.skills) return result @dataclass -class SystemNotificationDataKind: - "Structured metadata identifying what triggered this notification" - type: SystemNotificationDataKindType - agent_id: str | None = None - agent_type: str | None = None - status: SystemNotificationDataKindStatus | None = None - description: str | None = None - prompt: str | None = None - shell_id: str | None = None - exit_code: float | None = None +class SessionSnapshotRewindData: + "Session rewind details including target event and count of removed events" + events_removed: float + up_to_event_id: str @staticmethod - def from_dict(obj: Any) -> "SystemNotificationDataKind": + def from_dict(obj: Any) -> "SessionSnapshotRewindData": assert isinstance(obj, dict) - type = parse_enum(SystemNotificationDataKindType, obj.get("type")) - agent_id = from_union([from_none, from_str], obj.get("agentId")) - agent_type = from_union([from_none, from_str], obj.get("agentType")) - status = from_union([from_none, lambda x: parse_enum(SystemNotificationDataKindStatus, x)], obj.get("status")) - description = from_union([from_none, from_str], obj.get("description")) - prompt = from_union([from_none, from_str], obj.get("prompt")) - shell_id = from_union([from_none, from_str], obj.get("shellId")) - exit_code = from_union([from_none, from_float], obj.get("exitCode")) - return SystemNotificationDataKind( - type=type, - agent_id=agent_id, - agent_type=agent_type, - status=status, - description=description, - prompt=prompt, - shell_id=shell_id, - exit_code=exit_code, + events_removed = from_float(obj.get("eventsRemoved")) + up_to_event_id = from_str(obj.get("upToEventId")) + return SessionSnapshotRewindData( + events_removed=events_removed, + up_to_event_id=up_to_event_id, ) def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(SystemNotificationDataKindType, self.type) - if self.agent_id is not None: - result["agentId"] = from_union([from_none, from_str], self.agent_id) - if self.agent_type is not None: - result["agentType"] = from_union([from_none, from_str], self.agent_type) - if self.status is not None: - result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationDataKindStatus, x)], self.status) - if self.description is not None: - result["description"] = from_union([from_none, from_str], self.description) - if self.prompt is not None: - result["prompt"] = from_union([from_none, from_str], self.prompt) - if self.shell_id is not None: - result["shellId"] = from_union([from_none, from_str], self.shell_id) - if self.exit_code is not None: - result["exitCode"] = from_union([from_none, to_float], self.exit_code) + result["eventsRemoved"] = to_float(self.events_removed) + result["upToEventId"] = from_str(self.up_to_event_id) return result @dataclass -class SystemNotificationData: - "System-generated notification for runtime events like background task completion" - content: str - kind: SystemNotificationDataKind +class SessionStartData: + "Session initialization metadata including context and configuration" + copilot_version: str + producer: str + session_id: str + start_time: datetime + version: float + already_in_use: bool | None = None + context: WorkingDirectoryContext | None = None + reasoning_effort: str | None = None + remote_steerable: bool | None = None + selected_model: str | None = None @staticmethod - def from_dict(obj: Any) -> "SystemNotificationData": + def from_dict(obj: Any) -> "SessionStartData": assert isinstance(obj, dict) - content = from_str(obj.get("content")) - kind = SystemNotificationDataKind.from_dict(obj.get("kind")) - return SystemNotificationData( - content=content, - kind=kind, + copilot_version = from_str(obj.get("copilotVersion")) + producer = from_str(obj.get("producer")) + session_id = from_str(obj.get("sessionId")) + start_time = from_datetime(obj.get("startTime")) + version = from_float(obj.get("version")) + already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) + context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) + reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) + remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) + selected_model = from_union([from_none, from_str], obj.get("selectedModel")) + return SessionStartData( + copilot_version=copilot_version, + producer=producer, + session_id=session_id, + start_time=start_time, + version=version, + already_in_use=already_in_use, + context=context, + reasoning_effort=reasoning_effort, + remote_steerable=remote_steerable, + selected_model=selected_model, ) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["kind"] = to_class(SystemNotificationDataKind, self.kind) + result["copilotVersion"] = from_str(self.copilot_version) + result["producer"] = from_str(self.producer) + result["sessionId"] = from_str(self.session_id) + result["startTime"] = to_datetime(self.start_time) + result["version"] = to_float(self.version) + if self.already_in_use is not None: + result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) + if self.context is not None: + result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) + if self.remote_steerable is not None: + result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) + if self.selected_model is not None: + result["selectedModel"] = from_union([from_none, from_str], self.selected_model) return result @dataclass -class PermissionRequestShellCommand: - identifier: str - read_only: bool +class SessionTaskCompleteData: + "Task completion notification with summary from the agent" + success: bool | None = None + summary: str | None = None @staticmethod - def from_dict(obj: Any) -> "PermissionRequestShellCommand": + def from_dict(obj: Any) -> "SessionTaskCompleteData": assert isinstance(obj, dict) - identifier = from_str(obj.get("identifier")) - read_only = from_bool(obj.get("readOnly")) - return PermissionRequestShellCommand( - identifier=identifier, - read_only=read_only, + success = from_union([from_none, from_bool], obj.get("success")) + summary = from_union([from_none, from_str], obj.get("summary", "")) + return SessionTaskCompleteData( + success=success, + summary=summary, ) def to_dict(self) -> dict: result: dict = {} - result["identifier"] = from_str(self.identifier) - result["readOnly"] = from_bool(self.read_only) + if self.success is not None: + result["success"] = from_union([from_none, from_bool], self.success) + if self.summary is not None: + result["summary"] = from_union([from_none, from_str], self.summary) return result @dataclass -class PermissionRequestShellPossibleURL: - url: str +class SessionTitleChangedData: + "Session title change payload containing the new display title" + title: str @staticmethod - def from_dict(obj: Any) -> "PermissionRequestShellPossibleURL": + def from_dict(obj: Any) -> "SessionTitleChangedData": assert isinstance(obj, dict) - url = from_str(obj.get("url")) - return PermissionRequestShellPossibleURL( - url=url, + title = from_str(obj.get("title")) + return SessionTitleChangedData( + title=title, ) def to_dict(self) -> dict: result: dict = {} - result["url"] = from_str(self.url) + result["title"] = from_str(self.title) return result @dataclass -class PermissionRequest: - "Details of the permission being requested" - kind: PermissionRequestedDataPermissionRequestKind - tool_call_id: str | None = None - full_command_text: str | None = None - intention: str | None = None - commands: list[PermissionRequestShellCommand] | None = None - possible_paths: list[str] | None = None - possible_urls: list[PermissionRequestShellPossibleURL] | None = None - has_write_file_redirection: bool | None = None - can_offer_session_approval: bool | None = None - warning: str | None = None - file_name: str | None = None - diff: str | None = None - new_file_contents: str | None = None - path: str | None = None - server_name: str | None = None - tool_name: str | None = None - tool_title: str | None = None - args: Any = None - read_only: bool | None = None +class SessionToolsUpdatedData: + model: str + + @staticmethod + def from_dict(obj: Any) -> "SessionToolsUpdatedData": + assert isinstance(obj, dict) + model = from_str(obj.get("model")) + return SessionToolsUpdatedData( + model=model, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["model"] = from_str(self.model) + return result + + +@dataclass +class SessionTruncationData: + "Conversation truncation statistics including token counts and removed content metrics" + messages_removed_during_truncation: float + performed_by: str + post_truncation_messages_length: float + post_truncation_tokens_in_messages: float + pre_truncation_messages_length: float + pre_truncation_tokens_in_messages: float + token_limit: float + tokens_removed_during_truncation: float + + @staticmethod + def from_dict(obj: Any) -> "SessionTruncationData": + assert isinstance(obj, dict) + messages_removed_during_truncation = from_float(obj.get("messagesRemovedDuringTruncation")) + performed_by = from_str(obj.get("performedBy")) + post_truncation_messages_length = from_float(obj.get("postTruncationMessagesLength")) + post_truncation_tokens_in_messages = from_float(obj.get("postTruncationTokensInMessages")) + pre_truncation_messages_length = from_float(obj.get("preTruncationMessagesLength")) + pre_truncation_tokens_in_messages = from_float(obj.get("preTruncationTokensInMessages")) + token_limit = from_float(obj.get("tokenLimit")) + tokens_removed_during_truncation = from_float(obj.get("tokensRemovedDuringTruncation")) + return SessionTruncationData( + messages_removed_during_truncation=messages_removed_during_truncation, + performed_by=performed_by, + post_truncation_messages_length=post_truncation_messages_length, + post_truncation_tokens_in_messages=post_truncation_tokens_in_messages, + pre_truncation_messages_length=pre_truncation_messages_length, + pre_truncation_tokens_in_messages=pre_truncation_tokens_in_messages, + token_limit=token_limit, + tokens_removed_during_truncation=tokens_removed_during_truncation, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["messagesRemovedDuringTruncation"] = to_float(self.messages_removed_during_truncation) + result["performedBy"] = from_str(self.performed_by) + result["postTruncationMessagesLength"] = to_float(self.post_truncation_messages_length) + result["postTruncationTokensInMessages"] = to_float(self.post_truncation_tokens_in_messages) + result["preTruncationMessagesLength"] = to_float(self.pre_truncation_messages_length) + result["preTruncationTokensInMessages"] = to_float(self.pre_truncation_tokens_in_messages) + result["tokenLimit"] = to_float(self.token_limit) + result["tokensRemovedDuringTruncation"] = to_float(self.tokens_removed_during_truncation) + return result + + +@dataclass +class SessionUsageInfoData: + "Current context window usage statistics including token and message counts" + current_tokens: float + messages_length: float + token_limit: float + conversation_tokens: float | None = None + is_initial: bool | None = None + system_tokens: float | None = None + tool_definitions_tokens: float | None = None + + @staticmethod + def from_dict(obj: Any) -> "SessionUsageInfoData": + assert isinstance(obj, dict) + current_tokens = from_float(obj.get("currentTokens")) + messages_length = from_float(obj.get("messagesLength")) + token_limit = from_float(obj.get("tokenLimit")) + conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) + is_initial = from_union([from_none, from_bool], obj.get("isInitial")) + system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + return SessionUsageInfoData( + current_tokens=current_tokens, + messages_length=messages_length, + token_limit=token_limit, + conversation_tokens=conversation_tokens, + is_initial=is_initial, + system_tokens=system_tokens, + tool_definitions_tokens=tool_definitions_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["currentTokens"] = to_float(self.current_tokens) + result["messagesLength"] = to_float(self.messages_length) + result["tokenLimit"] = to_float(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) + if self.is_initial is not None: + result["isInitial"] = from_union([from_none, from_bool], self.is_initial) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) + return result + + +@dataclass +class SessionWarningData: + "Warning message for timeline display with categorization" + message: str + warning_type: str url: str | None = None - action: PermissionRequestMemoryAction | None = None - subject: str | None = None - fact: str | None = None - citations: str | None = None - direction: PermissionRequestMemoryDirection | None = None - reason: str | None = None - tool_description: str | None = None - tool_args: Any = None - hook_message: str | None = None @staticmethod - def from_dict(obj: Any) -> "PermissionRequest": + def from_dict(obj: Any) -> "SessionWarningData": assert isinstance(obj, dict) - kind = parse_enum(PermissionRequestedDataPermissionRequestKind, obj.get("kind")) - tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) - full_command_text = from_union([from_none, from_str], obj.get("fullCommandText")) - intention = from_union([from_none, from_str], obj.get("intention")) - commands = from_union([from_none, lambda x: from_list(PermissionRequestShellCommand.from_dict, x)], obj.get("commands")) - possible_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("possiblePaths")) - possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleURL.from_dict, x)], obj.get("possibleUrls")) - has_write_file_redirection = from_union([from_none, from_bool], obj.get("hasWriteFileRedirection")) - can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) - warning = from_union([from_none, from_str], obj.get("warning")) - file_name = from_union([from_none, from_str], obj.get("fileName")) - diff = from_union([from_none, from_str], obj.get("diff")) - new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) - path = from_union([from_none, from_str], obj.get("path")) - server_name = from_union([from_none, from_str], obj.get("serverName")) - tool_name = from_union([from_none, from_str], obj.get("toolName")) - tool_title = from_union([from_none, from_str], obj.get("toolTitle")) - args = obj.get("args") - read_only = from_union([from_none, from_bool], obj.get("readOnly")) + message = from_str(obj.get("message")) + warning_type = from_str(obj.get("warningType")) url = from_union([from_none, from_str], obj.get("url")) - action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store")) - subject = from_union([from_none, from_str], obj.get("subject")) - fact = from_union([from_none, from_str], obj.get("fact")) - citations = from_union([from_none, from_str], obj.get("citations")) - direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) - reason = from_union([from_none, from_str], obj.get("reason")) - tool_description = from_union([from_none, from_str], obj.get("toolDescription")) - tool_args = obj.get("toolArgs") - hook_message = from_union([from_none, from_str], obj.get("hookMessage")) - return PermissionRequest( - kind=kind, - tool_call_id=tool_call_id, - full_command_text=full_command_text, - intention=intention, - commands=commands, - possible_paths=possible_paths, - possible_urls=possible_urls, - has_write_file_redirection=has_write_file_redirection, - can_offer_session_approval=can_offer_session_approval, - warning=warning, - file_name=file_name, - diff=diff, - new_file_contents=new_file_contents, - path=path, - server_name=server_name, - tool_name=tool_name, - tool_title=tool_title, - args=args, - read_only=read_only, + return SessionWarningData( + message=message, + warning_type=warning_type, url=url, - action=action, - subject=subject, - fact=fact, - citations=citations, - direction=direction, - reason=reason, - tool_description=tool_description, - tool_args=tool_args, - hook_message=hook_message, ) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionRequestedDataPermissionRequestKind, self.kind) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) - if self.full_command_text is not None: - result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text) - if self.intention is not None: - result["intention"] = from_union([from_none, from_str], self.intention) - if self.commands is not None: - result["commands"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellCommand, x), x)], self.commands) - if self.possible_paths is not None: - result["possiblePaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.possible_paths) - if self.possible_urls is not None: - result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x)], self.possible_urls) - if self.has_write_file_redirection is not None: - result["hasWriteFileRedirection"] = from_union([from_none, from_bool], self.has_write_file_redirection) - if self.can_offer_session_approval is not None: - result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval) - if self.warning is not None: - result["warning"] = from_union([from_none, from_str], self.warning) - if self.file_name is not None: - result["fileName"] = from_union([from_none, from_str], self.file_name) - if self.diff is not None: - result["diff"] = from_union([from_none, from_str], self.diff) - if self.new_file_contents is not None: - result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) - if self.path is not None: - result["path"] = from_union([from_none, from_str], self.path) - if self.server_name is not None: - result["serverName"] = from_union([from_none, from_str], self.server_name) - if self.tool_name is not None: - result["toolName"] = from_union([from_none, from_str], self.tool_name) - if self.tool_title is not None: - result["toolTitle"] = from_union([from_none, from_str], self.tool_title) - if self.args is not None: - result["args"] = self.args - if self.read_only is not None: - result["readOnly"] = from_union([from_none, from_bool], self.read_only) + result["message"] = from_str(self.message) + result["warningType"] = from_str(self.warning_type) if self.url is not None: result["url"] = from_union([from_none, from_str], self.url) - if self.action is not None: - result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) - if self.subject is not None: - result["subject"] = from_union([from_none, from_str], self.subject) - if self.fact is not None: - result["fact"] = from_union([from_none, from_str], self.fact) - if self.citations is not None: - result["citations"] = from_union([from_none, from_str], self.citations) - if self.direction is not None: - result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) - if self.reason is not None: - result["reason"] = from_union([from_none, from_str], self.reason) - if self.tool_description is not None: - result["toolDescription"] = from_union([from_none, from_str], self.tool_description) - if self.tool_args is not None: - result["toolArgs"] = self.tool_args - if self.hook_message is not None: - result["hookMessage"] = from_union([from_none, from_str], self.hook_message) return result @dataclass -class PermissionRequestedData: - "Permission request notification requiring client approval with request details" - request_id: str - permission_request: PermissionRequest - resolved_by_hook: bool | None = None +class SessionWorkspaceFileChangedData: + "Workspace file change details including path and operation type" + operation: WorkspaceFileChangedOperation + path: str @staticmethod - def from_dict(obj: Any) -> "PermissionRequestedData": + def from_dict(obj: Any) -> "SessionWorkspaceFileChangedData": + assert isinstance(obj, dict) + operation = parse_enum(WorkspaceFileChangedOperation, obj.get("operation")) + path = from_str(obj.get("path")) + return SessionWorkspaceFileChangedData( + operation=operation, + path=path, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["operation"] = to_enum(WorkspaceFileChangedOperation, self.operation) + result["path"] = from_str(self.path) + return result + + +@dataclass +class ShutdownCodeChanges: + "Aggregate code change metrics for the session" + files_modified: list[str] + lines_added: float + lines_removed: float + + @staticmethod + def from_dict(obj: Any) -> "ShutdownCodeChanges": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - permission_request = PermissionRequest.from_dict(obj.get("permissionRequest")) - resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook")) - return PermissionRequestedData( - request_id=request_id, - permission_request=permission_request, - resolved_by_hook=resolved_by_hook, + files_modified = from_list(from_str, obj.get("filesModified")) + lines_added = from_float(obj.get("linesAdded")) + lines_removed = from_float(obj.get("linesRemoved")) + return ShutdownCodeChanges( + files_modified=files_modified, + lines_added=lines_added, + lines_removed=lines_removed, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["permissionRequest"] = to_class(PermissionRequest, self.permission_request) - if self.resolved_by_hook is not None: - result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook) + result["filesModified"] = from_list(from_str, self.files_modified) + result["linesAdded"] = to_float(self.lines_added) + result["linesRemoved"] = to_float(self.lines_removed) return result @dataclass -class PermissionCompletedDataResult: - "The result of the permission request" - kind: PermissionCompletedKind +class ShutdownModelMetric: + requests: ShutdownModelMetricRequests + usage: ShutdownModelMetricUsage @staticmethod - def from_dict(obj: Any) -> "PermissionCompletedDataResult": + def from_dict(obj: Any) -> "ShutdownModelMetric": assert isinstance(obj, dict) - kind = parse_enum(PermissionCompletedKind, obj.get("kind")) - return PermissionCompletedDataResult( - kind=kind, + requests = ShutdownModelMetricRequests.from_dict(obj.get("requests")) + usage = ShutdownModelMetricUsage.from_dict(obj.get("usage")) + return ShutdownModelMetric( + requests=requests, + usage=usage, ) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionCompletedKind, self.kind) + result["requests"] = to_class(ShutdownModelMetricRequests, self.requests) + result["usage"] = to_class(ShutdownModelMetricUsage, self.usage) return result @dataclass -class PermissionCompletedData: - "Permission request completion notification signaling UI dismissal" - request_id: str - result: PermissionCompletedDataResult +class ShutdownModelMetricRequests: + "Request count and cost metrics" + cost: float + count: float @staticmethod - def from_dict(obj: Any) -> "PermissionCompletedData": + def from_dict(obj: Any) -> "ShutdownModelMetricRequests": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = PermissionCompletedDataResult.from_dict(obj.get("result")) - return PermissionCompletedData( - request_id=request_id, - result=result, + cost = from_float(obj.get("cost")) + count = from_float(obj.get("count")) + return ShutdownModelMetricRequests( + cost=cost, + count=count, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionCompletedDataResult, self.result) + result["cost"] = to_float(self.cost) + result["count"] = to_float(self.count) return result @dataclass -class UserInputRequestedData: - "User input request notification with question and optional predefined choices" - request_id: str - question: str - choices: list[str] | None = None - allow_freeform: bool | None = None - tool_call_id: str | None = None +class ShutdownModelMetricUsage: + "Token usage breakdown" + cache_read_tokens: float + cache_write_tokens: float + input_tokens: float + output_tokens: float + reasoning_tokens: float | None = None @staticmethod - def from_dict(obj: Any) -> "UserInputRequestedData": + def from_dict(obj: Any) -> "ShutdownModelMetricUsage": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - question = from_str(obj.get("question")) - choices = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("choices")) - allow_freeform = from_union([from_none, from_bool], obj.get("allowFreeform")) - tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) - return UserInputRequestedData( - request_id=request_id, - question=question, - choices=choices, - allow_freeform=allow_freeform, - tool_call_id=tool_call_id, + cache_read_tokens = from_float(obj.get("cacheReadTokens")) + cache_write_tokens = from_float(obj.get("cacheWriteTokens")) + input_tokens = from_float(obj.get("inputTokens")) + output_tokens = from_float(obj.get("outputTokens")) + reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) + return ShutdownModelMetricUsage( + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + input_tokens=input_tokens, + output_tokens=output_tokens, + reasoning_tokens=reasoning_tokens, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["question"] = from_str(self.question) - if self.choices is not None: - result["choices"] = from_union([from_none, lambda x: from_list(from_str, x)], self.choices) - if self.allow_freeform is not None: - result["allowFreeform"] = from_union([from_none, from_bool], self.allow_freeform) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + result["cacheReadTokens"] = to_float(self.cache_read_tokens) + result["cacheWriteTokens"] = to_float(self.cache_write_tokens) + result["inputTokens"] = to_float(self.input_tokens) + result["outputTokens"] = to_float(self.output_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) return result @dataclass -class UserInputCompletedData: - "User input request completion with the user's response" - request_id: str - answer: str | None = None - was_freeform: bool | None = None +class SkillInvokedData: + "Skill invocation details including content, allowed tools, and plugin metadata" + content: str + name: str + path: str + allowed_tools: list[str] | None = None + description: str | None = None + plugin_name: str | None = None + plugin_version: str | None = None @staticmethod - def from_dict(obj: Any) -> "UserInputCompletedData": + def from_dict(obj: Any) -> "SkillInvokedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - answer = from_union([from_none, from_str], obj.get("answer")) - was_freeform = from_union([from_none, from_bool], obj.get("wasFreeform")) - return UserInputCompletedData( - request_id=request_id, - answer=answer, - was_freeform=was_freeform, + content = from_str(obj.get("content")) + name = from_str(obj.get("name")) + path = from_str(obj.get("path")) + allowed_tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("allowedTools")) + description = from_union([from_none, from_str], obj.get("description")) + plugin_name = from_union([from_none, from_str], obj.get("pluginName")) + plugin_version = from_union([from_none, from_str], obj.get("pluginVersion")) + return SkillInvokedData( + content=content, + name=name, + path=path, + allowed_tools=allowed_tools, + description=description, + plugin_name=plugin_name, + plugin_version=plugin_version, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.answer is not None: - result["answer"] = from_union([from_none, from_str], self.answer) - if self.was_freeform is not None: - result["wasFreeform"] = from_union([from_none, from_bool], self.was_freeform) + result["content"] = from_str(self.content) + result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + if self.allowed_tools is not None: + result["allowedTools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.allowed_tools) + if self.description is not None: + result["description"] = from_union([from_none, from_str], self.description) + if self.plugin_name is not None: + result["pluginName"] = from_union([from_none, from_str], self.plugin_name) + if self.plugin_version is not None: + result["pluginVersion"] = from_union([from_none, from_str], self.plugin_version) return result @dataclass -class ElicitationRequestedSchema: - "JSON Schema describing the form fields to present to the user (form mode only)" - type: str - properties: dict[str, Any] - required: list[str] | None = None +class SkillsLoadedSkill: + description: str + enabled: bool + name: str + source: str + user_invocable: bool + path: str | None = None @staticmethod - def from_dict(obj: Any) -> "ElicitationRequestedSchema": + def from_dict(obj: Any) -> "SkillsLoadedSkill": assert isinstance(obj, dict) - type = from_str(obj.get("type")) - properties = from_dict(lambda x: x, obj.get("properties")) - required = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("required")) - return ElicitationRequestedSchema( - type=type, - properties=properties, - required=required, + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_none, from_str], obj.get("path")) + return SkillsLoadedSkill( + description=description, + enabled=enabled, + name=name, + source=source, + user_invocable=user_invocable, + path=path, ) def to_dict(self) -> dict: result: dict = {} - result["type"] = from_str(self.type) - result["properties"] = from_dict(lambda x: x, self.properties) - if self.required is not None: - result["required"] = from_union([from_none, lambda x: from_list(from_str, x)], self.required) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_none, from_str], self.path) return result @dataclass -class ElicitationRequestedData: - "Elicitation request; may be form-based (structured input) or URL-based (browser redirect)" - request_id: str - message: str - tool_call_id: str | None = None - elicitation_source: str | None = None - mode: ElicitationRequestedMode | None = None - requested_schema: ElicitationRequestedSchema | None = None - url: str | None = None +class SubagentCompletedData: + "Sub-agent completion details for successful execution" + agent_display_name: str + agent_name: str + tool_call_id: str + duration_ms: float | None = None + model: str | None = None + total_tokens: float | None = None + total_tool_calls: float | None = None @staticmethod - def from_dict(obj: Any) -> "ElicitationRequestedData": + def from_dict(obj: Any) -> "SubagentCompletedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - message = from_str(obj.get("message")) - tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) - elicitation_source = from_union([from_none, from_str], obj.get("elicitationSource")) - mode = from_union([from_none, lambda x: parse_enum(ElicitationRequestedMode, x)], obj.get("mode")) - requested_schema = from_union([from_none, ElicitationRequestedSchema.from_dict], obj.get("requestedSchema")) - url = from_union([from_none, from_str], obj.get("url")) - return ElicitationRequestedData( - request_id=request_id, - message=message, + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_name = from_str(obj.get("agentName")) + tool_call_id = from_str(obj.get("toolCallId")) + duration_ms = from_union([from_none, from_float], obj.get("durationMs")) + model = from_union([from_none, from_str], obj.get("model")) + total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) + total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) + return SubagentCompletedData( + agent_display_name=agent_display_name, + agent_name=agent_name, tool_call_id=tool_call_id, - elicitation_source=elicitation_source, - mode=mode, - requested_schema=requested_schema, - url=url, + duration_ms=duration_ms, + model=model, + total_tokens=total_tokens, + total_tool_calls=total_tool_calls, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["message"] = from_str(self.message) - if self.tool_call_id is not None: - result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) - if self.elicitation_source is not None: - result["elicitationSource"] = from_union([from_none, from_str], self.elicitation_source) - if self.mode is not None: - result["mode"] = from_union([from_none, lambda x: to_enum(ElicitationRequestedMode, x)], self.mode) - if self.requested_schema is not None: - result["requestedSchema"] = from_union([from_none, lambda x: to_class(ElicitationRequestedSchema, x)], self.requested_schema) - if self.url is not None: - result["url"] = from_union([from_none, from_str], self.url) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentName"] = from_str(self.agent_name) + result["toolCallId"] = from_str(self.tool_call_id) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) return result @dataclass -class ElicitationCompletedData: - "Elicitation request completion with the user's response" - request_id: str - action: ElicitationCompletedAction | None = None - content: dict[str, Any] | None = None +class SubagentDeselectedData: + "Empty payload; the event signals that the custom agent was deselected, returning to the default agent" + @staticmethod + def from_dict(obj: Any) -> "SubagentDeselectedData": + assert isinstance(obj, dict) + return SubagentDeselectedData() + + def to_dict(self) -> dict: + return {} + + +@dataclass +class SubagentFailedData: + "Sub-agent failure details including error message and agent information" + agent_display_name: str + agent_name: str + error: str + tool_call_id: str + duration_ms: float | None = None + model: str | None = None + total_tokens: float | None = None + total_tool_calls: float | None = None @staticmethod - def from_dict(obj: Any) -> "ElicitationCompletedData": + def from_dict(obj: Any) -> "SubagentFailedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - action = from_union([from_none, lambda x: parse_enum(ElicitationCompletedAction, x)], obj.get("action")) - content = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("content")) - return ElicitationCompletedData( - request_id=request_id, - action=action, - content=content, + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_name = from_str(obj.get("agentName")) + error = from_str(obj.get("error")) + tool_call_id = from_str(obj.get("toolCallId")) + duration_ms = from_union([from_none, from_float], obj.get("durationMs")) + model = from_union([from_none, from_str], obj.get("model")) + total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) + total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) + return SubagentFailedData( + agent_display_name=agent_display_name, + agent_name=agent_name, + error=error, + tool_call_id=tool_call_id, + duration_ms=duration_ms, + model=model, + total_tokens=total_tokens, + total_tool_calls=total_tool_calls, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.action is not None: - result["action"] = from_union([from_none, lambda x: to_enum(ElicitationCompletedAction, x)], self.action) - if self.content is not None: - result["content"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.content) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentName"] = from_str(self.agent_name) + result["error"] = from_str(self.error) + result["toolCallId"] = from_str(self.tool_call_id) + if self.duration_ms is not None: + result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) + if self.total_tokens is not None: + result["totalTokens"] = from_union([from_none, to_float], self.total_tokens) + if self.total_tool_calls is not None: + result["totalToolCalls"] = from_union([from_none, to_float], self.total_tool_calls) return result @dataclass -class SamplingRequestedData: - "Sampling request from an MCP server; contains the server name and a requestId for correlation" - request_id: str - server_name: str - mcp_request_id: Any +class SubagentSelectedData: + "Custom agent selection details including name and available tools" + agent_display_name: str + agent_name: str + tools: list[str] | None @staticmethod - def from_dict(obj: Any) -> "SamplingRequestedData": + def from_dict(obj: Any) -> "SubagentSelectedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - server_name = from_str(obj.get("serverName")) - mcp_request_id = obj.get("mcpRequestId") - return SamplingRequestedData( - request_id=request_id, - server_name=server_name, - mcp_request_id=mcp_request_id, + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_name = from_str(obj.get("agentName")) + tools = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("tools")) + return SubagentSelectedData( + agent_display_name=agent_display_name, + agent_name=agent_name, + tools=tools, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["serverName"] = from_str(self.server_name) - result["mcpRequestId"] = self.mcp_request_id + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentName"] = from_str(self.agent_name) + result["tools"] = from_union([from_none, lambda x: from_list(from_str, x)], self.tools) return result @dataclass -class SamplingCompletedData: - "Sampling request completion notification signaling UI dismissal" - request_id: str +class SubagentStartedData: + "Sub-agent startup details including parent tool call and agent information" + agent_description: str + agent_display_name: str + agent_name: str + tool_call_id: str @staticmethod - def from_dict(obj: Any) -> "SamplingCompletedData": + def from_dict(obj: Any) -> "SubagentStartedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - return SamplingCompletedData( - request_id=request_id, + agent_description = from_str(obj.get("agentDescription")) + agent_display_name = from_str(obj.get("agentDisplayName")) + agent_name = from_str(obj.get("agentName")) + tool_call_id = from_str(obj.get("toolCallId")) + return SubagentStartedData( + agent_description=agent_description, + agent_display_name=agent_display_name, + agent_name=agent_name, + tool_call_id=tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) + result["agentDescription"] = from_str(self.agent_description) + result["agentDisplayName"] = from_str(self.agent_display_name) + result["agentName"] = from_str(self.agent_name) + result["toolCallId"] = from_str(self.tool_call_id) return result @dataclass -class MCPOauthRequiredStaticClientConfig: - "Static OAuth client configuration, if the server specifies one" - client_id: str - public_client: bool | None = None +class SystemMessageData: + "System/developer instruction content with role and optional template metadata" + content: str + role: SystemMessageRole + metadata: SystemMessageMetadata | None = None + name: str | None = None @staticmethod - def from_dict(obj: Any) -> "MCPOauthRequiredStaticClientConfig": + def from_dict(obj: Any) -> "SystemMessageData": assert isinstance(obj, dict) - client_id = from_str(obj.get("clientId")) - public_client = from_union([from_none, from_bool], obj.get("publicClient")) - return MCPOauthRequiredStaticClientConfig( - client_id=client_id, - public_client=public_client, + content = from_str(obj.get("content")) + role = parse_enum(SystemMessageRole, obj.get("role")) + metadata = from_union([from_none, SystemMessageMetadata.from_dict], obj.get("metadata")) + name = from_union([from_none, from_str], obj.get("name")) + return SystemMessageData( + content=content, + role=role, + metadata=metadata, + name=name, ) def to_dict(self) -> dict: result: dict = {} - result["clientId"] = from_str(self.client_id) - if self.public_client is not None: - result["publicClient"] = from_union([from_none, from_bool], self.public_client) + result["content"] = from_str(self.content) + result["role"] = to_enum(SystemMessageRole, self.role) + if self.metadata is not None: + result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageMetadata, x)], self.metadata) + if self.name is not None: + result["name"] = from_union([from_none, from_str], self.name) return result @dataclass -class McpOauthRequiredData: - "OAuth authentication request for an MCP server" - request_id: str - server_name: str - server_url: str - static_client_config: MCPOauthRequiredStaticClientConfig | None = None +class SystemMessageMetadata: + "Metadata about the prompt template and its construction" + prompt_version: str | None = None + variables: dict[str, Any] | None = None @staticmethod - def from_dict(obj: Any) -> "McpOauthRequiredData": + def from_dict(obj: Any) -> "SystemMessageMetadata": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - server_name = from_str(obj.get("serverName")) - server_url = from_str(obj.get("serverUrl")) - static_client_config = from_union([from_none, MCPOauthRequiredStaticClientConfig.from_dict], obj.get("staticClientConfig")) - return McpOauthRequiredData( - request_id=request_id, - server_name=server_name, - server_url=server_url, - static_client_config=static_client_config, + prompt_version = from_union([from_none, from_str], obj.get("promptVersion")) + variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) + return SystemMessageMetadata( + prompt_version=prompt_version, + variables=variables, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["serverName"] = from_str(self.server_name) - result["serverUrl"] = from_str(self.server_url) - if self.static_client_config is not None: - result["staticClientConfig"] = from_union([from_none, lambda x: to_class(MCPOauthRequiredStaticClientConfig, x)], self.static_client_config) + if self.prompt_version is not None: + result["promptVersion"] = from_union([from_none, from_str], self.prompt_version) + if self.variables is not None: + result["variables"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.variables) return result @dataclass -class McpOauthCompletedData: - "MCP OAuth request completion notification" - request_id: str +class SystemNotification: + "Structured metadata identifying what triggered this notification" + type: SystemNotificationType + agent_id: str | None = None + agent_type: str | None = None + description: str | None = None + entry_id: str | None = None + exit_code: float | None = None + prompt: str | None = None + sender_name: str | None = None + sender_type: str | None = None + shell_id: str | None = None + status: SystemNotificationAgentCompletedStatus | None = None + summary: str | None = None @staticmethod - def from_dict(obj: Any) -> "McpOauthCompletedData": + def from_dict(obj: Any) -> "SystemNotification": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - return McpOauthCompletedData( - request_id=request_id, + type = parse_enum(SystemNotificationType, obj.get("type")) + agent_id = from_union([from_none, from_str], obj.get("agentId")) + agent_type = from_union([from_none, from_str], obj.get("agentType")) + description = from_union([from_none, from_str], obj.get("description")) + entry_id = from_union([from_none, from_str], obj.get("entryId")) + exit_code = from_union([from_none, from_float], obj.get("exitCode")) + prompt = from_union([from_none, from_str], obj.get("prompt")) + sender_name = from_union([from_none, from_str], obj.get("senderName")) + sender_type = from_union([from_none, from_str], obj.get("senderType")) + shell_id = from_union([from_none, from_str], obj.get("shellId")) + status = from_union([from_none, lambda x: parse_enum(SystemNotificationAgentCompletedStatus, x)], obj.get("status")) + summary = from_union([from_none, from_str], obj.get("summary")) + return SystemNotification( + type=type, + agent_id=agent_id, + agent_type=agent_type, + description=description, + entry_id=entry_id, + exit_code=exit_code, + prompt=prompt, + sender_name=sender_name, + sender_type=sender_type, + shell_id=shell_id, + status=status, + summary=summary, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) + result["type"] = to_enum(SystemNotificationType, self.type) + if self.agent_id is not None: + result["agentId"] = from_union([from_none, from_str], self.agent_id) + if self.agent_type is not None: + result["agentType"] = from_union([from_none, from_str], self.agent_type) + if self.description is not None: + result["description"] = from_union([from_none, from_str], self.description) + if self.entry_id is not None: + result["entryId"] = from_union([from_none, from_str], self.entry_id) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, to_float], self.exit_code) + if self.prompt is not None: + result["prompt"] = from_union([from_none, from_str], self.prompt) + if self.sender_name is not None: + result["senderName"] = from_union([from_none, from_str], self.sender_name) + if self.sender_type is not None: + result["senderType"] = from_union([from_none, from_str], self.sender_type) + if self.shell_id is not None: + result["shellId"] = from_union([from_none, from_str], self.shell_id) + if self.status is not None: + result["status"] = from_union([from_none, lambda x: to_enum(SystemNotificationAgentCompletedStatus, x)], self.status) + if self.summary is not None: + result["summary"] = from_union([from_none, from_str], self.summary) return result @dataclass -class ExternalToolRequestedData: - "External tool invocation request for client-side tool execution" - request_id: str - session_id: str - tool_call_id: str - tool_name: str - arguments: Any = None - traceparent: str | None = None - tracestate: str | None = None +class SystemNotificationData: + "System-generated notification for runtime events like background task completion" + content: str + kind: SystemNotification @staticmethod - def from_dict(obj: Any) -> "ExternalToolRequestedData": + def from_dict(obj: Any) -> "SystemNotificationData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - session_id = from_str(obj.get("sessionId")) - tool_call_id = from_str(obj.get("toolCallId")) - tool_name = from_str(obj.get("toolName")) - arguments = obj.get("arguments") - traceparent = from_union([from_none, from_str], obj.get("traceparent")) - tracestate = from_union([from_none, from_str], obj.get("tracestate")) - return ExternalToolRequestedData( - request_id=request_id, - session_id=session_id, - tool_call_id=tool_call_id, - tool_name=tool_name, - arguments=arguments, - traceparent=traceparent, - tracestate=tracestate, + content = from_str(obj.get("content")) + kind = SystemNotification.from_dict(obj.get("kind")) + return SystemNotificationData( + content=content, + kind=kind, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["sessionId"] = from_str(self.session_id) - result["toolCallId"] = from_str(self.tool_call_id) - result["toolName"] = from_str(self.tool_name) - if self.arguments is not None: - result["arguments"] = self.arguments - if self.traceparent is not None: - result["traceparent"] = from_union([from_none, from_str], self.traceparent) - if self.tracestate is not None: - result["tracestate"] = from_union([from_none, from_str], self.tracestate) + result["content"] = from_str(self.content) + result["kind"] = to_class(SystemNotification, self.kind) return result @dataclass -class ExternalToolCompletedData: - "External tool completion notification signaling UI dismissal" - request_id: str +class ToolExecutionCompleteContent: + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource" + type: ToolExecutionCompleteContentType + cwd: str | None = None + data: str | None = None + description: str | None = None + exit_code: float | None = None + icons: list[ToolExecutionCompleteContentResourceLinkIcon] | None = None + mime_type: str | None = None + name: str | None = None + resource: Any = None + size: float | None = None + text: str | None = None + title: str | None = None + uri: str | None = None @staticmethod - def from_dict(obj: Any) -> "ExternalToolCompletedData": + def from_dict(obj: Any) -> "ToolExecutionCompleteContent": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - return ExternalToolCompletedData( - request_id=request_id, + type = parse_enum(ToolExecutionCompleteContentType, obj.get("type")) + cwd = from_union([from_none, from_str], obj.get("cwd")) + data = from_union([from_none, from_str], obj.get("data")) + description = from_union([from_none, from_str], obj.get("description")) + exit_code = from_union([from_none, from_float], obj.get("exitCode")) + icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteContentResourceLinkIcon.from_dict, x)], obj.get("icons")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) + name = from_union([from_none, from_str], obj.get("name")) + resource = obj.get("resource") + size = from_union([from_none, from_float], obj.get("size")) + text = from_union([from_none, from_str], obj.get("text")) + title = from_union([from_none, from_str], obj.get("title")) + uri = from_union([from_none, from_str], obj.get("uri")) + return ToolExecutionCompleteContent( + type=type, + cwd=cwd, + data=data, + description=description, + exit_code=exit_code, + icons=icons, + mime_type=mime_type, + name=name, + resource=resource, + size=size, + text=text, + title=title, + uri=uri, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) + result["type"] = to_enum(ToolExecutionCompleteContentType, self.type) + if self.cwd is not None: + result["cwd"] = from_union([from_none, from_str], self.cwd) + if self.data is not None: + result["data"] = from_union([from_none, from_str], self.data) + if self.description is not None: + result["description"] = from_union([from_none, from_str], self.description) + if self.exit_code is not None: + result["exitCode"] = from_union([from_none, to_float], self.exit_code) + if self.icons is not None: + result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContentResourceLinkIcon, x), x)], self.icons) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, from_str], self.mime_type) + if self.name is not None: + result["name"] = from_union([from_none, from_str], self.name) + if self.resource is not None: + result["resource"] = self.resource + if self.size is not None: + result["size"] = from_union([from_none, to_float], self.size) + if self.text is not None: + result["text"] = from_union([from_none, from_str], self.text) + if self.title is not None: + result["title"] = from_union([from_none, from_str], self.title) + if self.uri is not None: + result["uri"] = from_union([from_none, from_str], self.uri) return result @dataclass -class CommandQueuedData: - "Queued slash command dispatch request for client execution" - request_id: str - command: str +class ToolExecutionCompleteContentResourceLinkIcon: + "Icon image for a resource" + src: str + mime_type: str | None = None + sizes: list[str] | None = None + theme: ToolExecutionCompleteContentResourceLinkIconTheme | None = None @staticmethod - def from_dict(obj: Any) -> "CommandQueuedData": + def from_dict(obj: Any) -> "ToolExecutionCompleteContentResourceLinkIcon": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - command = from_str(obj.get("command")) - return CommandQueuedData( - request_id=request_id, - command=command, + src = from_str(obj.get("src")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) + sizes = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("sizes")) + theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], obj.get("theme")) + return ToolExecutionCompleteContentResourceLinkIcon( + src=src, + mime_type=mime_type, + sizes=sizes, + theme=theme, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["command"] = from_str(self.command) + result["src"] = from_str(self.src) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, from_str], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes) + if self.theme is not None: + result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], self.theme) return result @dataclass -class CommandExecuteData: - "Registered command dispatch request routed to the owning client" - request_id: str - command: str - command_name: str - args: str +class ToolExecutionCompleteData: + "Tool execution completion results including success status, detailed output, and error information" + success: bool + tool_call_id: str + error: ToolExecutionCompleteError | None = None + interaction_id: str | None = None + is_user_requested: bool | None = None + model: str | None = None + # Deprecated: this field is deprecated. + parent_tool_call_id: str | None = None + result: ToolExecutionCompleteResult | None = None + tool_telemetry: dict[str, Any] | None = None @staticmethod - def from_dict(obj: Any) -> "CommandExecuteData": + def from_dict(obj: Any) -> "ToolExecutionCompleteData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - command = from_str(obj.get("command")) - command_name = from_str(obj.get("commandName")) - args = from_str(obj.get("args")) - return CommandExecuteData( - request_id=request_id, - command=command, - command_name=command_name, - args=args, + success = from_bool(obj.get("success")) + tool_call_id = from_str(obj.get("toolCallId")) + error = from_union([from_none, ToolExecutionCompleteError.from_dict], obj.get("error")) + interaction_id = from_union([from_none, from_str], obj.get("interactionId")) + is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) + model = from_union([from_none, from_str], obj.get("model")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + result = from_union([from_none, ToolExecutionCompleteResult.from_dict], obj.get("result")) + tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) + return ToolExecutionCompleteData( + success=success, + tool_call_id=tool_call_id, + error=error, + interaction_id=interaction_id, + is_user_requested=is_user_requested, + model=model, + parent_tool_call_id=parent_tool_call_id, + result=result, + tool_telemetry=tool_telemetry, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["command"] = from_str(self.command) - result["commandName"] = from_str(self.command_name) - result["args"] = from_str(self.args) + result["success"] = from_bool(self.success) + result["toolCallId"] = from_str(self.tool_call_id) + if self.error is not None: + result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteError, x)], self.error) + if self.interaction_id is not None: + result["interactionId"] = from_union([from_none, from_str], self.interaction_id) + if self.is_user_requested is not None: + result["isUserRequested"] = from_union([from_none, from_bool], self.is_user_requested) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.result is not None: + result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteResult, x)], self.result) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) return result @dataclass -class CommandCompletedData: - "Queued command completion notification signaling UI dismissal" - request_id: str +class ToolExecutionCompleteError: + "Error details when the tool execution failed" + message: str + code: str | None = None @staticmethod - def from_dict(obj: Any) -> "CommandCompletedData": + def from_dict(obj: Any) -> "ToolExecutionCompleteError": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - return CommandCompletedData( - request_id=request_id, + message = from_str(obj.get("message")) + code = from_union([from_none, from_str], obj.get("code")) + return ToolExecutionCompleteError( + message=message, + code=code, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) + result["message"] = from_str(self.message) + if self.code is not None: + result["code"] = from_union([from_none, from_str], self.code) return result @dataclass -class CommandsChangedCommand: - name: str - description: str | None = None +class ToolExecutionCompleteResult: + "Tool execution result on success" + content: str + contents: list[ToolExecutionCompleteContent] | None = None + detailed_content: str | None = None @staticmethod - def from_dict(obj: Any) -> "CommandsChangedCommand": + def from_dict(obj: Any) -> "ToolExecutionCompleteResult": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - description = from_union([from_none, from_str], obj.get("description")) - return CommandsChangedCommand( - name=name, - description=description, + content = from_str(obj.get("content")) + contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteContent.from_dict, x)], obj.get("contents")) + detailed_content = from_union([from_none, from_str], obj.get("detailedContent")) + return ToolExecutionCompleteResult( + content=content, + contents=contents, + detailed_content=detailed_content, ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - if self.description is not None: - result["description"] = from_union([from_none, from_str], self.description) + result["content"] = from_str(self.content) + if self.contents is not None: + result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContent, x), x)], self.contents) + if self.detailed_content is not None: + result["detailedContent"] = from_union([from_none, from_str], self.detailed_content) return result @dataclass -class CommandsChangedData: - "SDK command registration change notification" - commands: list[CommandsChangedCommand] +class ToolExecutionPartialResultData: + "Streaming tool execution output for incremental result display" + partial_output: str + tool_call_id: str @staticmethod - def from_dict(obj: Any) -> "CommandsChangedData": + def from_dict(obj: Any) -> "ToolExecutionPartialResultData": assert isinstance(obj, dict) - commands = from_list(CommandsChangedCommand.from_dict, obj.get("commands")) - return CommandsChangedData( - commands=commands, + partial_output = from_str(obj.get("partialOutput")) + tool_call_id = from_str(obj.get("toolCallId")) + return ToolExecutionPartialResultData( + partial_output=partial_output, + tool_call_id=tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - result["commands"] = from_list(lambda x: to_class(CommandsChangedCommand, x), self.commands) + result["partialOutput"] = from_str(self.partial_output) + result["toolCallId"] = from_str(self.tool_call_id) return result @dataclass -class CapabilitiesChangedUI: - "UI capability changes" - elicitation: bool | None = None +class ToolExecutionProgressData: + "Tool execution progress notification with status message" + progress_message: str + tool_call_id: str @staticmethod - def from_dict(obj: Any) -> "CapabilitiesChangedUI": + def from_dict(obj: Any) -> "ToolExecutionProgressData": assert isinstance(obj, dict) - elicitation = from_union([from_none, from_bool], obj.get("elicitation")) - return CapabilitiesChangedUI( - elicitation=elicitation, + progress_message = from_str(obj.get("progressMessage")) + tool_call_id = from_str(obj.get("toolCallId")) + return ToolExecutionProgressData( + progress_message=progress_message, + tool_call_id=tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - if self.elicitation is not None: - result["elicitation"] = from_union([from_none, from_bool], self.elicitation) + result["progressMessage"] = from_str(self.progress_message) + result["toolCallId"] = from_str(self.tool_call_id) return result @dataclass -class CapabilitiesChangedData: - "Session capability change notification" - ui: CapabilitiesChangedUI | None = None +class ToolExecutionStartData: + "Tool execution startup details including MCP server information when applicable" + tool_call_id: str + tool_name: str + arguments: Any = None + mcp_server_name: str | None = None + mcp_tool_name: str | None = None + # Deprecated: this field is deprecated. + parent_tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> "CapabilitiesChangedData": + def from_dict(obj: Any) -> "ToolExecutionStartData": assert isinstance(obj, dict) - ui = from_union([from_none, CapabilitiesChangedUI.from_dict], obj.get("ui")) - return CapabilitiesChangedData( - ui=ui, + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) + mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) + parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + return ToolExecutionStartData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, + mcp_server_name=mcp_server_name, + mcp_tool_name=mcp_tool_name, + parent_tool_call_id=parent_tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - if self.ui is not None: - result["ui"] = from_union([from_none, lambda x: to_class(CapabilitiesChangedUI, x)], self.ui) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments + if self.mcp_server_name is not None: + result["mcpServerName"] = from_union([from_none, from_str], self.mcp_server_name) + if self.mcp_tool_name is not None: + result["mcpToolName"] = from_union([from_none, from_str], self.mcp_tool_name) + if self.parent_tool_call_id is not None: + result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) return result @dataclass -class ExitPlanModeRequestedData: - "Plan approval request with plan content and available user actions" - request_id: str - summary: str - plan_content: str - actions: list[str] - recommended_action: str +class ToolUserRequestedData: + "User-initiated tool invocation request with tool name and arguments" + tool_call_id: str + tool_name: str + arguments: Any = None @staticmethod - def from_dict(obj: Any) -> "ExitPlanModeRequestedData": + def from_dict(obj: Any) -> "ToolUserRequestedData": assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - summary = from_str(obj.get("summary")) - plan_content = from_str(obj.get("planContent")) - actions = from_list(from_str, obj.get("actions")) - recommended_action = from_str(obj.get("recommendedAction")) - return ExitPlanModeRequestedData( - request_id=request_id, - summary=summary, - plan_content=plan_content, - actions=actions, - recommended_action=recommended_action, + tool_call_id = from_str(obj.get("toolCallId")) + tool_name = from_str(obj.get("toolName")) + arguments = obj.get("arguments") + return ToolUserRequestedData( + tool_call_id=tool_call_id, + tool_name=tool_name, + arguments=arguments, ) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["summary"] = from_str(self.summary) - result["planContent"] = from_str(self.plan_content) - result["actions"] = from_list(from_str, self.actions) - result["recommendedAction"] = from_str(self.recommended_action) + result["toolCallId"] = from_str(self.tool_call_id) + result["toolName"] = from_str(self.tool_name) + if self.arguments is not None: + result["arguments"] = self.arguments return result @dataclass -class ExitPlanModeCompletedData: - "Plan mode exit completion with the user's approval decision and optional feedback" +class UserInputCompletedData: + "User input request completion with the user's response" request_id: str - approved: bool | None = None - selected_action: str | None = None - auto_approve_edits: bool | None = None - feedback: str | None = None + answer: str | None = None + was_freeform: bool | None = None @staticmethod - def from_dict(obj: Any) -> "ExitPlanModeCompletedData": + def from_dict(obj: Any) -> "UserInputCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - approved = from_union([from_none, from_bool], obj.get("approved")) - selected_action = from_union([from_none, from_str], obj.get("selectedAction")) - auto_approve_edits = from_union([from_none, from_bool], obj.get("autoApproveEdits")) - feedback = from_union([from_none, from_str], obj.get("feedback")) - return ExitPlanModeCompletedData( + answer = from_union([from_none, from_str], obj.get("answer")) + was_freeform = from_union([from_none, from_bool], obj.get("wasFreeform")) + return UserInputCompletedData( request_id=request_id, - approved=approved, - selected_action=selected_action, - auto_approve_edits=auto_approve_edits, - feedback=feedback, + answer=answer, + was_freeform=was_freeform, ) def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - if self.approved is not None: - result["approved"] = from_union([from_none, from_bool], self.approved) - if self.selected_action is not None: - result["selectedAction"] = from_union([from_none, from_str], self.selected_action) - if self.auto_approve_edits is not None: - result["autoApproveEdits"] = from_union([from_none, from_bool], self.auto_approve_edits) - if self.feedback is not None: - result["feedback"] = from_union([from_none, from_str], self.feedback) + if self.answer is not None: + result["answer"] = from_union([from_none, from_str], self.answer) + if self.was_freeform is not None: + result["wasFreeform"] = from_union([from_none, from_bool], self.was_freeform) return result @dataclass -class SessionToolsUpdatedData: - model: str +class UserInputRequestedData: + "User input request notification with question and optional predefined choices" + question: str + request_id: str + allow_freeform: bool | None = None + choices: list[str] | None = None + tool_call_id: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionToolsUpdatedData": + def from_dict(obj: Any) -> "UserInputRequestedData": assert isinstance(obj, dict) - model = from_str(obj.get("model")) - return SessionToolsUpdatedData( - model=model, + question = from_str(obj.get("question")) + request_id = from_str(obj.get("requestId")) + allow_freeform = from_union([from_none, from_bool], obj.get("allowFreeform")) + choices = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("choices")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + return UserInputRequestedData( + question=question, + request_id=request_id, + allow_freeform=allow_freeform, + choices=choices, + tool_call_id=tool_call_id, ) def to_dict(self) -> dict: result: dict = {} - result["model"] = from_str(self.model) + result["question"] = from_str(self.question) + result["requestId"] = from_str(self.request_id) + if self.allow_freeform is not None: + result["allowFreeform"] = from_union([from_none, from_bool], self.allow_freeform) + if self.choices is not None: + result["choices"] = from_union([from_none, lambda x: from_list(from_str, x)], self.choices) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @dataclass -class SessionBackgroundTasksChangedData: - @staticmethod - def from_dict(obj: Any) -> "SessionBackgroundTasksChangedData": - assert isinstance(obj, dict) - return SessionBackgroundTasksChangedData() - - def to_dict(self) -> dict: - return {} - - -@dataclass -class SkillsLoadedSkill: - name: str - description: str - source: str - user_invocable: bool - enabled: bool +class UserMessageAttachment: + "A user message attachment — a file, directory, code selection, blob, or GitHub reference" + type: UserMessageAttachmentType + data: str | None = None + display_name: str | None = None + file_path: str | None = None + line_range: UserMessageAttachmentFileLineRange | None = None + mime_type: str | None = None + number: float | None = None path: str | None = None + reference_type: UserMessageAttachmentGithubReferenceType | None = None + selection: UserMessageAttachmentSelectionDetails | None = None + state: str | None = None + text: str | None = None + title: str | None = None + url: str | None = None @staticmethod - def from_dict(obj: Any) -> "SkillsLoadedSkill": + def from_dict(obj: Any) -> "UserMessageAttachment": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - description = from_str(obj.get("description")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - enabled = from_bool(obj.get("enabled")) + type = parse_enum(UserMessageAttachmentType, obj.get("type")) + data = from_union([from_none, from_str], obj.get("data")) + display_name = from_union([from_none, from_str], obj.get("displayName")) + file_path = from_union([from_none, from_str], obj.get("filePath")) + line_range = from_union([from_none, UserMessageAttachmentFileLineRange.from_dict], obj.get("lineRange")) + mime_type = from_union([from_none, from_str], obj.get("mimeType")) + number = from_union([from_none, from_float], obj.get("number")) path = from_union([from_none, from_str], obj.get("path")) - return SkillsLoadedSkill( - name=name, - description=description, - source=source, - user_invocable=user_invocable, - enabled=enabled, + reference_type = from_union([from_none, lambda x: parse_enum(UserMessageAttachmentGithubReferenceType, x)], obj.get("referenceType")) + selection = from_union([from_none, UserMessageAttachmentSelectionDetails.from_dict], obj.get("selection")) + state = from_union([from_none, from_str], obj.get("state")) + text = from_union([from_none, from_str], obj.get("text")) + title = from_union([from_none, from_str], obj.get("title")) + url = from_union([from_none, from_str], obj.get("url")) + return UserMessageAttachment( + type=type, + data=data, + display_name=display_name, + file_path=file_path, + line_range=line_range, + mime_type=mime_type, + number=number, path=path, + reference_type=reference_type, + selection=selection, + state=state, + text=text, + title=title, + url=url, ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["description"] = from_str(self.description) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - result["enabled"] = from_bool(self.enabled) + result["type"] = to_enum(UserMessageAttachmentType, self.type) + if self.data is not None: + result["data"] = from_union([from_none, from_str], self.data) + if self.display_name is not None: + result["displayName"] = from_union([from_none, from_str], self.display_name) + if self.file_path is not None: + result["filePath"] = from_union([from_none, from_str], self.file_path) + if self.line_range is not None: + result["lineRange"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentFileLineRange, x)], self.line_range) + if self.mime_type is not None: + result["mimeType"] = from_union([from_none, from_str], self.mime_type) + if self.number is not None: + result["number"] = from_union([from_none, to_float], self.number) if self.path is not None: result["path"] = from_union([from_none, from_str], self.path) + if self.reference_type is not None: + result["referenceType"] = from_union([from_none, lambda x: to_enum(UserMessageAttachmentGithubReferenceType, x)], self.reference_type) + if self.selection is not None: + result["selection"] = from_union([from_none, lambda x: to_class(UserMessageAttachmentSelectionDetails, x)], self.selection) + if self.state is not None: + result["state"] = from_union([from_none, from_str], self.state) + if self.text is not None: + result["text"] = from_union([from_none, from_str], self.text) + if self.title is not None: + result["title"] = from_union([from_none, from_str], self.title) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) return result @dataclass -class SessionSkillsLoadedData: - skills: list[SkillsLoadedSkill] - - @staticmethod - def from_dict(obj: Any) -> "SessionSkillsLoadedData": - assert isinstance(obj, dict) - skills = from_list(SkillsLoadedSkill.from_dict, obj.get("skills")) - return SessionSkillsLoadedData( - skills=skills, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["skills"] = from_list(lambda x: to_class(SkillsLoadedSkill, x), self.skills) - return result - - -@dataclass -class CustomAgentsUpdatedAgent: - id: str - name: str - display_name: str - description: str - source: str - tools: list[str] - user_invocable: bool - model: str | None = None +class UserMessageAttachmentFileLineRange: + "Optional line range to scope the attachment to a specific section of the file" + end: float + start: float @staticmethod - def from_dict(obj: Any) -> "CustomAgentsUpdatedAgent": + def from_dict(obj: Any) -> "UserMessageAttachmentFileLineRange": assert isinstance(obj, dict) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - display_name = from_str(obj.get("displayName")) - description = from_str(obj.get("description")) - source = from_str(obj.get("source")) - tools = from_list(from_str, obj.get("tools")) - user_invocable = from_bool(obj.get("userInvocable")) - model = from_union([from_none, from_str], obj.get("model")) - return CustomAgentsUpdatedAgent( - id=id, - name=name, - display_name=display_name, - description=description, - source=source, - tools=tools, - user_invocable=user_invocable, - model=model, + end = from_float(obj.get("end")) + start = from_float(obj.get("start")) + return UserMessageAttachmentFileLineRange( + end=end, + start=start, ) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["displayName"] = from_str(self.display_name) - result["description"] = from_str(self.description) - result["source"] = from_str(self.source) - result["tools"] = from_list(from_str, self.tools) - result["userInvocable"] = from_bool(self.user_invocable) - if self.model is not None: - result["model"] = from_union([from_none, from_str], self.model) + result["end"] = to_float(self.end) + result["start"] = to_float(self.start) return result @dataclass -class SessionCustomAgentsUpdatedData: - agents: list[CustomAgentsUpdatedAgent] - warnings: list[str] - errors: list[str] +class UserMessageAttachmentSelectionDetails: + "Position range of the selection within the file" + end: UserMessageAttachmentSelectionDetailsEnd + start: UserMessageAttachmentSelectionDetailsStart @staticmethod - def from_dict(obj: Any) -> "SessionCustomAgentsUpdatedData": + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetails": assert isinstance(obj, dict) - agents = from_list(CustomAgentsUpdatedAgent.from_dict, obj.get("agents")) - warnings = from_list(from_str, obj.get("warnings")) - errors = from_list(from_str, obj.get("errors")) - return SessionCustomAgentsUpdatedData( - agents=agents, - warnings=warnings, - errors=errors, + end = UserMessageAttachmentSelectionDetailsEnd.from_dict(obj.get("end")) + start = UserMessageAttachmentSelectionDetailsStart.from_dict(obj.get("start")) + return UserMessageAttachmentSelectionDetails( + end=end, + start=start, ) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(CustomAgentsUpdatedAgent, x), self.agents) - result["warnings"] = from_list(from_str, self.warnings) - result["errors"] = from_list(from_str, self.errors) + result["end"] = to_class(UserMessageAttachmentSelectionDetailsEnd, self.end) + result["start"] = to_class(UserMessageAttachmentSelectionDetailsStart, self.start) return result @dataclass -class MCPServersLoadedServer: - name: str - status: MCPServerStatus - source: str | None = None - error: str | None = None +class UserMessageAttachmentSelectionDetailsEnd: + "End position of the selection" + character: float + line: float @staticmethod - def from_dict(obj: Any) -> "MCPServersLoadedServer": + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsEnd": assert isinstance(obj, dict) - name = from_str(obj.get("name")) - status = parse_enum(MCPServerStatus, obj.get("status")) - source = from_union([from_none, from_str], obj.get("source")) - error = from_union([from_none, from_str], obj.get("error")) - return MCPServersLoadedServer( - name=name, - status=status, - source=source, - error=error, + character = from_float(obj.get("character")) + line = from_float(obj.get("line")) + return UserMessageAttachmentSelectionDetailsEnd( + character=character, + line=line, ) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["status"] = to_enum(MCPServerStatus, self.status) - if self.source is not None: - result["source"] = from_union([from_none, from_str], self.source) - if self.error is not None: - result["error"] = from_union([from_none, from_str], self.error) + result["character"] = to_float(self.character) + result["line"] = to_float(self.line) return result @dataclass -class SessionMcpServersLoadedData: - servers: list[MCPServersLoadedServer] +class UserMessageAttachmentSelectionDetailsStart: + "Start position of the selection" + character: float + line: float @staticmethod - def from_dict(obj: Any) -> "SessionMcpServersLoadedData": + def from_dict(obj: Any) -> "UserMessageAttachmentSelectionDetailsStart": assert isinstance(obj, dict) - servers = from_list(MCPServersLoadedServer.from_dict, obj.get("servers")) - return SessionMcpServersLoadedData( - servers=servers, + character = from_float(obj.get("character")) + line = from_float(obj.get("line")) + return UserMessageAttachmentSelectionDetailsStart( + character=character, + line=line, ) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(MCPServersLoadedServer, x), self.servers) + result["character"] = to_float(self.character) + result["line"] = to_float(self.line) return result @dataclass -class SessionMcpServerStatusChangedData: - server_name: str - status: SessionMcpServerStatusChangedDataStatus +class UserMessageData: + content: str + agent_mode: UserMessageAgentMode | None = None + attachments: list[UserMessageAttachment] | None = None + interaction_id: str | None = None + native_document_path_fallback_paths: list[str] | None = None + source: str | None = None + supported_native_document_mime_types: list[str] | None = None + transformed_content: str | None = None @staticmethod - def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": + def from_dict(obj: Any) -> "UserMessageData": assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - status = parse_enum(SessionMcpServerStatusChangedDataStatus, obj.get("status")) - return SessionMcpServerStatusChangedData( - server_name=server_name, - status=status, + content = from_str(obj.get("content")) + 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")) + native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths")) + source = from_union([from_none, from_str], obj.get("source")) + supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) + transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) + return UserMessageData( + content=content, + agent_mode=agent_mode, + attachments=attachments, + interaction_id=interaction_id, + native_document_path_fallback_paths=native_document_path_fallback_paths, + source=source, + supported_native_document_mime_types=supported_native_document_mime_types, + transformed_content=transformed_content, ) def to_dict(self) -> dict: result: dict = {} - result["serverName"] = from_str(self.server_name) - result["status"] = to_enum(SessionMcpServerStatusChangedDataStatus, self.status) + result["content"] = from_str(self.content) + if self.agent_mode is not None: + result["agentMode"] = from_union([from_none, lambda x: to_enum(UserMessageAgentMode, x)], self.agent_mode) + if self.attachments is not None: + 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.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.source is not None: + result["source"] = from_union([from_none, from_str], self.source) + if self.supported_native_document_mime_types is not None: + result["supportedNativeDocumentMimeTypes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.supported_native_document_mime_types) + if self.transformed_content is not None: + result["transformedContent"] = from_union([from_none, from_str], self.transformed_content) return result @dataclass -class ExtensionsLoadedExtension: - id: str - name: str - source: ExtensionsLoadedExtensionSource - status: ExtensionsLoadedExtensionStatus +class WorkingDirectoryContext: + "Working directory and git context at session start" + cwd: str + base_commit: str | None = None + branch: str | None = None + git_root: str | None = None + head_commit: str | None = None + host_type: WorkingDirectoryContextHostType | None = None + repository: str | None = None + repository_host: str | None = None @staticmethod - def from_dict(obj: Any) -> "ExtensionsLoadedExtension": + def from_dict(obj: Any) -> "WorkingDirectoryContext": assert isinstance(obj, dict) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = parse_enum(ExtensionsLoadedExtensionSource, obj.get("source")) - status = parse_enum(ExtensionsLoadedExtensionStatus, obj.get("status")) - return ExtensionsLoadedExtension( - id=id, - name=name, - source=source, - status=status, + cwd = from_str(obj.get("cwd")) + base_commit = from_union([from_none, from_str], obj.get("baseCommit")) + branch = from_union([from_none, from_str], obj.get("branch")) + git_root = from_union([from_none, from_str], obj.get("gitRoot")) + head_commit = from_union([from_none, from_str], obj.get("headCommit")) + host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) + repository = from_union([from_none, from_str], obj.get("repository")) + repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) + return WorkingDirectoryContext( + cwd=cwd, + base_commit=base_commit, + branch=branch, + git_root=git_root, + head_commit=head_commit, + host_type=host_type, + repository=repository, + repository_host=repository_host, ) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionsLoadedExtensionSource, self.source) - result["status"] = to_enum(ExtensionsLoadedExtensionStatus, self.status) + result["cwd"] = from_str(self.cwd) + if self.base_commit is not None: + result["baseCommit"] = from_union([from_none, from_str], self.base_commit) + if self.branch is not None: + result["branch"] = from_union([from_none, from_str], self.branch) + if self.git_root is not None: + result["gitRoot"] = from_union([from_none, from_str], self.git_root) + if self.head_commit is not None: + result["headCommit"] = from_union([from_none, from_str], self.head_commit) + if self.host_type is not None: + result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) + if self.repository is not None: + result["repository"] = from_union([from_none, from_str], self.repository) + if self.repository_host is not None: + result["repositoryHost"] = from_union([from_none, from_str], self.repository_host) return result -@dataclass -class SessionExtensionsLoadedData: - extensions: list[ExtensionsLoadedExtension] +class AssistantMessageToolRequestType(Enum): + "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." + FUNCTION = "function" + CUSTOM = "custom" - @staticmethod - def from_dict(obj: Any) -> "SessionExtensionsLoadedData": - assert isinstance(obj, dict) - extensions = from_list(ExtensionsLoadedExtension.from_dict, obj.get("extensions")) - return SessionExtensionsLoadedData( - extensions=extensions, - ) - def to_dict(self) -> dict: - result: dict = {} - result["extensions"] = from_list(lambda x: to_class(ExtensionsLoadedExtension, x), self.extensions) - return result +class ElicitationCompletedAction(Enum): + "The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)" + ACCEPT = "accept" + DECLINE = "decline" + CANCEL = "cancel" -class WorkingDirectoryContextHostType(Enum): - "Hosting platform type of the repository (github or ado)" - GITHUB = "github" - ADO = "ado" +class ElicitationRequestedMode(Enum): + "Elicitation mode; \"form\" for structured input, \"url\" for browser-based. Defaults to \"form\" when absent." + FORM = "form" + URL = "url" -class SessionPlanChangedDataOperation(Enum): - "The type of operation performed on the plan file" - CREATE = "create" - UPDATE = "update" - DELETE = "delete" +class ExtensionsLoadedExtensionSource(Enum): + "Discovery source" + PROJECT = "project" + USER = "user" -class SessionWorkspaceFileChangedDataOperation(Enum): - "Whether the file was newly created or updated" - CREATE = "create" - UPDATE = "update" +class ExtensionsLoadedExtensionStatus(Enum): + "Current status: running, disabled, failed, or starting" + RUNNING = "running" + DISABLED = "disabled" + FAILED = "failed" + STARTING = "starting" class HandoffSourceType(Enum): @@ -3930,85 +3974,37 @@ class HandoffSourceType(Enum): LOCAL = "local" -class ShutdownType(Enum): - "Whether the session ended normally (\"routine\") or due to a crash/fatal error (\"error\")" - ROUTINE = "routine" - ERROR = "error" - - -class SessionContextChangedDataHostType(Enum): - "Hosting platform type of the repository (github or ado)" - GITHUB = "github" - ADO = "ado" - - -class UserMessageAttachmentType(Enum): - "A user message attachment — a file, directory, code selection, blob, or GitHub reference discriminator" - FILE = "file" - DIRECTORY = "directory" - SELECTION = "selection" - GITHUB_REFERENCE = "github_reference" - BLOB = "blob" - - -class UserMessageAttachmentGithubReferenceType(Enum): - "Type of GitHub reference" - ISSUE = "issue" - PR = "pr" - DISCUSSION = "discussion" - - -class UserMessageAgentMode(Enum): - "The agent mode that was active when this message was sent" - INTERACTIVE = "interactive" - PLAN = "plan" - AUTOPILOT = "autopilot" - SHELL = "shell" - - -class AssistantMessageToolRequestType(Enum): - "Tool call type: \"function\" for standard tool calls, \"custom\" for grammar-based tool calls. Defaults to \"function\" when absent." - FUNCTION = "function" - CUSTOM = "custom" - - -class ToolExecutionCompleteDataResultContentsItemType(Enum): - "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator" - TEXT = "text" - TERMINAL = "terminal" - IMAGE = "image" - AUDIO = "audio" - RESOURCE_LINK = "resource_link" - RESOURCE = "resource" - - -class ToolExecutionCompleteDataResultContentsItemIconsItemTheme(Enum): - "Theme variant this icon is intended for" - LIGHT = "light" - DARK = "dark" - - -class SystemMessageDataRole(Enum): - "Message role: \"system\" for system prompts, \"developer\" for developer-injected instructions" - SYSTEM = "system" - DEVELOPER = "developer" +class McpServerStatusChangedStatus(Enum): + "New connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + CONNECTED = "connected" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + PENDING = "pending" + DISABLED = "disabled" + NOT_CONFIGURED = "not_configured" -class SystemNotificationDataKindType(Enum): - "Structured metadata identifying what triggered this notification discriminator" - AGENT_COMPLETED = "agent_completed" - AGENT_IDLE = "agent_idle" - SHELL_COMPLETED = "shell_completed" - SHELL_DETACHED_COMPLETED = "shell_detached_completed" +class McpServersLoadedServerStatus(Enum): + "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" + CONNECTED = "connected" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + PENDING = "pending" + DISABLED = "disabled" + NOT_CONFIGURED = "not_configured" -class SystemNotificationDataKindStatus(Enum): - "Whether the agent completed successfully or failed" - COMPLETED = "completed" - FAILED = "failed" +class PermissionCompletedKind(Enum): + "The outcome of the permission request" + APPROVED = "approved" + DENIED_BY_RULES = "denied-by-rules" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" -class PermissionRequestedDataPermissionRequestKind(Enum): +class PermissionRequestKind(Enum): "Details of the permission being requested discriminator" SHELL = "shell" WRITE = "write" @@ -4032,61 +4028,90 @@ class PermissionRequestMemoryDirection(Enum): DOWNVOTE = "downvote" -class PermissionCompletedKind(Enum): - "The outcome of the permission request" - APPROVED = "approved" - DENIED_BY_RULES = "denied-by-rules" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" +class PlanChangedOperation(Enum): + "The type of operation performed on the plan file" + CREATE = "create" + UPDATE = "update" + DELETE = "delete" -class ElicitationRequestedMode(Enum): - "Elicitation mode; \"form\" for structured input, \"url\" for browser-based. Defaults to \"form\" when absent." - FORM = "form" - URL = "url" +class ShutdownType(Enum): + "Whether the session ended normally (\"routine\") or due to a crash/fatal error (\"error\")" + ROUTINE = "routine" + ERROR = "error" -class ElicitationCompletedAction(Enum): - "The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)" - ACCEPT = "accept" - DECLINE = "decline" - CANCEL = "cancel" +class SystemMessageRole(Enum): + "Message role: \"system\" for system prompts, \"developer\" for developer-injected instructions" + SYSTEM = "system" + DEVELOPER = "developer" -class MCPServerStatus(Enum): - "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" - CONNECTED = "connected" +class SystemNotificationAgentCompletedStatus(Enum): + "Whether the agent completed successfully or failed" + COMPLETED = "completed" FAILED = "failed" - NEEDS_AUTH = "needs-auth" - PENDING = "pending" - DISABLED = "disabled" - NOT_CONFIGURED = "not_configured" -class SessionMcpServerStatusChangedDataStatus(Enum): - "New connection status: connected, failed, needs-auth, pending, disabled, or not_configured" - CONNECTED = "connected" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - PENDING = "pending" - DISABLED = "disabled" - NOT_CONFIGURED = "not_configured" +class SystemNotificationType(Enum): + "Structured metadata identifying what triggered this notification discriminator" + AGENT_COMPLETED = "agent_completed" + AGENT_IDLE = "agent_idle" + NEW_INBOX_MESSAGE = "new_inbox_message" + SHELL_COMPLETED = "shell_completed" + SHELL_DETACHED_COMPLETED = "shell_detached_completed" -class ExtensionsLoadedExtensionSource(Enum): - "Discovery source" - PROJECT = "project" - USER = "user" +class ToolExecutionCompleteContentResourceLinkIconTheme(Enum): + "Theme variant this icon is intended for" + LIGHT = "light" + DARK = "dark" -class ExtensionsLoadedExtensionStatus(Enum): - "Current status: running, disabled, failed, or starting" - RUNNING = "running" - DISABLED = "disabled" - FAILED = "failed" - STARTING = "starting" +class ToolExecutionCompleteContentType(Enum): + "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator" + TEXT = "text" + TERMINAL = "terminal" + IMAGE = "image" + AUDIO = "audio" + RESOURCE_LINK = "resource_link" + RESOURCE = "resource" + + +class UserMessageAgentMode(Enum): + "The agent mode that was active when this message was sent" + INTERACTIVE = "interactive" + PLAN = "plan" + AUTOPILOT = "autopilot" + SHELL = "shell" + + +class UserMessageAttachmentGithubReferenceType(Enum): + "Type of GitHub reference" + ISSUE = "issue" + PR = "pr" + DISCUSSION = "discussion" + + +class UserMessageAttachmentType(Enum): + "A user message attachment — a file, directory, code selection, blob, or GitHub reference discriminator" + FILE = "file" + DIRECTORY = "directory" + SELECTION = "selection" + GITHUB_REFERENCE = "github_reference" + BLOB = "blob" + + +class WorkingDirectoryContextHostType(Enum): + "Hosting platform type of the repository (github or ado)" + GITHUB = "github" + ADO = "ado" + + +class WorkspaceFileChangedOperation(Enum): + "Whether the file was newly created or updated" + CREATE = "create" + UPDATE = "update" SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data diff --git a/python/copilot/session.py b/python/copilot/session.py index 148b1aa63..88f742afb 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -24,13 +24,11 @@ from .generated.rpc import ( ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, - Kind, LogRequest, ModelSwitchToRequest, PermissionDecision, + PermissionDecisionKind, PermissionDecisionRequest, - RequestedSchemaType, - SessionFsHandler, SessionLogLevel, SessionRpc, ToolCallResult, @@ -40,10 +38,11 @@ UIElicitationResponseAction, UIElicitationSchema, UIElicitationSchemaProperty, - UIElicitationSchemaPropertyNumberType, + UIElicitationSchemaPropertyType, + UIElicitationSchemaType, UIHandlePendingElicitationRequest, ) -from .generated.rpc import ModelCapabilitiesClass as _RpcModelCapabilitiesOverride +from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride from .generated.session_events import ( AssistantMessageData, CapabilitiesChangedData, @@ -61,6 +60,7 @@ if TYPE_CHECKING: from .client import ModelCapabilitiesOverride + from .session_fs_provider import SessionFsProvider # Re-export SessionEvent under an alias used internally SessionEventTypeAlias = SessionEvent @@ -410,7 +410,7 @@ class ElicitationContext(TypedDict, total=False): ] """Handler invoked when the server dispatches an elicitation request to this client.""" -CreateSessionFsHandler = Callable[["CopilotSession"], SessionFsHandler] +CreateSessionFsHandler = Callable[["CopilotSession"], "SessionFsProvider"] # ============================================================================ @@ -471,10 +471,10 @@ async def confirm(self, message: str) -> bool: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=RequestedSchemaType.OBJECT, + type=UIElicitationSchemaType.OBJECT, properties={ "confirmed": UIElicitationSchemaProperty( - type=UIElicitationSchemaPropertyNumberType.BOOLEAN, + type=UIElicitationSchemaPropertyType.BOOLEAN, default=True, ), }, @@ -506,10 +506,10 @@ async def select(self, message: str, options: list[str]) -> str | None: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=RequestedSchemaType.OBJECT, + type=UIElicitationSchemaType.OBJECT, properties={ "selection": UIElicitationSchemaProperty( - type=UIElicitationSchemaPropertyNumberType.STRING, + type=UIElicitationSchemaPropertyType.STRING, enum=options, ), }, @@ -1454,7 +1454,7 @@ async def _execute_permission_and_respond( return perm_result = PermissionDecision( - kind=Kind(result.kind), + kind=PermissionDecisionKind(result.kind), rules=result.rules, feedback=result.feedback, message=result.message, @@ -1473,7 +1473,7 @@ async def _execute_permission_and_respond( PermissionDecisionRequest( request_id=request_id, result=PermissionDecision( - kind=Kind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, + kind=PermissionDecisionKind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, ), ) ) diff --git a/python/copilot/session_fs_provider.py b/python/copilot/session_fs_provider.py new file mode 100644 index 000000000..ccef43d02 --- /dev/null +++ b/python/copilot/session_fs_provider.py @@ -0,0 +1,223 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# -------------------------------------------------------------------------------------------- + +"""Idiomatic base class for session filesystem providers. + +Subclasses override the abstract methods using standard Python patterns: +raise on error, return values directly. The :func:`create_session_fs_adapter` +function wraps a provider into the generated :class:`SessionFsHandler` +protocol expected by the SDK, converting exceptions into +:class:`SessionFSError` results. + +Errors whose ``errno`` matches :data:`errno.ENOENT` are mapped to the +``ENOENT`` error code; all others map to ``UNKNOWN``. +""" + +from __future__ import annotations + +import abc +import errno +from collections.abc import Sequence +from dataclasses import dataclass +from datetime import datetime + +from .generated.rpc import ( + SessionFSError, + SessionFSErrorCode, + SessionFSExistsResult, + SessionFsHandler, + SessionFSReaddirResult, + SessionFSReaddirWithTypesEntry, + SessionFSReaddirWithTypesResult, + SessionFSReadFileResult, + SessionFSStatResult, +) + + +@dataclass +class SessionFsFileInfo: + """File metadata returned by :meth:`SessionFsProvider.stat`.""" + + is_file: bool + is_directory: bool + size: int + mtime: datetime + birthtime: datetime + + +class SessionFsProvider(abc.ABC): + """Abstract base class for session filesystem providers. + + Subclasses implement the abstract methods below using idiomatic Python: + raise exceptions on errors and return values directly. Use + :func:`create_session_fs_adapter` to wrap a provider into the RPC + handler protocol. + """ + + @abc.abstractmethod + async def read_file(self, path: str) -> str: + """Read the full content of a file. Raise if the file does not exist.""" + + @abc.abstractmethod + async def write_file(self, path: str, content: str, mode: int | None = None) -> None: + """Write *content* to a file, creating parent directories if needed.""" + + @abc.abstractmethod + async def append_file(self, path: str, content: str, mode: int | None = None) -> None: + """Append *content* to a file, creating parent directories if needed.""" + + @abc.abstractmethod + async def exists(self, path: str) -> bool: + """Return whether *path* exists.""" + + @abc.abstractmethod + async def stat(self, path: str) -> SessionFsFileInfo: + """Return metadata for *path*. Raise if it does not exist.""" + + @abc.abstractmethod + async def mkdir(self, path: str, recursive: bool, mode: int | None = None) -> None: + """Create a directory. If *recursive* is ``True``, create parents.""" + + @abc.abstractmethod + async def readdir(self, path: str) -> list[str]: + """List entry names in a directory. Raise if it does not exist.""" + + @abc.abstractmethod + async def readdir_with_types(self, path: str) -> Sequence[SessionFSReaddirWithTypesEntry]: + """List entries with type info. Raise if the directory does not exist.""" + + @abc.abstractmethod + async def rm(self, path: str, recursive: bool, force: bool) -> None: + """Remove a file or directory.""" + + @abc.abstractmethod + async def rename(self, src: str, dest: str) -> None: + """Rename / move a file or directory.""" + + +def create_session_fs_adapter(provider: SessionFsProvider) -> SessionFsHandler: + """Wrap a :class:`SessionFsProvider` into a :class:`SessionFsHandler`. + + The adapter catches exceptions thrown by the provider and converts them + into :class:`SessionFSError` results expected by the runtime. + """ + return _SessionFsAdapter(provider) + + +class _SessionFsAdapter: + """Internal adapter that bridges SessionFsProvider → SessionFsHandler.""" + + def __init__(self, provider: SessionFsProvider) -> None: + self._p = provider + + async def read_file(self, params: object) -> SessionFSReadFileResult: + try: + content = await self._p.read_file(params.path) # type: ignore[attr-defined] + return SessionFSReadFileResult.from_dict({"content": content}) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReadFileResult.from_dict({"content": "", "error": err.to_dict()}) + + async def write_file(self, params: object) -> SessionFSError | None: + try: + await self._p.write_file(params.path, params.content, getattr(params, "mode", None)) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def append_file(self, params: object) -> SessionFSError | None: + try: + await self._p.append_file(params.path, params.content, getattr(params, "mode", None)) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def exists(self, params: object) -> SessionFSExistsResult: + try: + result = await self._p.exists(params.path) # type: ignore[attr-defined] + return SessionFSExistsResult.from_dict({"exists": result}) + except Exception: + return SessionFSExistsResult.from_dict({"exists": False}) + + async def stat(self, params: object) -> SessionFSStatResult: + try: + info = await self._p.stat(params.path) # type: ignore[attr-defined] + return SessionFSStatResult( + is_file=info.is_file, + is_directory=info.is_directory, + size=info.size, + mtime=info.mtime, + birthtime=info.birthtime, + ) + except Exception as exc: + now = datetime.now(datetime.UTC) # type: ignore[attr-defined] # ty doesn't resolve datetime.UTC (added in 3.11) + err = _to_session_fs_error(exc) + return SessionFSStatResult( + is_file=False, + is_directory=False, + size=0, + mtime=now, + birthtime=now, + error=err, + ) + + async def mkdir(self, params: object) -> SessionFSError | None: + try: + await self._p.mkdir( + params.path, # type: ignore[attr-defined] + getattr(params, "recursive", False), + getattr(params, "mode", None), + ) + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def readdir(self, params: object) -> SessionFSReaddirResult: + try: + entries = await self._p.readdir(params.path) # type: ignore[attr-defined] + return SessionFSReaddirResult.from_dict({"entries": entries}) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReaddirResult.from_dict({"entries": [], "error": err.to_dict()}) + + async def readdir_with_types(self, params: object) -> SessionFSReaddirWithTypesResult: + try: + entries = await self._p.readdir_with_types(params.path) # type: ignore[attr-defined] + return SessionFSReaddirWithTypesResult(entries=list(entries)) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReaddirWithTypesResult.from_dict( + {"entries": [], "error": err.to_dict()} + ) + + async def rm(self, params: object) -> SessionFSError | None: + try: + await self._p.rm( + params.path, # type: ignore[attr-defined] + getattr(params, "recursive", False), + getattr(params, "force", False), + ) + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def rename(self, params: object) -> SessionFSError | None: + try: + await self._p.rename(params.src, params.dest) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + +def _to_session_fs_error(exc: Exception) -> SessionFSError: + code = SessionFSErrorCode.ENOENT if _is_enoent(exc) else SessionFSErrorCode.UNKNOWN + return SessionFSError(code=code, message=str(exc)) + + +def _is_enoent(exc: Exception) -> bool: + if isinstance(exc, FileNotFoundError): + return True + if isinstance(exc, OSError) and exc.errno == errno.ENOENT: + return True + return False diff --git a/python/e2e/test_compaction.py b/python/e2e/test_compaction.py index c6df2bffa..b06a0312f 100644 --- a/python/e2e/test_compaction.py +++ b/python/e2e/test_compaction.py @@ -7,7 +7,12 @@ from .testharness import E2ETestContext -pytestmark = pytest.mark.asyncio(loop_scope="module") +pytestmark = [ + pytest.mark.asyncio(loop_scope="module"), + pytest.mark.skip( + reason="Compaction tests are skipped due to flakiness — re-enable once stabilized" + ), +] class TestCompaction: diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py index bc228707b..18c266c64 100644 --- a/python/e2e/test_session_fs.py +++ b/python/e2e/test_session_fs.py @@ -14,14 +14,12 @@ from copilot import CopilotClient, SessionFsConfig, define_tool from copilot.client import ExternalServerConfig, SubprocessConfig from copilot.generated.rpc import ( - SessionFSExistsResult, - SessionFSReaddirResult, - SessionFSReaddirWithTypesResult, - SessionFSReadFileResult, - SessionFSStatResult, + SessionFSReaddirWithTypesEntry, + SessionFSReaddirWithTypesEntryType, ) from copilot.generated.session_events import SessionCompactionCompleteData, SessionEvent from copilot.session import PermissionHandler +from copilot.session_fs_provider import SessionFsFileInfo, SessionFsProvider from .testharness import E2ETestContext @@ -214,90 +212,131 @@ def on_event(event: SessionEvent): await wait_for_content(events_path, "checkpointNumber") + async def test_should_write_workspace_metadata_via_sessionfs( + self, ctx: E2ETestContext, session_fs_client: CopilotClient + ): + provider_root = Path(ctx.work_dir) / "provider" + session = await session_fs_client.create_session( + on_permission_request=PermissionHandler.approve_all, + create_session_fs_handler=create_test_session_fs_handler(provider_root), + ) + + msg = await session.send_and_wait("What is 7 * 8?") + assert msg is not None + assert msg.data.content is not None + assert "56" in msg.data.content + + # WorkspaceManager should have created workspace.yaml via sessionFs + workspace_yaml_path = provider_path( + provider_root, session.session_id, "/session-state/workspace.yaml" + ) + await wait_for_path(workspace_yaml_path) + yaml_content = workspace_yaml_path.read_text(encoding="utf-8") + assert "id:" in yaml_content + + # Checkpoint index should also exist + index_path = provider_path( + provider_root, session.session_id, "/session-state/checkpoints/index.md" + ) + await wait_for_path(index_path) + + await session.disconnect() + + async def test_should_persist_plan_md_via_sessionfs( + self, ctx: E2ETestContext, session_fs_client: CopilotClient + ): + from copilot.generated.rpc import PlanUpdateRequest + + provider_root = Path(ctx.work_dir) / "provider" + session = await session_fs_client.create_session( + on_permission_request=PermissionHandler.approve_all, + create_session_fs_handler=create_test_session_fs_handler(provider_root), + ) + + # Write a plan via the session RPC + await session.send_and_wait("What is 2 + 3?") + await session.rpc.plan.update(PlanUpdateRequest(content="# Test Plan\n\nThis is a test.")) -class _SessionFsHandler: + plan_path = provider_path(provider_root, session.session_id, "/session-state/plan.md") + await wait_for_path(plan_path) + content = plan_path.read_text(encoding="utf-8") + assert "# Test Plan" in content + + await session.disconnect() + + +class _TestSessionFsProvider(SessionFsProvider): def __init__(self, provider_root: Path, session_id: str): self._provider_root = provider_root self._session_id = session_id - async def read_file(self, params) -> SessionFSReadFileResult: - content = provider_path(self._provider_root, self._session_id, params.path).read_text( - encoding="utf-8" - ) - return SessionFSReadFileResult.from_dict({"content": content}) - - async def write_file(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(params.content, encoding="utf-8") - - async def append_file(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - path.parent.mkdir(parents=True, exist_ok=True) - with path.open("a", encoding="utf-8") as handle: - handle.write(params.content) - - async def exists(self, params) -> SessionFSExistsResult: - path = provider_path(self._provider_root, self._session_id, params.path) - return SessionFSExistsResult.from_dict({"exists": path.exists()}) - - async def stat(self, params) -> SessionFSStatResult: - path = provider_path(self._provider_root, self._session_id, params.path) - info = path.stat() - timestamp = dt.datetime.fromtimestamp(info.st_mtime, tz=dt.UTC).isoformat() - if timestamp.endswith("+00:00"): - timestamp = f"{timestamp[:-6]}Z" - return SessionFSStatResult.from_dict( - { - "isFile": not path.is_dir(), - "isDirectory": path.is_dir(), - "size": info.st_size, - "mtime": timestamp, - "birthtime": timestamp, - } + def _path(self, path: str) -> Path: + return provider_path(self._provider_root, self._session_id, path) + + async def read_file(self, path: str) -> str: + return self._path(path).read_text(encoding="utf-8") + + async def write_file(self, path: str, content: str, mode: int | None = None) -> None: + p = self._path(path) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(content, encoding="utf-8") + + async def append_file(self, path: str, content: str, mode: int | None = None) -> None: + p = self._path(path) + p.parent.mkdir(parents=True, exist_ok=True) + with p.open("a", encoding="utf-8") as handle: + handle.write(content) + + async def exists(self, path: str) -> bool: + return self._path(path).exists() + + async def stat(self, path: str) -> SessionFsFileInfo: + p = self._path(path) + info = p.stat() + timestamp = dt.datetime.fromtimestamp(info.st_mtime, tz=dt.UTC) + return SessionFsFileInfo( + is_file=not p.is_dir(), + is_directory=p.is_dir(), + size=info.st_size, + mtime=timestamp, + birthtime=timestamp, ) - async def mkdir(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - if params.recursive: - path.mkdir(parents=True, exist_ok=True) + async def mkdir(self, path: str, recursive: bool, mode: int | None = None) -> None: + p = self._path(path) + if recursive: + p.mkdir(parents=True, exist_ok=True) else: - path.mkdir() + p.mkdir() - async def readdir(self, params) -> SessionFSReaddirResult: - entries = sorted( - entry.name - for entry in provider_path(self._provider_root, self._session_id, params.path).iterdir() - ) - return SessionFSReaddirResult.from_dict({"entries": entries}) + async def readdir(self, path: str) -> list[str]: + return sorted(entry.name for entry in self._path(path).iterdir()) - async def readdir_with_types(self, params) -> SessionFSReaddirWithTypesResult: + async def readdir_with_types(self, path: str) -> list[SessionFSReaddirWithTypesEntry]: entries = [] - for entry in sorted( - provider_path(self._provider_root, self._session_id, params.path).iterdir(), - key=lambda item: item.name, - ): + for entry in sorted(self._path(path).iterdir(), key=lambda item: item.name): entries.append( - { - "name": entry.name, - "type": "directory" if entry.is_dir() else "file", - } + SessionFSReaddirWithTypesEntry( + name=entry.name, + type=SessionFSReaddirWithTypesEntryType.DIRECTORY + if entry.is_dir() + else SessionFSReaddirWithTypesEntryType.FILE, + ) ) - return SessionFSReaddirWithTypesResult.from_dict({"entries": entries}) + return entries - async def rm(self, params) -> None: - provider_path(self._provider_root, self._session_id, params.path).unlink() + async def rm(self, path: str, recursive: bool, force: bool) -> None: + self._path(path).unlink() - async def rename(self, params) -> None: - src = provider_path(self._provider_root, self._session_id, params.src) - dest = provider_path(self._provider_root, self._session_id, params.dest) - dest.parent.mkdir(parents=True, exist_ok=True) - src.rename(dest) + async def rename(self, src: str, dest: str) -> None: + d = self._path(dest) + d.parent.mkdir(parents=True, exist_ok=True) + self._path(src).rename(d) def create_test_session_fs_handler(provider_root: Path): def create_handler(session): - return _SessionFsHandler(provider_root, session.session_id) + return _TestSessionFsProvider(provider_root, session.session_id) return create_handler diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index d9a4b0f96..8416d4e40 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -30,6 +30,7 @@ import { isSchemaDeprecated, isObjectSchema, isVoidSchema, + getNullableInner, REPO_ROOT, type ApiSchema, type DefinitionCollections, @@ -150,17 +151,19 @@ function collectRpcMethods(node: Record): RpcMethod[] { } function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: Map): string { - if (schema.anyOf) { - const nonNull = schema.anyOf.filter((s) => typeof s === "object" && s.type !== "null"); - if (nonNull.length === 1 && typeof nonNull[0] === "object") { - // Pass required=true to get the base type, then add "?" for nullable - return schemaTypeToCSharp(nonNull[0] as JSONSchema7, true, knownTypes) + "?"; - } + const nullableInner = getNullableInner(schema); + if (nullableInner) { + // Pass required=true to get the base type, then add "?" for nullable + return schemaTypeToCSharp(nullableInner, true, knownTypes) + "?"; } if (schema.$ref) { const refName = schema.$ref.split("/").pop()!; return knownTypes.get(refName) || refName; } + // Titled union schemas (anyOf with a title) — use the title if it's a known generated type + if (schema.title && schema.anyOf && knownTypes.has(schema.title)) { + return required ? schema.title : `${schema.title}?`; + } const type = schema.type; const format = schema.format; // Handle type: ["string", "null"] patterns (nullable string) @@ -378,7 +381,7 @@ function findDiscriminator(variants: JSONSchema7[]): { property: string; mapping const firstVariant = variants[0]; if (!firstVariant.properties) return null; - for (const [propName, propSchema] of Object.entries(firstVariant.properties)) { + for (const [propName, propSchema] of Object.entries(firstVariant.properties).sort(([a], [b]) => a.localeCompare(b))) { if (typeof propSchema !== "object") continue; const schema = propSchema as JSONSchema7; if (schema.const === undefined) continue; @@ -471,7 +474,7 @@ function generateDerivedClass( lines.push(""); if (schema.properties) { - for (const [propName, propSchema] of Object.entries(schema.properties)) { + for (const [propName, propSchema] of Object.entries(schema.properties).sort(([a], [b]) => a.localeCompare(b))) { if (typeof propSchema !== "object") continue; if (propName === discriminatorProperty) continue; @@ -508,7 +511,7 @@ function generateNestedClass( if (isSchemaDeprecated(schema)) lines.push(`[Obsolete("This member is deprecated and will be removed in a future version.")]`); lines.push(`public partial class ${className}`, `{`); - for (const [propName, propSchema] of Object.entries(schema.properties || {})) { + for (const [propName, propSchema] of Object.entries(schema.properties || {}).sort(([a], [b]) => a.localeCompare(b))) { if (typeof propSchema !== "object") continue; const prop = propSchema as JSONSchema7; const isReq = required.has(propName); @@ -561,16 +564,24 @@ function resolveSessionPropertyType( return resolveSessionPropertyType(refSchema, parentClassName, propName, isRequired, knownTypes, nestedClasses, enumOutput); } if (propSchema.anyOf) { - const hasNull = propSchema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); - const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null"); - if (nonNull.length === 1) { - return resolveSessionPropertyType(nonNull[0] as JSONSchema7, parentClassName, propName, isRequired && !hasNull, knownTypes, nestedClasses, enumOutput); + const simpleNullable = getNullableInner(propSchema); + if (simpleNullable) { + return resolveSessionPropertyType(simpleNullable, parentClassName, propName, false, knownTypes, nestedClasses, enumOutput); } // Discriminated union: anyOf with multiple object variants sharing a const discriminator + const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && s !== null && (s as JSONSchema7).type !== "null"); if (nonNull.length > 1) { - const variants = nonNull as JSONSchema7[]; + // Resolve $ref variants to their actual schemas + const variants = (nonNull as JSONSchema7[]).map((v) => { + if (v.$ref) { + const resolved = resolveRef(v.$ref, sessionDefinitions); + return resolved ?? v; + } + return v; + }); const discriminatorInfo = findDiscriminator(variants); if (discriminatorInfo) { + const hasNull = propSchema.anyOf.length > nonNull.length; const baseClassName = (propSchema.title as string) ?? `${parentClassName}${propName}`; const renamedBase = applyTypeRename(baseClassName); const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, knownTypes, nestedClasses, enumOutput, propSchema.description); @@ -578,7 +589,7 @@ function resolveSessionPropertyType( return isRequired && !hasNull ? renamedBase : `${renamedBase}?`; } } - return hasNull || !isRequired ? "object?" : "object"; + return !isRequired ? "object?" : "object"; } if (propSchema.enum && Array.isArray(propSchema.enum)) { const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined, isSchemaDeprecated(propSchema)); @@ -602,6 +613,19 @@ function resolveSessionPropertyType( ); return isRequired ? `${itemType}[]` : `${itemType}[]?`; } + if (propSchema.type === "object" && propSchema.additionalProperties && typeof propSchema.additionalProperties === "object") { + const valueSchema = propSchema.additionalProperties as JSONSchema7; + const valueType = resolveSessionPropertyType( + valueSchema, + parentClassName, + `${propName}Value`, + true, + knownTypes, + nestedClasses, + enumOutput + ); + return isRequired ? `IDictionary` : `IDictionary?`; + } return schemaTypeToCSharp(propSchema, isRequired, knownTypes); } @@ -620,7 +644,7 @@ function generateDataClass(variant: EventVariant, knownTypes: Map a.localeCompare(b))) { if (typeof propSchema !== "object") continue; const isReq = required.has(propName); const csharpName = toPascalCase(propName); @@ -787,6 +811,27 @@ function resultTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(getMethodResultSchema(method), `${typeToClassName(method.rpcMethod)}Result`); } +/** Returns the C# type for a method's result, accounting for nullable anyOf wrappers. */ +function resolvedResultTypeName(method: RpcMethod): string { + const schema = getMethodResultSchema(method); + if (!schema) return resultTypeName(method); + const inner = getNullableInner(schema); + if (inner) { + // Nullable wrapper: resolve the inner $ref type name with "?" suffix + const innerName = inner.$ref + ? typeToClassName(refTypeName(inner.$ref, rpcDefinitions)) + : getRpcSchemaTypeName(inner, resultTypeName(method)); + return `${innerName}?`; + } + return resultTypeName(method); +} + +/** Returns the Task or Task string for a method's result type. */ +function resultTaskType(method: RpcMethod): string { + const schema = getMethodResultSchema(method); + return !isVoidSchema(schema) ? `Task<${resolvedResultTypeName(method)}>` : "Task"; +} + function paramsTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(resolveMethodParamsSchema(method), `${typeToClassName(method.rpcMethod)}Request`); } @@ -833,12 +878,35 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam return resolveRpcType(refSchema, isRequired, parentClassName, propName, classes); } - // Handle anyOf: [T, null] → T? (nullable typed property) - if (schema.anyOf) { - const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); - const nonNull = schema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null"); - if (nonNull.length === 1) { - return resolveRpcType(nonNull[0] as JSONSchema7, isRequired && !hasNull, parentClassName, propName, classes); + // Handle anyOf: [T, null/{not:{}}] → T? (nullable typed property) + const nullableInner = getNullableInner(schema); + if (nullableInner) { + return resolveRpcType(nullableInner, false, parentClassName, propName, classes); + } + // Discriminated union: anyOf with multiple variants sharing a const discriminator + if (schema.anyOf && Array.isArray(schema.anyOf)) { + const nonNull = schema.anyOf.filter((s) => typeof s === "object" && s !== null && (s as JSONSchema7).type !== "null"); + if (nonNull.length > 1) { + const variants = (nonNull as JSONSchema7[]).map((v) => { + if (v.$ref) { + const resolved = resolveRef(v.$ref, rpcDefinitions); + return resolved ?? v; + } + return v; + }); + const discriminatorInfo = findDiscriminator(variants); + if (discriminatorInfo) { + const hasNull = schema.anyOf.length > nonNull.length; + const baseClassName = (schema.title as string) ?? `${parentClassName}${propName}`; + if (!emittedRpcClassSchemas.has(baseClassName)) { + emittedRpcClassSchemas.set(baseClassName, "polymorphic"); + const nestedMap = new Map(); + const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, rpcKnownTypes, nestedMap, rpcEnumOutput, schema.description); + classes.push(polymorphicCode); + for (const nested of nestedMap.values()) classes.push(nested); + } + return isRequired && !hasNull ? baseClassName : `${baseClassName}?`; + } } } // Handle enums (string unions like "interactive" | "plan" | "autopilot") @@ -911,7 +979,7 @@ function emitRpcClass( } lines.push(`${visibility} sealed class ${className}`, `{`); - const props = Object.entries(effectiveSchema.properties || {}); + const props = Object.entries(effectiveSchema.properties || {}).sort(([a], [b]) => a.localeCompare(b)); for (let i = 0; i < props.length; i++) { const [propName, propSchema] = props[i]; if (typeof propSchema !== "object") continue; @@ -1089,6 +1157,13 @@ function emitServerInstanceMethod( const paramEntries = effectiveParams?.properties ? Object.entries(effectiveParams.properties) : []; const requiredSet = new Set(effectiveParams?.required || []); + // Sort so required params come before optional (C# requires defaults at end) + paramEntries.sort((a, b) => { + const aReq = requiredSet.has(a[0]) ? 0 : 1; + const bReq = requiredSet.has(b[0]) ? 0 : 1; + return aReq - bReq; + }); + let requestClassName: string | null = null; if (paramEntries.length > 0) { requestClassName = paramsTypeName(method); @@ -1337,7 +1412,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, const effectiveParams = resolveMethodParamsSchema(method); const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; const resultSchema = getMethodResultSchema(method); - const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; + const taskType = resultTaskType(method); lines.push(` /// Handles "${method.rpcMethod}".`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` [Experimental(Diagnostics.Experimental)]`); @@ -1385,7 +1460,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; const resultSchema = getMethodResultSchema(method); const paramsClass = paramsTypeName(method); - const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; + const taskType = resultTaskType(method); const registrationVar = `register${typeToClassName(method.rpcMethod)}Method`; if (hasParams) { diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 8f9d40321..bb7d85319 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -17,12 +17,12 @@ import { getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, - hoistTitledSchemas, hasSchemaPayload, isNodeFullyExperimental, isNodeFullyDeprecated, isSchemaDeprecated, isVoidSchema, + getNullableInner, isRpcMethod, postProcessSchema, writeGeneratedFile, @@ -110,7 +110,7 @@ function postProcessEnumConstants(code: string): string { return code; } -function collapsePlaceholderGoStructs(code: string): string { +function collapsePlaceholderGoStructs(code: string, knownDefinitionNames?: Set): string { const structBlockRe = /((?:\/\/.*\r?\n)*)type\s+(\w+)\s+struct\s*\{[\s\S]*?^\}/gm; const matches = [...code.matchAll(structBlockRe)].map((match) => ({ fullBlock: match[0], @@ -128,12 +128,14 @@ function collapsePlaceholderGoStructs(code: string): string { for (const group of groups.values()) { if (group.length < 2) continue; - const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name), knownDefinitionNames); if (!canonical) continue; for (const duplicate of group) { if (duplicate.name === canonical) continue; - if (!isPlaceholderTypeName(duplicate.name)) continue; + // Only collapse types that quicktype invented (Class suffix or not + // in the schema's named definitions). Preserve intentionally-named types. + if (!isPlaceholderTypeName(duplicate.name) && knownDefinitionNames?.has(duplicate.name.toLowerCase())) continue; code = code.replace(duplicate.fullBlock, ""); code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); @@ -145,7 +147,7 @@ function collapsePlaceholderGoStructs(code: string): string { function normalizeGoStructBlock(block: string, name: string): string { return block - .replace(/^\/\/.*\r?\n/gm, "") + .replace(/^\s*\/\/.*\r?\n/gm, "") .replace(new RegExp(`^type\\s+${name}\\s+struct\\s*\\{`, "m"), "type struct {") .split(/\r?\n/) .map((line) => line.trim()) @@ -153,10 +155,16 @@ function normalizeGoStructBlock(block: string, name: string): string { .join("\n"); } -function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { +function chooseCanonicalPlaceholderDuplicate(names: string[], knownDefinitionNames?: Set): string | undefined { + // Prefer the name that matches a schema definition — it's intentionally named. + if (knownDefinitionNames) { + const definedName = names.find((name) => knownDefinitionNames.has(name.toLowerCase())); + if (definedName) return definedName; + } + // Fallback for Class-suffix placeholders: pick the non-placeholder name. const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); if (specificNames.length === 0) return undefined; - return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; + return specificNames[0]; } function isPlaceholderTypeName(name: string): boolean { @@ -266,6 +274,14 @@ function goResultTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); } +function goNullableResultTypeName(method: RpcMethod, innerSchema: JSONSchema7): string { + if (innerSchema.$ref) { + const refName = innerSchema.$ref.split("/").pop(); + if (refName) return toPascalCase(refName); + } + return getRpcSchemaTypeName(innerSchema, toPascalCase(method.rpcMethod) + "Result"); +} + function goParamsTypeName(method: RpcMethod): string { const fallback = goRequestFallbackName(method); if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { @@ -428,6 +444,17 @@ function resolveGoPropertyType( // Handle anyOf if (propSchema.anyOf) { + const nullableInnerSchema = getNullableInner(propSchema); + if (nullableInnerSchema) { + // anyOf [T, null/{not:{}}] → nullable T + const innerType = resolveGoPropertyType(nullableInnerSchema, parentTypeName, jsonPropName, true, ctx); + if (isRequired) return innerType; + // Pointer-wrap if not already a pointer, slice, or map + if (innerType.startsWith("*") || innerType.startsWith("[]") || innerType.startsWith("map[")) { + return innerType; + } + return `*${innerType}`; + } const nonNull = (propSchema.anyOf as JSONSchema7[]).filter((s) => s.type !== "null"); const hasNull = (propSchema.anyOf as JSONSchema7[]).some((s) => s.type === "null"); @@ -435,7 +462,6 @@ function resolveGoPropertyType( // anyOf [T, null] → nullable T const innerType = resolveGoPropertyType(nonNull[0], parentTypeName, jsonPropName, true, ctx); if (isRequired && !hasNull) return innerType; - // Pointer-wrap if not already a pointer, slice, or map if (innerType.startsWith("*") || innerType.startsWith("[]") || innerType.startsWith("map[")) { return innerType; } @@ -443,8 +469,15 @@ function resolveGoPropertyType( } if (nonNull.length > 1) { + // Resolve $refs in variants before discriminator analysis + const resolvedVariants = nonNull.map((v) => { + if (v.$ref && typeof v.$ref === "string") { + return resolveRef(v.$ref, ctx.definitions) ?? v; + } + return v; + }); // Check for discriminated union - const disc = findGoDiscriminator(nonNull); + const disc = findGoDiscriminator(resolvedVariants); if (disc) { const unionName = (propSchema.title as string) || nestedName; emitGoFlatDiscriminatedUnion(unionName, disc.property, disc.mapping, ctx, propSchema.description); @@ -571,7 +604,7 @@ function emitGoStruct( } lines.push(`type ${typeName} struct {`); - for (const [propName, propSchema] of Object.entries(schema.properties || {})) { + for (const [propName, propSchema] of Object.entries(schema.properties || {}).sort(([a], [b]) => a.localeCompare(b))) { if (typeof propSchema !== "object") continue; const prop = propSchema as JSONSchema7; const isReq = required.has(propName); @@ -667,7 +700,7 @@ function emitGoFlatDiscriminatedUnion( lines.push(`\t${discGoName} ${discEnumName} \`json:"${discriminatorProp}"\``); // Emit remaining fields - for (const [propName, info] of allProps) { + for (const [propName, info] of [...allProps.entries()].sort(([a], [b]) => a.localeCompare(b))) { if (propName === discriminatorProp) continue; const goName = toGoFieldName(propName); const goType = resolveGoPropertyType(info.schema, typeName, propName, info.requiredInAll, ctx); @@ -713,7 +746,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { } lines.push(`type ${variant.dataClassName} struct {`); - for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties || {})) { + for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties || {}).sort(([a], [b]) => a.localeCompare(b))) { if (typeof propSchema !== "object") continue; const prop = propSchema as JSONSchema7; const isReq = required.has(propName); @@ -899,19 +932,19 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { out.push(``); // Per-event data structs - for (const ds of dataStructs) { + for (const ds of dataStructs.sort()) { out.push(ds); out.push(``); } // Nested structs - for (const s of ctx.structs) { + for (const s of ctx.structs.sort()) { out.push(s); out.push(``); } // Enums - for (const e of ctx.enums) { + for (const e of ctx.enums.sort()) { out.push(e); out.push(``); } @@ -919,7 +952,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { // Type aliases for types referenced by non-generated SDK code under their short names. const TYPE_ALIASES: Record = { PermissionRequestCommand: "PermissionRequestShellCommand", - PossibleURL: "PermissionRequestShellPossibleUrl", + PossibleURL: "PermissionRequestShellPossibleURL", Attachment: "UserMessageAttachment", AttachmentType: "UserMessageAttachmentType", }; @@ -989,7 +1022,11 @@ async function generateRpc(schemaPath?: string): Promise { for (const method of allMethods) { const resultSchema = getMethodResultSchema(method); - if (isVoidSchema(resultSchema)) { + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + if (nullableInner) { + // Nullable results (e.g., *SessionFSError) don't need a wrapper type; + // the inner type is already in definitions via shared hoisting. + } else if (isVoidSchema(resultSchema)) { // Emit an empty struct for void results (forward-compatible with adding fields later) combinedSchema.definitions![goResultTypeName(method)] = { title: goResultTypeName(method), @@ -1029,22 +1066,25 @@ async function generateRpc(schemaPath?: string): Promise { } } - const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); - const allDefinitions = { ...rootDefinitions, ...sharedDefinitions }; + const allDefinitions = combinedSchema.definitions! as Record; const allDefinitionCollections: DefinitionCollections = { definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, }; - // Generate types via quicktype + // Generate types via quicktype — use a single combined schema source so quicktype + // sees each definition exactly once, preventing whimsical prefix disambiguation. const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(rootDefinitions)) { - const schemaWithDefs = withSharedDefinitions( - typeof def === "object" ? (def as JSONSchema7) : {}, - allDefinitionCollections - ); - await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); - } + const singleSchema: JSONSchema7 = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + definitions: allDefinitions as Record, + properties: Object.fromEntries( + Object.keys(allDefinitions).map((name) => [name, { $ref: `#/definitions/${name}` }]) + ), + required: Object.keys(allDefinitions), + }; + await schemaInput.addSource({ name: "RpcTypes", schema: JSON.stringify(singleSchema) }); const inputData = new InputData(); inputData.addInput(schemaInput); @@ -1060,7 +1100,8 @@ async function generateRpc(schemaPath?: string): Promise { const quicktypeImports = extractQuicktypeImports(qtCode); qtCode = quicktypeImports.code; qtCode = postProcessEnumConstants(qtCode); - qtCode = collapsePlaceholderGoStructs(qtCode); + const knownDefNames = new Set(Object.keys(allDefinitions).map((n) => n.toLowerCase())); + qtCode = collapsePlaceholderGoStructs(qtCode, knownDefNames); // Strip trailing whitespace from quicktype output (gofmt requirement) qtCode = qtCode.replace(/[ \t]+$/gm, ""); @@ -1082,7 +1123,7 @@ async function generateRpc(schemaPath?: string): Promise { if (method.stability !== "experimental") continue; experimentalTypeNames.add(goResultTypeName(method)); const paramsTypeName = goParamsTypeName(method); - if (rootDefinitions[paramsTypeName]) { + if (allDefinitions[paramsTypeName]) { experimentalTypeNames.add(paramsTypeName); } } @@ -1102,7 +1143,7 @@ async function generateRpc(schemaPath?: string): Promise { } if (!method.params?.$ref) { const paramsTypeName = goParamsTypeName(method); - if (rootDefinitions[paramsTypeName]) { + if (allDefinitions[paramsTypeName]) { deprecatedTypeNames.add(paramsTypeName); } } @@ -1277,7 +1318,11 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>, groupExperimental = false, isWrapper = false, groupDeprecated = false): void { const methodName = toPascalCase(name); - const resultType = resolveType(goResultTypeName(method)); + const resultSchema = getMethodResultSchema(method); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const resultType = nullableInner + ? resolveType(goNullableResultTypeName(method, nullableInner)) + : resolveType(goResultTypeName(method)); const effectiveParams = getMethodParamsSchema(method); const paramProps = effectiveParams?.properties || {}; @@ -1388,7 +1433,11 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< lines.push(`\t// Experimental: ${clientHandlerMethodName(method.rpcMethod)} is an experimental API and may change or be removed in future versions.`); } const paramsType = resolveType(goParamsTypeName(method)); - const resultType = resolveType(goResultTypeName(method)); + const resultSchema = getMethodResultSchema(method); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const resultType = nullableInner + ? resolveType(goNullableResultTypeName(method, nullableInner)) + : resolveType(goResultTypeName(method)); lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (*${resultType}, error)`); } lines.push(`}`); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 175c5175b..6fe931994 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -16,9 +16,9 @@ import { getApiSchemaPath, getRpcSchemaTypeName, getSessionEventsSchemaPath, - hoistTitledSchemas, isObjectSchema, isVoidSchema, + getNullableInner, isRpcMethod, isNodeFullyExperimental, isNodeFullyDeprecated, @@ -152,7 +152,7 @@ function unwrapRedundantPythonLambdas(code: string): string { ); } -function collapsePlaceholderPythonDataclasses(code: string): string { +function collapsePlaceholderPythonDataclasses(code: string, knownDefinitionNames?: Set): string { const classBlockRe = /(@dataclass\r?\nclass\s+(\w+):[\s\S]*?)(?=^@dataclass|^class\s+\w+|^def\s+\w+|\Z)/gm; const matches = [...code.matchAll(classBlockRe)].map((match) => ({ fullBlock: match[1], @@ -170,12 +170,14 @@ function collapsePlaceholderPythonDataclasses(code: string): string { for (const group of groups.values()) { if (group.length < 2) continue; - const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name), knownDefinitionNames); if (!canonical) continue; for (const duplicate of group) { if (duplicate.name === canonical) continue; - if (!isPlaceholderTypeName(duplicate.name)) continue; + // Only collapse types that quicktype invented (Class suffix or not + // in the schema's named definitions). Preserve intentionally-named types. + if (!isPlaceholderTypeName(duplicate.name) && knownDefinitionNames?.has(duplicate.name.toLowerCase())) continue; code = code.replace(duplicate.fullBlock, ""); code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); @@ -346,16 +348,23 @@ function normalizePythonDataclassBlock(block: string, name: string): string { .join("\n"); } -function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { +function chooseCanonicalPlaceholderDuplicate(names: string[], knownDefinitionNames?: Set): string | undefined { + // Prefer the name that matches a schema definition — it's intentionally named. + if (knownDefinitionNames) { + const definedName = names.find((name) => knownDefinitionNames.has(name.toLowerCase())); + if (definedName) return definedName; + } + // Fallback for Class-suffix placeholders: pick the non-placeholder name. const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); if (specificNames.length === 0) return undefined; - return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; + return specificNames[0]; } function isPlaceholderTypeName(name: string): boolean { - return name.endsWith("Class"); + return name.endsWith("Class") || name.endsWith("Enum"); } + function toSnakeCase(s: string): string { return s .replace(/([a-z])([A-Z])/g, "$1_$2") @@ -419,8 +428,14 @@ function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { ); } -function pythonResultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); +function pythonResultTypeName(method: RpcMethod, schemaOverride?: JSONSchema7): string { + const schema = schemaOverride ?? getMethodResultSchema(method); + // If schema is a $ref, derive the type name from the ref path + if (schema?.$ref) { + const refName = schema.$ref.split("/").pop(); + if (refName) return toPascalCase(refName); + } + return getRpcSchemaTypeName(schema, toPascalCase(method.rpcMethod) + "Result"); } function pythonParamsTypeName(method: RpcMethod): string { @@ -705,7 +720,8 @@ function resolvePyPropertyType( isRequired: boolean, ctx: PyCodegenCtx ): PyResolvedType { - const nestedName = parentTypeName + toPascalCase(jsonPropName); + const fallbackName = parentTypeName + toPascalCase(jsonPropName); + const nestedName = typeof propSchema.title === "string" ? propSchema.title : fallbackName; if (propSchema.$ref && typeof propSchema.$ref === "string") { const typeName = toPascalCase(refTypeName(propSchema.$ref, ctx.definitions)); @@ -995,8 +1011,8 @@ function emitPyClass( ([, value]) => typeof value === "object" ) as Array<[string, JSONSchema7]>; const orderedFieldEntries = [ - ...fieldEntries.filter(([name]) => required.has(name)), - ...fieldEntries.filter(([name]) => !required.has(name)), + ...fieldEntries.filter(([name]) => required.has(name)).sort(([a], [b]) => a.localeCompare(b)), + ...fieldEntries.filter(([name]) => !required.has(name)).sort(([a], [b]) => a.localeCompare(b)), ]; const fieldInfos = orderedFieldEntries.map(([propName, propSchema]) => { @@ -1007,7 +1023,9 @@ function emitPyClass( fieldName: toSnakeCase(propName), isRequired, resolved, - defaultLiteral: isRequired ? undefined : toPythonLiteral(propSchema.default), + defaultLiteral: isRequired ? undefined : toPythonLiteral( + propSchema.default ?? resolveSchema(propSchema, ctx.definitions)?.default + ), }; }); @@ -1140,8 +1158,8 @@ function emitPyFlatDiscriminatedUnion( ]; const orderedFieldEntries = [ - ...fieldEntries.filter(([, , requiredInAll]) => requiredInAll), - ...fieldEntries.filter(([, , requiredInAll]) => !requiredInAll), + ...fieldEntries.filter(([, , requiredInAll]) => requiredInAll).sort(([a], [b]) => a.localeCompare(b)), + ...fieldEntries.filter(([, , requiredInAll]) => !requiredInAll).sort(([a], [b]) => a.localeCompare(b)), ]; const fieldInfos = orderedFieldEntries.map(([propName, propSchema, requiredInAll]) => { @@ -1161,7 +1179,9 @@ function emitPyFlatDiscriminatedUnion( fieldName: toSnakeCase(propName), isRequired: requiredInAll, resolved, - defaultLiteral: requiredInAll ? undefined : toPythonLiteral(propSchema.default), + defaultLiteral: requiredInAll ? undefined : toPythonLiteral( + propSchema.default ?? resolveSchema(propSchema, ctx.definitions)?.default + ), }; }); @@ -1446,12 +1466,12 @@ export function generatePythonSessionEventsCode(schema: JSONSchema7): string { ); out.push(``); out.push(``); - for (const classDef of ctx.classes) { + for (const classDef of ctx.classes.sort()) { out.push(classDef); out.push(``); out.push(``); } - for (const enumDef of ctx.enums) { + for (const enumDef of ctx.enums.sort()) { out.push(enumDef); out.push(``); out.push(``); @@ -1567,10 +1587,14 @@ async function generateRpc(schemaPath?: string): Promise { for (const method of allMethods) { const resultSchema = getMethodResultSchema(method); if (!isVoidSchema(resultSchema)) { - combinedSchema.definitions![pythonResultTypeName(method)] = withRootTitle( - schemaSourceForNamedDefinition(method.result, resultSchema), - pythonResultTypeName(method) - ); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + if (!nullableInner) { + combinedSchema.definitions![pythonResultTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.result, resultSchema), + pythonResultTypeName(method) + ); + } + // For nullable results, the inner type (e.g., SessionFsError) is already in definitions } const resolvedParams = getMethodParamsSchema(method); if (method.params && hasSchemaPayload(resolvedParams)) { @@ -1597,22 +1621,25 @@ async function generateRpc(schemaPath?: string): Promise { } } - const { rootDefinitions, sharedDefinitions } = hoistTitledSchemas(combinedSchema.definitions! as Record); - const allDefinitions = { ...rootDefinitions, ...sharedDefinitions }; + const allDefinitions = combinedSchema.definitions! as Record; const allDefinitionCollections: DefinitionCollections = { definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, }; - // Generate types via quicktype + // Generate types via quicktype — use a single combined schema source to avoid + // quicktype inventing Purple/Fluffy disambiguation prefixes for shared types const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(rootDefinitions)) { - const schemaWithDefs = withSharedDefinitions( - typeof def === "object" ? (def as JSONSchema7) : {}, - allDefinitionCollections - ); - await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); - } + const singleSchema: Record = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + definitions: allDefinitions, + properties: Object.fromEntries( + Object.keys(allDefinitions).map((name) => [name, { $ref: `#/definitions/${name}` }]) + ), + required: Object.keys(allDefinitions), + }; + await schemaInput.addSource({ name: "RPC", schema: JSON.stringify(singleSchema) }); const inputData = new InputData(); inputData.addInput(schemaInput); @@ -1632,7 +1659,26 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = typesCode.replace(/^(\s*)pass\n\n(\s*@staticmethod)/gm, "$2"); // Modernize to Python 3.11+ syntax typesCode = modernizePython(typesCode); - typesCode = collapsePlaceholderPythonDataclasses(typesCode); + const knownDefNames = new Set(Object.keys(allDefinitions).map((n) => n.toLowerCase())); + typesCode = collapsePlaceholderPythonDataclasses(typesCode, knownDefNames); + + // Fix quicktype's Enum-suffix renaming: quicktype sometimes renames "Xyz" to + // "XyzEnum" to avoid internal collisions. Strip the suffix to match our schema + // definition names, but fail the build if that introduces a duplicate definition. + for (const defName of Object.keys(allDefinitions)) { + const enumSuffixed = defName + "Enum"; + if (!new RegExp(`\\bclass ${enumSuffixed}\\b`).test(typesCode)) continue; + const renamed = typesCode.replace(new RegExp(`\\b${enumSuffixed}\\b`, "g"), defName); + const classCount = (renamed.match(new RegExp(`^class ${defName}\\b`, "gm")) ?? []).length; + if (classCount > 1) { + throw new Error( + `Python codegen: stripping quicktype's "Enum" suffix from "${enumSuffixed}" ` + + `would produce a duplicate definition for "${defName}". ` + + `Fix the schema definition name or add .withTypeName() to disambiguate.` + ); + } + typesCode = renamed; + } // Reorder class/enum definitions to resolve forward references. // Quicktype may emit classes before their dependencies are defined. @@ -1652,7 +1698,7 @@ async function generateRpc(schemaPath?: string): Promise { if (method.stability !== "experimental") continue; experimentalTypeNames.add(pythonResultTypeName(method)); const paramsTypeName = pythonParamsTypeName(method); - if (rootDefinitions[paramsTypeName]) { + if (allDefinitions[paramsTypeName]) { experimentalTypeNames.add(paramsTypeName); } } @@ -1672,7 +1718,7 @@ async function generateRpc(schemaPath?: string): Promise { } if (!method.params?.$ref) { const paramsTypeName = pythonParamsTypeName(method); - if (rootDefinitions[paramsTypeName]) { + if (allDefinitions[paramsTypeName]) { deprecatedTypeNames.add(paramsTypeName); } } @@ -1876,9 +1922,21 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false, groupDeprecated = false): void { const methodName = toSnakeCase(name); const resultSchema = getMethodResultSchema(method); - const hasResult = !isVoidSchema(resultSchema); - const resultType = hasResult ? resolveType(pythonResultTypeName(method)) : "None"; - const resultIsObject = isObjectSchema(resultSchema); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const effectiveResultSchema = nullableInner ?? resultSchema; + const hasResult = !isVoidSchema(resultSchema) && !nullableInner; + const hasNullableResult = !!nullableInner; + const resultIsObject = isObjectSchema(effectiveResultSchema); + + let resultType: string; + if (hasNullableResult) { + const innerTypeName = resolveType(pythonResultTypeName(method, nullableInner)); + resultType = `${innerTypeName} | None`; + } else if (hasResult) { + resultType = resolveType(pythonResultTypeName(method)); + } else { + resultType = "None"; + } const effectiveParams = getMethodParamsSchema(method); const paramProps = effectiveParams?.properties || {}; @@ -1900,40 +1958,46 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); } - // For object results use .from_dict(); for enums/primitives use direct construction - const deserialize = (expr: string) => resultIsObject ? `${resultType}.from_dict(${expr})` : `${resultType}(${expr})`; + // Deserialize helper + const innerTypeName = hasNullableResult ? resolveType(pythonResultTypeName(method, nullableInner)) : resultType; + const deserialize = (expr: string) => { + if (hasNullableResult) { + return resultIsObject + ? `${innerTypeName}.from_dict(${expr}) if ${expr} is not None else None` + : `${innerTypeName}(${expr}) if ${expr} is not None else None`; + } + return resultIsObject ? `${innerTypeName}.from_dict(${expr})` : `${innerTypeName}(${expr})`; + }; // Build request body with proper serialization/deserialization + const emitRequestCall = (paramsExpr: string) => { + const callExpr = `await self._client.request("${method.rpcMethod}", ${paramsExpr}, **_timeout_kwargs(timeout))`; + if (hasResult || hasNullableResult) { + if (hasNullableResult) { + lines.push(` _result = ${callExpr}`); + lines.push(` return ${deserialize("_result")}`); + } else { + lines.push(` return ${deserialize(callExpr)}`); + } + } else { + lines.push(` ${callExpr}`); + } + }; + if (isSession) { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); lines.push(` params_dict["sessionId"] = self._session_id`); - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); - } + emitRequestCall("params_dict"); } else { - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`); - } + emitRequestCall(`{"sessionId": self._session_id}`); } } else { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); - } + emitRequestCall("params_dict"); } else { - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`); - } + emitRequestCall("{}"); } } lines.push(``); @@ -2009,7 +2073,15 @@ function emitClientSessionHandlerMethod( ): void { const paramsType = resolveType(pythonParamsTypeName(method)); const resultSchema = getMethodResultSchema(method); - const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : "None"; + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + let resultType: string; + if (nullableInner) { + resultType = `${resolveType(pythonResultTypeName(method, nullableInner))} | None`; + } else if (!isVoidSchema(resultSchema)) { + resultType = resolveType(pythonResultTypeName(method)); + } else { + resultType = "None"; + } lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`); if (method.deprecated && !groupDeprecated) { lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`); @@ -2030,7 +2102,8 @@ function emitClientSessionRegistrationMethod( const handlerVariableName = `handle_${toSnakeCase(groupName)}_${toSnakeCase(methodName)}`; const paramsType = resolveType(pythonParamsTypeName(method)); const resultSchema = getMethodResultSchema(method); - const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : null; + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const hasResult = !isVoidSchema(resultSchema) && !nullableInner; const handlerField = toSnakeCase(groupName); const handlerMethod = toSnakeCase(methodName); @@ -2040,13 +2113,21 @@ function emitClientSessionRegistrationMethod( lines.push( ` if handler is None: raise RuntimeError(f"No ${handlerField} handler registered for session: {request.session_id}")` ); - if (resultType) { + if (hasResult) { lines.push(` result = await handler.${handlerMethod}(request)`); if (isObjectSchema(resultSchema)) { lines.push(` return result.to_dict()`); } else { lines.push(` return result.value if hasattr(result, 'value') else result`); } + } else if (nullableInner) { + lines.push(` result = await handler.${handlerMethod}(request)`); + const resolvedInner = resolveSchema(nullableInner, rpcDefinitions) ?? nullableInner; + if (isObjectSchema(resolvedInner) || nullableInner.$ref) { + lines.push(` return result.to_dict() if result is not None else None`); + } else { + lines.push(` return result`); + } } else { lines.push(` await handler.${handlerMethod}(request)`); lines.push(` return None`); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 1aba7384c..208e05941 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -12,9 +12,9 @@ import { compile } from "json-schema-to-typescript"; import { getApiSchemaPath, fixNullableRequiredRefsInApiSchema, + getNullableInner, getRpcSchemaTypeName, getSessionEventsSchemaPath, - normalizeSchemaTitles, postProcessSchema, writeGeneratedFile, collectDefinitionCollections, @@ -26,7 +26,6 @@ import { isNodeFullyExperimental, isNodeFullyDeprecated, isVoidSchema, - stripNonAnnotationTitles, type ApiSchema, type DefinitionCollections, type RpcMethod, @@ -143,15 +142,14 @@ function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { const draftDefinitionAliases = new Map(); for (const [key, value] of Object.entries(root.$defs ?? {})) { - let alias = key; - if (alias in definitions) { - alias = `$defs_${key}`; - while (alias in definitions) { - alias = `$defs_${alias}`; - } + if (key in definitions) { + // The definitions entry is authoritative (it went through the full pipeline). + // Drop the $defs duplicate and rewrite any $ref pointing at it to use definitions. + draftDefinitionAliases.set(key, key); + } else { + draftDefinitionAliases.set(key, key); + definitions[key] = value; } - draftDefinitionAliases.set(key, alias); - definitions[alias] = value; } root.definitions = definitions; @@ -169,9 +167,19 @@ function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { Object.entries(value as Record).map(([key, child]) => [key, rewrite(child)]) ) as Record; - if (typeof rewritten.$ref === "string" && rewritten.$ref.startsWith("#/$defs/")) { - const definitionName = rewritten.$ref.slice("#/$defs/".length); - rewritten.$ref = `#/definitions/${draftDefinitionAliases.get(definitionName) ?? definitionName}`; + if (typeof rewritten.$ref === "string") { + if (rewritten.$ref.startsWith("#/$defs/")) { + const definitionName = rewritten.$ref.slice("#/$defs/".length); + rewritten.$ref = `#/definitions/${draftDefinitionAliases.get(definitionName) ?? definitionName}`; + } + // json-schema-to-typescript treats sibling keywords alongside $ref as a + // new inline type instead of reusing the referenced definition. Strip + // siblings so that $ref-only objects compile to a single shared type. + for (const key of Object.keys(rewritten)) { + if (key !== "$ref") { + delete rewritten[key]; + } + } } return rewritten; @@ -180,65 +188,6 @@ function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { return rewrite(root) as JSONSchema7; } -function stableStringify(value: unknown): string { - if (Array.isArray(value)) { - return `[${value.map((item) => stableStringify(item)).join(",")}]`; - } - if (value && typeof value === "object") { - const entries = Object.entries(value as Record).sort(([a], [b]) => a.localeCompare(b)); - return `{${entries.map(([key, child]) => `${JSON.stringify(key)}:${stableStringify(child)}`).join(",")}}`; - } - return JSON.stringify(value); -} - -function replaceDuplicateTitledSchemasWithRefs( - value: unknown, - definitions: Record, - isRoot = false -): unknown { - if (Array.isArray(value)) { - return value.map((item) => replaceDuplicateTitledSchemasWithRefs(item, definitions)); - } - if (!value || typeof value !== "object") { - return value; - } - - const rewritten = Object.fromEntries( - Object.entries(value as Record).map(([key, child]) => [ - key, - replaceDuplicateTitledSchemasWithRefs(child, definitions), - ]) - ) as Record; - - if (!isRoot && typeof rewritten.title === "string") { - const sharedSchema = definitions[rewritten.title]; - if ( - sharedSchema && - typeof sharedSchema === "object" && - stableStringify(normalizeSchemaTitles(rewritten as JSONSchema7)) === - stableStringify(normalizeSchemaTitles(sharedSchema as JSONSchema7)) - ) { - return { $ref: `#/definitions/${rewritten.title}` }; - } - } - - return rewritten; -} - -function reuseSharedTitledSchemas(schema: JSONSchema7): JSONSchema7 { - const definitions = { ...((schema.definitions ?? {}) as Record) }; - - return { - ...schema, - definitions: Object.fromEntries( - Object.entries(definitions).map(([name, definition]) => [ - name, - replaceDuplicateTitledSchemasWithRefs(definition, definitions, true), - ]) - ), - }; -} - // ── Session Events ────────────────────────────────────────────────────────── async function generateSessionEvents(schemaPath?: string): Promise { @@ -246,7 +195,7 @@ async function generateSessionEvents(schemaPath?: string): Promise { const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; - const processed = postProcessSchema(stripNonAnnotationTitles(schema)); + const processed = postProcessSchema(schema); const definitionCollections = collectDefinitionCollections(processed as Record); const sessionEvent = resolveSchema({ $ref: "#/definitions/SessionEvent" }, definitionCollections) ?? @@ -309,6 +258,25 @@ function resultTypeName(method: RpcMethod): string { ); } +function tsNullableResultTypeName(method: RpcMethod): string | undefined { + const resultSchema = getMethodResultSchema(method); + if (!resultSchema) return undefined; + const inner = getNullableInner(resultSchema); + if (!inner) return undefined; + // Resolve $ref to a type name + if (inner.$ref) { + const refName = inner.$ref.split("/").pop(); + if (refName) return `${toPascalCase(refName)} | undefined`; + } + const innerName = getRpcSchemaTypeName(inner, method.rpcMethod.split(".").map(toPascalCase).join("") + "Result"); + return `${innerName} | undefined`; +} + +function tsResultType(method: RpcMethod): string { + if (isVoidSchema(getMethodResultSchema(method))) return "void"; + return tsNullableResultTypeName(method) ?? resultTypeName(method); +} + function paramsTypeName(method: RpcMethod): string { const fallback = rpcRequestFallbackName(method); if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { @@ -354,7 +322,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; for (const method of [...allMethods, ...clientSessionMethods]) { const resultSchema = getMethodResultSchema(method); - if (!isVoidSchema(resultSchema)) { + if (!isVoidSchema(resultSchema) && !getNullableInner(resultSchema)) { combinedSchema.definitions![resultTypeName(method)] = withRootTitle( schemaSourceForNamedDefinition(method.result, resultSchema), resultTypeName(method) @@ -404,7 +372,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; } } - const schemaForCompile = reuseSharedTitledSchemas(stripNonAnnotationTitles(combinedSchema)); + const schemaForCompile = combinedSchema; const compiled = await compile(normalizeSchemaForTypeScript(schemaForCompile), "_RpcSchemaRoot", { bannerComment: "", @@ -477,7 +445,7 @@ function emitGroup(node: Record, indent: string, isSession: boo for (const [key, value] of Object.entries(node)) { if (isRpcMethod(value)) { const { rpcMethod, params } = value; - const resultType = !isVoidSchema(getMethodResultSchema(value)) ? resultTypeName(value) : "void"; + const resultType = tsResultType(value); const paramsType = paramsTypeName(value); const effectiveParams = getMethodParamsSchema(value); @@ -582,7 +550,7 @@ function emitClientSessionApiRegistration(clientSchema: Record) const name = handlerMethodName(method.rpcMethod); const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); const pType = hasParams ? paramsTypeName(method) : ""; - const rType = !isVoidSchema(getMethodResultSchema(method)) ? resultTypeName(method) : "void"; + const rType = tsResultType(method); if (method.deprecated && !groupDeprecated) { lines.push(` /** @deprecated */`); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index bc144bf75..9abc9c8fb 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -74,7 +74,7 @@ export function postProcessSchema(schema: JSONSchema7): JSONSchema7 { if (processed.properties) { const newProps: Record = {}; - for (const [key, value] of Object.entries(processed.properties)) { + for (const [key, value] of Object.entries(processed.properties).sort(([a], [b]) => a.localeCompare(b))) { newProps[key] = typeof value === "object" ? postProcessSchema(value as JSONSchema7) : value; } processed.properties = newProps; @@ -245,43 +245,39 @@ export function isVoidSchema(schema: JSONSchema7 | null | undefined): boolean { return schema.type === "null"; } +/** + * If the schema is a nullable anyOf (anyOf: [nullLike, T] or [T, nullLike]), + * returns the non-null inner schema. Recognizes both `{ type: "null" }` and + * `{ not: {} }` (zod-to-json-schema 2019-09 format for undefined). + * Returns undefined if the schema is not a nullable wrapper. + */ +export function getNullableInner(schema: JSONSchema7): JSONSchema7 | undefined { + if (!schema.anyOf || !Array.isArray(schema.anyOf) || schema.anyOf.length !== 2) return undefined; + const [a, b] = schema.anyOf; + if (isNullLike(a) && !isNullLike(b)) return b as JSONSchema7; + if (isNullLike(b) && !isNullLike(a)) return a as JSONSchema7; + return undefined; +} + +function isNullLike(s: unknown): boolean { + if (!s || typeof s !== "object") return false; + const obj = s as Record; + if (obj.type === "null") return true; + if ("not" in obj && typeof obj.not === "object" && obj.not !== null && Object.keys(obj.not).length === 0) return true; + return false; +} + export function cloneSchemaForCodegen(value: T): T { if (Array.isArray(value)) { return value.map((item) => cloneSchemaForCodegen(item)) as T; } if (value && typeof value === "object") { + const source = value as Record; const result: Record = {}; - for (const [key, child] of Object.entries(value as Record)) { - if (key === "titleSource") { - continue; - } - result[key] = cloneSchemaForCodegen(child); - } - - return result as T; - } - return value; -} - -export function stripNonAnnotationTitles(value: T): T { - if (Array.isArray(value)) { - return value.map((item) => stripNonAnnotationTitles(item)) as T; - } - - if (value && typeof value === "object") { - const result: Record = {}; - const source = value as Record; - const keepTitle = typeof source.title === "string" && source.titleSource === "annotation"; for (const [key, child] of Object.entries(source)) { - if (key === "titleSource") { - continue; - } - if (key === "title" && !keepTitle) { - continue; - } - result[key] = stripNonAnnotationTitles(child); + result[key] = cloneSchemaForCodegen(child); } return result as T; @@ -290,99 +286,6 @@ export function stripNonAnnotationTitles(value: T): T { return value; } -export function hoistTitledSchemas( - rootDefinitions: Record -): { rootDefinitions: Record; sharedDefinitions: Record } { - const sharedDefinitions: Record = {}; - const processedRoots: Record = {}; - - for (const [rootName, definition] of Object.entries(rootDefinitions)) { - processedRoots[rootName] = visitSchema(definition, rootName, sharedDefinitions); - } - - return { rootDefinitions: processedRoots, sharedDefinitions }; -} - -function visitSchema( - schema: JSONSchema7, - rootName: string, - sharedDefinitions: Record -): JSONSchema7 { - const result: JSONSchema7 = { ...schema }; - - if (result.properties) { - result.properties = Object.fromEntries( - Object.entries(result.properties).map(([key, value]) => [ - key, - typeof value === "object" && value !== null && !Array.isArray(value) - ? visitSchema(value as JSONSchema7, rootName, sharedDefinitions) - : value, - ]) - ); - } - - if (result.items) { - if (Array.isArray(result.items)) { - result.items = result.items.map((item) => - typeof item === "object" && item !== null && !Array.isArray(item) - ? visitSchema(item as JSONSchema7, rootName, sharedDefinitions) - : item - ) as JSONSchema7Definition[]; - } else if (typeof result.items === "object" && result.items !== null) { - result.items = visitSchema(result.items as JSONSchema7, rootName, sharedDefinitions); - } - } - - if (typeof result.additionalProperties === "object" && result.additionalProperties !== null) { - result.additionalProperties = visitSchema(result.additionalProperties as JSONSchema7, rootName, sharedDefinitions); - } - - for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { - if (result[combiner]) { - result[combiner] = result[combiner]!.map((item) => - typeof item === "object" && item !== null && !Array.isArray(item) - ? visitSchema(item as JSONSchema7, rootName, sharedDefinitions) - : item - ) as JSONSchema7Definition[]; - } - } - - if (typeof result.title === "string" && result.title !== rootName) { - const existing = sharedDefinitions[result.title]; - if (existing) { - if (stableStringify(existing) !== stableStringify(result)) { - throw new Error(`Conflicting titled schemas for "${result.title}" while preparing quicktype inputs.`); - } - } else { - sharedDefinitions[result.title] = result; - } - return { $ref: `#/definitions/${result.title}`, description: result.description } as JSONSchema7; - } - - return result; -} - -function stableStringify(value: unknown): string { - return JSON.stringify(sortJsonValue(value)); -} - -function sortJsonValue(value: unknown): unknown { - if (Array.isArray(value)) { - return value.map(sortJsonValue); - } - - if (value && typeof value === "object") { - return Object.fromEntries( - Object.entries(value as Record) - .filter(([key]) => key !== "description" && key !== "titleSource") - .sort(([left], [right]) => left.localeCompare(right)) - .map(([key, child]) => [key, sortJsonValue(child)]) - ); - } - - return value; -} - export interface ApiSchema { definitions?: Record; $defs?: Record; @@ -395,135 +298,6 @@ export function isRpcMethod(node: unknown): node is RpcMethod { return typeof node === "object" && node !== null && "rpcMethod" in node; } -function normalizeSchemaDefinitionTitles(definition: JSONSchema7Definition): JSONSchema7Definition { - return typeof definition === "object" && definition !== null - ? normalizeSchemaTitles(definition as JSONSchema7) - : definition; -} - -export function normalizeSchemaTitles(schema: JSONSchema7): JSONSchema7 { - if (typeof schema !== "object" || schema === null) return schema; - - const normalized = { ...schema } as JSONSchema7WithDefs & Record; - delete normalized.title; - delete normalized.titleSource; - - if (normalized.properties) { - const newProps: Record = {}; - for (const [key, value] of Object.entries(normalized.properties)) { - newProps[key] = normalizeSchemaDefinitionTitles(value); - } - normalized.properties = newProps; - } - - if (normalized.items) { - if (typeof normalized.items === "object" && !Array.isArray(normalized.items)) { - normalized.items = normalizeSchemaTitles(normalized.items as JSONSchema7); - } else if (Array.isArray(normalized.items)) { - normalized.items = normalized.items.map((item) => normalizeSchemaDefinitionTitles(item)) as JSONSchema7Definition[]; - } - } - - for (const combiner of ["anyOf", "allOf", "oneOf"] as const) { - if (normalized[combiner]) { - normalized[combiner] = normalized[combiner]!.map((item) => normalizeSchemaDefinitionTitles(item)) as JSONSchema7Definition[]; - } - } - - if (normalized.additionalProperties && typeof normalized.additionalProperties === "object") { - normalized.additionalProperties = normalizeSchemaTitles(normalized.additionalProperties as JSONSchema7); - } - - if (normalized.propertyNames && typeof normalized.propertyNames === "object" && !Array.isArray(normalized.propertyNames)) { - normalized.propertyNames = normalizeSchemaTitles(normalized.propertyNames as JSONSchema7); - } - - if (normalized.contains && typeof normalized.contains === "object" && !Array.isArray(normalized.contains)) { - normalized.contains = normalizeSchemaTitles(normalized.contains as JSONSchema7); - } - - if (normalized.not && typeof normalized.not === "object" && !Array.isArray(normalized.not)) { - normalized.not = normalizeSchemaTitles(normalized.not as JSONSchema7); - } - - if (normalized.if && typeof normalized.if === "object" && !Array.isArray(normalized.if)) { - normalized.if = normalizeSchemaTitles(normalized.if as JSONSchema7); - } - if (normalized.then && typeof normalized.then === "object" && !Array.isArray(normalized.then)) { - normalized.then = normalizeSchemaTitles(normalized.then as JSONSchema7); - } - if (normalized.else && typeof normalized.else === "object" && !Array.isArray(normalized.else)) { - normalized.else = normalizeSchemaTitles(normalized.else as JSONSchema7); - } - - if (normalized.patternProperties) { - const newPatternProps: Record = {}; - for (const [key, value] of Object.entries(normalized.patternProperties)) { - newPatternProps[key] = normalizeSchemaDefinitionTitles(value); - } - normalized.patternProperties = newPatternProps; - } - - const { definitions, $defs } = collectDefinitionCollections(normalized as Record); - if (Object.keys(definitions).length > 0) { - const newDefs: Record = {}; - for (const [key, value] of Object.entries(definitions)) { - newDefs[key] = normalizeSchemaDefinitionTitles(value); - } - normalized.definitions = newDefs; - } - if (Object.keys($defs).length > 0) { - const newDraftDefs: Record = {}; - for (const [key, value] of Object.entries($defs)) { - newDraftDefs[key] = normalizeSchemaDefinitionTitles(value); - } - normalized.$defs = newDraftDefs; - } - - return normalized; -} - -function normalizeApiNode(node: Record | undefined): Record | undefined { - if (!node) return undefined; - - const normalizedNode: Record = {}; - for (const [key, value] of Object.entries(node)) { - if (isRpcMethod(value)) { - const method = value as RpcMethod; - normalizedNode[key] = { - ...method, - params: method.params ? normalizeSchemaTitles(method.params) : method.params, - result: method.result ? normalizeSchemaTitles(method.result) : method.result, - }; - } else if (typeof value === "object" && value !== null) { - normalizedNode[key] = normalizeApiNode(value as Record); - } else { - normalizedNode[key] = value; - } - } - - return normalizedNode; -} - -export function normalizeApiSchema(schema: ApiSchema): ApiSchema { - return { - ...schema, - definitions: schema.definitions - ? Object.fromEntries( - Object.entries(schema.definitions).map(([key, value]) => [key, normalizeSchemaDefinitionTitles(value)]) - ) - : schema.definitions, - $defs: schema.$defs - ? Object.fromEntries( - Object.entries(schema.$defs).map(([key, value]) => [key, normalizeSchemaDefinitionTitles(value)]) - ) - : schema.$defs, - server: normalizeApiNode(schema.server), - session: normalizeApiNode(schema.session), - clientSession: normalizeApiNode(schema.clientSession), - }; -} - /** * Apply `normalizeNullableRequiredRefs` to every JSON Schema reachable from the API schema * (method params, results, and shared definitions). Call after `cloneSchemaForCodegen` to diff --git a/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml b/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml index 89af253b5..f4e32f773 100644 --- a/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml +++ b/test/snapshots/builtin_tools/should_search_for_patterns_in_files.yaml @@ -43,10 +43,10 @@ conversations: - role: tool tool_call_id: toolcall_1 content: |- - ${workdir}/data.txt:1:apple - ${workdir}/data.txt:3:apricot + ./data.txt:1:apple + ./data.txt:3:apricot - role: assistant content: |- - Two lines matched: + The search found **2 lines** starting with 'ap': - Line 1: `apple` - Line 3: `apricot` diff --git a/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml new file mode 100644 index 000000000..5b0e81b22 --- /dev/null +++ b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 2 + 3? + - role: assistant + content: 2 + 3 = 5 diff --git a/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml new file mode 100644 index 000000000..0a0325417 --- /dev/null +++ b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 7 * 8? + - role: assistant + content: 7 * 8 = 56 From b4ef955c54c87f878c7579c1f4bcf884fd41a532 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 23 Apr 2026 02:36:53 -0700 Subject: [PATCH 33/34] Add configurable session idle timeout option (#1093) * Add sessionIdleTimeoutMs option to CopilotClientOptions Add a new optional sessionIdleTimeoutMs field to CopilotClientOptions that allows consumers to configure the server-wide session idle timeout. When set to a positive value, the SDK passes --session-idle-timeout to the CLI process. Sessions have no idle timeout by default (infinite lifetime). The minimum configurable value is 300000ms (5 minutes). Also updates the session persistence documentation to reflect the new default behavior and configuration option. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * refactor: rename sessionIdleTimeoutMs to sessionIdleTimeoutSeconds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: correct @default tag for sessionIdleTimeoutSeconds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: format client.ts with prettier Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * feat: add sessionIdleTimeoutSeconds to Go, Python, and .NET SDKs Add the session idle timeout option across all remaining SDK languages, consistent with the Node.js implementation and the runtime CLI's --session-idle-timeout flag. - Go: SessionIdleTimeoutSeconds int on ClientOptions - Python: session_idle_timeout_seconds on SubprocessConfig - .NET: SessionIdleTimeoutSeconds int? on CopilotClientOptions Each SDK passes --session-idle-timeout to the CLI when the value is positive, and omits it otherwise (disabled by default). Includes unit tests for all three languages and updates the .NET clone test to cover the new property. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: add external-server caveat to Node.js and Python idle timeout docs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test: add Node.js unit tests for sessionIdleTimeoutSeconds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: format test_client.py with ruff Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * docs: remove incorrect minimum value from idle timeout docs The runtime does not enforce a minimum value for the session idle timeout - any positive value is accepted. Remove the 'Minimum value: 300 (5 minutes)' note from all SDK docstrings and docs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: copy SessionIdleTimeoutSeconds unconditionally in Go NewClient() The option was only copied when > 0, which silently normalized negative inputs. Other SDKs preserve the caller's value and gate only at spawn time. Align Go to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/features/session-persistence.md | 18 +++++++++++++++--- dotnet/src/Client.cs | 5 +++++ dotnet/src/Types.cs | 10 ++++++++++ dotnet/test/ClientTests.cs | 19 +++++++++++++++++++ dotnet/test/CloneTests.cs | 2 ++ go/client.go | 8 ++++++++ go/client_test.go | 20 ++++++++++++++++++++ go/types.go | 6 ++++++ nodejs/src/client.ts | 11 +++++++++++ nodejs/src/types.ts | 10 ++++++++++ nodejs/test/client.test.ts | 19 +++++++++++++++++++ python/copilot/client.py | 11 +++++++++++ python/test_client.py | 18 ++++++++++++++++++ 13 files changed, 154 insertions(+), 3 deletions(-) diff --git a/docs/features/session-persistence.md b/docs/features/session-persistence.md index 19e53c385..53caaff11 100644 --- a/docs/features/session-persistence.md +++ b/docs/features/session-persistence.md @@ -433,14 +433,26 @@ await client.deleteSession("user-123-task-456"); ## Automatic Cleanup: Idle Timeout -The CLI has a built-in 30-minute idle timeout. Sessions without activity are automatically cleaned up: +By default, sessions have **no idle timeout** and live indefinitely until explicitly disconnected or deleted. You can optionally configure a server-wide idle timeout via `CopilotClientOptions.sessionIdleTimeoutSeconds`: + +```typescript +const client = new CopilotClient({ + sessionIdleTimeoutSeconds: 30 * 60, // 30 minutes +}); +``` + +When a timeout is configured, sessions without activity for that duration are automatically cleaned up. Set to `0` or omit to disable. + +> **Note:** This option only applies when the SDK spawns the runtime process. When connecting to an existing server via `cliUrl`, the server's own timeout configuration applies. ```mermaid flowchart LR - A["⚡ Last Activity"] --> B["⏳ 25 min
timeout_warning"] --> C["🧹 30 min
destroyed"] + A["⚡ Last Activity"] --> B["⏳ ~5 min before
timeout_warning"] --> C["🧹 Timeout
destroyed"] ``` -Listen for idle events to know when work completes: +Sessions with active work (running commands, background agents) are always protected from idle cleanup, regardless of the timeout setting. + +Listen for idle events to react to session inactivity: ```typescript session.on("session.idle", (event) => { diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 3a161a391..ae507a3c1 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1190,6 +1190,11 @@ private async Task VerifyProtocolVersionAsync(Connection connection, Cancellatio args.Add("--no-auto-login"); } + if (options.SessionIdleTimeoutSeconds is > 0) + { + args.AddRange(["--session-idle-timeout", options.SessionIdleTimeoutSeconds.Value.ToString(CultureInfo.InvariantCulture)]); + } + var (fileName, processArgs) = ResolveCliCommand(cliPath, args); var startInfo = new ProcessStartInfo diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index e42c34f5d..d84cd835f 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -69,6 +69,7 @@ protected CopilotClientOptions(CopilotClientOptions? other) UseStdio = other.UseStdio; OnListModels = other.OnListModels; SessionFs = other.SessionFs; + SessionIdleTimeoutSeconds = other.SessionIdleTimeoutSeconds; } /// @@ -165,6 +166,15 @@ public string? GithubToken /// public TelemetryConfig? Telemetry { get; set; } + /// + /// Server-wide idle timeout for sessions in seconds. + /// Sessions without activity for this duration are automatically cleaned up. + /// Set to 0 or leave as to disable (sessions live indefinitely). + /// This option is only used when the SDK spawns the CLI process; it is ignored + /// when connecting to an external server via . + /// + public int? SessionIdleTimeoutSeconds { get; set; } + /// /// Creates a shallow clone of this instance. /// diff --git a/dotnet/test/ClientTests.cs b/dotnet/test/ClientTests.cs index c62c5bc3f..e8c36776f 100644 --- a/dotnet/test/ClientTests.cs +++ b/dotnet/test/ClientTests.cs @@ -216,6 +216,25 @@ public void Should_Throw_When_UseLoggedInUser_Used_With_CliUrl() }); } + [Fact] + public void Should_Default_SessionIdleTimeoutSeconds_To_Null() + { + var options = new CopilotClientOptions(); + + Assert.Null(options.SessionIdleTimeoutSeconds); + } + + [Fact] + public void Should_Accept_SessionIdleTimeoutSeconds_Option() + { + var options = new CopilotClientOptions + { + SessionIdleTimeoutSeconds = 600 + }; + + Assert.Equal(600, options.SessionIdleTimeoutSeconds); + } + [Fact] public async Task Should_Not_Throw_When_Disposing_Session_After_Stopping_Client() { diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index 5c326dcc4..8ed45b062 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -26,6 +26,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() Environment = new Dictionary { ["KEY"] = "value" }, GitHubToken = "ghp_test", UseLoggedInUser = false, + SessionIdleTimeoutSeconds = 600, }; var clone = original.Clone(); @@ -42,6 +43,7 @@ public void CopilotClientOptions_Clone_CopiesAllProperties() Assert.Equal(original.Environment, clone.Environment); Assert.Equal(original.GitHubToken, clone.GitHubToken); Assert.Equal(original.UseLoggedInUser, clone.UseLoggedInUser); + Assert.Equal(original.SessionIdleTimeoutSeconds, clone.SessionIdleTimeoutSeconds); } [Fact] diff --git a/go/client.go b/go/client.go index 4eb56e639..0c72e963f 100644 --- a/go/client.go +++ b/go/client.go @@ -215,6 +215,10 @@ func NewClient(options *ClientOptions) *Client { sessionFs := *options.SessionFs opts.SessionFs = &sessionFs } + if options.Telemetry != nil { + opts.Telemetry = options.Telemetry + } + opts.SessionIdleTimeoutSeconds = options.SessionIdleTimeoutSeconds } // Default Env to current environment if not set @@ -1378,6 +1382,10 @@ func (c *Client) startCLIServer(ctx context.Context) error { args = append(args, "--no-auto-login") } + if c.options.SessionIdleTimeoutSeconds > 0 { + args = append(args, "--session-idle-timeout", strconv.Itoa(c.options.SessionIdleTimeoutSeconds)) + } + // If CLIPath is a .js file, run it with node // Note we can't rely on the shebang as Windows doesn't support it command := cliPath diff --git a/go/client_test.go b/go/client_test.go index 8840e8269..83e791333 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -391,6 +391,26 @@ func TestClient_EnvOptions(t *testing.T) { }) } +func TestClient_SessionIdleTimeoutSeconds(t *testing.T) { + t.Run("should store SessionIdleTimeoutSeconds option", func(t *testing.T) { + client := NewClient(&ClientOptions{ + SessionIdleTimeoutSeconds: 600, + }) + + if client.options.SessionIdleTimeoutSeconds != 600 { + t.Errorf("Expected SessionIdleTimeoutSeconds to be 600, got %d", client.options.SessionIdleTimeoutSeconds) + } + }) + + t.Run("should default SessionIdleTimeoutSeconds to zero", func(t *testing.T) { + client := NewClient(&ClientOptions{}) + + if client.options.SessionIdleTimeoutSeconds != 0 { + t.Errorf("Expected SessionIdleTimeoutSeconds to be 0, got %d", client.options.SessionIdleTimeoutSeconds) + } + }) +} + func findCLIPathForTest() string { abs, _ := filepath.Abs("../nodejs/node_modules/@github/copilot/index.js") if fileExistsForTest(abs) { diff --git a/go/types.go b/go/types.go index e11d21402..14905ec13 100644 --- a/go/types.go +++ b/go/types.go @@ -71,6 +71,12 @@ type ClientOptions struct { // When non-nil, COPILOT_OTEL_ENABLED=true is set and any populated fields // are mapped to the corresponding environment variables. Telemetry *TelemetryConfig + // SessionIdleTimeoutSeconds configures the server-wide session idle timeout in seconds. + // Sessions without activity for this duration are automatically cleaned up. + // Set to 0 or leave unset to disable (sessions live indefinitely). + // This option is only used when the SDK spawns the CLI process; it is ignored + // when connecting to an external server via CLIUrl. + SessionIdleTimeoutSeconds int } // TelemetryConfig configures OpenTelemetry integration for the Copilot CLI process. diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index a8eba8c37..0ef19038f 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -340,6 +340,7 @@ export class CopilotClient { // Default useLoggedInUser to false when githubToken is provided, otherwise true useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true), telemetry: options.telemetry, + sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, }; } @@ -1414,6 +1415,16 @@ export class CopilotClient { args.push("--no-auto-login"); } + if ( + this.options.sessionIdleTimeoutSeconds !== undefined && + this.options.sessionIdleTimeoutSeconds > 0 + ) { + args.push( + "--session-idle-timeout", + this.options.sessionIdleTimeoutSeconds.toString() + ); + } + // Suppress debug/trace output that might pollute stdout const envWithoutNodeDebug = { ...this.options.env }; delete envWithoutNodeDebug.NODE_DEBUG; diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 9f6eaf11d..bb4e862b4 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -184,6 +184,16 @@ export interface CopilotClientOptions { * instead of the server's default local filesystem storage. */ sessionFs?: SessionFsConfig; + + /** + * Server-wide idle timeout for sessions in seconds. + * Sessions without activity for this duration are automatically cleaned up. + * Set to 0 or omit to disable (sessions live indefinitely). + * This option is only used when the SDK spawns the CLI process; it is ignored + * when connecting to an external server via {@link cliUrl}. + * @default undefined (disabled) + */ + sessionIdleTimeoutSeconds?: number; } /** diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 4ea74b576..23824061c 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -1258,4 +1258,23 @@ describe("CopilotClient", () => { rpcSpy.mockRestore(); }); }); + + describe("sessionIdleTimeoutSeconds", () => { + it("should default to 0 when not specified", () => { + const client = new CopilotClient({ + logLevel: "error", + }); + + expect((client as any).options.sessionIdleTimeoutSeconds).toBe(0); + }); + + it("should store a custom value", () => { + const client = new CopilotClient({ + sessionIdleTimeoutSeconds: 600, + logLevel: "error", + }); + + expect((client as any).options.sessionIdleTimeoutSeconds).toBe(600); + }); + }); }); diff --git a/python/copilot/client.py b/python/copilot/client.py index a51940a96..cf89476ed 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -150,6 +150,14 @@ class SubprocessConfig: session_fs: SessionFsConfig | None = None """Connection-level session filesystem provider configuration.""" + session_idle_timeout_seconds: int | None = None + """Server-wide session idle timeout in seconds. + + Sessions without activity for this duration are automatically cleaned up. + Set to ``None`` or ``0`` to disable (sessions live indefinitely). + This option is only used when the SDK spawns the CLI process. + """ + @dataclass class ExternalServerConfig: @@ -2261,6 +2269,9 @@ async def _start_cli_server(self) -> None: if not cfg.use_logged_in_user: args.append("--no-auto-login") + if cfg.session_idle_timeout_seconds is not None and cfg.session_idle_timeout_seconds > 0: + args.extend(["--session-idle-timeout", str(cfg.session_idle_timeout_seconds)]) + # If cli_path is a .js file, run it with node # Note that we can't rely on the shebang as Windows doesn't support it if cli_path.endswith(".js"): diff --git a/python/test_client.py b/python/test_client.py index eb132cd0d..ac1b735bf 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -204,6 +204,24 @@ def test_explicit_use_logged_in_user_false_without_token(self): assert client._config.use_logged_in_user is False +class TestSessionIdleTimeoutSeconds: + def test_accepts_session_idle_timeout_seconds(self): + client = CopilotClient( + SubprocessConfig( + cli_path=CLI_PATH, + session_idle_timeout_seconds=600, + log_level="error", + ) + ) + assert isinstance(client._config, SubprocessConfig) + assert client._config.session_idle_timeout_seconds == 600 + + def test_default_session_idle_timeout_seconds_is_none(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH, log_level="error")) + assert isinstance(client._config, SubprocessConfig) + assert client._config.session_idle_timeout_seconds is None + + class TestOverridesBuiltInTool: @pytest.mark.asyncio async def test_overrides_built_in_tool_sent_in_tool_definition(self): From dd2dcbc439256acfb9feb2cff07c0b9c820091b8 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 24 Apr 2026 14:49:10 +0100 Subject: [PATCH 34/34] Per-session GitHub authentication for all SDK languages, plus update runtime and permissions to match (#1124) --- docs/auth/index.md | 6 +- docs/setup/backend-services.md | 2 +- docs/setup/github-oauth.md | 8 +- docs/troubleshooting/compatibility.md | 2 +- docs/troubleshooting/debugging.md | 2 +- dotnet/src/Client.cs | 14 +- dotnet/src/Generated/Rpc.cs | 561 ++++++++- dotnet/src/Generated/SessionEvents.cs | 484 +++++++- dotnet/src/Session.cs | 4 +- dotnet/src/Types.cs | 45 +- dotnet/test/Harness/CapiProxy.cs | 21 + dotnet/test/Harness/E2ETestContext.cs | 5 + dotnet/test/MultiClientTests.cs | 2 +- dotnet/test/PerSessionAuthTests.cs | 123 ++ .../test/PermissionRequestResultKindTests.cs | 45 +- dotnet/test/PermissionTests.cs | 6 +- dotnet/test/ToolsTests.cs | 2 +- go/client.go | 2 + go/generated_session_events.go | 176 ++- go/internal/e2e/multi_client_test.go | 2 +- go/internal/e2e/per_session_auth_test.go | 134 ++ go/internal/e2e/permissions_test.go | 6 +- go/internal/e2e/rpc_test.go | 4 +- go/internal/e2e/testharness/context.go | 5 + go/internal/e2e/testharness/proxy.go | 30 + go/internal/e2e/tools_test.go | 2 +- go/rpc/generated_rpc.go | 851 +++++++++---- go/session.go | 8 +- go/types.go | 36 +- go/types_test.go | 21 +- nodejs/README.md | 4 +- nodejs/package-lock.json | 56 +- nodejs/package.json | 2 +- nodejs/src/client.ts | 24 +- nodejs/src/generated/rpc.ts | 345 ++++-- nodejs/src/generated/session-events.ts | 394 +++++- nodejs/src/session.ts | 6 +- nodejs/src/types.ts | 21 +- nodejs/test/client.test.ts | 26 +- nodejs/test/e2e/harness/CapiProxy.ts | 19 +- nodejs/test/e2e/harness/sdkTestContext.ts | 2 +- nodejs/test/e2e/multi-client.test.ts | 4 +- nodejs/test/e2e/per_session_auth.test.ts | 100 ++ nodejs/test/e2e/permissions.test.ts | 14 +- nodejs/test/e2e/session.test.ts | 2 +- nodejs/test/e2e/streaming_fidelity.test.ts | 2 +- nodejs/test/e2e/tools.test.ts | 4 +- python/copilot/client.py | 25 +- python/copilot/generated/rpc.py | 1076 ++++++++++++++--- python/copilot/generated/session_events.py | 349 +++++- python/copilot/session.py | 22 +- python/e2e/test_multi_client.py | 4 +- python/e2e/test_per_session_auth.py | 115 ++ python/e2e/test_permissions.py | 10 +- python/e2e/test_tools.py | 4 +- python/e2e/testharness/context.py | 7 + python/e2e/testharness/proxy.py | 12 + scripts/codegen/csharp.ts | 32 +- scripts/codegen/go.ts | 6 + scripts/codegen/python.ts | 39 +- scripts/codegen/typescript.ts | 27 +- scripts/codegen/utils.ts | 9 +- test/harness/replayingCapiProxy.ts | 63 + ...should_set_model_with_reasoningeffort.yaml | 8 + 64 files changed, 4631 insertions(+), 811 deletions(-) create mode 100644 dotnet/test/PerSessionAuthTests.cs create mode 100644 go/internal/e2e/per_session_auth_test.go create mode 100644 nodejs/test/e2e/per_session_auth.test.ts create mode 100644 python/e2e/test_per_session_auth.py create mode 100644 test/snapshots/session/should_set_model_with_reasoningeffort.yaml diff --git a/docs/auth/index.md b/docs/auth/index.md index 069556a9b..5b2f667da 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -110,7 +110,7 @@ Use an OAuth GitHub App to authenticate users through your application and pass **How it works:** 1. User authorizes your OAuth GitHub App 2. Your app receives a user access token (`gho_` or `ghu_` prefix) -3. Pass the token to the SDK via `githubToken` option +3. Pass the token to the SDK via `gitHubToken` option **SDK Configuration:** @@ -121,7 +121,7 @@ Use an OAuth GitHub App to authenticate users through your application and pass import { CopilotClient } from "@github/copilot-sdk"; const client = new CopilotClient({ - githubToken: userAccessToken, // Token from OAuth flow + gitHubToken: userAccessToken, // Token from OAuth flow useLoggedInUser: false, // Don't use stored CLI credentials }); ``` @@ -299,7 +299,7 @@ BYOK allows you to use your own API keys from model providers like Azure AI Foun When multiple authentication methods are available, the SDK uses them in this priority order: -1. **Explicit `githubToken`** - Token passed directly to SDK constructor +1. **Explicit `gitHubToken`** - Token passed directly to SDK constructor 2. **HMAC key** - `CAPI_HMAC_KEY` or `COPILOT_HMAC_KEY` environment variables 3. **Direct API token** - `GITHUB_COPILOT_API_TOKEN` with `COPILOT_API_URL` 4. **Environment variable tokens** - `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` diff --git a/docs/setup/backend-services.md b/docs/setup/backend-services.md index cc5a055b4..a2c8620ab 100644 --- a/docs/setup/backend-services.md +++ b/docs/setup/backend-services.md @@ -290,7 +290,7 @@ Pass individual user tokens when creating sessions. See [GitHub OAuth](./github- app.post("/chat", authMiddleware, async (req, res) => { const client = new CopilotClient({ cliUrl: "localhost:4321", - githubToken: req.user.githubToken, + gitHubToken: req.user.githubToken, useLoggedInUser: false, }); diff --git a/docs/setup/github-oauth.md b/docs/setup/github-oauth.md index 553dde1cb..0f2be236e 100644 --- a/docs/setup/github-oauth.md +++ b/docs/setup/github-oauth.md @@ -26,7 +26,7 @@ sequenceDiagram GH-->>App: Access token (gho_xxx) App->>SDK: Create client with token - SDK->>CLI: Start with githubToken + SDK->>CLI: Start with gitHubToken CLI->>API: Request (as user) API-->>CLI: Response CLI-->>SDK: Result @@ -124,7 +124,7 @@ import { CopilotClient } from "@github/copilot-sdk"; // Create a client for an authenticated user function createClientForUser(userToken: string): CopilotClient { return new CopilotClient({ - githubToken: userToken, + gitHubToken: userToken, useLoggedInUser: false, // Don't fall back to CLI login }); } @@ -373,7 +373,7 @@ For GitHub Enterprise Managed Users, the flow is identical — EMU users authent // No special SDK configuration needed for EMU // Enterprise policies are enforced server-side by GitHub const client = new CopilotClient({ - githubToken: emuUserToken, // Works the same as regular tokens + gitHubToken: emuUserToken, // Works the same as regular tokens useLoggedInUser: false, }); ``` @@ -438,7 +438,7 @@ const clients = new Map(); function getClientForUser(userId: string, token: string): CopilotClient { if (!clients.has(userId)) { clients.set(userId, new CopilotClient({ - githubToken: token, + gitHubToken: token, useLoggedInUser: false, })); } diff --git a/docs/troubleshooting/compatibility.md b/docs/troubleshooting/compatibility.md index 44632ab6a..aed5286bf 100644 --- a/docs/troubleshooting/compatibility.md +++ b/docs/troubleshooting/compatibility.md @@ -55,7 +55,7 @@ The Copilot SDK communicates with the CLI via JSON-RPC protocol. Features must b | Create workspace file | `session.rpc.workspace.createFile()` | Create file in workspace | | **Authentication** | | | | Get auth status | `getAuthStatus()` | Check login state | -| Use token | `githubToken` option | Programmatic auth | +| Use token | `gitHubToken` option | Programmatic auth | | **Connectivity** | | | | Ping | `client.ping()` | Health check with server timestamp | | Get server status | `client.getStatus()` | Protocol version and server info | diff --git a/docs/troubleshooting/debugging.md b/docs/troubleshooting/debugging.md index 802798b21..4d060cdd3 100644 --- a/docs/troubleshooting/debugging.md +++ b/docs/troubleshooting/debugging.md @@ -268,7 +268,7 @@ var client = new CopilotClient(new CopilotClientOptions ```typescript const client = new CopilotClient({ - githubToken: process.env.GITHUB_TOKEN, + gitHubToken: process.env.GITHUB_TOKEN, }); ``` diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index ae507a3c1..8e4d1eefb 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -513,7 +513,8 @@ public async Task CreateSessionAsync(SessionConfig config, Cance RequestElicitation: config.OnElicitationRequest != null, Traceparent: traceparent, Tracestate: tracestate, - ModelCapabilities: config.ModelCapabilities); + ModelCapabilities: config.ModelCapabilities, + GitHubToken: config.GitHubToken); var response = await InvokeRpcAsync( connection.Rpc, "session.create", [request], cancellationToken); @@ -638,7 +639,8 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes RequestElicitation: config.OnElicitationRequest != null, Traceparent: traceparent, Tracestate: tracestate, - ModelCapabilities: config.ModelCapabilities); + ModelCapabilities: config.ModelCapabilities, + GitHubToken: config.GitHubToken); var response = await InvokeRpcAsync( connection.Rpc, "session.resume", [request], cancellationToken); @@ -1600,7 +1602,7 @@ public async Task OnPermissionRequestV2(string sess { return new PermissionRequestResponseV2(new PermissionRequestResult { - Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser + Kind = PermissionRequestResultKind.UserNotAvailable }); } } @@ -1661,7 +1663,8 @@ internal record CreateSessionRequest( bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, - ModelCapabilitiesOverride? ModelCapabilities = null); + ModelCapabilitiesOverride? ModelCapabilities = null, + string? GitHubToken = null); internal record ToolDefinition( string Name, @@ -1716,7 +1719,8 @@ internal record ResumeSessionRequest( bool? RequestElicitation = null, string? Traceparent = null, string? Tracestate = null, - ModelCapabilitiesOverride? ModelCapabilities = null); + ModelCapabilitiesOverride? ModelCapabilities = null, + string? GitHubToken = null); internal record ResumeSessionResponse( string SessionId, diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 8de5b2fd4..9b6f60c45 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -129,7 +129,7 @@ public sealed class ModelPolicy /// Usage terms or conditions for this model. [JsonPropertyName("terms")] - public string Terms { get; set; } = string.Empty; + public string? Terms { get; set; } } /// RPC data type for Model operations. @@ -172,6 +172,14 @@ public sealed class ModelList public IList Models { get => field ??= []; set; } } +/// RPC data type for ModelsList operations. +internal sealed class ModelsListRequest +{ + /// GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. + [JsonPropertyName("gitHubToken")] + public string? GitHubToken { get; set; } +} + /// RPC data type for Tool operations. public sealed class Tool { @@ -258,6 +266,14 @@ public sealed class AccountGetQuotaResult public IDictionary QuotaSnapshots { get => field ??= new Dictionary(); set; } } +/// RPC data type for AccountGetQuota operations. +internal sealed class AccountGetQuotaRequest +{ + /// GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + [JsonPropertyName("gitHubToken")] + public string? GitHubToken { get; set; } +} + /// RPC data type for DiscoveredMcpServer operations. public sealed class DiscoveredMcpServer { @@ -266,7 +282,9 @@ public sealed class DiscoveredMcpServer public bool Enabled { get; set; } /// Server name (config key). - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -311,7 +329,9 @@ internal sealed class McpConfigAddRequest public object Config { get; set; } = null!; /// Unique name for the MCP server. - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; } @@ -324,7 +344,9 @@ internal sealed class McpConfigUpdateRequest public object Config { get; set; } = null!; /// Name of the MCP server to update. - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; } @@ -333,11 +355,29 @@ internal sealed class McpConfigUpdateRequest internal sealed class McpConfigRemoveRequest { /// Name of the MCP server to remove. - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; } +/// RPC data type for McpConfigEnable operations. +internal sealed class McpConfigEnableRequest +{ + /// Names of MCP servers to enable. Each server is removed from the persisted disabled list so new sessions spawn it. Unknown or already-enabled names are ignored. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} + +/// RPC data type for McpConfigDisable operations. +internal sealed class McpConfigDisableRequest +{ + /// Names of MCP servers to disable. Each server is added to the persisted disabled list so new sessions skip it. Already-disabled names are ignored. Active sessions keep their current connections until they end. + [JsonPropertyName("names")] + public IList Names { get => field ??= []; set; } +} + /// RPC data type for ServerSkill operations. public sealed class ServerSkill { @@ -478,6 +518,42 @@ internal sealed class LogRequest public string? Url { get; set; } } +/// RPC data type for SessionAuthStatus operations. +public sealed class SessionAuthStatus +{ + /// Authentication type. + [JsonPropertyName("authType")] + public AuthInfoType? AuthType { get; set; } + + /// Copilot plan tier (e.g., individual_pro, business). + [JsonPropertyName("copilotPlan")] + public string? CopilotPlan { get; set; } + + /// Authentication host URL. + [JsonPropertyName("host")] + public string? Host { get; set; } + + /// Whether the session has resolved authentication. + [JsonPropertyName("isAuthenticated")] + public bool IsAuthenticated { get; set; } + + /// Authenticated login/username, if available. + [JsonPropertyName("login")] + public string? Login { get; set; } + + /// Human-readable authentication status description. + [JsonPropertyName("statusMessage")] + public string? StatusMessage { get; set; } +} + +/// RPC data type for SessionAuthGetStatus operations. +internal sealed class SessionAuthGetStatusRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// RPC data type for CurrentModel operations. public sealed class CurrentModel { @@ -1087,7 +1163,9 @@ public sealed class McpServer public string? Error { get; set; } /// Server name (config key). - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; @@ -1123,7 +1201,9 @@ internal sealed class SessionMcpListRequest internal sealed class McpEnableRequest { /// Name of the MCP server to enable. - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; @@ -1137,7 +1217,9 @@ internal sealed class McpEnableRequest internal sealed class McpDisableRequest { /// Name of the MCP server to disable. - [RegularExpression("^[0-9a-zA-Z_.@-]+(\\/[0-9a-zA-Z_.@-]+)*$")] + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] [JsonPropertyName("serverName")] public string ServerName { get; set; } = string.Empty; @@ -1155,6 +1237,43 @@ internal sealed class SessionMcpReloadRequest public string SessionId { get; set; } = string.Empty; } +/// RPC data type for McpOauthLogin operations. +[Experimental(Diagnostics.Experimental)] +public sealed class McpOauthLoginResult +{ + /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. + [JsonPropertyName("authorizationUrl")] + public string? AuthorizationUrl { get; set; } +} + +/// RPC data type for McpOauthLogin operations. +[Experimental(Diagnostics.Experimental)] +internal sealed class McpOauthLoginRequest +{ + /// Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. + [JsonPropertyName("callbackSuccessMessage")] + public string? CallbackSuccessMessage { get; set; } + + /// Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. + [JsonPropertyName("clientName")] + public string? ClientName { get; set; } + + /// When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. + [JsonPropertyName("forceReauth")] + public bool? ForceReauth { get; set; } + + /// Name of the remote MCP server to authenticate. + [RegularExpression("^[^\\x00-\\x1f/\\x7f-\\x9f}]+(?:\\/[^\\x00-\\x1f/\\x7f-\\x9f}]+)*$")] + [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Safe for generated string properties: JSON Schema minLength/maxLength map to string length validation, not reflection over trimmed Count members")] + [MinLength(1)] + [JsonPropertyName("serverName")] + public string ServerName { get; set; } = string.Empty; + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// RPC data type for Plugin operations. public sealed class Plugin { @@ -1402,12 +1521,11 @@ public sealed class PermissionRequestResult [JsonPolymorphic( TypeDiscriminatorPropertyName = "kind", UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] -[JsonDerivedType(typeof(PermissionDecisionApproved), "approved")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByRules), "denied-by-rules")] -[JsonDerivedType(typeof(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] -[JsonDerivedType(typeof(PermissionDecisionDeniedInteractivelyByUser), "denied-interactively-by-user")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] -[JsonDerivedType(typeof(PermissionDecisionDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +[JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocation), "approve-for-location")] +[JsonDerivedType(typeof(PermissionDecisionReject), "reject")] +[JsonDerivedType(typeof(PermissionDecisionUserNotAvailable), "user-not-available")] public partial class PermissionDecision { /// The type discriminator. @@ -1416,79 +1534,253 @@ public partial class PermissionDecision } -/// The approved variant of . -public partial class PermissionDecisionApproved : PermissionDecision +/// The approve-once variant of . +public partial class PermissionDecisionApproveOnce : PermissionDecision { /// [JsonIgnore] - public override string Kind => "approved"; + public override string Kind => "approve-once"; } -/// The denied-by-rules variant of . -public partial class PermissionDecisionDeniedByRules : PermissionDecision +/// The approval to add as a session-scoped rule. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCommands), "commands")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalRead), "read")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalWrite), "write")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcp), "mcp")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMcpSampling), "mcp-sampling")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalMemory), "memory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForSessionApprovalCustomTool), "custom-tool")] +public partial class PermissionDecisionApproveForSessionApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The commands variant of . +public partial class PermissionDecisionApproveForSessionApprovalCommands : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "denied-by-rules"; + public override string Kind => "commands"; - /// Rules that denied the request. - [JsonPropertyName("rules")] - public required object[] Rules { get; set; } + /// Gets or sets the commandIdentifiers value. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } } -/// The denied-no-approval-rule-and-could-not-request-from-user variant of . -public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +/// The read variant of . +public partial class PermissionDecisionApproveForSessionApprovalRead : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; + public override string Kind => "read"; } -/// The denied-interactively-by-user variant of . -public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +/// The write variant of . +public partial class PermissionDecisionApproveForSessionApprovalWrite : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "denied-interactively-by-user"; + public override string Kind => "write"; +} - /// Optional feedback from the user explaining the denial. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("feedback")] - public string? Feedback { get; set; } +/// The mcp variant of . +public partial class PermissionDecisionApproveForSessionApprovalMcp : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// Gets or sets the serverName value. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } } -/// The denied-by-content-exclusion-policy variant of . -public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +/// The mcp-sampling variant of . +public partial class PermissionDecisionApproveForSessionApprovalMcpSampling : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "denied-by-content-exclusion-policy"; + public override string Kind => "mcp-sampling"; - /// Human-readable explanation of why the path was excluded. - [JsonPropertyName("message")] - public required string Message { get; set; } + /// Gets or sets the serverName value. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} - /// File path that triggered the exclusion. - [JsonPropertyName("path")] - public required string Path { get; set; } +/// The memory variant of . +public partial class PermissionDecisionApproveForSessionApprovalMemory : PermissionDecisionApproveForSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "memory"; } -/// The denied-by-permission-request-hook variant of . -public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +/// The custom-tool variant of . +public partial class PermissionDecisionApproveForSessionApprovalCustomTool : PermissionDecisionApproveForSessionApproval { /// [JsonIgnore] - public override string Kind => "denied-by-permission-request-hook"; + public override string Kind => "custom-tool"; - /// Whether to interrupt the current agent turn. - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("interrupt")] - public bool? Interrupt { get; set; } + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// The approve-for-session variant of . +public partial class PermissionDecisionApproveForSession : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-for-session"; + + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required PermissionDecisionApproveForSessionApproval Approval { get; set; } +} + +/// The approval to persist for this location. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCommands), "commands")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalRead), "read")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalWrite), "write")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcp), "mcp")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMcpSampling), "mcp-sampling")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalMemory), "memory")] +[JsonDerivedType(typeof(PermissionDecisionApproveForLocationApprovalCustomTool), "custom-tool")] +public partial class PermissionDecisionApproveForLocationApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The commands variant of . +public partial class PermissionDecisionApproveForLocationApprovalCommands : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Gets or sets the commandIdentifiers value. + [JsonPropertyName("commandIdentifiers")] + public required IList CommandIdentifiers { get; set; } +} + +/// The read variant of . +public partial class PermissionDecisionApproveForLocationApprovalRead : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} + +/// The write variant of . +public partial class PermissionDecisionApproveForLocationApprovalWrite : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} + +/// The mcp variant of . +public partial class PermissionDecisionApproveForLocationApprovalMcp : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// Gets or sets the serverName value. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } +} + +/// The mcp-sampling variant of . +public partial class PermissionDecisionApproveForLocationApprovalMcpSampling : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp-sampling"; + + /// Gets or sets the serverName value. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } +} + +/// The memory variant of . +public partial class PermissionDecisionApproveForLocationApprovalMemory : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "memory"; +} + +/// The custom-tool variant of . +public partial class PermissionDecisionApproveForLocationApprovalCustomTool : PermissionDecisionApproveForLocationApproval +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Gets or sets the toolName value. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// The approve-for-location variant of . +public partial class PermissionDecisionApproveForLocation : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-for-location"; - /// Optional message from the hook explaining the denial. + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required PermissionDecisionApproveForLocationApproval Approval { get; set; } + + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } +} + +/// The reject variant of . +public partial class PermissionDecisionReject : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "reject"; + + /// Optional feedback from the user explaining the denial. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - [JsonPropertyName("message")] - public string? Message { get; set; } + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } +} + +/// The user-not-available variant of . +public partial class PermissionDecisionUserNotAvailable : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "user-not-available"; } /// RPC data type for PermissionDecision operations. @@ -1507,6 +1799,42 @@ internal sealed class PermissionDecisionRequest public string SessionId { get; set; } = string.Empty; } +/// RPC data type for PermissionsSetApproveAll operations. +public sealed class PermissionsSetApproveAllResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// RPC data type for PermissionsSetApproveAll operations. +internal sealed class PermissionsSetApproveAllRequest +{ + /// Whether to auto-approve all tool permission requests. + [JsonPropertyName("enabled")] + public bool Enabled { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// RPC data type for PermissionsResetSessionApprovals operations. +public sealed class PermissionsResetSessionApprovalsResult +{ + /// Whether the operation succeeded. + [JsonPropertyName("success")] + public bool Success { get; set; } +} + +/// RPC data type for PermissionsResetSessionApprovals operations. +internal sealed class PermissionsResetSessionApprovalsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// RPC data type for ShellExec operations. public sealed class ShellExecResult { @@ -2097,6 +2425,34 @@ public enum SessionLogLevel } +/// Authentication type. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum AuthInfoType +{ + /// The hmac variant. + [JsonStringEnumMemberName("hmac")] + Hmac, + /// The env variant. + [JsonStringEnumMemberName("env")] + Env, + /// The user variant. + [JsonStringEnumMemberName("user")] + User, + /// The gh-cli variant. + [JsonStringEnumMemberName("gh-cli")] + GhCli, + /// The api-key variant. + [JsonStringEnumMemberName("api-key")] + ApiKey, + /// The token variant. + [JsonStringEnumMemberName("token")] + Token, + /// The copilot-api-token variant. + [JsonStringEnumMemberName("copilot-api-token")] + CopilotApiToken, +} + + /// The agent mode. Valid values: "interactive", "plan", "autopilot". [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionMode @@ -2374,9 +2730,10 @@ internal ServerModelsApi(JsonRpc rpc) } /// Calls "models.list". - public async Task ListAsync(CancellationToken cancellationToken = default) + public async Task ListAsync(string? gitHubToken = null, CancellationToken cancellationToken = default) { - return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [], cancellationToken); + var request = new ModelsListRequest { GitHubToken = gitHubToken }; + return await CopilotClient.InvokeRpcAsync(_rpc, "models.list", [request], cancellationToken); } } @@ -2409,9 +2766,10 @@ internal ServerAccountApi(JsonRpc rpc) } /// Calls "account.getQuota". - public async Task GetQuotaAsync(CancellationToken cancellationToken = default) + public async Task GetQuotaAsync(string? gitHubToken = null, CancellationToken cancellationToken = default) { - return await CopilotClient.InvokeRpcAsync(_rpc, "account.getQuota", [], cancellationToken); + var request = new AccountGetQuotaRequest { GitHubToken = gitHubToken }; + return await CopilotClient.InvokeRpcAsync(_rpc, "account.getQuota", [request], cancellationToken); } } @@ -2473,6 +2831,20 @@ public async Task RemoveAsync(string name, CancellationToken cancellationToken = var request = new McpConfigRemoveRequest { Name = name }; await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.remove", [request], cancellationToken); } + + /// Calls "mcp.config.enable". + public async Task EnableAsync(IList names, CancellationToken cancellationToken = default) + { + var request = new McpConfigEnableRequest { Names = names }; + await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.enable", [request], cancellationToken); + } + + /// Calls "mcp.config.disable". + public async Task DisableAsync(IList names, CancellationToken cancellationToken = default) + { + var request = new McpConfigDisableRequest { Names = names }; + await CopilotClient.InvokeRpcAsync(_rpc, "mcp.config.disable", [request], cancellationToken); + } } /// Provides server-scoped Skills APIs. @@ -2562,6 +2934,7 @@ internal SessionRpc(JsonRpc rpc, string sessionId) { _rpc = rpc; _sessionId = sessionId; + Auth = new AuthApi(rpc, sessionId); Model = new ModelApi(rpc, sessionId); Mode = new ModeApi(rpc, sessionId); Name = new NameApi(rpc, sessionId); @@ -2583,6 +2956,9 @@ internal SessionRpc(JsonRpc rpc, string sessionId) Usage = new UsageApi(rpc, sessionId); } + /// Auth APIs. + public AuthApi Auth { get; } + /// Model APIs. public ModelApi Model { get; } @@ -2648,6 +3024,26 @@ public async Task LogAsync(string message, SessionLogLevel? level = n } } +/// Provides session-scoped Auth APIs. +public sealed class AuthApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal AuthApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.auth.getStatus". + public async Task GetStatusAsync(CancellationToken cancellationToken = default) + { + var request = new SessionAuthGetStatusRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.auth.getStatus", [request], cancellationToken); + } +} + /// Provides session-scoped Model APIs. public sealed class ModelApi { @@ -2947,6 +3343,7 @@ internal McpApi(JsonRpc rpc, string sessionId) { _rpc = rpc; _sessionId = sessionId; + Oauth = new McpOauthApi(rpc, sessionId); } /// Calls "session.mcp.list". @@ -2976,6 +3373,30 @@ public async Task ReloadAsync(CancellationToken cancellationToken = default) var request = new SessionMcpReloadRequest { SessionId = _sessionId }; await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.reload", [request], cancellationToken); } + + /// Oauth APIs. + public McpOauthApi Oauth { get; } +} + +/// Provides session-scoped McpOauth APIs. +[Experimental(Diagnostics.Experimental)] +public sealed class McpOauthApi +{ + private readonly JsonRpc _rpc; + private readonly string _sessionId; + + internal McpOauthApi(JsonRpc rpc, string sessionId) + { + _rpc = rpc; + _sessionId = sessionId; + } + + /// Calls "session.mcp.oauth.login". + public async Task LoginAsync(string serverName, bool? forceReauth = null, string? clientName = null, string? callbackSuccessMessage = null, CancellationToken cancellationToken = default) + { + var request = new McpOauthLoginRequest { SessionId = _sessionId, ServerName = serverName, ForceReauth = forceReauth, ClientName = clientName, CallbackSuccessMessage = callbackSuccessMessage }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.mcp.oauth.login", [request], cancellationToken); + } } /// Provides session-scoped Plugins APIs. @@ -3126,6 +3547,20 @@ public async Task HandlePendingPermissionRequestAsync(s var request = new PermissionDecisionRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); } + + /// Calls "session.permissions.setApproveAll". + public async Task SetApproveAllAsync(bool enabled, CancellationToken cancellationToken = default) + { + var request = new PermissionsSetApproveAllRequest { SessionId = _sessionId, Enabled = enabled }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.setApproveAll", [request], cancellationToken); + } + + /// Calls "session.permissions.resetSessionApprovals". + public async Task ResetSessionApprovalsAsync(CancellationToken cancellationToken = default) + { + var request = new PermissionsResetSessionApprovalsRequest { SessionId = _sessionId }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.resetSessionApprovals", [request], cancellationToken); + } } /// Provides session-scoped Shell APIs. @@ -3353,6 +3788,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncAuto mode switch request notification requiring user approval.
+/// Represents the auto_mode_switch.requested event. +public partial class AutoModeSwitchRequestedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "auto_mode_switch.requested"; + + /// The auto_mode_switch.requested event payload. + [JsonPropertyName("data")] + public required AutoModeSwitchRequestedData Data { get; set; } +} + +/// Auto mode switch completion notification. +/// Represents the auto_mode_switch.completed event. +public partial class AutoModeSwitchCompletedEvent : SessionEvent +{ + /// + [JsonIgnore] + public override string Type => "auto_mode_switch.completed"; + + /// The auto_mode_switch.completed event payload. + [JsonPropertyName("data")] + public required AutoModeSwitchCompletedData Data { get; set; } +} + /// SDK command registration change notification. /// Represents the commands.changed event. public partial class CommandsChangedEvent : SessionEvent @@ -1580,7 +1608,7 @@ public partial class SessionCompactionCompleteData [JsonPropertyName("checkpointPath")] public string? CheckpointPath { get; set; } - /// Token usage breakdown for the compaction LLM call. + /// Token usage breakdown for the compaction LLM call (aligned with assistant.usage format). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("compactionTokensUsed")] public CompactionCompleteCompactionTokensUsed? CompactionTokensUsed { get; set; } @@ -2300,6 +2328,11 @@ public partial class PermissionRequestedData [JsonPropertyName("permissionRequest")] public required PermissionRequest PermissionRequest { get; set; } + /// Derived user-facing permission prompt details for UI consumers. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("promptRequest")] + public PermissionPromptRequest? PromptRequest { get; set; } + /// Unique identifier for this permission request; used to respond via session.respondToPermission(). [JsonPropertyName("requestId")] public required string RequestId { get; set; } @@ -2320,6 +2353,11 @@ public partial class PermissionCompletedData /// The result of the permission request. [JsonPropertyName("result")] public required PermissionCompletedResult Result { get; set; } + + /// Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } } /// User input request notification with question and optional predefined choices. @@ -2558,6 +2596,31 @@ public partial class CommandCompletedData public required string RequestId { get; set; } } +/// Auto mode switch request notification requiring user approval. +public partial class AutoModeSwitchRequestedData +{ + /// The rate limit error code that triggered this request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("errorCode")] + public string? ErrorCode { get; set; } + + /// Unique identifier for this request; used to respond via session.respondToAutoModeSwitch(). + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } +} + +/// Auto mode switch completion notification. +public partial class AutoModeSwitchCompletedData +{ + /// Request ID of the resolved request; clients should dismiss any UI for this request. + [JsonPropertyName("requestId")] + public required string RequestId { get; set; } + + /// The user's choice: 'yes', 'yes_always', or 'no'. + [JsonPropertyName("response")] + public required string Response { get; set; } +} + /// SDK command registration change notification. public partial class CommandsChangedData { @@ -2822,21 +2885,78 @@ public partial class ShutdownModelMetric public required ShutdownModelMetricUsage Usage { get; set; } } -/// Token usage breakdown for the compaction LLM call. +/// Token usage detail for a single billing category. +/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail. +public partial class CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail +{ + /// Number of tokens in this billing batch. + [JsonPropertyName("batchSize")] + public required double BatchSize { get; set; } + + /// Cost per batch of tokens. + [JsonPropertyName("costPerBatch")] + public required double CostPerBatch { get; set; } + + /// Total token count for this entry. + [JsonPropertyName("tokenCount")] + public required double TokenCount { get; set; } + + /// Token category (e.g., "input", "output"). + [JsonPropertyName("tokenType")] + public required string TokenType { get; set; } +} + +/// Per-request cost and usage data from the CAPI copilot_usage response field. +/// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsage. +public partial class CompactionCompleteCompactionTokensUsedCopilotUsage +{ + /// Itemized token usage breakdown. + [JsonPropertyName("tokenDetails")] + public required CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[] TokenDetails { get; set; } + + /// Total cost in nano-AIU (AI Units) for this request. + [JsonPropertyName("totalNanoAiu")] + public required double TotalNanoAiu { get; set; } +} + +/// Token usage breakdown for the compaction LLM call (aligned with assistant.usage format). /// Nested data type for CompactionCompleteCompactionTokensUsed. public partial class CompactionCompleteCompactionTokensUsed { /// Cached input tokens reused in the compaction LLM call. - [JsonPropertyName("cachedInput")] - public required double CachedInput { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheReadTokens")] + public double? CacheReadTokens { get; set; } + + /// Tokens written to prompt cache in the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("cacheWriteTokens")] + public double? CacheWriteTokens { get; set; } + + /// Per-request cost and usage data from the CAPI copilot_usage response field. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("copilotUsage")] + public CompactionCompleteCompactionTokensUsedCopilotUsage? CopilotUsage { get; set; } + + /// Duration of the compaction LLM call in milliseconds. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("duration")] + public double? Duration { get; set; } /// Input tokens consumed by the compaction LLM call. - [JsonPropertyName("input")] - public required double Input { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("inputTokens")] + public double? InputTokens { get; set; } + + /// Model identifier used for the compaction LLM call. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("model")] + public string? Model { get; set; } /// Output tokens produced by the compaction LLM call. - [JsonPropertyName("output")] - public required double Output { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("outputTokens")] + public double? OutputTokens { get; set; } } /// Optional line range to scope the attachment to a specific section of the file. @@ -3798,6 +3918,293 @@ public partial class PermissionRequest } +/// Shell command permission prompt. +/// The commands variant of . +public partial class PermissionPromptRequestCommands : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Whether the UI can offer session-wide approval for this command pattern. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Command identifiers covered by this approval prompt. + [JsonPropertyName("commandIdentifiers")] + public required string[] CommandIdentifiers { get; set; } + + /// The complete shell command text to be executed. + [JsonPropertyName("fullCommandText")] + public required string FullCommandText { get; set; } + + /// Human-readable description of what the command intends to do. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Optional warning message about risks of running this command. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("warning")] + public string? Warning { get; set; } +} + +/// File write permission prompt. +/// The write variant of . +public partial class PermissionPromptRequestWrite : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "write"; + + /// Whether the UI can offer session-wide approval for file write operations. + [JsonPropertyName("canOfferSessionApproval")] + public required bool CanOfferSessionApproval { get; set; } + + /// Unified diff showing the proposed changes. + [JsonPropertyName("diff")] + public required string Diff { get; set; } + + /// Path of the file being written to. + [JsonPropertyName("fileName")] + public required string FileName { get; set; } + + /// Human-readable description of the intended file change. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + + /// Complete new file contents for newly created files. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("newFileContents")] + public string? NewFileContents { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// File read permission prompt. +/// The read variant of . +public partial class PermissionPromptRequestRead : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "read"; + + /// Human-readable description of why the file is being read. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + + /// Path of the file or directory being read. + [JsonPropertyName("path")] + public required string Path { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// MCP tool invocation permission prompt. +/// The mcp variant of . +public partial class PermissionPromptRequestMcp : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// Arguments to pass to the MCP tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public object? Args { get; set; } + + /// Name of the MCP server providing the tool. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Internal name of the MCP tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } + + /// Human-readable title of the MCP tool. + [JsonPropertyName("toolTitle")] + public required string ToolTitle { get; set; } +} + +/// URL access permission prompt. +/// The url variant of . +public partial class PermissionPromptRequestUrl : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "url"; + + /// Human-readable description of why the URL is being accessed. + [JsonPropertyName("intention")] + public required string Intention { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// URL to be fetched. + [JsonPropertyName("url")] + public required string Url { get; set; } +} + +/// Memory operation permission prompt. +/// The memory variant of . +public partial class PermissionPromptRequestMemory : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "memory"; + + /// Whether this is a store or vote memory operation. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("action")] + public PermissionPromptRequestMemoryAction? Action { get; set; } + + /// Source references for the stored fact (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("citations")] + public string? Citations { get; set; } + + /// Vote direction (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("direction")] + public PermissionPromptRequestMemoryDirection? Direction { get; set; } + + /// The fact being stored or voted on. + [JsonPropertyName("fact")] + public required string Fact { get; set; } + + /// Reason for the vote (vote only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } + + /// Topic or subject of the memory (store only). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("subject")] + public string? Subject { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Custom tool invocation permission prompt. +/// The custom-tool variant of . +public partial class PermissionPromptRequestCustomTool : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Arguments to pass to the custom tool. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("args")] + public object? Args { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Description of what the custom tool does. + [JsonPropertyName("toolDescription")] + public required string ToolDescription { get; set; } + + /// Name of the custom tool. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Path access permission prompt. +/// The path variant of . +public partial class PermissionPromptRequestPath : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "path"; + + /// Underlying permission kind that needs path approval. + [JsonPropertyName("accessKind")] + public required PermissionPromptRequestPathAccessKind AccessKind { get; set; } + + /// File paths that require explicit approval. + [JsonPropertyName("paths")] + public required string[] Paths { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } +} + +/// Hook confirmation permission prompt. +/// The hook variant of . +public partial class PermissionPromptRequestHook : PermissionPromptRequest +{ + /// + [JsonIgnore] + public override string Kind => "hook"; + + /// Optional message from the hook explaining why confirmation is needed. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("hookMessage")] + public string? HookMessage { get; set; } + + /// Arguments of the tool call being gated. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolArgs")] + public object? ToolArgs { get; set; } + + /// Tool call ID that triggered this permission request. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("toolCallId")] + public string? ToolCallId { get; set; } + + /// Name of the tool the hook is gating. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// Derived user-facing permission prompt details for UI consumers. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionPromptRequestCommands), "commands")] +[JsonDerivedType(typeof(PermissionPromptRequestWrite), "write")] +[JsonDerivedType(typeof(PermissionPromptRequestRead), "read")] +[JsonDerivedType(typeof(PermissionPromptRequestMcp), "mcp")] +[JsonDerivedType(typeof(PermissionPromptRequestUrl), "url")] +[JsonDerivedType(typeof(PermissionPromptRequestMemory), "memory")] +[JsonDerivedType(typeof(PermissionPromptRequestCustomTool), "custom-tool")] +[JsonDerivedType(typeof(PermissionPromptRequestPath), "path")] +[JsonDerivedType(typeof(PermissionPromptRequestHook), "hook")] +public partial class PermissionPromptRequest +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + /// The result of the permission request. /// Nested data type for PermissionCompletedResult. public partial class PermissionCompletedResult @@ -4138,6 +4545,45 @@ public enum PermissionRequestMemoryDirection Downvote, } +/// Whether this is a store or vote memory operation. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionPromptRequestMemoryAction +{ + /// The store variant. + [JsonStringEnumMemberName("store")] + Store, + /// The vote variant. + [JsonStringEnumMemberName("vote")] + Vote, +} + +/// Vote direction (vote only). +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionPromptRequestMemoryDirection +{ + /// The upvote variant. + [JsonStringEnumMemberName("upvote")] + Upvote, + /// The downvote variant. + [JsonStringEnumMemberName("downvote")] + Downvote, +} + +/// Underlying permission kind that needs path approval. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum PermissionPromptRequestPathAccessKind +{ + /// The read variant. + [JsonStringEnumMemberName("read")] + Read, + /// The shell variant. + [JsonStringEnumMemberName("shell")] + Shell, + /// The write variant. + [JsonStringEnumMemberName("write")] + Write, +} + /// The outcome of the permission request. [JsonConverter(typeof(JsonStringEnumConverter))] public enum PermissionCompletedKind @@ -4145,6 +4591,12 @@ public enum PermissionCompletedKind /// The approved variant. [JsonStringEnumMemberName("approved")] Approved, + /// The approved-for-session variant. + [JsonStringEnumMemberName("approved-for-session")] + ApprovedForSession, + /// The approved-for-location variant. + [JsonStringEnumMemberName("approved-for-location")] + ApprovedForLocation, /// The denied-by-rules variant. [JsonStringEnumMemberName("denied-by-rules")] DeniedByRules, @@ -4296,6 +4748,10 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(AssistantUsageData))] [JsonSerializable(typeof(AssistantUsageEvent))] [JsonSerializable(typeof(AssistantUsageQuotaSnapshot))] +[JsonSerializable(typeof(AutoModeSwitchCompletedData))] +[JsonSerializable(typeof(AutoModeSwitchCompletedEvent))] +[JsonSerializable(typeof(AutoModeSwitchRequestedData))] +[JsonSerializable(typeof(AutoModeSwitchRequestedEvent))] [JsonSerializable(typeof(CapabilitiesChangedData))] [JsonSerializable(typeof(CapabilitiesChangedEvent))] [JsonSerializable(typeof(CapabilitiesChangedUI))] @@ -4309,6 +4765,8 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(CommandsChangedData))] [JsonSerializable(typeof(CommandsChangedEvent))] [JsonSerializable(typeof(CompactionCompleteCompactionTokensUsed))] +[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsage))] +[JsonSerializable(typeof(CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail))] [JsonSerializable(typeof(CustomAgentsUpdatedAgent))] [JsonSerializable(typeof(ElicitationCompletedData))] [JsonSerializable(typeof(ElicitationCompletedEvent))] @@ -4341,6 +4799,16 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(PermissionCompletedData))] [JsonSerializable(typeof(PermissionCompletedEvent))] [JsonSerializable(typeof(PermissionCompletedResult))] +[JsonSerializable(typeof(PermissionPromptRequest))] +[JsonSerializable(typeof(PermissionPromptRequestCommands))] +[JsonSerializable(typeof(PermissionPromptRequestCustomTool))] +[JsonSerializable(typeof(PermissionPromptRequestHook))] +[JsonSerializable(typeof(PermissionPromptRequestMcp))] +[JsonSerializable(typeof(PermissionPromptRequestMemory))] +[JsonSerializable(typeof(PermissionPromptRequestPath))] +[JsonSerializable(typeof(PermissionPromptRequestRead))] +[JsonSerializable(typeof(PermissionPromptRequestUrl))] +[JsonSerializable(typeof(PermissionPromptRequestWrite))] [JsonSerializable(typeof(PermissionRequest))] [JsonSerializable(typeof(PermissionRequestCustomTool))] [JsonSerializable(typeof(PermissionRequestHook))] diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 455cecdba..a97d54a30 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -412,7 +412,7 @@ internal async Task HandlePermissionRequestAsync(JsonEl { return new PermissionRequestResult { - Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser + Kind = PermissionRequestResultKind.UserNotAvailable }; } @@ -615,7 +615,7 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission { await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { - Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser.Value + Kind = PermissionRequestResultKind.UserNotAvailable.Value }); } catch (IOException) diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index d84cd835f..03589e16d 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -458,21 +458,30 @@ public class ToolInvocation [DebuggerDisplay("{Value,nq}")] public readonly struct PermissionRequestResultKind : IEquatable { - /// Gets the kind indicating the permission was approved. - public static PermissionRequestResultKind Approved { get; } = new("approved"); - - /// Gets the kind indicating the permission was denied by rules. - public static PermissionRequestResultKind DeniedByRules { get; } = new("denied-by-rules"); - - /// Gets the kind indicating the permission was denied because no approval rule was found and the user could not be prompted. - public static PermissionRequestResultKind DeniedCouldNotRequestFromUser { get; } = new("denied-no-approval-rule-and-could-not-request-from-user"); + /// Gets the kind indicating the permission was approved for this one instance. + public static PermissionRequestResultKind Approved { get; } = new("approve-once"); /// Gets the kind indicating the permission was denied interactively by the user. - public static PermissionRequestResultKind DeniedInteractivelyByUser { get; } = new("denied-interactively-by-user"); + public static PermissionRequestResultKind Rejected { get; } = new("reject"); - /// Gets the kind indicating the permission was denied interactively by the user. + /// Gets the kind indicating the permission was denied because user confirmation was unavailable. + public static PermissionRequestResultKind UserNotAvailable { get; } = new("user-not-available"); + + /// Gets the kind indicating no permission decision was made. public static PermissionRequestResultKind NoResult { get; } = new("no-result"); + /// Deprecated. Use instead. + [Obsolete("Use Rejected instead.")] + public static PermissionRequestResultKind DeniedInteractivelyByUser => Rejected; + + /// Deprecated. Use instead. + [Obsolete("Use UserNotAvailable instead.")] + public static PermissionRequestResultKind DeniedCouldNotRequestFromUser => UserNotAvailable; + + /// Deprecated. Use instead. + [Obsolete("Use UserNotAvailable instead.")] + public static PermissionRequestResultKind DeniedByRules => UserNotAvailable; + /// Gets the underlying string value of this . public string Value => _value ?? string.Empty; @@ -1755,6 +1764,7 @@ protected SessionConfig(SessionConfig? other) Provider = other.Provider; ReasoningEffort = other.ReasoningEffort; CreateSessionFsHandler = other.CreateSessionFsHandler; + GitHubToken = other.GitHubToken; SessionId = other.SessionId; SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; Streaming = other.Streaming; @@ -1944,6 +1954,13 @@ protected SessionConfig(SessionConfig? other) ///
public Func? CreateSessionFsHandler { get; set; } + /// + /// GitHub token for per-session authentication. + /// When provided, the runtime resolves this token into a full GitHub identity + /// and stores it on the session for content exclusion, model routing, and quota checks. + /// + public string? GitHubToken { get; set; } + /// /// Creates a shallow clone of this instance. /// @@ -2005,6 +2022,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) Provider = other.Provider; ReasoningEffort = other.ReasoningEffort; CreateSessionFsHandler = other.CreateSessionFsHandler; + GitHubToken = other.GitHubToken; SkillDirectories = other.SkillDirectories is not null ? [.. other.SkillDirectories] : null; Streaming = other.Streaming; IncludeSubAgentStreamingEvents = other.IncludeSubAgentStreamingEvents; @@ -2191,6 +2209,13 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// public Func? CreateSessionFsHandler { get; set; } + /// + /// GitHub token for per-session authentication. + /// When provided, the runtime resolves this token into a full GitHub identity + /// and stores it on the session for content exclusion, model routing, and quota checks. + /// + public string? GitHubToken { get; set; } + /// /// Creates a shallow clone of this instance. /// diff --git a/dotnet/test/Harness/CapiProxy.cs b/dotnet/test/Harness/CapiProxy.cs index 1c775adb0..8b167972e 100644 --- a/dotnet/test/Harness/CapiProxy.cs +++ b/dotnet/test/Harness/CapiProxy.cs @@ -132,6 +132,16 @@ public async Task> GetExchangesAsync() ?? []; } + public async Task SetCopilotUserByTokenAsync(string token, CopilotUserConfig response) + { + var url = await (_startupTask ?? throw new InvalidOperationException("Proxy not started")); + + using var client = new HttpClient(); + var payload = new CopilotUserByTokenRequest(token, response); + var resp = await client.PostAsJsonAsync($"{url}/copilot-user-config", payload, CapiProxyJsonContext.Default.CopilotUserByTokenRequest); + resp.EnsureSuccessStatusCode(); + } + public async ValueTask DisposeAsync() { await StopAsync(); @@ -152,9 +162,20 @@ private static string FindRepoRoot() [JsonSourceGenerationOptions(JsonSerializerDefaults.Web)] [JsonSerializable(typeof(ConfigureRequest))] [JsonSerializable(typeof(List))] + [JsonSerializable(typeof(CopilotUserByTokenRequest))] private partial class CapiProxyJsonContext : JsonSerializerContext; } +public record CopilotUserByTokenRequest(string Token, CopilotUserConfig Response); + +public record CopilotUserConfig( + string Login, + string CopilotPlan, + CopilotUserEndpoints Endpoints, + string AnalyticsTrackingId); + +public record CopilotUserEndpoints(string Api, string Telemetry); + public record ParsedHttpExchange(ChatCompletionRequest Request, ChatCompletionResponse? Response); public record ChatCompletionRequest( diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 7b47ab0b7..1d5cf8839 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -83,6 +83,11 @@ public Task> GetExchangesAsync() return _proxy.GetExchangesAsync(); } + public Task SetCopilotUserByTokenAsync(string token, CopilotUserConfig response) + { + return _proxy.SetCopilotUserByTokenAsync(token, response); + } + public IReadOnlyDictionary GetEnvironment() { var env = Environment.GetEnvironmentVariables() diff --git a/dotnet/test/MultiClientTests.cs b/dotnet/test/MultiClientTests.cs index 7dbed65fe..e3cc3512b 100644 --- a/dotnet/test/MultiClientTests.cs +++ b/dotnet/test/MultiClientTests.cs @@ -207,7 +207,7 @@ public async Task One_Client_Rejects_Permission_And_Both_See_The_Result() { OnPermissionRequest = (_, _) => Task.FromResult(new PermissionRequestResult { - Kind = PermissionRequestResultKind.DeniedInteractivelyByUser, + Kind = PermissionRequestResultKind.Rejected, }), }); diff --git a/dotnet/test/PerSessionAuthTests.cs b/dotnet/test/PerSessionAuthTests.cs new file mode 100644 index 000000000..b707a7546 --- /dev/null +++ b/dotnet/test/PerSessionAuthTests.cs @@ -0,0 +1,123 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using GitHub.Copilot.SDK.Test.Harness; +using Xunit; +using Xunit.Abstractions; + +namespace GitHub.Copilot.SDK.Test; + +public class PerSessionAuthTests(E2ETestFixture fixture, ITestOutputHelper output) : E2ETestBase(fixture, "per-session-auth", output) +{ + /// + /// Creates a client with COPILOT_DEBUG_GITHUB_API_URL redirected to the proxy + /// so per-session auth token resolution (fetchCopilotUser) is intercepted. + /// + private CopilotClient CreateAuthTestClient() + { + var env = new Dictionary(Ctx.GetEnvironment()) + { + ["COPILOT_DEBUG_GITHUB_API_URL"] = Ctx.ProxyUrl, + }; + return Ctx.CreateClient(options: new CopilotClientOptions { Environment = env }); + } + + private async Task SetupCopilotUsersAsync() + { + await Ctx.SetCopilotUserByTokenAsync("token-alice", new CopilotUserConfig( + Login: "alice", + CopilotPlan: "individual_pro", + Endpoints: new CopilotUserEndpoints(Api: Ctx.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), + AnalyticsTrackingId: "alice-tracking-id" + )); + + await Ctx.SetCopilotUserByTokenAsync("token-bob", new CopilotUserConfig( + Login: "bob", + CopilotPlan: "business", + Endpoints: new CopilotUserEndpoints(Api: Ctx.ProxyUrl, Telemetry: "https://localhost:1/telemetry"), + AnalyticsTrackingId: "bob-tracking-id" + )); + } + + private CopilotClient? _authClient; + + private CopilotClient AuthClient => _authClient ??= CreateAuthTestClient(); + + [Fact] + public async Task ShouldAuthenticateWithGitHubToken() + { + await SetupCopilotUsersAsync(); + + await using var session = await AuthClient.CreateSessionAsync(new SessionConfig + { + GitHubToken = "token-alice", + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + var authStatus = await session.Rpc.Auth.GetStatusAsync(); + + Assert.True(authStatus.IsAuthenticated); + Assert.Equal("alice", authStatus.Login); + } + + [Fact] + public async Task ShouldIsolateAuthBetweenSessions() + { + await SetupCopilotUsersAsync(); + + await using var sessionA = await AuthClient.CreateSessionAsync(new SessionConfig + { + GitHubToken = "token-alice", + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + await using var sessionB = await AuthClient.CreateSessionAsync(new SessionConfig + { + GitHubToken = "token-bob", + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + var statusA = await sessionA.Rpc.Auth.GetStatusAsync(); + var statusB = await sessionB.Rpc.Auth.GetStatusAsync(); + + Assert.True(statusA.IsAuthenticated); + Assert.Equal("alice", statusA.Login); + + Assert.True(statusB.IsAuthenticated); + Assert.Equal("bob", statusB.Login); + } + + [Fact] + public async Task ShouldBeUnauthenticatedWithoutToken() + { + await using var session = await AuthClient.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + + var authStatus = await session.Rpc.Auth.GetStatusAsync(); + + // Without a per-session token, there is no per-session identity. + // In CI the process-level fake token may still authenticate globally, + // so we check Login rather than IsAuthenticated. + Assert.Null(authStatus.Login); + } + + [Fact] + public async Task ShouldFailWithInvalidToken() + { + await SetupCopilotUsersAsync(); + + var ex = await Assert.ThrowsAnyAsync(async () => + { + await using var session = await AuthClient.CreateSessionAsync(new SessionConfig + { + GitHubToken = "invalid-token", + OnPermissionRequest = PermissionHandler.ApproveAll, + }); + }); + + Assert.NotNull(ex); + } +} diff --git a/dotnet/test/PermissionRequestResultKindTests.cs b/dotnet/test/PermissionRequestResultKindTests.cs index ea77295e2..7349e1e9f 100644 --- a/dotnet/test/PermissionRequestResultKindTests.cs +++ b/dotnet/test/PermissionRequestResultKindTests.cs @@ -17,17 +17,23 @@ public class PermissionRequestResultKindTests [Fact] public void WellKnownKinds_HaveExpectedValues() { - Assert.Equal("approved", PermissionRequestResultKind.Approved.Value); - Assert.Equal("denied-by-rules", PermissionRequestResultKind.DeniedByRules.Value); - Assert.Equal("denied-no-approval-rule-and-could-not-request-from-user", PermissionRequestResultKind.DeniedCouldNotRequestFromUser.Value); - Assert.Equal("denied-interactively-by-user", PermissionRequestResultKind.DeniedInteractivelyByUser.Value); - Assert.Equal("no-result", new PermissionRequestResultKind("no-result").Value); + Assert.Equal("approve-once", PermissionRequestResultKind.Approved.Value); + Assert.Equal("reject", PermissionRequestResultKind.Rejected.Value); + Assert.Equal("user-not-available", PermissionRequestResultKind.UserNotAvailable.Value); + Assert.Equal("no-result", PermissionRequestResultKind.NoResult.Value); + + // Deprecated aliases still resolve +#pragma warning disable CS0618 + Assert.Equal(PermissionRequestResultKind.Rejected, PermissionRequestResultKind.DeniedInteractivelyByUser); + Assert.Equal(PermissionRequestResultKind.UserNotAvailable, PermissionRequestResultKind.DeniedCouldNotRequestFromUser); + Assert.Equal(PermissionRequestResultKind.UserNotAvailable, PermissionRequestResultKind.DeniedByRules); +#pragma warning restore CS0618 } [Fact] public void Equals_SameValue_ReturnsTrue() { - var a = new PermissionRequestResultKind("approved"); + var a = new PermissionRequestResultKind("approve-once"); Assert.True(a == PermissionRequestResultKind.Approved); Assert.True(a.Equals(PermissionRequestResultKind.Approved)); Assert.True(a.Equals((object)PermissionRequestResultKind.Approved)); @@ -36,29 +42,29 @@ public void Equals_SameValue_ReturnsTrue() [Fact] public void Equals_DifferentValue_ReturnsFalse() { - Assert.True(PermissionRequestResultKind.Approved != PermissionRequestResultKind.DeniedByRules); - Assert.False(PermissionRequestResultKind.Approved.Equals(PermissionRequestResultKind.DeniedByRules)); + Assert.True(PermissionRequestResultKind.Approved != PermissionRequestResultKind.Rejected); + Assert.False(PermissionRequestResultKind.Approved.Equals(PermissionRequestResultKind.Rejected)); } [Fact] public void Equals_IsCaseInsensitive() { - var upper = new PermissionRequestResultKind("APPROVED"); + var upper = new PermissionRequestResultKind("APPROVE-ONCE"); Assert.Equal(PermissionRequestResultKind.Approved, upper); } [Fact] public void GetHashCode_IsCaseInsensitive() { - var upper = new PermissionRequestResultKind("APPROVED"); + var upper = new PermissionRequestResultKind("APPROVE-ONCE"); Assert.Equal(PermissionRequestResultKind.Approved.GetHashCode(), upper.GetHashCode()); } [Fact] public void ToString_ReturnsValue() { - Assert.Equal("approved", PermissionRequestResultKind.Approved.ToString()); - Assert.Equal("denied-by-rules", PermissionRequestResultKind.DeniedByRules.ToString()); + Assert.Equal("approve-once", PermissionRequestResultKind.Approved.ToString()); + Assert.Equal("reject", PermissionRequestResultKind.Rejected.ToString()); } [Fact] @@ -88,7 +94,7 @@ public void Default_HasEmptyStringValue() [Fact] public void Equals_NonPermissionRequestResultKindObject_ReturnsFalse() { - Assert.False(PermissionRequestResultKind.Approved.Equals("approved")); + Assert.False(PermissionRequestResultKind.Approved.Equals("approve-once")); } [Fact] @@ -96,15 +102,15 @@ public void JsonSerialize_WritesStringValue() { var result = new PermissionRequestResult { Kind = PermissionRequestResultKind.Approved }; var json = JsonSerializer.Serialize(result, s_jsonOptions); - Assert.Contains("\"kind\":\"approved\"", json); + Assert.Contains("\"kind\":\"approve-once\"", json); } [Fact] public void JsonDeserialize_ReadsStringValue() { - var json = """{"kind":"denied-by-rules"}"""; + var json = """{"kind":"reject"}"""; var result = JsonSerializer.Deserialize(json, s_jsonOptions)!; - Assert.Equal(PermissionRequestResultKind.DeniedByRules, result.Kind); + Assert.Equal(PermissionRequestResultKind.Rejected, result.Kind); } [Fact] @@ -113,10 +119,9 @@ public void JsonRoundTrip_PreservesAllKinds() var kinds = new[] { PermissionRequestResultKind.Approved, - PermissionRequestResultKind.DeniedByRules, - PermissionRequestResultKind.DeniedCouldNotRequestFromUser, - PermissionRequestResultKind.DeniedInteractivelyByUser, - new PermissionRequestResultKind("no-result"), + PermissionRequestResultKind.Rejected, + PermissionRequestResultKind.UserNotAvailable, + PermissionRequestResultKind.NoResult, }; foreach (var kind in kinds) diff --git a/dotnet/test/PermissionTests.cs b/dotnet/test/PermissionTests.cs index 3ab36dad1..766200b2b 100644 --- a/dotnet/test/PermissionTests.cs +++ b/dotnet/test/PermissionTests.cs @@ -50,7 +50,7 @@ public async Task Should_Deny_Permission_When_Handler_Returns_Denied() { return Task.FromResult(new PermissionRequestResult { - Kind = PermissionRequestResultKind.DeniedInteractivelyByUser + Kind = PermissionRequestResultKind.Rejected }); } }); @@ -76,7 +76,7 @@ public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies() var session = await CreateSessionAsync(new SessionConfig { OnPermissionRequest = (_, _) => - Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser }) + Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.UserNotAvailable }) }); var permissionDenied = false; @@ -201,7 +201,7 @@ public async Task Should_Deny_Tool_Operations_When_Handler_Explicitly_Denies_Aft var session2 = await ResumeSessionAsync(sessionId, new ResumeSessionConfig { OnPermissionRequest = (_, _) => - Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser }) + Task.FromResult(new PermissionRequestResult { Kind = PermissionRequestResultKind.UserNotAvailable }) }); var permissionDenied = false; diff --git a/dotnet/test/ToolsTests.cs b/dotnet/test/ToolsTests.cs index ec0ba0936..fb1c4c49d 100644 --- a/dotnet/test/ToolsTests.cs +++ b/dotnet/test/ToolsTests.cs @@ -290,7 +290,7 @@ public async Task Denies_Custom_Tool_When_Permission_Denied() var session = await Client.CreateSessionAsync(new SessionConfig { Tools = [AIFunctionFactory.Create(EncryptStringDenied, "encrypt_string")], - OnPermissionRequest = async (request, invocation) => new() { Kind = PermissionRequestResultKind.DeniedInteractivelyByUser }, + OnPermissionRequest = async (request, invocation) => new() { Kind = PermissionRequestResultKind.Rejected }, }); await session.SendAsync(new MessageOptions diff --git a/go/client.go b/go/client.go index 0c72e963f..abeae6db3 100644 --- a/go/client.go +++ b/go/client.go @@ -601,6 +601,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills req.InfiniteSessions = config.InfiniteSessions + req.GitHubToken = config.GitHubToken if len(config.Commands) > 0 { cmds := make([]wireCommand, 0, len(config.Commands)) @@ -786,6 +787,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, req.SkillDirectories = config.SkillDirectories req.DisabledSkills = config.DisabledSkills req.InfiniteSessions = config.InfiniteSessions + req.GitHubToken = config.GitHubToken req.RequestPermission = Bool(true) if len(config.Commands) > 0 { diff --git a/go/generated_session_events.go b/go/generated_session_events.go index d0bbde414..8b393bec9 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -449,6 +449,18 @@ func (e *SessionEvent) UnmarshalJSON(data []byte) error { return err } e.Data = &d + case SessionEventTypeAutoModeSwitchRequested: + var d AutoModeSwitchRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAutoModeSwitchCompleted: + var d AutoModeSwitchCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d case SessionEventTypeCommandsChanged: var d CommandsChangedData if err := json.Unmarshal(raw.Data, &d); err != nil { @@ -607,6 +619,8 @@ const ( SessionEventTypeCommandQueued SessionEventType = "command.queued" SessionEventTypeCommandExecute SessionEventType = "command.execute" SessionEventTypeCommandCompleted SessionEventType = "command.completed" + SessionEventTypeAutoModeSwitchRequested SessionEventType = "auto_mode_switch.requested" + SessionEventTypeAutoModeSwitchCompleted SessionEventType = "auto_mode_switch.completed" SessionEventTypeCommandsChanged SessionEventType = "commands.changed" SessionEventTypeCapabilitiesChanged SessionEventType = "capabilities.changed" SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" @@ -677,6 +691,26 @@ type AssistantMessageData struct { func (*AssistantMessageData) sessionEventData() {} +// Auto mode switch completion notification +type AutoModeSwitchCompletedData struct { + // Request ID of the resolved request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // The user's choice: 'yes', 'yes_always', or 'no' + Response string `json:"response"` +} + +func (*AutoModeSwitchCompletedData) sessionEventData() {} + +// Auto mode switch request notification requiring user approval +type AutoModeSwitchRequestedData struct { + // The rate limit error code that triggered this request + ErrorCode *string `json:"errorCode,omitempty"` + // Unique identifier for this request; used to respond via session.respondToAutoModeSwitch() + RequestID string `json:"requestId"` +} + +func (*AutoModeSwitchRequestedData) sessionEventData() {} + // Context window breakdown at the start of LLM-powered conversation compaction type SessionCompactionStartData struct { // Token count from non-system messages (user, assistant, tool) at compaction start @@ -695,7 +729,7 @@ type SessionCompactionCompleteData struct { CheckpointNumber *float64 `json:"checkpointNumber,omitempty"` // File path where the checkpoint was stored CheckpointPath *string `json:"checkpointPath,omitempty"` - // Token usage breakdown for the compaction LLM call + // Token usage breakdown for the compaction LLM call (aligned with assistant.usage format) CompactionTokensUsed *CompactionCompleteCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` // Token count from non-system messages (user, assistant, tool) after compaction ConversationTokens *float64 `json:"conversationTokens,omitempty"` @@ -1008,6 +1042,8 @@ type PermissionCompletedData struct { RequestID string `json:"requestId"` // The result of the permission request Result PermissionCompletedResult `json:"result"` + // Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts + ToolCallID *string `json:"toolCallId,omitempty"` } func (*PermissionCompletedData) sessionEventData() {} @@ -1016,6 +1052,8 @@ func (*PermissionCompletedData) sessionEventData() {} type PermissionRequestedData struct { // Details of the permission being requested PermissionRequest PermissionRequest `json:"permissionRequest"` + // Derived user-facing permission prompt details for UI consumers + PromptRequest *PermissionPromptRequest `json:"promptRequest,omitempty"` // Unique identifier for this permission request; used to respond via session.respondToPermission() RequestID string `json:"requestId"` // When true, this permission was already resolved by a permissionRequest hook and requires no client action @@ -1732,6 +1770,64 @@ type ShutdownCodeChanges struct { LinesRemoved float64 `json:"linesRemoved"` } +// Derived user-facing permission prompt details for UI consumers +type PermissionPromptRequest struct { + // Kind discriminator + Kind PermissionPromptRequestKind `json:"kind"` + // Underlying permission kind that needs path approval + AccessKind *PermissionPromptRequestPathAccessKind `json:"accessKind,omitempty"` + // Whether this is a store or vote memory operation + Action *PermissionPromptRequestMemoryAction `json:"action,omitempty"` + // Arguments to pass to the MCP tool + Args *any `json:"args,omitempty"` + // Whether the UI can offer session-wide approval for this command pattern + CanOfferSessionApproval *bool `json:"canOfferSessionApproval,omitempty"` + // Source references for the stored fact (store only) + Citations *string `json:"citations,omitempty"` + // Command identifiers covered by this approval prompt + CommandIdentifiers []string `json:"commandIdentifiers,omitempty"` + // Unified diff showing the proposed changes + Diff *string `json:"diff,omitempty"` + // Vote direction (vote only) + Direction *PermissionPromptRequestMemoryDirection `json:"direction,omitempty"` + // The fact being stored or voted on + Fact *string `json:"fact,omitempty"` + // Path of the file being written to + FileName *string `json:"fileName,omitempty"` + // The complete shell command text to be executed + FullCommandText *string `json:"fullCommandText,omitempty"` + // Optional message from the hook explaining why confirmation is needed + HookMessage *string `json:"hookMessage,omitempty"` + // Human-readable description of what the command intends to do + Intention *string `json:"intention,omitempty"` + // Complete new file contents for newly created files + NewFileContents *string `json:"newFileContents,omitempty"` + // Path of the file or directory being read + Path *string `json:"path,omitempty"` + // File paths that require explicit approval + Paths []string `json:"paths,omitempty"` + // Reason for the vote (vote only) + Reason *string `json:"reason,omitempty"` + // Name of the MCP server providing the tool + ServerName *string `json:"serverName,omitempty"` + // Topic or subject of the memory (store only) + Subject *string `json:"subject,omitempty"` + // Arguments of the tool call being gated + ToolArgs any `json:"toolArgs,omitempty"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` + // Description of what the custom tool does + ToolDescription *string `json:"toolDescription,omitempty"` + // Internal name of the MCP tool + ToolName *string `json:"toolName,omitempty"` + // Human-readable title of the MCP tool + ToolTitle *string `json:"toolTitle,omitempty"` + // URL to be fetched + URL *string `json:"url,omitempty"` + // Optional warning message about risks of running this command + Warning *string `json:"warning,omitempty"` +} + // Details of the permission being requested type PermissionRequest struct { // Kind discriminator @@ -1864,6 +1960,14 @@ type AssistantUsageCopilotUsage struct { TotalNanoAiu float64 `json:"totalNanoAiu"` } +// Per-request cost and usage data from the CAPI copilot_usage response field +type CompactionCompleteCompactionTokensUsedCopilotUsage struct { + // Itemized token usage breakdown + TokenDetails []CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail `json:"tokenDetails"` + // Total cost in nano-AIU (AI Units) for this request + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + // Position range of the selection within the file type UserMessageAttachmentSelectionDetails struct { // End position of the selection @@ -1954,14 +2058,22 @@ type ShutdownModelMetricUsage struct { ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` } -// Token usage breakdown for the compaction LLM call +// Token usage breakdown for the compaction LLM call (aligned with assistant.usage format) type CompactionCompleteCompactionTokensUsed struct { // Cached input tokens reused in the compaction LLM call - CachedInput float64 `json:"cachedInput"` + CacheReadTokens *float64 `json:"cacheReadTokens,omitempty"` + // Tokens written to prompt cache in the compaction LLM call + CacheWriteTokens *float64 `json:"cacheWriteTokens,omitempty"` + // Per-request cost and usage data from the CAPI copilot_usage response field + CopilotUsage *CompactionCompleteCompactionTokensUsedCopilotUsage `json:"copilotUsage,omitempty"` + // Duration of the compaction LLM call in milliseconds + Duration *float64 `json:"duration,omitempty"` // Input tokens consumed by the compaction LLM call - Input float64 `json:"input"` + InputTokens *float64 `json:"inputTokens,omitempty"` + // Model identifier used for the compaction LLM call + Model *string `json:"model,omitempty"` // Output tokens produced by the compaction LLM call - Output float64 `json:"output"` + OutputTokens *float64 `json:"outputTokens,omitempty"` } // Token usage detail for a single billing category @@ -1976,6 +2088,18 @@ type AssistantUsageCopilotUsageTokenDetail struct { TokenType string `json:"tokenType"` } +// Token usage detail for a single billing category +type CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail struct { + // Number of tokens in this billing batch + BatchSize float64 `json:"batchSize"` + // Cost per batch of tokens + CostPerBatch float64 `json:"costPerBatch"` + // Total token count for this entry + TokenCount float64 `json:"tokenCount"` + // Token category (e.g., "input", "output") + TokenType string `json:"tokenType"` +} + // Tool execution result on success type ToolExecutionCompleteResult struct { // Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency @@ -2157,6 +2281,21 @@ const ( WorkingDirectoryContextHostTypeAdo WorkingDirectoryContextHostType = "ado" ) +// Kind discriminator for PermissionPromptRequest. +type PermissionPromptRequestKind string + +const ( + PermissionPromptRequestKindCommands PermissionPromptRequestKind = "commands" + PermissionPromptRequestKindWrite PermissionPromptRequestKind = "write" + PermissionPromptRequestKindRead PermissionPromptRequestKind = "read" + PermissionPromptRequestKindMcp PermissionPromptRequestKind = "mcp" + PermissionPromptRequestKindURL PermissionPromptRequestKind = "url" + PermissionPromptRequestKindMemory PermissionPromptRequestKind = "memory" + PermissionPromptRequestKindCustomTool PermissionPromptRequestKind = "custom-tool" + PermissionPromptRequestKindPath PermissionPromptRequestKind = "path" + PermissionPromptRequestKindHook PermissionPromptRequestKind = "hook" +) + // Kind discriminator for PermissionRequest. type PermissionRequestKind string @@ -2214,6 +2353,8 @@ type PermissionCompletedKind string const ( PermissionCompletedKindApproved PermissionCompletedKind = "approved" + PermissionCompletedKindApprovedForSession PermissionCompletedKind = "approved-for-session" + PermissionCompletedKindApprovedForLocation PermissionCompletedKind = "approved-for-location" PermissionCompletedKindDeniedByRules PermissionCompletedKind = "denied-by-rules" PermissionCompletedKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedKind = "denied-no-approval-rule-and-could-not-request-from-user" PermissionCompletedKindDeniedInteractivelyByUser PermissionCompletedKind = "denied-interactively-by-user" @@ -2298,6 +2439,23 @@ const ( UserMessageAttachmentGithubReferenceTypeDiscussion UserMessageAttachmentGithubReferenceType = "discussion" ) +// Underlying permission kind that needs path approval +type PermissionPromptRequestPathAccessKind string + +const ( + PermissionPromptRequestPathAccessKindRead PermissionPromptRequestPathAccessKind = "read" + PermissionPromptRequestPathAccessKindShell PermissionPromptRequestPathAccessKind = "shell" + PermissionPromptRequestPathAccessKindWrite PermissionPromptRequestPathAccessKind = "write" +) + +// Vote direction (vote only) +type PermissionPromptRequestMemoryDirection string + +const ( + PermissionPromptRequestMemoryDirectionUpvote PermissionPromptRequestMemoryDirection = "upvote" + PermissionPromptRequestMemoryDirectionDownvote PermissionPromptRequestMemoryDirection = "downvote" +) + // Vote direction (vote only) type PermissionRequestMemoryDirection string @@ -2330,6 +2488,14 @@ const ( ShutdownTypeError ShutdownType = "error" ) +// Whether this is a store or vote memory operation +type PermissionPromptRequestMemoryAction string + +const ( + PermissionPromptRequestMemoryActionStore PermissionPromptRequestMemoryAction = "store" + PermissionPromptRequestMemoryActionVote PermissionPromptRequestMemoryAction = "vote" +) + // Whether this is a store or vote memory operation type PermissionRequestMemoryAction string diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index 3b009e898..45eb19bc8 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -237,7 +237,7 @@ func TestMultiClient(t *testing.T) { // Client 1 creates a session and denies all permission requests session1, err := client1.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { - return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedInteractivelyByUser}, nil + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindRejected}, nil }, }) if err != nil { diff --git a/go/internal/e2e/per_session_auth_test.go b/go/internal/e2e/per_session_auth_test.go new file mode 100644 index 000000000..8d773c559 --- /dev/null +++ b/go/internal/e2e/per_session_auth_test.go @@ -0,0 +1,134 @@ +package e2e + +import ( + "testing" + + copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/internal/e2e/testharness" +) + +func TestPerSessionAuth(t *testing.T) { + ctx := testharness.NewTestContext(t) + + // Create client with COPILOT_DEBUG_GITHUB_API_URL redirected to the proxy + // so per-session auth token resolution (fetchCopilotUser) is intercepted. + client := ctx.NewClient(func(opts *copilot.ClientOptions) { + opts.Env = append(opts.Env, "COPILOT_DEBUG_GITHUB_API_URL="+ctx.ProxyURL) + }) + t.Cleanup(func() { client.ForceStop() }) + // Register per-token user configs on the proxy + if err := ctx.SetCopilotUserByToken("token-alice", map[string]interface{}{ + "login": "alice", + "copilot_plan": "individual_pro", + "endpoints": map[string]interface{}{"api": ctx.ProxyURL, "telemetry": "https://localhost:1/telemetry"}, + "analytics_tracking_id": "alice-tracking-id", + }); err != nil { + t.Fatalf("Failed to set copilot user for alice: %v", err) + } + + if err := ctx.SetCopilotUserByToken("token-bob", map[string]interface{}{ + "login": "bob", + "copilot_plan": "business", + "endpoints": map[string]interface{}{"api": ctx.ProxyURL, "telemetry": "https://localhost:1/telemetry"}, + "analytics_tracking_id": "bob-tracking-id", + }); err != nil { + t.Fatalf("Failed to set copilot user for bob: %v", err) + } + + t.Run("should authenticate with per-session token", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + GitHubToken: "token-alice", + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + authStatus, err := session.RPC.Auth.GetStatus(t.Context()) + if err != nil { + t.Fatalf("Failed to get auth status: %v", err) + } + + if !authStatus.IsAuthenticated { + t.Errorf("Expected session to be authenticated") + } + if authStatus.Login == nil || *authStatus.Login != "alice" { + t.Errorf("Expected login to be 'alice', got %v", authStatus.Login) + } + }) + + t.Run("should isolate auth between sessions", func(t *testing.T) { + ctx.ConfigureForTest(t) + + sessionA, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + GitHubToken: "token-alice", + }) + if err != nil { + t.Fatalf("Failed to create session A: %v", err) + } + + sessionB, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + GitHubToken: "token-bob", + }) + if err != nil { + t.Fatalf("Failed to create session B: %v", err) + } + + statusA, err := sessionA.RPC.Auth.GetStatus(t.Context()) + if err != nil { + t.Fatalf("Failed to get auth status for session A: %v", err) + } + + statusB, err := sessionB.RPC.Auth.GetStatus(t.Context()) + if err != nil { + t.Fatalf("Failed to get auth status for session B: %v", err) + } + + if statusA.Login == nil || *statusA.Login != "alice" { + t.Errorf("Expected session A login to be 'alice', got %v", statusA.Login) + } + if statusB.Login == nil || *statusB.Login != "bob" { + t.Errorf("Expected session B login to be 'bob', got %v", statusB.Login) + } + }) + + t.Run("should be unauthenticated without token", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + authStatus, err := session.RPC.Auth.GetStatus(t.Context()) + if err != nil { + t.Fatalf("Failed to get auth status: %v", err) + } + + // Without a per-session token, there is no per-session identity. + // In CI the process-level fake token may still authenticate globally, + // so we check Login rather than IsAuthenticated. + if authStatus.Login != nil && *authStatus.Login != "" { + t.Errorf("Expected no per-session login without token, got %q", *authStatus.Login) + } + }) + + t.Run("should fail with invalid token", func(t *testing.T) { + ctx.ConfigureForTest(t) + + _, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + GitHubToken: "invalid-token", + }) + if err == nil { + t.Fatal("Expected session creation to fail with invalid token") + } + t.Logf("Got expected error: %v", err) + }) +} diff --git a/go/internal/e2e/permissions_test.go b/go/internal/e2e/permissions_test.go index 784cf897f..4df9683e8 100644 --- a/go/internal/e2e/permissions_test.go +++ b/go/internal/e2e/permissions_test.go @@ -117,7 +117,7 @@ func TestPermissions(t *testing.T) { ctx.ConfigureForTest(t) onPermissionRequest := func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { - return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedInteractivelyByUser}, nil + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindRejected}, nil } session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ @@ -162,7 +162,7 @@ func TestPermissions(t *testing.T) { session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { - return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedCouldNotRequestFromUser}, nil + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindUserNotAvailable}, nil }, }) if err != nil { @@ -214,7 +214,7 @@ func TestPermissions(t *testing.T) { session2, err := client.ResumeSession(t.Context(), sessionID, &copilot.ResumeSessionConfig{ OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { - return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedCouldNotRequestFromUser}, nil + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindUserNotAvailable}, nil }, }) if err != nil { diff --git a/go/internal/e2e/rpc_test.go b/go/internal/e2e/rpc_test.go index 819e8ccca..3ca20d43b 100644 --- a/go/internal/e2e/rpc_test.go +++ b/go/internal/e2e/rpc_test.go @@ -64,7 +64,7 @@ func TestRpc(t *testing.T) { t.Skip("Not authenticated - skipping models.list test") } - result, err := client.RPC.Models.List(t.Context()) + result, err := client.RPC.Models.List(t.Context(), nil) if err != nil { t.Fatalf("Failed to call RPC.Models.List: %v", err) } @@ -101,7 +101,7 @@ func TestRpc(t *testing.T) { t.Skip("Not authenticated - skipping account.getQuota test") } - result, err := client.RPC.Account.GetQuota(t.Context()) + result, err := client.RPC.Account.GetQuota(t.Context(), nil) if err != nil { t.Fatalf("Failed to call RPC.Account.GetQuota: %v", err) } diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index 269b53789..9f73c2267 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -144,6 +144,11 @@ func (c *TestContext) GetExchanges() ([]ParsedHttpExchange, error) { return c.proxy.GetExchanges() } +// SetCopilotUserByToken registers a per-token user configuration on the proxy. +func (c *TestContext) SetCopilotUserByToken(token string, response map[string]interface{}) error { + return c.proxy.SetCopilotUserByToken(token, response) +} + // Env returns environment variables configured for isolated testing. func (c *TestContext) Env() []string { env := os.Environ() diff --git a/go/internal/e2e/testharness/proxy.go b/go/internal/e2e/testharness/proxy.go index 0caf19403..887f7134d 100644 --- a/go/internal/e2e/testharness/proxy.go +++ b/go/internal/e2e/testharness/proxy.go @@ -2,6 +2,7 @@ package testharness import ( "bufio" + "bytes" "encoding/json" "fmt" "io" @@ -251,3 +252,32 @@ func (p *CapiProxy) URL() string { defer p.mu.Unlock() return p.proxyURL } + +// SetCopilotUserByToken registers a per-token user configuration on the proxy. +func (p *CapiProxy) SetCopilotUserByToken(token string, response map[string]interface{}) error { + p.mu.Lock() + url := p.proxyURL + p.mu.Unlock() + + if url == "" { + return fmt.Errorf("proxy not started") + } + + body := map[string]interface{}{ + "token": token, + "response": response, + } + data, err := json.Marshal(body) + if err != nil { + return err + } + resp, err := http.Post(url+"/copilot-user-config", "application/json", bytes.NewReader(data)) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("setCopilotUserByToken: unexpected status %d", resp.StatusCode) + } + return nil +} diff --git a/go/internal/e2e/tools_test.go b/go/internal/e2e/tools_test.go index c67ae1b5d..cb7c6863b 100644 --- a/go/internal/e2e/tools_test.go +++ b/go/internal/e2e/tools_test.go @@ -429,7 +429,7 @@ func TestTools(t *testing.T) { }), }, OnPermissionRequest: func(request copilot.PermissionRequest, invocation copilot.PermissionInvocation) (copilot.PermissionRequestResult, error) { - return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindDeniedInteractivelyByUser}, nil + return copilot.PermissionRequestResult{Kind: copilot.PermissionRequestResultKindRejected}, nil }, }) if err != nil { diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 683fb2a5c..2efcb494f 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -13,187 +13,222 @@ import ( ) type RPCTypes struct { - AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` - AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` - AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` - AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` - AgentInfo AgentInfo `json:"AgentInfo"` - AgentList AgentList `json:"AgentList"` - AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` - AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` - AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` - CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` - CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` - CurrentModel CurrentModel `json:"CurrentModel"` - DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` - DiscoveredMCPServerSource MCPServerSource `json:"DiscoveredMcpServerSource"` - DiscoveredMCPServerType DiscoveredMCPServerType `json:"DiscoveredMcpServerType"` - Extension Extension `json:"Extension"` - ExtensionList ExtensionList `json:"ExtensionList"` - ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` - ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` - ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` - ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` - ExtensionSource ExtensionSource `json:"ExtensionSource"` - ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` - ExtensionStatus ExtensionStatus `json:"ExtensionStatus"` - FilterMapping *FilterMapping `json:"FilterMapping"` - FilterMappingString FilterMappingString `json:"FilterMappingString"` - FilterMappingValue FilterMappingString `json:"FilterMappingValue"` - FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` - FleetStartResult FleetStartResult `json:"FleetStartResult"` - HandleToolCallResult HandleToolCallResult `json:"HandleToolCallResult"` - HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` - HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` - HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` - HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` - InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` - InstructionsSources InstructionsSources `json:"InstructionsSources"` - InstructionsSourcesLocation InstructionsSourcesLocation `json:"InstructionsSourcesLocation"` - InstructionsSourcesType InstructionsSourcesType `json:"InstructionsSourcesType"` - LogRequest LogRequest `json:"LogRequest"` - LogResult LogResult `json:"LogResult"` - MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` - MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` - MCPConfigList MCPConfigList `json:"McpConfigList"` - MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` - MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` - MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` - MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` - MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` - MCPDisableResult MCPDisableResult `json:"McpDisableResult"` - MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` - MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` - MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` - MCPEnableResult MCPEnableResult `json:"McpEnableResult"` - MCPReloadResult MCPReloadResult `json:"McpReloadResult"` - MCPServer MCPServer `json:"McpServer"` - MCPServerConfig MCPServerConfig `json:"McpServerConfig"` - MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` - MCPServerConfigHTTPType MCPServerConfigHTTPType `json:"McpServerConfigHttpType"` - MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` - MCPServerConfigLocalType MCPServerConfigLocalType `json:"McpServerConfigLocalType"` - MCPServerList MCPServerList `json:"McpServerList"` - MCPServerSource MCPServerSource `json:"McpServerSource"` - MCPServerStatus MCPServerStatus `json:"McpServerStatus"` - Model ModelElement `json:"Model"` - ModelBilling ModelBilling `json:"ModelBilling"` - ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` - ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` - ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` - ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` - ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` - ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` - ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` - ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` - ModelList ModelList `json:"ModelList"` - ModelPolicy ModelPolicy `json:"ModelPolicy"` - ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` - ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` - ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` - ModeSetResult ModeSetResult `json:"ModeSetResult"` - NameGetResult NameGetResult `json:"NameGetResult"` - NameSetRequest NameSetRequest `json:"NameSetRequest"` - NameSetResult NameSetResult `json:"NameSetResult"` - PermissionDecision PermissionDecision `json:"PermissionDecision"` - PermissionDecisionApproved PermissionDecisionApproved `json:"PermissionDecisionApproved"` - PermissionDecisionDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicy `json:"PermissionDecisionDeniedByContentExclusionPolicy"` - PermissionDecisionDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHook `json:"PermissionDecisionDeniedByPermissionRequestHook"` - PermissionDecisionDeniedByRules PermissionDecisionDeniedByRules `json:"PermissionDecisionDeniedByRules"` - PermissionDecisionDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUser `json:"PermissionDecisionDeniedInteractivelyByUser"` - PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser `json:"PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"` - PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` - PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` - PingRequest PingRequest `json:"PingRequest"` - PingResult PingResult `json:"PingResult"` - PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` - PlanReadResult PlanReadResult `json:"PlanReadResult"` - PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` - PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` - Plugin PluginElement `json:"Plugin"` - PluginList PluginList `json:"PluginList"` - ServerSkill ServerSkill `json:"ServerSkill"` - ServerSkillList ServerSkillList `json:"ServerSkillList"` - SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` - SessionFSError SessionFSError `json:"SessionFsError"` - SessionFSErrorCode SessionFSErrorCode `json:"SessionFsErrorCode"` - SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` - SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` - SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` - SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` - SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` - SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` - SessionFSReaddirWithTypesEntryType SessionFSReaddirWithTypesEntryType `json:"SessionFsReaddirWithTypesEntryType"` - SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` - SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` - SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` - SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` - SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` - SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` - SessionFSSetProviderConventions SessionFSSetProviderConventions `json:"SessionFsSetProviderConventions"` - SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` - SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` - SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` - SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` - SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` - SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` - SessionMode SessionMode `json:"SessionMode"` - SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` - SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` - ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` - ShellExecResult ShellExecResult `json:"ShellExecResult"` - ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` - ShellKillResult ShellKillResult `json:"ShellKillResult"` - ShellKillSignal ShellKillSignal `json:"ShellKillSignal"` - Skill Skill `json:"Skill"` - SkillList SkillList `json:"SkillList"` - SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` - SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` - SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` - SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` - SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` - SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` - SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` - SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` - Tool Tool `json:"Tool"` - ToolCallResult ToolCallResult `json:"ToolCallResult"` - ToolList ToolList `json:"ToolList"` - ToolsHandlePendingToolCall *ToolsHandlePendingToolCall `json:"ToolsHandlePendingToolCall"` - ToolsHandlePendingToolCallRequest ToolsHandlePendingToolCallRequest `json:"ToolsHandlePendingToolCallRequest"` - ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` - UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` - UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` - UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` - UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` - UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` - UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` - UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` - UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` - UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` - UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` - UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` - UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` - UIElicitationSchemaProperty UIElicitationSchemaProperty `json:"UIElicitationSchemaProperty"` - UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` - UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` - UIElicitationSchemaPropertyNumberType UIElicitationSchemaPropertyNumberTypeEnum `json:"UIElicitationSchemaPropertyNumberType"` - UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` - UIElicitationSchemaPropertyStringFormat UIElicitationSchemaPropertyStringFormat `json:"UIElicitationSchemaPropertyStringFormat"` - UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` - UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` - UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` - UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` - UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` - UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` - UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` - UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` - UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` - WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` - WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` - WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` - WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` - WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` - WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` + AccountGetQuotaRequest AccountGetQuotaRequest `json:"AccountGetQuotaRequest"` + AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` + AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` + AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` + AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` + AgentInfo AgentInfo `json:"AgentInfo"` + AgentList AgentList `json:"AgentList"` + AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` + AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` + AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` + AuthInfoType AuthInfoType `json:"AuthInfoType"` + CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` + CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` + CurrentModel CurrentModel `json:"CurrentModel"` + DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` + DiscoveredMCPServerSource MCPServerSource `json:"DiscoveredMcpServerSource"` + DiscoveredMCPServerType DiscoveredMCPServerType `json:"DiscoveredMcpServerType"` + Extension Extension `json:"Extension"` + ExtensionList ExtensionList `json:"ExtensionList"` + ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` + ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` + ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` + ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` + ExtensionSource ExtensionSource `json:"ExtensionSource"` + ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` + ExtensionStatus ExtensionStatus `json:"ExtensionStatus"` + FilterMapping *FilterMapping `json:"FilterMapping"` + FilterMappingString FilterMappingString `json:"FilterMappingString"` + FilterMappingValue FilterMappingString `json:"FilterMappingValue"` + FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` + FleetStartResult FleetStartResult `json:"FleetStartResult"` + HandleToolCallResult HandleToolCallResult `json:"HandleToolCallResult"` + HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` + HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` + HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` + HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` + InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` + InstructionsSources InstructionsSources `json:"InstructionsSources"` + InstructionsSourcesLocation InstructionsSourcesLocation `json:"InstructionsSourcesLocation"` + InstructionsSourcesType InstructionsSourcesType `json:"InstructionsSourcesType"` + LogRequest LogRequest `json:"LogRequest"` + LogResult LogResult `json:"LogResult"` + MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` + MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` + MCPConfigDisableRequest MCPConfigDisableRequest `json:"McpConfigDisableRequest"` + MCPConfigDisableResult MCPConfigDisableResult `json:"McpConfigDisableResult"` + MCPConfigEnableRequest MCPConfigEnableRequest `json:"McpConfigEnableRequest"` + MCPConfigEnableResult MCPConfigEnableResult `json:"McpConfigEnableResult"` + MCPConfigList MCPConfigList `json:"McpConfigList"` + MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` + MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` + MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` + MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` + MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` + MCPDisableResult MCPDisableResult `json:"McpDisableResult"` + MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` + MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` + MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` + MCPEnableResult MCPEnableResult `json:"McpEnableResult"` + MCPOauthLoginRequest MCPOauthLoginRequest `json:"McpOauthLoginRequest"` + MCPOauthLoginResult MCPOauthLoginResult `json:"McpOauthLoginResult"` + MCPReloadResult MCPReloadResult `json:"McpReloadResult"` + MCPServer MCPServer `json:"McpServer"` + MCPServerConfig MCPServerConfig `json:"McpServerConfig"` + MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` + MCPServerConfigHTTPType MCPServerConfigHTTPType `json:"McpServerConfigHttpType"` + MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` + MCPServerConfigLocalType MCPServerConfigLocalType `json:"McpServerConfigLocalType"` + MCPServerList MCPServerList `json:"McpServerList"` + MCPServerSource MCPServerSource `json:"McpServerSource"` + MCPServerStatus MCPServerStatus `json:"McpServerStatus"` + Model ModelElement `json:"Model"` + ModelBilling ModelBilling `json:"ModelBilling"` + ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` + ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` + ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` + ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` + ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` + ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` + ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` + ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` + ModelList ModelList `json:"ModelList"` + ModelPolicy ModelPolicy `json:"ModelPolicy"` + ModelsListRequest ModelsListRequest `json:"ModelsListRequest"` + ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` + ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` + ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` + ModeSetResult ModeSetResult `json:"ModeSetResult"` + NameGetResult NameGetResult `json:"NameGetResult"` + NameSetRequest NameSetRequest `json:"NameSetRequest"` + NameSetResult NameSetResult `json:"NameSetResult"` + PermissionDecision PermissionDecision `json:"PermissionDecision"` + PermissionDecisionApproveForLocation PermissionDecisionApproveForLocation `json:"PermissionDecisionApproveForLocation"` + PermissionDecisionApproveForLocationApproval PermissionDecisionApproveForLocationApproval `json:"PermissionDecisionApproveForLocationApproval"` + PermissionDecisionApproveForLocationApprovalCommands PermissionDecisionApproveForLocationApprovalCommands `json:"PermissionDecisionApproveForLocationApprovalCommands"` + PermissionDecisionApproveForLocationApprovalCustomTool PermissionDecisionApproveForLocationApprovalCustomTool `json:"PermissionDecisionApproveForLocationApprovalCustomTool"` + PermissionDecisionApproveForLocationApprovalMCP PermissionDecisionApproveForLocationApprovalMCP `json:"PermissionDecisionApproveForLocationApprovalMcp"` + PermissionDecisionApproveForLocationApprovalMCPSampling PermissionDecisionApproveForLocationApprovalMCPSampling `json:"PermissionDecisionApproveForLocationApprovalMcpSampling"` + PermissionDecisionApproveForLocationApprovalMemory PermissionDecisionApproveForLocationApprovalMemory `json:"PermissionDecisionApproveForLocationApprovalMemory"` + PermissionDecisionApproveForLocationApprovalRead PermissionDecisionApproveForLocationApprovalRead `json:"PermissionDecisionApproveForLocationApprovalRead"` + PermissionDecisionApproveForLocationApprovalWrite PermissionDecisionApproveForLocationApprovalWrite `json:"PermissionDecisionApproveForLocationApprovalWrite"` + PermissionDecisionApproveForSession PermissionDecisionApproveForSession `json:"PermissionDecisionApproveForSession"` + PermissionDecisionApproveForSessionApproval PermissionDecisionApproveForSessionApproval `json:"PermissionDecisionApproveForSessionApproval"` + PermissionDecisionApproveForSessionApprovalCommands PermissionDecisionApproveForSessionApprovalCommands `json:"PermissionDecisionApproveForSessionApprovalCommands"` + PermissionDecisionApproveForSessionApprovalCustomTool PermissionDecisionApproveForSessionApprovalCustomTool `json:"PermissionDecisionApproveForSessionApprovalCustomTool"` + PermissionDecisionApproveForSessionApprovalMCP PermissionDecisionApproveForSessionApprovalMCP `json:"PermissionDecisionApproveForSessionApprovalMcp"` + PermissionDecisionApproveForSessionApprovalMCPSampling PermissionDecisionApproveForSessionApprovalMCPSampling `json:"PermissionDecisionApproveForSessionApprovalMcpSampling"` + PermissionDecisionApproveForSessionApprovalMemory PermissionDecisionApproveForSessionApprovalMemory `json:"PermissionDecisionApproveForSessionApprovalMemory"` + PermissionDecisionApproveForSessionApprovalRead PermissionDecisionApproveForSessionApprovalRead `json:"PermissionDecisionApproveForSessionApprovalRead"` + PermissionDecisionApproveForSessionApprovalWrite PermissionDecisionApproveForSessionApprovalWrite `json:"PermissionDecisionApproveForSessionApprovalWrite"` + PermissionDecisionApproveOnce PermissionDecisionApproveOnce `json:"PermissionDecisionApproveOnce"` + PermissionDecisionReject PermissionDecisionReject `json:"PermissionDecisionReject"` + PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` + PermissionDecisionUserNotAvailable PermissionDecisionUserNotAvailable `json:"PermissionDecisionUserNotAvailable"` + PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` + PermissionsResetSessionApprovalsRequest PermissionsResetSessionApprovalsRequest `json:"PermissionsResetSessionApprovalsRequest"` + PermissionsResetSessionApprovalsResult PermissionsResetSessionApprovalsResult `json:"PermissionsResetSessionApprovalsResult"` + PermissionsSetApproveAllRequest PermissionsSetApproveAllRequest `json:"PermissionsSetApproveAllRequest"` + PermissionsSetApproveAllResult PermissionsSetApproveAllResult `json:"PermissionsSetApproveAllResult"` + PingRequest PingRequest `json:"PingRequest"` + PingResult PingResult `json:"PingResult"` + PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` + PlanReadResult PlanReadResult `json:"PlanReadResult"` + PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` + PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` + Plugin PluginElement `json:"Plugin"` + PluginList PluginList `json:"PluginList"` + ServerSkill ServerSkill `json:"ServerSkill"` + ServerSkillList ServerSkillList `json:"ServerSkillList"` + SessionAuthStatus SessionAuthStatus `json:"SessionAuthStatus"` + SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` + SessionFSError SessionFSError `json:"SessionFsError"` + SessionFSErrorCode SessionFSErrorCode `json:"SessionFsErrorCode"` + SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` + SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` + SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` + SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` + SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` + SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` + SessionFSReaddirWithTypesEntryType SessionFSReaddirWithTypesEntryType `json:"SessionFsReaddirWithTypesEntryType"` + SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` + SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` + SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` + SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` + SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` + SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` + SessionFSSetProviderConventions SessionFSSetProviderConventions `json:"SessionFsSetProviderConventions"` + SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` + SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` + SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` + SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` + SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` + SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` + SessionMode SessionMode `json:"SessionMode"` + SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` + SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` + ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` + ShellExecResult ShellExecResult `json:"ShellExecResult"` + ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` + ShellKillResult ShellKillResult `json:"ShellKillResult"` + ShellKillSignal ShellKillSignal `json:"ShellKillSignal"` + Skill Skill `json:"Skill"` + SkillList SkillList `json:"SkillList"` + SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` + SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` + SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` + SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` + SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` + SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` + SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` + SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` + Tool Tool `json:"Tool"` + ToolCallResult ToolCallResult `json:"ToolCallResult"` + ToolList ToolList `json:"ToolList"` + ToolsHandlePendingToolCall *ToolsHandlePendingToolCall `json:"ToolsHandlePendingToolCall"` + ToolsHandlePendingToolCallRequest ToolsHandlePendingToolCallRequest `json:"ToolsHandlePendingToolCallRequest"` + ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` + UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` + UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` + UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` + UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` + UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` + UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` + UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` + UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` + UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` + UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` + UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` + UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` + UIElicitationSchemaProperty UIElicitationSchemaProperty `json:"UIElicitationSchemaProperty"` + UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` + UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` + UIElicitationSchemaPropertyNumberType UIElicitationSchemaPropertyNumberTypeEnum `json:"UIElicitationSchemaPropertyNumberType"` + UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` + UIElicitationSchemaPropertyStringFormat UIElicitationSchemaPropertyStringFormat `json:"UIElicitationSchemaPropertyStringFormat"` + UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` + UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` + UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` + UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` + UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` + UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` + UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` + UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` + UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` + WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` + WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` + WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` + WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` + WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` + WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` +} + +type AccountGetQuotaRequest struct { + // GitHub token for per-user quota lookup. When provided, resolves this token to determine + // the user's quota instead of using the global auth. + GitHubToken *string `json:"gitHubToken,omitempty"` } type AccountGetQuotaResult struct { @@ -463,6 +498,25 @@ type MCPServerConfig struct { type MCPConfigAddResult struct { } +type MCPConfigDisableRequest struct { + // Names of MCP servers to disable. Each server is added to the persisted disabled list so + // new sessions skip it. Already-disabled names are ignored. Active sessions keep their + // current connections until they end. + Names []string `json:"names"` +} + +type MCPConfigDisableResult struct { +} + +type MCPConfigEnableRequest struct { + // Names of MCP servers to enable. Each server is removed from the persisted disabled list + // so new sessions spawn it. Unknown or already-enabled names are ignored. + Names []string `json:"names"` +} + +type MCPConfigEnableResult struct { +} + type MCPConfigList struct { // All MCP servers from user config, keyed by name Servers map[string]MCPServerConfig `json:"servers"` @@ -512,6 +566,34 @@ type MCPEnableRequest struct { type MCPEnableResult struct { } +type MCPOauthLoginRequest struct { + // Optional override for the body text shown on the OAuth loopback callback success page. + // When omitted, the runtime applies a neutral fallback; callers driving interactive auth + // should pass surface-specific copy telling the user where to return. + CallbackSuccessMessage *string `json:"callbackSuccessMessage,omitempty"` + // Optional override for the OAuth client display name shown on the consent screen. Applies + // to newly registered dynamic clients only — existing registrations keep the name they were + // created with. When omitted, the runtime applies a neutral fallback; callers driving + // interactive auth should pass their own surface-specific label so the consent screen + // matches the product the user sees. + ClientName *string `json:"clientName,omitempty"` + // When true, clears any cached OAuth token for the server and runs a full new + // authorization. Use when the user explicitly wants to switch accounts or believes their + // session is stuck. + ForceReauth *bool `json:"forceReauth,omitempty"` + // Name of the remote MCP server to authenticate + ServerName string `json:"serverName"` +} + +type MCPOauthLoginResult struct { + // URL the caller should open in a browser to complete OAuth. Omitted when cached tokens + // were still valid and no browser interaction was needed — the server is already + // reconnected in that case. When present, the runtime starts the callback listener before + // returning and continues the flow in the background; completion is signaled via + // session.mcp_server_status_changed. + AuthorizationURL *string `json:"authorizationUrl,omitempty"` +} + type MCPReloadResult struct { } @@ -634,7 +716,7 @@ type ModelPolicy struct { // Current policy state for this model State string `json:"state"` // Usage terms or conditions for this model - Terms string `json:"terms"` + Terms *string `json:"terms,omitempty"` } // Override individual model capabilities resolved by the runtime @@ -688,6 +770,12 @@ type ModelSwitchToResult struct { ModelID *string `json:"modelId,omitempty"` } +type ModelsListRequest struct { + // GitHub token for per-user model listing. When provided, resolves this token to determine + // the user's Copilot plan and available models instead of using the global auth. + GitHubToken *string `json:"gitHubToken,omitempty"` +} + type NameGetResult struct { // The session name, falling back to the auto-generated summary, or null if neither exists Name *string `json:"name"` @@ -702,72 +790,134 @@ type NameSetResult struct { } type PermissionDecision struct { - // The permission request was approved + // The permission request was approved for this one instance // - // Denied because approval rules explicitly blocked it + // Approved and remembered for the rest of the session // - // Denied because no approval rule matched and user confirmation was unavailable + // Approved and persisted for this project location // // Denied by the user during an interactive prompt // - // Denied by the organization's content exclusion policy - // - // Denied by a permission request hook registered by an extension or plugin + // Denied because user confirmation was unavailable Kind PermissionDecisionKind `json:"kind"` - // Rules that denied the request - Rules []any `json:"rules,omitempty"` + // The approval to add as a session-scoped rule + // + // The approval to persist for this location + Approval *PermissionDecisionApproveForLocationApproval `json:"approval,omitempty"` + // The location key (git root or cwd) to persist the approval to + LocationKey *string `json:"locationKey,omitempty"` // Optional feedback from the user explaining the denial Feedback *string `json:"feedback,omitempty"` - // Human-readable explanation of why the path was excluded - // - // Optional message from the hook explaining the denial - Message *string `json:"message,omitempty"` - // File path that triggered the exclusion - Path *string `json:"path,omitempty"` - // Whether to interrupt the current agent turn - Interrupt *bool `json:"interrupt,omitempty"` } -type PermissionDecisionApproved struct { - // The permission request was approved - Kind PermissionDecisionApprovedKind `json:"kind"` +type PermissionDecisionApproveForLocation struct { + // The approval to persist for this location + Approval PermissionDecisionApproveForLocationApproval `json:"approval"` + // Approved and persisted for this project location + Kind PermissionDecisionApproveForLocationKind `json:"kind"` + // The location key (git root or cwd) to persist the approval to + LocationKey string `json:"locationKey"` } -type PermissionDecisionDeniedByContentExclusionPolicy struct { - // Denied by the organization's content exclusion policy - Kind PermissionDecisionDeniedByContentExclusionPolicyKind `json:"kind"` - // Human-readable explanation of why the path was excluded - Message string `json:"message"` - // File path that triggered the exclusion - Path string `json:"path"` +// The approval to persist for this location +type PermissionDecisionApproveForLocationApproval struct { + CommandIdentifiers []string `json:"commandIdentifiers,omitempty"` + Kind ApprovalKind `json:"kind"` + ServerName *string `json:"serverName,omitempty"` + ToolName *string `json:"toolName"` } -type PermissionDecisionDeniedByPermissionRequestHook struct { - // Whether to interrupt the current agent turn - Interrupt *bool `json:"interrupt,omitempty"` - // Denied by a permission request hook registered by an extension or plugin - Kind PermissionDecisionDeniedByPermissionRequestHookKind `json:"kind"` - // Optional message from the hook explaining the denial - Message *string `json:"message,omitempty"` +type PermissionDecisionApproveForLocationApprovalCommands struct { + CommandIdentifiers []string `json:"commandIdentifiers"` + Kind PermissionDecisionApproveForLocationApprovalCommandsKind `json:"kind"` +} + +type PermissionDecisionApproveForLocationApprovalCustomTool struct { + Kind PermissionDecisionApproveForLocationApprovalCustomToolKind `json:"kind"` + ToolName string `json:"toolName"` +} + +type PermissionDecisionApproveForLocationApprovalMCP struct { + Kind PermissionDecisionApproveForLocationApprovalMCPKind `json:"kind"` + ServerName string `json:"serverName"` + ToolName *string `json:"toolName"` +} + +type PermissionDecisionApproveForLocationApprovalMCPSampling struct { + Kind PermissionDecisionApproveForLocationApprovalMCPSamplingKind `json:"kind"` + ServerName string `json:"serverName"` +} + +type PermissionDecisionApproveForLocationApprovalMemory struct { + Kind PermissionDecisionApproveForLocationApprovalMemoryKind `json:"kind"` +} + +type PermissionDecisionApproveForLocationApprovalRead struct { + Kind PermissionDecisionApproveForLocationApprovalReadKind `json:"kind"` } -type PermissionDecisionDeniedByRules struct { - // Denied because approval rules explicitly blocked it - Kind PermissionDecisionDeniedByRulesKind `json:"kind"` - // Rules that denied the request - Rules []any `json:"rules"` +type PermissionDecisionApproveForLocationApprovalWrite struct { + Kind PermissionDecisionApproveForLocationApprovalWriteKind `json:"kind"` } -type PermissionDecisionDeniedInteractivelyByUser struct { +type PermissionDecisionApproveForSession struct { + // The approval to add as a session-scoped rule + Approval PermissionDecisionApproveForSessionApproval `json:"approval"` + // Approved and remembered for the rest of the session + Kind PermissionDecisionApproveForSessionKind `json:"kind"` +} + +// The approval to add as a session-scoped rule +type PermissionDecisionApproveForSessionApproval struct { + CommandIdentifiers []string `json:"commandIdentifiers,omitempty"` + Kind ApprovalKind `json:"kind"` + ServerName *string `json:"serverName,omitempty"` + ToolName *string `json:"toolName"` +} + +type PermissionDecisionApproveForSessionApprovalCommands struct { + CommandIdentifiers []string `json:"commandIdentifiers"` + Kind PermissionDecisionApproveForLocationApprovalCommandsKind `json:"kind"` +} + +type PermissionDecisionApproveForSessionApprovalCustomTool struct { + Kind PermissionDecisionApproveForLocationApprovalCustomToolKind `json:"kind"` + ToolName string `json:"toolName"` +} + +type PermissionDecisionApproveForSessionApprovalMCP struct { + Kind PermissionDecisionApproveForLocationApprovalMCPKind `json:"kind"` + ServerName string `json:"serverName"` + ToolName *string `json:"toolName"` +} + +type PermissionDecisionApproveForSessionApprovalMCPSampling struct { + Kind PermissionDecisionApproveForLocationApprovalMCPSamplingKind `json:"kind"` + ServerName string `json:"serverName"` +} + +type PermissionDecisionApproveForSessionApprovalMemory struct { + Kind PermissionDecisionApproveForLocationApprovalMemoryKind `json:"kind"` +} + +type PermissionDecisionApproveForSessionApprovalRead struct { + Kind PermissionDecisionApproveForLocationApprovalReadKind `json:"kind"` +} + +type PermissionDecisionApproveForSessionApprovalWrite struct { + Kind PermissionDecisionApproveForLocationApprovalWriteKind `json:"kind"` +} + +type PermissionDecisionApproveOnce struct { + // The permission request was approved for this one instance + Kind PermissionDecisionApproveOnceKind `json:"kind"` +} + +type PermissionDecisionReject struct { // Optional feedback from the user explaining the denial Feedback *string `json:"feedback,omitempty"` // Denied by the user during an interactive prompt - Kind PermissionDecisionDeniedInteractivelyByUserKind `json:"kind"` -} - -type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser struct { - // Denied because no approval rule matched and user confirmation was unavailable - Kind PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind `json:"kind"` + Kind PermissionDecisionRejectKind `json:"kind"` } type PermissionDecisionRequest struct { @@ -776,11 +926,34 @@ type PermissionDecisionRequest struct { Result PermissionDecision `json:"result"` } +type PermissionDecisionUserNotAvailable struct { + // Denied because user confirmation was unavailable + Kind PermissionDecisionUserNotAvailableKind `json:"kind"` +} + type PermissionRequestResult struct { // Whether the permission request was handled successfully Success bool `json:"success"` } +type PermissionsResetSessionApprovalsRequest struct { +} + +type PermissionsResetSessionApprovalsResult struct { + // Whether the operation succeeded + Success bool `json:"success"` +} + +type PermissionsSetApproveAllRequest struct { + // Whether to auto-approve all tool permission requests + Enabled bool `json:"enabled"` +} + +type PermissionsSetApproveAllResult struct { + // Whether the operation succeeded + Success bool `json:"success"` +} + type PingRequest struct { // Optional message to echo back Message *string `json:"message,omitempty"` @@ -854,6 +1027,21 @@ type ServerSkillList struct { Skills []ServerSkill `json:"skills"` } +type SessionAuthStatus struct { + // Authentication type + AuthType *AuthInfoType `json:"authType,omitempty"` + // Copilot plan tier (e.g., individual_pro, business) + CopilotPlan *string `json:"copilotPlan,omitempty"` + // Authentication host URL + Host *string `json:"host,omitempty"` + // Whether the session has resolved authentication + IsAuthenticated bool `json:"isAuthenticated"` + // Authenticated login/username, if available + Login *string `json:"login,omitempty"` + // Human-readable authentication status description + StatusMessage *string `json:"statusMessage,omitempty"` +} + type SessionFSAppendFileRequest struct { // Content to append Content string `json:"content"` @@ -1414,6 +1602,19 @@ type WorkspacesReadFileResult struct { Content string `json:"content"` } +// Authentication type +type AuthInfoType string + +const ( + AuthInfoTypeAPIKey AuthInfoType = "api-key" + AuthInfoTypeUser AuthInfoType = "user" + AuthInfoTypeCopilotAPIToken AuthInfoType = "copilot-api-token" + AuthInfoTypeEnv AuthInfoType = "env" + AuthInfoTypeGhCli AuthInfoType = "gh-cli" + AuthInfoTypeHmac AuthInfoType = "hmac" + AuthInfoTypeToken AuthInfoType = "token" +) + // Configuration source // // Configuration source: user, workspace, plugin, or builtin @@ -1431,9 +1632,9 @@ type DiscoveredMCPServerType string const ( DiscoveredMCPServerTypeHTTP DiscoveredMCPServerType = "http" + DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" DiscoveredMCPServerTypeSSE DiscoveredMCPServerType = "sse" DiscoveredMCPServerTypeStdio DiscoveredMCPServerType = "stdio" - DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" ) // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) @@ -1539,51 +1740,98 @@ const ( SessionModePlan SessionMode = "plan" ) +type ApprovalKind string + +const ( + ApprovalKindCommands ApprovalKind = "commands" + ApprovalKindCustomTool ApprovalKind = "custom-tool" + ApprovalKindMcp ApprovalKind = "mcp" + ApprovalKindMcpSampling ApprovalKind = "mcp-sampling" + ApprovalKindMemory ApprovalKind = "memory" + ApprovalKindRead ApprovalKind = "read" + ApprovalKindWrite ApprovalKind = "write" +) + type PermissionDecisionKind string const ( - PermissionDecisionKindApproved PermissionDecisionKind = "approved" - PermissionDecisionKindDeniedByContentExclusionPolicy PermissionDecisionKind = "denied-by-content-exclusion-policy" - PermissionDecisionKindDeniedByPermissionRequestHook PermissionDecisionKind = "denied-by-permission-request-hook" - PermissionDecisionKindDeniedByRules PermissionDecisionKind = "denied-by-rules" - PermissionDecisionKindDeniedInteractivelyByUser PermissionDecisionKind = "denied-interactively-by-user" - PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionDecisionKindApproveForLocation PermissionDecisionKind = "approve-for-location" + PermissionDecisionKindApproveForSession PermissionDecisionKind = "approve-for-session" + PermissionDecisionKindApproveOnce PermissionDecisionKind = "approve-once" + PermissionDecisionKindReject PermissionDecisionKind = "reject" + PermissionDecisionKindUserNotAvailable PermissionDecisionKind = "user-not-available" +) + +type PermissionDecisionApproveForLocationKind string + +const ( + PermissionDecisionApproveForLocationKindApproveForLocation PermissionDecisionApproveForLocationKind = "approve-for-location" +) + +type PermissionDecisionApproveForLocationApprovalCommandsKind string + +const ( + PermissionDecisionApproveForLocationApprovalCommandsKindCommands PermissionDecisionApproveForLocationApprovalCommandsKind = "commands" +) + +type PermissionDecisionApproveForLocationApprovalCustomToolKind string + +const ( + PermissionDecisionApproveForLocationApprovalCustomToolKindCustomTool PermissionDecisionApproveForLocationApprovalCustomToolKind = "custom-tool" +) + +type PermissionDecisionApproveForLocationApprovalMCPKind string + +const ( + PermissionDecisionApproveForLocationApprovalMCPKindMcp PermissionDecisionApproveForLocationApprovalMCPKind = "mcp" +) + +type PermissionDecisionApproveForLocationApprovalMCPSamplingKind string + +const ( + PermissionDecisionApproveForLocationApprovalMCPSamplingKindMcpSampling PermissionDecisionApproveForLocationApprovalMCPSamplingKind = "mcp-sampling" +) + +type PermissionDecisionApproveForLocationApprovalMemoryKind string + +const ( + PermissionDecisionApproveForLocationApprovalMemoryKindMemory PermissionDecisionApproveForLocationApprovalMemoryKind = "memory" ) -type PermissionDecisionApprovedKind string +type PermissionDecisionApproveForLocationApprovalReadKind string const ( - PermissionDecisionApprovedKindApproved PermissionDecisionApprovedKind = "approved" + PermissionDecisionApproveForLocationApprovalReadKindRead PermissionDecisionApproveForLocationApprovalReadKind = "read" ) -type PermissionDecisionDeniedByContentExclusionPolicyKind string +type PermissionDecisionApproveForLocationApprovalWriteKind string const ( - PermissionDecisionDeniedByContentExclusionPolicyKindDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicyKind = "denied-by-content-exclusion-policy" + PermissionDecisionApproveForLocationApprovalWriteKindWrite PermissionDecisionApproveForLocationApprovalWriteKind = "write" ) -type PermissionDecisionDeniedByPermissionRequestHookKind string +type PermissionDecisionApproveForSessionKind string const ( - PermissionDecisionDeniedByPermissionRequestHookKindDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHookKind = "denied-by-permission-request-hook" + PermissionDecisionApproveForSessionKindApproveForSession PermissionDecisionApproveForSessionKind = "approve-for-session" ) -type PermissionDecisionDeniedByRulesKind string +type PermissionDecisionApproveOnceKind string const ( - PermissionDecisionDeniedByRulesKindDeniedByRules PermissionDecisionDeniedByRulesKind = "denied-by-rules" + PermissionDecisionApproveOnceKindApproveOnce PermissionDecisionApproveOnceKind = "approve-once" ) -type PermissionDecisionDeniedInteractivelyByUserKind string +type PermissionDecisionRejectKind string const ( - PermissionDecisionDeniedInteractivelyByUserKindDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUserKind = "denied-interactively-by-user" + PermissionDecisionRejectKindReject PermissionDecisionRejectKind = "reject" ) -type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind string +type PermissionDecisionUserNotAvailableKind string const ( - PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionDecisionUserNotAvailableKindUserNotAvailable PermissionDecisionUserNotAvailableKind = "user-not-available" ) // Error classification @@ -1717,8 +1965,8 @@ type serverApi struct { type ServerModelsApi serverApi -func (a *ServerModelsApi) List(ctx context.Context) (*ModelList, error) { - raw, err := a.client.Request("models.list", nil) +func (a *ServerModelsApi) List(ctx context.Context, params *ModelsListRequest) (*ModelList, error) { + raw, err := a.client.Request("models.list", params) if err != nil { return nil, err } @@ -1745,8 +1993,8 @@ func (a *ServerToolsApi) List(ctx context.Context, params *ToolsListRequest) (*T type ServerAccountApi serverApi -func (a *ServerAccountApi) GetQuota(ctx context.Context) (*AccountGetQuotaResult, error) { - raw, err := a.client.Request("account.getQuota", nil) +func (a *ServerAccountApi) GetQuota(ctx context.Context, params *AccountGetQuotaRequest) (*AccountGetQuotaResult, error) { + raw, err := a.client.Request("account.getQuota", params) if err != nil { return nil, err } @@ -1821,6 +2069,30 @@ func (a *ServerMcpConfigApi) Remove(ctx context.Context, params *MCPConfigRemove return &result, nil } +func (a *ServerMcpConfigApi) Enable(ctx context.Context, params *MCPConfigEnableRequest) (*MCPConfigEnableResult, error) { + raw, err := a.client.Request("mcp.config.enable", params) + if err != nil { + return nil, err + } + var result MCPConfigEnableResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *ServerMcpConfigApi) Disable(ctx context.Context, params *MCPConfigDisableRequest) (*MCPConfigDisableResult, error) { + raw, err := a.client.Request("mcp.config.disable", params) + if err != nil { + return nil, err + } + var result MCPConfigDisableResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + func (s *ServerMcpApi) Config() *ServerMcpConfigApi { return (*ServerMcpConfigApi)(s) } @@ -1929,6 +2201,21 @@ type sessionApi struct { sessionID string } +type AuthApi sessionApi + +func (a *AuthApi) GetStatus(ctx context.Context) (*SessionAuthStatus, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.auth.getStatus", req) + if err != nil { + return nil, err + } + var result SessionAuthStatus + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + type ModelApi sessionApi func (a *ModelApi) GetCurrent(ctx context.Context) (*CurrentModel, error) { @@ -2362,6 +2649,39 @@ func (a *McpApi) Reload(ctx context.Context) (*MCPReloadResult, error) { return &result, nil } +// Experimental: McpOauthApi contains experimental APIs that may change or be removed. +type McpOauthApi sessionApi + +func (a *McpOauthApi) Login(ctx context.Context, params *MCPOauthLoginRequest) (*MCPOauthLoginResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["serverName"] = params.ServerName + if params.ForceReauth != nil { + req["forceReauth"] = *params.ForceReauth + } + if params.ClientName != nil { + req["clientName"] = *params.ClientName + } + if params.CallbackSuccessMessage != nil { + req["callbackSuccessMessage"] = *params.CallbackSuccessMessage + } + } + raw, err := a.client.Request("session.mcp.oauth.login", req) + if err != nil { + return nil, err + } + var result MCPOauthLoginResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +// Experimental: Oauth returns experimental APIs that may change or be removed. +func (s *McpApi) Oauth() *McpOauthApi { + return (*McpOauthApi)(s) +} + // Experimental: PluginsApi contains experimental APIs that may change or be removed. type PluginsApi sessionApi @@ -2539,6 +2859,35 @@ func (a *PermissionsApi) HandlePendingPermissionRequest(ctx context.Context, par return &result, nil } +func (a *PermissionsApi) SetApproveAll(ctx context.Context, params *PermissionsSetApproveAllRequest) (*PermissionsSetApproveAllResult, error) { + req := map[string]any{"sessionId": a.sessionID} + if params != nil { + req["enabled"] = params.Enabled + } + raw, err := a.client.Request("session.permissions.setApproveAll", req) + if err != nil { + return nil, err + } + var result PermissionsSetApproveAllResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + +func (a *PermissionsApi) ResetSessionApprovals(ctx context.Context) (*PermissionsResetSessionApprovalsResult, error) { + req := map[string]any{"sessionId": a.sessionID} + raw, err := a.client.Request("session.permissions.resetSessionApprovals", req) + if err != nil { + return nil, err + } + var result PermissionsResetSessionApprovalsResult + if err := json.Unmarshal(raw, &result); err != nil { + return nil, err + } + return &result, nil +} + type ShellApi sessionApi func (a *ShellApi) Exec(ctx context.Context, params *ShellExecRequest) (*ShellExecResult, error) { @@ -2634,6 +2983,7 @@ func (a *UsageApi) GetMetrics(ctx context.Context) (*UsageGetMetricsResult, erro type SessionRpc struct { common sessionApi // Reuse a single struct instead of allocating one for each service on the heap. + Auth *AuthApi Model *ModelApi Mode *ModeApi Name *NameApi @@ -2683,6 +3033,7 @@ func (a *SessionRpc) Log(ctx context.Context, params *LogRequest) (*LogResult, e func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { r := &SessionRpc{} r.common = sessionApi{client: client, sessionID: sessionID} + r.Auth = (*AuthApi)(&r.common) r.Model = (*ModelApi)(&r.common) r.Mode = (*ModeApi)(&r.common) r.Name = (*NameApi)(&r.common) diff --git a/go/session.go b/go/session.go index 99256856d..8bcdf57c8 100644 --- a/go/session.go +++ b/go/session.go @@ -1029,7 +1029,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindUserNotAvailable, }, }) } @@ -1044,7 +1044,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindUserNotAvailable, }, }) return @@ -1056,9 +1056,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.PermissionDecisionKind(result.Kind), - Rules: result.Rules, - Feedback: nil, + Kind: rpc.PermissionDecisionKind(result.Kind), }, }) } diff --git a/go/types.go b/go/types.go index 14905ec13..9b971aad4 100644 --- a/go/types.go +++ b/go/types.go @@ -207,21 +207,27 @@ type SystemMessageConfig struct { type PermissionRequestResultKind string const ( - // PermissionRequestResultKindApproved indicates the permission was approved. - PermissionRequestResultKindApproved PermissionRequestResultKind = "approved" + // PermissionRequestResultKindApproved indicates the permission was approved for this one instance. + PermissionRequestResultKindApproved PermissionRequestResultKind = "approve-once" - // PermissionRequestResultKindDeniedByRules indicates the permission was denied by rules. - PermissionRequestResultKindDeniedByRules PermissionRequestResultKind = "denied-by-rules" + // PermissionRequestResultKindRejected indicates the permission was denied interactively by the user. + PermissionRequestResultKindRejected PermissionRequestResultKind = "reject" - // PermissionRequestResultKindDeniedCouldNotRequestFromUser indicates the permission was denied because - // no approval rule was found and the user could not be prompted. - PermissionRequestResultKindDeniedCouldNotRequestFromUser PermissionRequestResultKind = "denied-no-approval-rule-and-could-not-request-from-user" - - // PermissionRequestResultKindDeniedInteractivelyByUser indicates the permission was denied interactively by the user. - PermissionRequestResultKindDeniedInteractivelyByUser PermissionRequestResultKind = "denied-interactively-by-user" + // PermissionRequestResultKindUserNotAvailable indicates the permission was denied because + // user confirmation was unavailable. + PermissionRequestResultKindUserNotAvailable PermissionRequestResultKind = "user-not-available" // PermissionRequestResultKindNoResult indicates no permission decision was made. PermissionRequestResultKindNoResult PermissionRequestResultKind = "no-result" + + // Deprecated: Use PermissionRequestResultKindRejected instead. + PermissionRequestResultKindDeniedInteractivelyByUser = PermissionRequestResultKindRejected + + // Deprecated: Use PermissionRequestResultKindUserNotAvailable instead. + PermissionRequestResultKindDeniedCouldNotRequestFromUser = PermissionRequestResultKindUserNotAvailable + + // Deprecated: Use PermissionRequestResultKindUserNotAvailable instead. + PermissionRequestResultKindDeniedByRules = PermissionRequestResultKindUserNotAvailable ) // PermissionRequestResult represents the result of a permission request @@ -588,6 +594,10 @@ type SessionConfig struct { // When provided, the server may call back to this client for form-based UI dialogs // (e.g. from MCP tools). Also enables the elicitation capability on the session. OnElicitationRequest ElicitationHandler + // GitHubToken is an optional per-session GitHub token used for authentication. + // When provided, the session authenticates as the token's owner instead of + // using the global client-level auth. + GitHubToken string `json:"-"` } type Tool struct { Name string `json:"name"` @@ -787,6 +797,10 @@ type ResumeSessionConfig struct { DisabledSkills []string // InfiniteSessions configures infinite sessions for persistent workspaces and automatic compaction. InfiniteSessions *InfiniteSessionConfig + // GitHubToken is an optional per-session GitHub token used for authentication. + // When provided, the session authenticates as the token's owner instead of + // using the global client-level auth. + GitHubToken string `json:"-"` // DisableResume, when true, skips emitting the session.resume event. // Useful for reconnecting to a session without triggering resume-related side effects. DisableResume bool @@ -999,6 +1013,7 @@ type createSessionRequest struct { InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` + GitHubToken string `json:"gitHubToken,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` } @@ -1047,6 +1062,7 @@ type resumeSessionRequest struct { InfiniteSessions *InfiniteSessionConfig `json:"infiniteSessions,omitempty"` Commands []wireCommand `json:"commands,omitempty"` RequestElicitation *bool `json:"requestElicitation,omitempty"` + GitHubToken string `json:"gitHubToken,omitempty"` Traceparent string `json:"traceparent,omitempty"` Tracestate string `json:"tracestate,omitempty"` } diff --git a/go/types_test.go b/go/types_test.go index b37e94f15..ef02424a3 100644 --- a/go/types_test.go +++ b/go/types_test.go @@ -11,11 +11,14 @@ func TestPermissionRequestResultKind_Constants(t *testing.T) { kind PermissionRequestResultKind expected string }{ - {"Approved", PermissionRequestResultKindApproved, "approved"}, - {"DeniedByRules", PermissionRequestResultKindDeniedByRules, "denied-by-rules"}, - {"DeniedCouldNotRequestFromUser", PermissionRequestResultKindDeniedCouldNotRequestFromUser, "denied-no-approval-rule-and-could-not-request-from-user"}, - {"DeniedInteractivelyByUser", PermissionRequestResultKindDeniedInteractivelyByUser, "denied-interactively-by-user"}, - {"NoResult", PermissionRequestResultKind("no-result"), "no-result"}, + {"Approved", PermissionRequestResultKindApproved, "approve-once"}, + {"Rejected", PermissionRequestResultKindRejected, "reject"}, + {"UserNotAvailable", PermissionRequestResultKindUserNotAvailable, "user-not-available"}, + {"NoResult", PermissionRequestResultKindNoResult, "no-result"}, + // Deprecated aliases + {"DeprecatedDeniedInteractivelyByUser", PermissionRequestResultKindDeniedInteractivelyByUser, "reject"}, + {"DeprecatedDeniedCouldNotRequestFromUser", PermissionRequestResultKindDeniedCouldNotRequestFromUser, "user-not-available"}, + {"DeprecatedDeniedByRules", PermissionRequestResultKindDeniedByRules, "user-not-available"}, } for _, tt := range tests { @@ -68,14 +71,14 @@ func TestPermissionRequestResult_JSONRoundTrip(t *testing.T) { } func TestPermissionRequestResult_JSONDeserialize(t *testing.T) { - jsonStr := `{"kind":"denied-by-rules"}` + jsonStr := `{"kind":"reject"}` var result PermissionRequestResult if err := json.Unmarshal([]byte(jsonStr), &result); err != nil { t.Fatalf("failed to unmarshal: %v", err) } - if result.Kind != PermissionRequestResultKindDeniedByRules { - t.Errorf("expected %q, got %q", PermissionRequestResultKindDeniedByRules, result.Kind) + if result.Kind != PermissionRequestResultKindRejected { + t.Errorf("expected %q, got %q", PermissionRequestResultKindRejected, result.Kind) } } @@ -86,7 +89,7 @@ func TestPermissionRequestResult_JSONSerialize(t *testing.T) { t.Fatalf("failed to marshal: %v", err) } - expected := `{"kind":"approved"}` + expected := `{"kind":"approve-once"}` if string(data) != expected { t.Errorf("expected %s, got %s", expected, string(data)) } diff --git a/nodejs/README.md b/nodejs/README.md index 20e91adbf..af978454c 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -86,8 +86,8 @@ new CopilotClient(options?: CopilotClientOptions) - `useStdio?: boolean` - Use stdio transport instead of TCP (default: true) - `logLevel?: string` - Log level (default: "info") - `autoStart?: boolean` - Auto-start server (default: true) -- `githubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods. -- `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `githubToken` is provided). Cannot be used with `cliUrl`. +- `gitHubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods. +- `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `gitHubToken` is provided). Cannot be used with `cliUrl`. - `telemetry?: TelemetryConfig` - OpenTelemetry configuration for the CLI process. Providing this object enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below. - `onGetTraceContext?: TraceContextProvider` - Advanced: callback for linking your application's own OpenTelemetry spans into the same distributed trace as the CLI's spans. Not needed for normal telemetry collection. See [Telemetry](#telemetry) below. diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 574bc86a9..ac03d9a99 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.35-0", + "@github/copilot": "^1.0.36-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.35-0.tgz", - "integrity": "sha512-daPkiDXeXwsoEHy4XvZywVX3Voyaubir27qm/3uyifxeruMGOcUT/XC8tkJhE6VfSy3nvtjV4xXrZ43Wr0x2cg==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.36-0.tgz", + "integrity": "sha512-M1mxNbdRkiQv4qApKgV33jK6AsA3TqlMAtKyaDv9sJzE/kZa4IRUAUrmO+3d3C+ojZa/Yffjy0/6dC6kllhI4g==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.35-0", - "@github/copilot-darwin-x64": "1.0.35-0", - "@github/copilot-linux-arm64": "1.0.35-0", - "@github/copilot-linux-x64": "1.0.35-0", - "@github/copilot-win32-arm64": "1.0.35-0", - "@github/copilot-win32-x64": "1.0.35-0" + "@github/copilot-darwin-arm64": "1.0.36-0", + "@github/copilot-darwin-x64": "1.0.36-0", + "@github/copilot-linux-arm64": "1.0.36-0", + "@github/copilot-linux-x64": "1.0.36-0", + "@github/copilot-win32-arm64": "1.0.36-0", + "@github/copilot-win32-x64": "1.0.36-0" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.35-0.tgz", - "integrity": "sha512-Uc3PIw60y/9fk1F2JlLqBl0VkParTiCIxlLWKFs8N6TJwFafKmLt7B5r4nqoFhsYZOov6ww4nIxxaMiVdFF0YA==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.36-0.tgz", + "integrity": "sha512-S0/oT9eo2WvjteWjtjougfh6tokq1Upye6tWeTHWq001E2UvrBN69+cQJNcNQUkO2C2AVvoqiI5RJT/E+HDrww==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.35-0.tgz", - "integrity": "sha512-5R5hkZ4Z2CnHVdXnKMNjkFi00mdBYF9H9kkzQjmaN8cG4JwZFf209lo1bEzpXWKHl136LXNwLVhHCYfi3FgzXQ==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.36-0.tgz", + "integrity": "sha512-msY1h6J2j005HMHxYqXO6Q5rJdqAjkUnnBwne5p3s71EHGekOl5U8GJs1Q2Y287+e9ZKSY68ANt/JB0Gq2ivmA==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.35-0.tgz", - "integrity": "sha512-I+kDV2xhvq2t6ux2/ZmWoRkReq8fNlYgW1GfWRmp4c+vQKvH+WsQ5P0WWSt8BmmQGK9hUrTcXg2nvVAPQJ2D8Q==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.36-0.tgz", + "integrity": "sha512-2yIKaU5XdC0xkFXt80pU+Uqr7pU2lHHzcBehzhDHfDeZVNq8jQj61Ka9r9NBjU3W8c3f99ctPMN8gErFnc5L/A==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.35-0.tgz", - "integrity": "sha512-mnG6lpzmWvkasdYgmvotb2PQKW/GaCAdZbuv34iOT84Iz3VyEamcUNurw+KCrxitCYRa68cnCQFbGMf8p6Q22A==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.36-0.tgz", + "integrity": "sha512-tiEnl40MmEc4uybJ8at9TagmEcMsNHDiqzER72PYAD4mKwWklZ3RAClaVgzQlTxn1tMdNM7gbajyqSivLmo+rA==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.35-0.tgz", - "integrity": "sha512-suB5kxHQtD5Hu7NUqH3bUkNBg6e0rPLSf54jCN8UjyxJBfV2mL7BZeqr77Du3UzHHkRKxqITiZ4LBZH8q0bOEg==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.36-0.tgz", + "integrity": "sha512-c6hT1lnl7B44tLJGmyugvqPQ51bIMXtTeCb7Z5riPd4Sv17gsU+gLWewJLddAnu+0XhjzlBsIHv9GUtxGPCgWQ==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.35-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.35-0.tgz", - "integrity": "sha512-KKuxw+rKpfEn/575l+3aef72/MiGlH8D9CIX6+3+qPQqojt7YBDlEqgL3/aAk9JUrQbiqSUXXKD3mMEHdgNoWQ==", + "version": "1.0.36-0", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.36-0.tgz", + "integrity": "sha512-RJy3RtlX+34denR3+ttBZsbyeaGoA2nlYoEtnijsQquK37on4gTwVavRbvjfVa2pL+aMuKhRD+xdvsUd+Fu4Lg==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index c33b8cb2c..a7caa354a 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.35-0", + "@github/copilot": "^1.0.36-0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 0ef19038f..5eadf58dd 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -215,7 +215,7 @@ export class CopilotClient { CopilotClientOptions, | "cliPath" | "cliUrl" - | "githubToken" + | "gitHubToken" | "useLoggedInUser" | "onListModels" | "telemetry" @@ -225,7 +225,7 @@ export class CopilotClient { > & { cliPath?: string; cliUrl?: string; - githubToken?: string; + gitHubToken?: string; useLoggedInUser?: boolean; telemetry?: TelemetryConfig; }; @@ -294,9 +294,9 @@ export class CopilotClient { } // Validate auth options with external server - if (options.cliUrl && (options.githubToken || options.useLoggedInUser !== undefined)) { + if (options.cliUrl && (options.gitHubToken || options.useLoggedInUser !== undefined)) { throw new Error( - "githubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)" + "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)" ); } @@ -336,9 +336,9 @@ export class CopilotClient { autoRestart: false, env: effectiveEnv, - githubToken: options.githubToken, - // Default useLoggedInUser to false when githubToken is provided, otherwise true - useLoggedInUser: options.useLoggedInUser ?? (options.githubToken ? false : true), + gitHubToken: options.gitHubToken, + // Default useLoggedInUser to false when gitHubToken is provided, otherwise true + useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true), telemetry: options.telemetry, sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0, }; @@ -763,6 +763,7 @@ export class CopilotClient { skillDirectories: config.skillDirectories, disabledSkills: config.disabledSkills, infiniteSessions: config.infiniteSessions, + gitHubToken: config.gitHubToken, }); const { workspacePath, capabilities } = response as { @@ -906,6 +907,7 @@ export class CopilotClient { disabledSkills: config.disabledSkills, infiniteSessions: config.infiniteSessions, disableResume: config.disableResume, + gitHubToken: config.gitHubToken, }); const { workspacePath, capabilities } = response as { @@ -1408,7 +1410,7 @@ export class CopilotClient { } // Add auth-related flags - if (this.options.githubToken) { + if (this.options.gitHubToken) { args.push("--auth-token-env", "COPILOT_SDK_AUTH_TOKEN"); } if (!this.options.useLoggedInUser) { @@ -1430,8 +1432,8 @@ export class CopilotClient { delete envWithoutNodeDebug.NODE_DEBUG; // Set auth token in environment if provided - if (this.options.githubToken) { - envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.githubToken; + if (this.options.gitHubToken) { + envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken; } if (!this.options.cliPath) { @@ -1961,7 +1963,7 @@ export class CopilotClient { } return { result: { - kind: "denied-no-approval-rule-and-could-not-request-from-user", + kind: "user-not-available", }, }; } diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index b40ffc701..6ee14deed 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,6 +5,13 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; +/** + * Authentication type + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "AuthInfoType". + */ +export type AuthInfoType = "hmac" | "env" | "user" | "gh-cli" | "api-key" | "token" | "copilot-api-token"; /** * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) * @@ -103,12 +110,39 @@ export type McpServerSource = "user" | "workspace" | "plugin" | "builtin"; export type SessionMode = "interactive" | "plan" | "autopilot"; export type PermissionDecision = - | PermissionDecisionApproved - | PermissionDecisionDeniedByRules - | PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser - | PermissionDecisionDeniedInteractivelyByUser - | PermissionDecisionDeniedByContentExclusionPolicy - | PermissionDecisionDeniedByPermissionRequestHook; + | PermissionDecisionApproveOnce + | PermissionDecisionApproveForSession + | PermissionDecisionApproveForLocation + | PermissionDecisionReject + | PermissionDecisionUserNotAvailable; +/** + * The approval to add as a session-scoped rule + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForSessionApproval". + */ +export type PermissionDecisionApproveForSessionApproval = + | PermissionDecisionApproveForSessionApprovalCommands + | PermissionDecisionApproveForSessionApprovalRead + | PermissionDecisionApproveForSessionApprovalWrite + | PermissionDecisionApproveForSessionApprovalMcp + | PermissionDecisionApproveForSessionApprovalMcpSampling + | PermissionDecisionApproveForSessionApprovalMemory + | PermissionDecisionApproveForSessionApprovalCustomTool; +/** + * The approval to persist for this location + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "PermissionDecisionApproveForLocationApproval". + */ +export type PermissionDecisionApproveForLocationApproval = + | PermissionDecisionApproveForLocationApprovalCommands + | PermissionDecisionApproveForLocationApprovalRead + | PermissionDecisionApproveForLocationApprovalWrite + | PermissionDecisionApproveForLocationApprovalMcp + | PermissionDecisionApproveForLocationApprovalMcpSampling + | PermissionDecisionApproveForLocationApprovalMemory + | PermissionDecisionApproveForLocationApprovalCustomTool; /** * Error classification * @@ -167,6 +201,13 @@ export type UIElicitationSchemaPropertyNumberType = "number" | "integer"; */ export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; +export interface AccountGetQuotaRequest { + /** + * GitHub token for per-user quota lookup. When provided, resolves this token to determine the user's quota instead of using the global auth. + */ + gitHubToken?: string; +} + export interface AccountGetQuotaResult { /** * Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) @@ -537,6 +578,20 @@ export interface McpServerConfigHttp { oauthPublicClient?: boolean; } +export interface McpConfigDisableRequest { + /** + * Names of MCP servers to disable. Each server is added to the persisted disabled list so new sessions skip it. Already-disabled names are ignored. Active sessions keep their current connections until they end. + */ + names: string[]; +} + +export interface McpConfigEnableRequest { + /** + * Names of MCP servers to enable. Each server is removed from the persisted disabled list so new sessions spawn it. Unknown or already-enabled names are ignored. + */ + names: string[]; +} + export interface McpConfigList { /** * All MCP servers from user config, keyed by name @@ -591,6 +646,34 @@ export interface McpEnableRequest { serverName: string; } +/** @experimental */ +export interface McpOauthLoginRequest { + /** + * Name of the remote MCP server to authenticate + */ + serverName: string; + /** + * When true, clears any cached OAuth token for the server and runs a full new authorization. Use when the user explicitly wants to switch accounts or believes their session is stuck. + */ + forceReauth?: boolean; + /** + * Optional override for the OAuth client display name shown on the consent screen. Applies to newly registered dynamic clients only — existing registrations keep the name they were created with. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass their own surface-specific label so the consent screen matches the product the user sees. + */ + clientName?: string; + /** + * Optional override for the body text shown on the OAuth loopback callback success page. When omitted, the runtime applies a neutral fallback; callers driving interactive auth should pass surface-specific copy telling the user where to return. + */ + callbackSuccessMessage?: string; +} + +/** @experimental */ +export interface McpOauthLoginResult { + /** + * URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. + */ + authorizationUrl?: string; +} + export interface McpServer { /** * Server name (config key) @@ -714,7 +797,7 @@ export interface ModelPolicy { /** * Usage terms or conditions for this model */ - terms: string; + terms?: string; } /** * Billing information @@ -786,6 +869,13 @@ export interface ModelList { models: Model[]; } +export interface ModelsListRequest { + /** + * GitHub token for per-user model listing. When provided, resolves this token to determine the user's Copilot plan and available models instead of using the global auth. + */ + gitHubToken?: string; +} + export interface ModelSwitchToRequest { /** * Model identifier to switch to @@ -823,83 +913,151 @@ export interface NameSetRequest { name: string; } -export interface PermissionDecisionApproved { +export interface PermissionDecisionApproveOnce { /** - * The permission request was approved + * The permission request was approved for this one instance */ - kind: "approved"; + kind: "approve-once"; } -export interface PermissionDecisionDeniedByRules { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; +export interface PermissionDecisionApproveForSession { /** - * Rules that denied the request + * Approved and remembered for the rest of the session */ - rules: unknown[]; + kind: "approve-for-session"; + approval: PermissionDecisionApproveForSessionApproval; +} + +export interface PermissionDecisionApproveForSessionApprovalCommands { + kind: "commands"; + commandIdentifiers: string[]; +} + +export interface PermissionDecisionApproveForSessionApprovalRead { + kind: "read"; +} + +export interface PermissionDecisionApproveForSessionApprovalWrite { + kind: "write"; +} + +export interface PermissionDecisionApproveForSessionApprovalMcp { + kind: "mcp"; + serverName: string; + toolName: string | null; +} + +export interface PermissionDecisionApproveForSessionApprovalMcpSampling { + kind: "mcp-sampling"; + serverName: string; +} + +export interface PermissionDecisionApproveForSessionApprovalMemory { + kind: "memory"; } -export interface PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { +export interface PermissionDecisionApproveForSessionApprovalCustomTool { + kind: "custom-tool"; + toolName: string; +} + +export interface PermissionDecisionApproveForLocation { + /** + * Approved and persisted for this project location + */ + kind: "approve-for-location"; + approval: PermissionDecisionApproveForLocationApproval; /** - * Denied because no approval rule matched and user confirmation was unavailable + * The location key (git root or cwd) to persist the approval to */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; + locationKey: string; +} + +export interface PermissionDecisionApproveForLocationApprovalCommands { + kind: "commands"; + commandIdentifiers: string[]; +} + +export interface PermissionDecisionApproveForLocationApprovalRead { + kind: "read"; +} + +export interface PermissionDecisionApproveForLocationApprovalWrite { + kind: "write"; +} + +export interface PermissionDecisionApproveForLocationApprovalMcp { + kind: "mcp"; + serverName: string; + toolName: string | null; +} + +export interface PermissionDecisionApproveForLocationApprovalMcpSampling { + kind: "mcp-sampling"; + serverName: string; } -export interface PermissionDecisionDeniedInteractivelyByUser { +export interface PermissionDecisionApproveForLocationApprovalMemory { + kind: "memory"; +} + +export interface PermissionDecisionApproveForLocationApprovalCustomTool { + kind: "custom-tool"; + toolName: string; +} + +export interface PermissionDecisionReject { /** * Denied by the user during an interactive prompt */ - kind: "denied-interactively-by-user"; + kind: "reject"; /** * Optional feedback from the user explaining the denial */ feedback?: string; } -export interface PermissionDecisionDeniedByContentExclusionPolicy { +export interface PermissionDecisionUserNotAvailable { /** - * Denied by the organization's content exclusion policy + * Denied because user confirmation was unavailable */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; + kind: "user-not-available"; } -export interface PermissionDecisionDeniedByPermissionRequestHook { +export interface PermissionDecisionRequest { /** - * Denied by a permission request hook registered by an extension or plugin + * Request ID of the pending permission request */ - kind: "denied-by-permission-request-hook"; + requestId: string; + result: PermissionDecision; +} + +export interface PermissionRequestResult { /** - * Optional message from the hook explaining the denial + * Whether the permission request was handled successfully */ - message?: string; + success: boolean; +} + +export interface PermissionsResetSessionApprovalsRequest {} + +export interface PermissionsResetSessionApprovalsResult { /** - * Whether to interrupt the current agent turn + * Whether the operation succeeded */ - interrupt?: boolean; + success: boolean; } -export interface PermissionDecisionRequest { +export interface PermissionsSetApproveAllRequest { /** - * Request ID of the pending permission request + * Whether to auto-approve all tool permission requests */ - requestId: string; - result: PermissionDecision; + enabled: boolean; } -export interface PermissionRequestResult { +export interface PermissionsSetApproveAllResult { /** - * Whether the permission request was handled successfully + * Whether the operation succeeded */ success: boolean; } @@ -1013,6 +1171,30 @@ export interface ServerSkillList { skills: ServerSkill[]; } +export interface SessionAuthStatus { + /** + * Whether the session has resolved authentication + */ + isAuthenticated: boolean; + authType?: AuthInfoType; + /** + * Authentication host URL + */ + host?: string; + /** + * Authenticated login/username, if available + */ + login?: string; + /** + * Human-readable authentication status description + */ + statusMessage?: string; + /** + * Copilot plan tier (e.g., individual_pro, business) + */ + copilotPlan?: string; +} + export interface SessionFsAppendFileRequest { /** * Target session identifier @@ -1769,16 +1951,16 @@ export function createServerRpc(connection: MessageConnection) { ping: async (params: PingRequest): Promise => connection.sendRequest("ping", params), models: { - list: async (): Promise => - connection.sendRequest("models.list", {}), + list: async (params?: ModelsListRequest): Promise => + connection.sendRequest("models.list", params), }, tools: { list: async (params: ToolsListRequest): Promise => connection.sendRequest("tools.list", params), }, account: { - getQuota: async (): Promise => - connection.sendRequest("account.getQuota", {}), + getQuota: async (params?: AccountGetQuotaRequest): Promise => + connection.sendRequest("account.getQuota", params), }, mcp: { config: { @@ -1790,6 +1972,10 @@ export function createServerRpc(connection: MessageConnection) { connection.sendRequest("mcp.config.update", params), remove: async (params: McpConfigRemoveRequest): Promise => connection.sendRequest("mcp.config.remove", params), + enable: async (params: McpConfigEnableRequest): Promise => + connection.sendRequest("mcp.config.enable", params), + disable: async (params: McpConfigDisableRequest): Promise => + connection.sendRequest("mcp.config.disable", params), }, discover: async (params: McpDiscoverRequest): Promise => connection.sendRequest("mcp.discover", params), @@ -1817,28 +2003,32 @@ export function createServerRpc(connection: MessageConnection) { /** Create typed session-scoped RPC methods. */ export function createSessionRpc(connection: MessageConnection, sessionId: string) { return { + auth: { + getStatus: async (): Promise => + connection.sendRequest("session.auth.getStatus", { sessionId }), + }, model: { getCurrent: async (): Promise => connection.sendRequest("session.model.getCurrent", { sessionId }), - switchTo: async (params: Omit): Promise => + switchTo: async (params: ModelSwitchToRequest): Promise => connection.sendRequest("session.model.switchTo", { sessionId, ...params }), }, mode: { get: async (): Promise => connection.sendRequest("session.mode.get", { sessionId }), - set: async (params: Omit): Promise => + set: async (params: ModeSetRequest): Promise => connection.sendRequest("session.mode.set", { sessionId, ...params }), }, name: { get: async (): Promise => connection.sendRequest("session.name.get", { sessionId }), - set: async (params: Omit): Promise => + set: async (params: NameSetRequest): Promise => connection.sendRequest("session.name.set", { sessionId, ...params }), }, plan: { read: async (): Promise => connection.sendRequest("session.plan.read", { sessionId }), - update: async (params: Omit): Promise => + update: async (params: PlanUpdateRequest): Promise => connection.sendRequest("session.plan.update", { sessionId, ...params }), delete: async (): Promise => connection.sendRequest("session.plan.delete", { sessionId }), @@ -1848,9 +2038,9 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.workspaces.getWorkspace", { sessionId }), listFiles: async (): Promise => connection.sendRequest("session.workspaces.listFiles", { sessionId }), - readFile: async (params: Omit): Promise => + readFile: async (params: WorkspacesReadFileRequest): Promise => connection.sendRequest("session.workspaces.readFile", { sessionId, ...params }), - createFile: async (params: Omit): Promise => + createFile: async (params: WorkspacesCreateFileRequest): Promise => connection.sendRequest("session.workspaces.createFile", { sessionId, ...params }), }, instructions: { @@ -1859,7 +2049,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin }, /** @experimental */ fleet: { - start: async (params: Omit): Promise => + start: async (params: FleetStartRequest): Promise => connection.sendRequest("session.fleet.start", { sessionId, ...params }), }, /** @experimental */ @@ -1868,7 +2058,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.agent.list", { sessionId }), getCurrent: async (): Promise => connection.sendRequest("session.agent.getCurrent", { sessionId }), - select: async (params: Omit): Promise => + select: async (params: AgentSelectRequest): Promise => connection.sendRequest("session.agent.select", { sessionId, ...params }), deselect: async (): Promise => connection.sendRequest("session.agent.deselect", { sessionId }), @@ -1879,9 +2069,9 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin skills: { list: async (): Promise => connection.sendRequest("session.skills.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: SkillsEnableRequest): Promise => connection.sendRequest("session.skills.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: SkillsDisableRequest): Promise => connection.sendRequest("session.skills.disable", { sessionId, ...params }), reload: async (): Promise => connection.sendRequest("session.skills.reload", { sessionId }), @@ -1890,12 +2080,17 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin mcp: { list: async (): Promise => connection.sendRequest("session.mcp.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: McpEnableRequest): Promise => connection.sendRequest("session.mcp.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: McpDisableRequest): Promise => connection.sendRequest("session.mcp.disable", { sessionId, ...params }), reload: async (): Promise => connection.sendRequest("session.mcp.reload", { sessionId }), + /** @experimental */ + oauth: { + login: async (params: McpOauthLoginRequest): Promise => + connection.sendRequest("session.mcp.oauth.login", { sessionId, ...params }), + }, }, /** @experimental */ plugins: { @@ -1906,44 +2101,48 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin extensions: { list: async (): Promise => connection.sendRequest("session.extensions.list", { sessionId }), - enable: async (params: Omit): Promise => + enable: async (params: ExtensionsEnableRequest): Promise => connection.sendRequest("session.extensions.enable", { sessionId, ...params }), - disable: async (params: Omit): Promise => + disable: async (params: ExtensionsDisableRequest): Promise => connection.sendRequest("session.extensions.disable", { sessionId, ...params }), reload: async (): Promise => connection.sendRequest("session.extensions.reload", { sessionId }), }, tools: { - handlePendingToolCall: async (params: Omit): Promise => + handlePendingToolCall: async (params: ToolsHandlePendingToolCallRequest): Promise => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params }), }, commands: { - handlePendingCommand: async (params: Omit): Promise => + handlePendingCommand: async (params: CommandsHandlePendingCommandRequest): Promise => connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params }), }, ui: { - elicitation: async (params: Omit): Promise => + elicitation: async (params: UIElicitationRequest): Promise => connection.sendRequest("session.ui.elicitation", { sessionId, ...params }), - handlePendingElicitation: async (params: Omit): Promise => + handlePendingElicitation: async (params: UIHandlePendingElicitationRequest): Promise => connection.sendRequest("session.ui.handlePendingElicitation", { sessionId, ...params }), }, permissions: { - handlePendingPermissionRequest: async (params: Omit): Promise => + handlePendingPermissionRequest: async (params: PermissionDecisionRequest): Promise => connection.sendRequest("session.permissions.handlePendingPermissionRequest", { sessionId, ...params }), + setApproveAll: async (params: PermissionsSetApproveAllRequest): Promise => + connection.sendRequest("session.permissions.setApproveAll", { sessionId, ...params }), + resetSessionApprovals: async (): Promise => + connection.sendRequest("session.permissions.resetSessionApprovals", { sessionId }), }, - log: async (params: Omit): Promise => + log: async (params: LogRequest): Promise => connection.sendRequest("session.log", { sessionId, ...params }), shell: { - exec: async (params: Omit): Promise => + exec: async (params: ShellExecRequest): Promise => connection.sendRequest("session.shell.exec", { sessionId, ...params }), - kill: async (params: Omit): Promise => + kill: async (params: ShellKillRequest): Promise => connection.sendRequest("session.shell.kill", { sessionId, ...params }), }, /** @experimental */ history: { compact: async (): Promise => connection.sendRequest("session.history.compact", { sessionId }), - truncate: async (params: Omit): Promise => + truncate: async (params: HistoryTruncateRequest): Promise => connection.sendRequest("session.history.truncate", { sessionId, ...params }), }, /** @experimental */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index b35ab7c59..ebaf68a01 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -67,6 +67,8 @@ export type SessionEvent = | CommandQueuedEvent | CommandExecuteEvent | CommandCompletedEvent + | AutoModeSwitchRequestedEvent + | AutoModeSwitchCompletedEvent | CommandsChangedEvent | CapabilitiesChangedEvent | ExitPlanModeRequestedEvent @@ -174,11 +176,38 @@ export type PermissionRequestMemoryAction = "store" | "vote"; * Vote direction (vote only) */ export type PermissionRequestMemoryDirection = "upvote" | "downvote"; +/** + * Derived user-facing permission prompt details for UI consumers + */ +export type PermissionPromptRequest = + | PermissionPromptRequestCommands + | PermissionPromptRequestWrite + | PermissionPromptRequestRead + | PermissionPromptRequestMcp + | PermissionPromptRequestUrl + | PermissionPromptRequestMemory + | PermissionPromptRequestCustomTool + | PermissionPromptRequestPath + | PermissionPromptRequestHook; +/** + * Whether this is a store or vote memory operation + */ +export type PermissionPromptRequestMemoryAction = "store" | "vote"; +/** + * Vote direction (vote only) + */ +export type PermissionPromptRequestMemoryDirection = "upvote" | "downvote"; +/** + * Underlying permission kind that needs path approval + */ +export type PermissionPromptRequestPathAccessKind = "read" | "shell" | "write"; /** * The outcome of the permission request */ export type PermissionCompletedKind = | "approved" + | "approved-for-session" + | "approved-for-location" | "denied-by-rules" | "denied-no-approval-rule-and-could-not-request-from-user" | "denied-interactively-by-user" @@ -1251,21 +1280,68 @@ export interface CompactionCompleteData { toolDefinitionsTokens?: number; } /** - * Token usage breakdown for the compaction LLM call + * Token usage breakdown for the compaction LLM call (aligned with assistant.usage format) */ export interface CompactionCompleteCompactionTokensUsed { /** * Cached input tokens reused in the compaction LLM call */ - cachedInput: number; + cacheReadTokens?: number; + /** + * Tokens written to prompt cache in the compaction LLM call + */ + cacheWriteTokens?: number; + copilotUsage?: CompactionCompleteCompactionTokensUsedCopilotUsage; + /** + * Duration of the compaction LLM call in milliseconds + */ + duration?: number; /** * Input tokens consumed by the compaction LLM call */ - input: number; + inputTokens?: number; + /** + * Model identifier used for the compaction LLM call + */ + model?: string; /** * Output tokens produced by the compaction LLM call */ - output: number; + outputTokens?: number; +} +/** + * Per-request cost and usage data from the CAPI copilot_usage response field + */ +export interface CompactionCompleteCompactionTokensUsedCopilotUsage { + /** + * Itemized token usage breakdown + */ + tokenDetails: CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[]; + /** + * Total cost in nano-AIU (AI Units) for this request + */ + totalNanoAiu: number; +} +/** + * Token usage detail for a single billing category + */ +export interface CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail { + /** + * Number of tokens in this billing batch + */ + batchSize: number; + /** + * Cost per batch of tokens + */ + costPerBatch: number; + /** + * Total token count for this entry + */ + tokenCount: number; + /** + * Token category (e.g., "input", "output") + */ + tokenType: string; } export interface TaskCompleteEvent { /** @@ -3082,6 +3158,7 @@ export interface PermissionRequestedEvent { */ export interface PermissionRequestedData { permissionRequest: PermissionRequest; + promptRequest?: PermissionPromptRequest; /** * Unique identifier for this permission request; used to respond via session.respondToPermission() */ @@ -3347,6 +3424,243 @@ export interface PermissionRequestHook { */ toolName: string; } +/** + * Shell command permission prompt + */ +export interface PermissionPromptRequestCommands { + /** + * Whether the UI can offer session-wide approval for this command pattern + */ + canOfferSessionApproval: boolean; + /** + * Command identifiers covered by this approval prompt + */ + commandIdentifiers: string[]; + /** + * The complete shell command text to be executed + */ + fullCommandText: string; + /** + * Human-readable description of what the command intends to do + */ + intention: string; + /** + * Prompt kind discriminator + */ + kind: "commands"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Optional warning message about risks of running this command + */ + warning?: string; +} +/** + * File write permission prompt + */ +export interface PermissionPromptRequestWrite { + /** + * Whether the UI can offer session-wide approval for file write operations + */ + canOfferSessionApproval: boolean; + /** + * Unified diff showing the proposed changes + */ + diff: string; + /** + * Path of the file being written to + */ + fileName: string; + /** + * Human-readable description of the intended file change + */ + intention: string; + /** + * Prompt kind discriminator + */ + kind: "write"; + /** + * Complete new file contents for newly created files + */ + newFileContents?: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * File read permission prompt + */ +export interface PermissionPromptRequestRead { + /** + * Human-readable description of why the file is being read + */ + intention: string; + /** + * Prompt kind discriminator + */ + kind: "read"; + /** + * Path of the file or directory being read + */ + path: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * MCP tool invocation permission prompt + */ +export interface PermissionPromptRequestMcp { + args?: unknown; + /** + * Prompt kind discriminator + */ + kind: "mcp"; + /** + * Name of the MCP server providing the tool + */ + serverName: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Internal name of the MCP tool + */ + toolName: string; + /** + * Human-readable title of the MCP tool + */ + toolTitle: string; +} +/** + * URL access permission prompt + */ +export interface PermissionPromptRequestUrl { + /** + * Human-readable description of why the URL is being accessed + */ + intention: string; + /** + * Prompt kind discriminator + */ + kind: "url"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * URL to be fetched + */ + url: string; +} +/** + * Memory operation permission prompt + */ +export interface PermissionPromptRequestMemory { + action?: PermissionPromptRequestMemoryAction; + /** + * Source references for the stored fact (store only) + */ + citations?: string; + direction?: PermissionPromptRequestMemoryDirection; + /** + * The fact being stored or voted on + */ + fact: string; + /** + * Prompt kind discriminator + */ + kind: "memory"; + /** + * Reason for the vote (vote only) + */ + reason?: string; + /** + * Topic or subject of the memory (store only) + */ + subject?: string; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * Custom tool invocation permission prompt + */ +export interface PermissionPromptRequestCustomTool { + /** + * Arguments to pass to the custom tool + */ + args?: { + [k: string]: unknown; + }; + /** + * Prompt kind discriminator + */ + kind: "custom-tool"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Description of what the custom tool does + */ + toolDescription: string; + /** + * Name of the custom tool + */ + toolName: string; +} +/** + * Path access permission prompt + */ +export interface PermissionPromptRequestPath { + accessKind: PermissionPromptRequestPathAccessKind; + /** + * Prompt kind discriminator + */ + kind: "path"; + /** + * File paths that require explicit approval + */ + paths: string[]; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; +} +/** + * Hook confirmation permission prompt + */ +export interface PermissionPromptRequestHook { + /** + * Optional message from the hook explaining why confirmation is needed + */ + hookMessage?: string; + /** + * Prompt kind discriminator + */ + kind: "hook"; + /** + * Arguments of the tool call being gated + */ + toolArgs?: { + [k: string]: unknown; + }; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Name of the tool the hook is gating + */ + toolName: string; +} export interface PermissionCompletedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. @@ -3377,6 +3691,10 @@ export interface PermissionCompletedData { */ requestId: string; result: PermissionCompletedResult; + /** + * Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts + */ + toolCallId?: string; } /** * The result of the permission request @@ -3916,6 +4234,74 @@ export interface CommandCompletedData { */ requestId: string; } +export interface AutoModeSwitchRequestedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AutoModeSwitchRequestedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "auto_mode_switch.requested"; +} +/** + * Auto mode switch request notification requiring user approval + */ +export interface AutoModeSwitchRequestedData { + /** + * The rate limit error code that triggered this request + */ + errorCode?: string; + /** + * Unique identifier for this request; used to respond via session.respondToAutoModeSwitch() + */ + requestId: string; +} +export interface AutoModeSwitchCompletedEvent { + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + data: AutoModeSwitchCompletedData; + ephemeral: true; + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + type: "auto_mode_switch.completed"; +} +/** + * Auto mode switch completion notification + */ +export interface AutoModeSwitchCompletedData { + /** + * Request ID of the resolved request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * The user's choice: 'yes', 'yes_always', or 'no' + */ + response: string; +} export interface CommandsChangedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. diff --git a/nodejs/src/session.ts b/nodejs/src/session.ts index eae4cab94..f2ea1de36 100644 --- a/nodejs/src/session.ts +++ b/nodejs/src/session.ts @@ -517,7 +517,7 @@ export class CopilotSession { await this.rpc.permissions.handlePendingPermissionRequest({ requestId, result: { - kind: "denied-no-approval-rule-and-could-not-request-from-user", + kind: "user-not-available", }, }); } catch (rpcError) { @@ -831,7 +831,7 @@ export class CopilotSession { */ async _handlePermissionRequestV2(request: unknown): Promise { if (!this.permissionHandler) { - return { kind: "denied-no-approval-rule-and-could-not-request-from-user" }; + return { kind: "user-not-available" }; } try { @@ -846,7 +846,7 @@ export class CopilotSession { if (error instanceof Error && error.message === NO_RESULT_PERMISSION_V2_ERROR) { throw error; } - return { kind: "denied-no-approval-rule-and-could-not-request-from-user" }; + return { kind: "user-not-available" }; } } diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index bb4e862b4..335571b50 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -126,13 +126,13 @@ export interface CopilotClientOptions { * When provided, the token is passed to the CLI server via environment variable. * This takes priority over other authentication methods. */ - githubToken?: string; + gitHubToken?: string; /** * Whether to use the logged-in user for authentication. * When true, the CLI server will attempt to use stored OAuth tokens or gh CLI auth. - * When false, only explicit tokens (githubToken or environment variables) are used. - * @default true (but defaults to false when githubToken is provided) + * When false, only explicit tokens (gitHubToken or environment variables) are used. + * @default true (but defaults to false when gitHubToken is provided) */ useLoggedInUser?: boolean; @@ -764,7 +764,7 @@ export type PermissionHandler = ( invocation: { sessionId: string } ) => Promise | PermissionRequestResult; -export const approveAll: PermissionHandler = () => ({ kind: "approved" }); +export const approveAll: PermissionHandler = () => ({ kind: "approve-once" }); export const defaultJoinSessionPermissionHandler: PermissionHandler = (): PermissionRequestResult => ({ @@ -1350,6 +1350,18 @@ export interface SessionConfig { */ infiniteSessions?: InfiniteSessionConfig; + /** + * GitHub token for per-session authentication. + * When provided, the runtime resolves this token into a full GitHub identity + * (login, Copilot plan, endpoints) and stores it on the session. This enables + * multitenancy — different sessions can have different GitHub identities. + * + * This is independent of the client-level `gitHubToken` in {@link CopilotClientOptions}, + * which authenticates the CLI process itself. The session-level token determines + * the identity used for content exclusion, model routing, and quota checks. + */ + gitHubToken?: string; + /** * Optional event handler that is registered on the session before the * session.create RPC is issued. This guarantees that early events emitted @@ -1399,6 +1411,7 @@ export type ResumeSessionConfig = Pick< | "skillDirectories" | "disabledSkills" | "infiniteSessions" + | "gitHubToken" | "onEvent" | "createSessionFsHandler" > & { diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 23824061c..39d6dfa76 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -530,16 +530,16 @@ describe("CopilotClient", () => { }); describe("Auth options", () => { - it("should accept githubToken option", () => { + it("should accept gitHubToken option", () => { const client = new CopilotClient({ - githubToken: "gho_test_token", + gitHubToken: "gho_test_token", logLevel: "error", }); - expect((client as any).options.githubToken).toBe("gho_test_token"); + expect((client as any).options.gitHubToken).toBe("gho_test_token"); }); - it("should default useLoggedInUser to true when no githubToken", () => { + it("should default useLoggedInUser to true when no gitHubToken", () => { const client = new CopilotClient({ logLevel: "error", }); @@ -547,18 +547,18 @@ describe("CopilotClient", () => { expect((client as any).options.useLoggedInUser).toBe(true); }); - it("should default useLoggedInUser to false when githubToken is provided", () => { + it("should default useLoggedInUser to false when gitHubToken is provided", () => { const client = new CopilotClient({ - githubToken: "gho_test_token", + gitHubToken: "gho_test_token", logLevel: "error", }); expect((client as any).options.useLoggedInUser).toBe(false); }); - it("should allow explicit useLoggedInUser: true with githubToken", () => { + it("should allow explicit useLoggedInUser: true with gitHubToken", () => { const client = new CopilotClient({ - githubToken: "gho_test_token", + gitHubToken: "gho_test_token", useLoggedInUser: true, logLevel: "error", }); @@ -566,7 +566,7 @@ describe("CopilotClient", () => { expect((client as any).options.useLoggedInUser).toBe(true); }); - it("should allow explicit useLoggedInUser: false without githubToken", () => { + it("should allow explicit useLoggedInUser: false without gitHubToken", () => { const client = new CopilotClient({ useLoggedInUser: false, logLevel: "error", @@ -575,14 +575,14 @@ describe("CopilotClient", () => { expect((client as any).options.useLoggedInUser).toBe(false); }); - it("should throw error when githubToken is used with cliUrl", () => { + it("should throw error when gitHubToken is used with cliUrl", () => { expect(() => { new CopilotClient({ cliUrl: "localhost:8080", - githubToken: "gho_test_token", + gitHubToken: "gho_test_token", logLevel: "error", }); - }).toThrow(/githubToken and useLoggedInUser cannot be used with cliUrl/); + }).toThrow(/gitHubToken and useLoggedInUser cannot be used with cliUrl/); }); it("should throw error when useLoggedInUser is used with cliUrl", () => { @@ -592,7 +592,7 @@ describe("CopilotClient", () => { useLoggedInUser: false, logLevel: "error", }); - }).toThrow(/githubToken and useLoggedInUser cannot be used with cliUrl/); + }).toThrow(/gitHubToken and useLoggedInUser cannot be used with cliUrl/); }); }); diff --git a/nodejs/test/e2e/harness/CapiProxy.ts b/nodejs/test/e2e/harness/CapiProxy.ts index f08ffc575..e0a270da1 100644 --- a/nodejs/test/e2e/harness/CapiProxy.ts +++ b/nodejs/test/e2e/harness/CapiProxy.ts @@ -1,7 +1,10 @@ import { spawn } from "child_process"; import { resolve } from "path"; import { expect } from "vitest"; -import { ParsedHttpExchange } from "../../../../test/harness/replayingCapiProxy"; +import { + CopilotUserResponse, + ParsedHttpExchange, +} from "../../../../test/harness/replayingCapiProxy"; const HARNESS_SERVER_PATH = resolve(__dirname, "../../../../test/harness/server.ts"); @@ -50,4 +53,18 @@ export class CapiProxy { const response = await fetch(url, { method: "POST" }); expect(response.ok).toBe(true); } + + /** + * Register a per-token response for the `/copilot_internal/user` endpoint. + * When a request with `Authorization: Bearer ` arrives at the proxy, + * the matching response is returned. + */ + async setCopilotUserByToken(token: string, response: CopilotUserResponse): Promise { + const res = await fetch(`${this.proxyUrl}/copilot-user-config`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ token, response }), + }); + expect(res.ok).toBe(true); + } } diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index c6d413936..d9680a9ba 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -51,7 +51,7 @@ export async function createSdkTestContext({ logLevel: logLevel || "error", cliPath: process.env.COPILOT_CLI_PATH, // Use fake token in CI to allow cached responses without real auth - githubToken: isCI ? "fake-token-for-e2e-tests" : undefined, + gitHubToken: isCI ? "fake-token-for-e2e-tests" : undefined, useStdio: useStdio, ...copilotClientOptions, }); diff --git a/nodejs/test/e2e/multi-client.test.ts b/nodejs/test/e2e/multi-client.test.ts index 369e84a43..f23ae4459 100644 --- a/nodejs/test/e2e/multi-client.test.ts +++ b/nodejs/test/e2e/multi-client.test.ts @@ -88,7 +88,7 @@ describe("Multi-client broadcast", async () => { const session1 = await client1.createSession({ onPermissionRequest: (request) => { client1PermissionRequests.push(request); - return { kind: "approved" as const }; + return { kind: "approve-once" as const }; }, }); @@ -142,7 +142,7 @@ describe("Multi-client broadcast", async () => { it("one client rejects permission and both see the result", async () => { // Client 1 creates a session and denies all permission requests const session1 = await client1.createSession({ - onPermissionRequest: () => ({ kind: "denied-interactively-by-user" as const }), + onPermissionRequest: () => ({ kind: "reject" as const }), }); // Client 2 resumes — its handler never resolves so only client 1's denial takes effect diff --git a/nodejs/test/e2e/per_session_auth.test.ts b/nodejs/test/e2e/per_session_auth.test.ts new file mode 100644 index 000000000..d795f89b2 --- /dev/null +++ b/nodejs/test/e2e/per_session_auth.test.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { describe, expect, it } from "vitest"; +import { approveAll } from "../../src/index.js"; +import { createSdkTestContext } from "./harness/sdkTestContext.js"; + +describe("Per-session GitHub auth", async () => { + const { copilotClient: client, openAiEndpoint, env } = await createSdkTestContext(); + + // Redirect GitHub API calls (e.g., fetchCopilotUser) to the proxy + // so per-session auth token resolution can be tested + env.COPILOT_DEBUG_GITHUB_API_URL = env.COPILOT_API_URL; + + // Configure per-token responses on the proxy. + // endpoints.api points back to the proxy so subsequent CAPI calls are also intercepted. + const proxyUrl = env.COPILOT_API_URL; + await openAiEndpoint.setCopilotUserByToken("token-alice", { + login: "alice", + copilot_plan: "individual_pro", + endpoints: { + api: proxyUrl, + telemetry: "https://localhost:1/telemetry", + }, + analytics_tracking_id: "alice-tracking-id", + }); + + await openAiEndpoint.setCopilotUserByToken("token-bob", { + login: "bob", + copilot_plan: "business", + endpoints: { + api: proxyUrl, + telemetry: "https://localhost:1/telemetry", + }, + analytics_tracking_id: "bob-tracking-id", + }); + + it("should create session with gitHubToken and check auth status", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + gitHubToken: "token-alice", + }); + + const authStatus = await session.rpc.auth.getStatus(); + expect(authStatus.isAuthenticated).toBe(true); + expect(authStatus.login).toBe("alice"); + expect(authStatus.copilotPlan).toBe("individual_pro"); + + await session.disconnect(); + }); + + it("should isolate auth between sessions with different tokens", async () => { + const sessionA = await client.createSession({ + onPermissionRequest: approveAll, + gitHubToken: "token-alice", + }); + const sessionB = await client.createSession({ + onPermissionRequest: approveAll, + gitHubToken: "token-bob", + }); + + const statusA = await sessionA.rpc.auth.getStatus(); + const statusB = await sessionB.rpc.auth.getStatus(); + + expect(statusA.isAuthenticated).toBe(true); + expect(statusA.login).toBe("alice"); + expect(statusA.copilotPlan).toBe("individual_pro"); + + expect(statusB.isAuthenticated).toBe(true); + expect(statusB.login).toBe("bob"); + expect(statusB.copilotPlan).toBe("business"); + + await sessionA.disconnect(); + await sessionB.disconnect(); + }); + + it("should return unauthenticated when no token is provided", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + }); + + const authStatus = await session.rpc.auth.getStatus(); + // Without a per-session GitHub token, there is no per-session identity. + // In CI the process-level fake token may still authenticate globally, + // so we check login rather than isAuthenticated. + expect(authStatus.login).toBeFalsy(); + + await session.disconnect(); + }); + + it("should error when creating session with invalid token", async () => { + await expect( + client.createSession({ + onPermissionRequest: approveAll, + gitHubToken: "invalid-token-12345", + }) + ).rejects.toThrow(); + }); +}); diff --git a/nodejs/test/e2e/permissions.test.ts b/nodejs/test/e2e/permissions.test.ts index 2203e34a8..c70861bdf 100644 --- a/nodejs/test/e2e/permissions.test.ts +++ b/nodejs/test/e2e/permissions.test.ts @@ -21,7 +21,7 @@ describe("Permission callbacks", async () => { expect(invocation.sessionId).toBe(session.sessionId); // Approve the permission - const result: PermissionRequestResult = { kind: "approved" }; + const result: PermissionRequestResult = { kind: "approve-once" }; return result; }, }); @@ -45,7 +45,7 @@ describe("Permission callbacks", async () => { it("should deny permission when handler returns denied", async () => { const session = await client.createSession({ onPermissionRequest: () => { - return { kind: "denied-interactively-by-user" }; + return { kind: "reject" }; }, }); @@ -69,7 +69,7 @@ describe("Permission callbacks", async () => { const session = await client.createSession({ onPermissionRequest: () => ({ - kind: "denied-no-approval-rule-and-could-not-request-from-user", + kind: "user-not-available", }), }); session.on((event) => { @@ -96,7 +96,7 @@ describe("Permission callbacks", async () => { const session2 = await client.resumeSession(sessionId, { onPermissionRequest: () => ({ - kind: "denied-no-approval-rule-and-could-not-request-from-user", + kind: "user-not-available", }), }); let permissionDenied = false; @@ -138,7 +138,7 @@ describe("Permission callbacks", async () => { // Simulate async permission check (e.g., user prompt) await new Promise((resolve) => setTimeout(resolve, 10)); - return { kind: "approved" }; + return { kind: "approve-once" }; }, }); @@ -163,7 +163,7 @@ describe("Permission callbacks", async () => { const session2 = await client.resumeSession(sessionId, { onPermissionRequest: (request) => { permissionRequests.push(request); - return { kind: "approved" }; + return { kind: "approve-once" }; }, }); @@ -204,7 +204,7 @@ describe("Permission callbacks", async () => { expect(typeof request.toolCallId).toBe("string"); expect(request.toolCallId.length).toBeGreaterThan(0); } - return { kind: "approved" }; + return { kind: "approve-once" }; }, }); diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 6153d4e4c..f5a181380 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -244,7 +244,7 @@ describe("Sessions", async () => { // Resume using a new client const newClient = new CopilotClient({ env, - githubToken: isCI ? "fake-token-for-e2e-tests" : undefined, + gitHubToken: isCI ? "fake-token-for-e2e-tests" : undefined, }); onTestFinished(() => newClient.forceStop()); diff --git a/nodejs/test/e2e/streaming_fidelity.test.ts b/nodejs/test/e2e/streaming_fidelity.test.ts index 11edee1ca..d91e6c5d8 100644 --- a/nodejs/test/e2e/streaming_fidelity.test.ts +++ b/nodejs/test/e2e/streaming_fidelity.test.ts @@ -83,7 +83,7 @@ describe("Streaming Fidelity", async () => { // Resume using a new client const newClient = new CopilotClient({ env, - githubToken: isCI ? "fake-token-for-e2e-tests" : undefined, + gitHubToken: isCI ? "fake-token-for-e2e-tests" : undefined, }); onTestFinished(() => newClient.forceStop()); const session2 = await newClient.resumeSession(session.sessionId, { diff --git a/nodejs/test/e2e/tools.test.ts b/nodejs/test/e2e/tools.test.ts index 83d733686..05b0932c8 100644 --- a/nodejs/test/e2e/tools.test.ts +++ b/nodejs/test/e2e/tools.test.ts @@ -144,7 +144,7 @@ describe("Custom tools", async () => { ], onPermissionRequest: (request) => { permissionRequests.push(request); - return { kind: "approved" }; + return { kind: "approve-once" }; }, }); @@ -223,7 +223,7 @@ describe("Custom tools", async () => { }), ], onPermissionRequest: () => { - return { kind: "denied-interactively-by-user" }; + return { kind: "reject" }; }, }); diff --git a/python/copilot/client.py b/python/copilot/client.py index cf89476ed..c800e2158 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -1220,6 +1220,7 @@ async def create_session( commands: list[CommandDefinition] | None = None, on_elicitation_request: ElicitationHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, + github_token: str | None = None, ) -> CopilotSession: """ Create a new conversation session with the Copilot CLI. @@ -1352,6 +1353,10 @@ async def create_session( if hooks and any(hooks.values()): payload["hooks"] = True + # Add GitHub token for per-session authentication + if github_token is not None: + payload["gitHubToken"] = github_token + # Add working directory if provided if working_directory: payload["workingDirectory"] = working_directory @@ -1507,6 +1512,7 @@ async def resume_session( commands: list[CommandDefinition] | None = None, on_elicitation_request: ElicitationHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, + github_token: str | None = None, ) -> CopilotSession: """ Resume an existing conversation session by its ID. @@ -1650,6 +1656,10 @@ async def resume_session( if hooks and any(hooks.values()): payload["hooks"] = True + # Add GitHub token for per-session authentication + if github_token is not None: + payload["gitHubToken"] = github_token + if working_directory: payload["workingDirectory"] = working_directory if config_dir: @@ -2715,27 +2725,18 @@ async def _handle_permission_request_v2(self, params: dict) -> dict: result = await session._handle_permission_request(perm_request) if result.kind == "no-result": raise ValueError(NO_RESULT_PERMISSION_V2_ERROR) - result_payload: dict = {"kind": result.kind} - if result.rules is not None: - result_payload["rules"] = result.rules - if result.feedback is not None: - result_payload["feedback"] = result.feedback - if result.message is not None: - result_payload["message"] = result.message - if result.path is not None: - result_payload["path"] = result.path - return {"result": result_payload} + return {"result": {"kind": result.kind}} except ValueError as exc: if str(exc) == NO_RESULT_PERMISSION_V2_ERROR: raise return { "result": { - "kind": "denied-no-approval-rule-and-could-not-request-from-user", + "kind": "user-not-available", } } except Exception: # pylint: disable=broad-except return { "result": { - "kind": "denied-no-approval-rule-and-could-not-request-from-user", + "kind": "user-not-available", } } diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 67c41fc96..00eaae928 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -21,18 +21,6 @@ EnumT = TypeVar("EnumT", bound=Enum) -def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x - -def from_bool(x: Any) -> bool: - assert isinstance(x, bool) - return x - -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) - def from_str(x: Any) -> str: assert isinstance(x, str) return x @@ -49,6 +37,18 @@ def from_union(fs, x): pass assert False +def from_int(x: Any) -> int: + assert isinstance(x, int) and not isinstance(x, bool) + return x + +def from_bool(x: Any) -> bool: + assert isinstance(x, bool) + return x + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + def to_float(x: Any) -> float: assert isinstance(x, (int, float)) return x @@ -72,6 +72,25 @@ def to_enum(c: type[EnumT], x: Any) -> EnumT: def from_datetime(x: Any) -> datetime: return dateutil.parser.parse(x) +@dataclass +class AccountGetQuotaRequest: + git_hub_token: str | None = None + """GitHub token for per-user quota lookup. When provided, resolves this token to determine + the user's quota instead of using the global auth. + """ + + @staticmethod + def from_dict(obj: Any) -> 'AccountGetQuotaRequest': + assert isinstance(obj, dict) + git_hub_token = from_union([from_str, from_none], obj.get("gitHubToken")) + return AccountGetQuotaRequest(git_hub_token) + + def to_dict(self) -> dict: + result: dict = {} + if self.git_hub_token is not None: + result["gitHubToken"] = from_union([from_str, from_none], self.git_hub_token) + return result + @dataclass class AccountQuotaSnapshot: entitlement_requests: int @@ -169,6 +188,17 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result +class AuthInfoType(Enum): + """Authentication type""" + + API_KEY = "api-key" + COPILOT_API_TOKEN = "copilot-api-token" + ENV = "env" + GH_CLI = "gh-cli" + HMAC = "hmac" + TOKEN = "token" + USER = "user" + @dataclass class CommandsHandlePendingCommandRequest: request_id: str @@ -475,6 +505,43 @@ class MCPServerConfigType(Enum): SSE = "sse" STDIO = "stdio" +@dataclass +class MCPConfigDisableRequest: + names: list[str] + """Names of MCP servers to disable. Each server is added to the persisted disabled list so + new sessions skip it. Already-disabled names are ignored. Active sessions keep their + current connections until they end. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigDisableRequest': + assert isinstance(obj, dict) + names = from_list(from_str, obj.get("names")) + return MCPConfigDisableRequest(names) + + def to_dict(self) -> dict: + result: dict = {} + result["names"] = from_list(from_str, self.names) + return result + +@dataclass +class MCPConfigEnableRequest: + names: list[str] + """Names of MCP servers to enable. Each server is removed from the persisted disabled list + so new sessions spawn it. Unknown or already-enabled names are ignored. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigEnableRequest': + assert isinstance(obj, dict) + names = from_list(from_str, obj.get("names")) + return MCPConfigEnableRequest(names) + + def to_dict(self) -> dict: + result: dict = {} + result["names"] = from_list(from_str, self.names) + return result + @dataclass class MCPConfigRemoveRequest: name: str @@ -540,6 +607,71 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) return result +@dataclass +class MCPOauthLoginRequest: + server_name: str + """Name of the remote MCP server to authenticate""" + + callback_success_message: str | None = None + """Optional override for the body text shown on the OAuth loopback callback success page. + When omitted, the runtime applies a neutral fallback; callers driving interactive auth + should pass surface-specific copy telling the user where to return. + """ + client_name: str | None = None + """Optional override for the OAuth client display name shown on the consent screen. Applies + to newly registered dynamic clients only — existing registrations keep the name they were + created with. When omitted, the runtime applies a neutral fallback; callers driving + interactive auth should pass their own surface-specific label so the consent screen + matches the product the user sees. + """ + force_reauth: bool | None = None + """When true, clears any cached OAuth token for the server and runs a full new + authorization. Use when the user explicitly wants to switch accounts or believes their + session is stuck. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthLoginRequest': + assert isinstance(obj, dict) + server_name = from_str(obj.get("serverName")) + callback_success_message = from_union([from_str, from_none], obj.get("callbackSuccessMessage")) + client_name = from_union([from_str, from_none], obj.get("clientName")) + force_reauth = from_union([from_bool, from_none], obj.get("forceReauth")) + return MCPOauthLoginRequest(server_name, callback_success_message, client_name, force_reauth) + + def to_dict(self) -> dict: + result: dict = {} + result["serverName"] = from_str(self.server_name) + if self.callback_success_message is not None: + result["callbackSuccessMessage"] = from_union([from_str, from_none], self.callback_success_message) + if self.client_name is not None: + result["clientName"] = from_union([from_str, from_none], self.client_name) + if self.force_reauth is not None: + result["forceReauth"] = from_union([from_bool, from_none], self.force_reauth) + return result + +@dataclass +class MCPOauthLoginResult: + authorization_url: str | None = None + """URL the caller should open in a browser to complete OAuth. Omitted when cached tokens + were still valid and no browser interaction was needed — the server is already + reconnected in that case. When present, the runtime starts the callback listener before + returning and continues the flow in the background; completion is signaled via + session.mcp_server_status_changed. + """ + + @staticmethod + def from_dict(obj: Any) -> 'MCPOauthLoginResult': + assert isinstance(obj, dict) + authorization_url = from_union([from_str, from_none], obj.get("authorizationUrl")) + return MCPOauthLoginResult(authorization_url) + + def to_dict(self) -> dict: + result: dict = {} + if self.authorization_url is not None: + result["authorizationUrl"] = from_union([from_str, from_none], self.authorization_url) + return result + class MCPServerStatus(Enum): """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" @@ -645,20 +777,21 @@ class ModelPolicy: state: str """Current policy state for this model""" - terms: str + terms: str | None = None """Usage terms or conditions for this model""" @staticmethod def from_dict(obj: Any) -> 'ModelPolicy': assert isinstance(obj, dict) state = from_str(obj.get("state")) - terms = from_str(obj.get("terms")) + terms = from_union([from_str, from_none], obj.get("terms")) return ModelPolicy(state, terms) def to_dict(self) -> dict: result: dict = {} result["state"] = from_str(self.state) - result["terms"] = from_str(self.terms) + if self.terms is not None: + result["terms"] = from_union([from_str, from_none], self.terms) return result @dataclass @@ -729,6 +862,25 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result +@dataclass +class ModelsListRequest: + git_hub_token: str | None = None + """GitHub token for per-user model listing. When provided, resolves this token to determine + the user's Copilot plan and available models instead of using the global auth. + """ + + @staticmethod + def from_dict(obj: Any) -> 'ModelsListRequest': + assert isinstance(obj, dict) + git_hub_token = from_union([from_str, from_none], obj.get("gitHubToken")) + return ModelsListRequest(git_hub_token) + + def to_dict(self) -> dict: + result: dict = {} + if self.git_hub_token is not None: + result["gitHubToken"] = from_union([from_str, from_none], self.git_hub_token) + return result + @dataclass class NameGetResult: name: str | None = None @@ -761,31 +913,57 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result +class ApprovalKind(Enum): + COMMANDS = "commands" + CUSTOM_TOOL = "custom-tool" + MCP = "mcp" + MCP_SAMPLING = "mcp-sampling" + MEMORY = "memory" + READ = "read" + WRITE = "write" + class PermissionDecisionKind(Enum): - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + APPROVE_FOR_LOCATION = "approve-for-location" + APPROVE_FOR_SESSION = "approve-for-session" + APPROVE_ONCE = "approve-once" + REJECT = "reject" + USER_NOT_AVAILABLE = "user-not-available" -class PermissionDecisionApprovedKind(Enum): - APPROVED = "approved" +class PermissionDecisionApproveForLocationKind(Enum): + APPROVE_FOR_LOCATION = "approve-for-location" -class PermissionDecisionDeniedByContentExclusionPolicyKind(Enum): - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" +class PermissionDecisionApproveForLocationApprovalCommandsKind(Enum): + COMMANDS = "commands" -class PermissionDecisionDeniedByPermissionRequestHookKind(Enum): - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" +class PermissionDecisionApproveForLocationApprovalCustomToolKind(Enum): + CUSTOM_TOOL = "custom-tool" -class PermissionDecisionDeniedByRulesKind(Enum): - DENIED_BY_RULES = "denied-by-rules" +class PermissionDecisionApproveForLocationApprovalMCPKind(Enum): + MCP = "mcp" -class PermissionDecisionDeniedInteractivelyByUserKind(Enum): - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" +class PermissionDecisionApproveForLocationApprovalMCPSamplingKind(Enum): + MCP_SAMPLING = "mcp-sampling" -class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(Enum): - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" +class PermissionDecisionApproveForLocationApprovalMemoryKind(Enum): + MEMORY = "memory" + +class PermissionDecisionApproveForLocationApprovalReadKind(Enum): + READ = "read" + +class PermissionDecisionApproveForLocationApprovalWriteKind(Enum): + WRITE = "write" + +class PermissionDecisionApproveForSessionKind(Enum): + APPROVE_FOR_SESSION = "approve-for-session" + +class PermissionDecisionApproveOnceKind(Enum): + APPROVE_ONCE = "approve-once" + +class PermissionDecisionRejectKind(Enum): + REJECT = "reject" + +class PermissionDecisionUserNotAvailableKind(Enum): + USER_NOT_AVAILABLE = "user-not-available" @dataclass class PermissionRequestResult: @@ -803,6 +981,65 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) return result +@dataclass +class PermissionsResetSessionApprovalsRequest: + @staticmethod + def from_dict(obj: Any) -> 'PermissionsResetSessionApprovalsRequest': + assert isinstance(obj, dict) + return PermissionsResetSessionApprovalsRequest() + + def to_dict(self) -> dict: + result: dict = {} + return result + +@dataclass +class PermissionsResetSessionApprovalsResult: + success: bool + """Whether the operation succeeded""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsResetSessionApprovalsResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return PermissionsResetSessionApprovalsResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +@dataclass +class PermissionsSetApproveAllRequest: + enabled: bool + """Whether to auto-approve all tool permission requests""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsSetApproveAllRequest': + assert isinstance(obj, dict) + enabled = from_bool(obj.get("enabled")) + return PermissionsSetApproveAllRequest(enabled) + + def to_dict(self) -> dict: + result: dict = {} + result["enabled"] = from_bool(self.enabled) + return result + +@dataclass +class PermissionsSetApproveAllResult: + success: bool + """Whether the operation succeeded""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionsSetApproveAllResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return PermissionsSetApproveAllResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + @dataclass class PingRequest: message: str | None = None @@ -1939,6 +2176,52 @@ def to_dict(self) -> dict: result["agent"] = to_class(AgentInfo, self.agent) return result +@dataclass +class SessionAuthStatus: + is_authenticated: bool + """Whether the session has resolved authentication""" + + auth_type: AuthInfoType | None = None + """Authentication type""" + + copilot_plan: str | None = None + """Copilot plan tier (e.g., individual_pro, business)""" + + host: str | None = None + """Authentication host URL""" + + login: str | None = None + """Authenticated login/username, if available""" + + status_message: str | None = None + """Human-readable authentication status description""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionAuthStatus': + assert isinstance(obj, dict) + is_authenticated = from_bool(obj.get("isAuthenticated")) + auth_type = from_union([AuthInfoType, from_none], obj.get("authType")) + copilot_plan = from_union([from_str, from_none], obj.get("copilotPlan")) + host = from_union([from_str, from_none], obj.get("host")) + login = from_union([from_str, from_none], obj.get("login")) + status_message = from_union([from_str, from_none], obj.get("statusMessage")) + return SessionAuthStatus(is_authenticated, auth_type, copilot_plan, host, login, status_message) + + def to_dict(self) -> dict: + result: dict = {} + result["isAuthenticated"] = from_bool(self.is_authenticated) + if self.auth_type is not None: + result["authType"] = from_union([lambda x: to_enum(AuthInfoType, x), from_none], self.auth_type) + if self.copilot_plan is not None: + result["copilotPlan"] = from_union([from_str, from_none], self.copilot_plan) + if self.host is not None: + result["host"] = from_union([from_str, from_none], self.host) + if self.login is not None: + result["login"] = from_union([from_str, from_none], self.login) + if self.status_message is not None: + result["statusMessage"] = from_union([from_str, from_none], self.status_message) + return result + @dataclass class DiscoveredMCPServer: enabled: bool @@ -2422,190 +2705,386 @@ def to_dict(self) -> dict: return result @dataclass -class PermissionDecision: - kind: PermissionDecisionKind - """The permission request was approved +class PermissionDecisionApproveForIonApproval: + """The approval to add as a session-scoped rule - Denied because approval rules explicitly blocked it + The approval to persist for this location + """ + kind: ApprovalKind + command_identifiers: list[str] | None = None + server_name: str | None = None + tool_name: str | None = None - Denied because no approval rule matched and user confirmation was unavailable + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForIonApproval': + assert isinstance(obj, dict) + kind = ApprovalKind(obj.get("kind")) + command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) + server_name = from_union([from_str, from_none], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return PermissionDecisionApproveForIonApproval(kind, command_identifiers, server_name, tool_name) - Denied by the user during an interactive prompt + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(ApprovalKind, self.kind) + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) + if self.server_name is not None: + result["serverName"] = from_union([from_str, from_none], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + return result - Denied by the organization's content exclusion policy +@dataclass +class PermissionDecisionApproveForLocationApproval: + """The approval to persist for this location""" - Denied by a permission request hook registered by an extension or plugin - """ - rules: list[Any] | None = None - """Rules that denied the request""" + kind: ApprovalKind + command_identifiers: list[str] | None = None + server_name: str | None = None + tool_name: str | None = None - feedback: str | None = None - """Optional feedback from the user explaining the denial""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApproval': + assert isinstance(obj, dict) + kind = ApprovalKind(obj.get("kind")) + command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) + server_name = from_union([from_str, from_none], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return PermissionDecisionApproveForLocationApproval(kind, command_identifiers, server_name, tool_name) - message: str | None = None - """Human-readable explanation of why the path was excluded + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(ApprovalKind, self.kind) + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) + if self.server_name is not None: + result["serverName"] = from_union([from_str, from_none], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + return result - Optional message from the hook explaining the denial - """ - path: str | None = None - """File path that triggered the exclusion""" +@dataclass +class PermissionDecisionApproveForSessionApproval: + """The approval to add as a session-scoped rule""" - interrupt: bool | None = None - """Whether to interrupt the current agent turn""" + kind: ApprovalKind + command_identifiers: list[str] | None = None + server_name: str | None = None + tool_name: str | None = None @staticmethod - def from_dict(obj: Any) -> 'PermissionDecision': + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApproval': assert isinstance(obj, dict) - kind = PermissionDecisionKind(obj.get("kind")) - rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) - feedback = from_union([from_str, from_none], obj.get("feedback")) - message = from_union([from_str, from_none], obj.get("message")) - path = from_union([from_str, from_none], obj.get("path")) - interrupt = from_union([from_bool, from_none], obj.get("interrupt")) - return PermissionDecision(kind, rules, feedback, message, path, interrupt) + kind = ApprovalKind(obj.get("kind")) + command_identifiers = from_union([lambda x: from_list(from_str, x), from_none], obj.get("commandIdentifiers")) + server_name = from_union([from_str, from_none], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return PermissionDecisionApproveForSessionApproval(kind, command_identifiers, server_name, tool_name) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionKind, self.kind) - if self.rules is not None: - result["rules"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.rules) - if self.feedback is not None: - result["feedback"] = from_union([from_str, from_none], self.feedback) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.interrupt is not None: - result["interrupt"] = from_union([from_bool, from_none], self.interrupt) + result["kind"] = to_enum(ApprovalKind, self.kind) + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([lambda x: from_list(from_str, x), from_none], self.command_identifiers) + if self.server_name is not None: + result["serverName"] = from_union([from_str, from_none], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) return result @dataclass -class PermissionDecisionApproved: - kind: PermissionDecisionApprovedKind - """The permission request was approved""" +class PermissionDecisionApproveForLocationApprovalCommands: + command_identifiers: list[str] + kind: PermissionDecisionApproveForLocationApprovalCommandsKind @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionApproved': + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalCommands': assert isinstance(obj, dict) - kind = PermissionDecisionApprovedKind(obj.get("kind")) - return PermissionDecisionApproved(kind) + command_identifiers = from_list(from_str, obj.get("commandIdentifiers")) + kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind")) + return PermissionDecisionApproveForLocationApprovalCommands(command_identifiers, kind) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionApprovedKind, self.kind) + result["commandIdentifiers"] = from_list(from_str, self.command_identifiers) + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind) return result @dataclass -class PermissionDecisionDeniedByContentExclusionPolicy: - kind: PermissionDecisionDeniedByContentExclusionPolicyKind - """Denied by the organization's content exclusion policy""" +class PermissionDecisionApproveForSessionApprovalCommands: + command_identifiers: list[str] + kind: PermissionDecisionApproveForLocationApprovalCommandsKind - message: str - """Human-readable explanation of why the path was excluded""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalCommands': + assert isinstance(obj, dict) + command_identifiers = from_list(from_str, obj.get("commandIdentifiers")) + kind = PermissionDecisionApproveForLocationApprovalCommandsKind(obj.get("kind")) + return PermissionDecisionApproveForSessionApprovalCommands(command_identifiers, kind) - path: str - """File path that triggered the exclusion""" + def to_dict(self) -> dict: + result: dict = {} + result["commandIdentifiers"] = from_list(from_str, self.command_identifiers) + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCommandsKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalCustomTool: + kind: PermissionDecisionApproveForLocationApprovalCustomToolKind + tool_name: str @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionDeniedByContentExclusionPolicy': + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalCustomTool': assert isinstance(obj, dict) - kind = PermissionDecisionDeniedByContentExclusionPolicyKind(obj.get("kind")) - message = from_str(obj.get("message")) - path = from_str(obj.get("path")) - return PermissionDecisionDeniedByContentExclusionPolicy(kind, message, path) + kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind")) + tool_name = from_str(obj.get("toolName")) + return PermissionDecisionApproveForLocationApprovalCustomTool(kind, tool_name) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionDeniedByContentExclusionPolicyKind, self.kind) - result["message"] = from_str(self.message) - result["path"] = from_str(self.path) + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind) + result["toolName"] = from_str(self.tool_name) return result @dataclass -class PermissionDecisionDeniedByPermissionRequestHook: - kind: PermissionDecisionDeniedByPermissionRequestHookKind - """Denied by a permission request hook registered by an extension or plugin""" +class PermissionDecisionApproveForSessionApprovalCustomTool: + kind: PermissionDecisionApproveForLocationApprovalCustomToolKind + tool_name: str - interrupt: bool | None = None - """Whether to interrupt the current agent turn""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalCustomTool': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalCustomToolKind(obj.get("kind")) + tool_name = from_str(obj.get("toolName")) + return PermissionDecisionApproveForSessionApprovalCustomTool(kind, tool_name) - message: str | None = None - """Optional message from the hook explaining the denial""" + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalCustomToolKind, self.kind) + result["toolName"] = from_str(self.tool_name) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalMCP: + kind: PermissionDecisionApproveForLocationApprovalMCPKind + server_name: str + tool_name: str | None = None @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionDeniedByPermissionRequestHook': + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMCP': assert isinstance(obj, dict) - kind = PermissionDecisionDeniedByPermissionRequestHookKind(obj.get("kind")) - interrupt = from_union([from_bool, from_none], obj.get("interrupt")) - message = from_union([from_str, from_none], obj.get("message")) - return PermissionDecisionDeniedByPermissionRequestHook(kind, interrupt, message) + kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind")) + server_name = from_str(obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return PermissionDecisionApproveForLocationApprovalMCP(kind, server_name, tool_name) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionDeniedByPermissionRequestHookKind, self.kind) - if self.interrupt is not None: - result["interrupt"] = from_union([from_bool, from_none], self.interrupt) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind) + result["serverName"] = from_str(self.server_name) + result["toolName"] = from_union([from_none, from_str], self.tool_name) return result @dataclass -class PermissionDecisionDeniedByRules: - kind: PermissionDecisionDeniedByRulesKind - """Denied because approval rules explicitly blocked it""" +class PermissionDecisionApproveForSessionApprovalMCP: + kind: PermissionDecisionApproveForLocationApprovalMCPKind + server_name: str + tool_name: str | None = None - rules: list[Any] - """Rules that denied the request""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMCP': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalMCPKind(obj.get("kind")) + server_name = from_str(obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return PermissionDecisionApproveForSessionApprovalMCP(kind, server_name, tool_name) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPKind, self.kind) + result["serverName"] = from_str(self.server_name) + result["toolName"] = from_union([from_none, from_str], self.tool_name) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalMCPSampling: + kind: PermissionDecisionApproveForLocationApprovalMCPSamplingKind + server_name: str @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionDeniedByRules': + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMCPSampling': assert isinstance(obj, dict) - kind = PermissionDecisionDeniedByRulesKind(obj.get("kind")) - rules = from_list(lambda x: x, obj.get("rules")) - return PermissionDecisionDeniedByRules(kind, rules) + kind = PermissionDecisionApproveForLocationApprovalMCPSamplingKind(obj.get("kind")) + server_name = from_str(obj.get("serverName")) + return PermissionDecisionApproveForLocationApprovalMCPSampling(kind, server_name) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionDeniedByRulesKind, self.kind) - result["rules"] = from_list(lambda x: x, self.rules) + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPSamplingKind, self.kind) + result["serverName"] = from_str(self.server_name) return result @dataclass -class PermissionDecisionDeniedInteractivelyByUser: - kind: PermissionDecisionDeniedInteractivelyByUserKind +class PermissionDecisionApproveForSessionApprovalMCPSampling: + kind: PermissionDecisionApproveForLocationApprovalMCPSamplingKind + server_name: str + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMCPSampling': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalMCPSamplingKind(obj.get("kind")) + server_name = from_str(obj.get("serverName")) + return PermissionDecisionApproveForSessionApprovalMCPSampling(kind, server_name) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMCPSamplingKind, self.kind) + result["serverName"] = from_str(self.server_name) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalMemory: + kind: PermissionDecisionApproveForLocationApprovalMemoryKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalMemory': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind")) + return PermissionDecisionApproveForLocationApprovalMemory(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForSessionApprovalMemory: + kind: PermissionDecisionApproveForLocationApprovalMemoryKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalMemory': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalMemoryKind(obj.get("kind")) + return PermissionDecisionApproveForSessionApprovalMemory(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalMemoryKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalRead: + kind: PermissionDecisionApproveForLocationApprovalReadKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalRead': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind")) + return PermissionDecisionApproveForLocationApprovalRead(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForSessionApprovalRead: + kind: PermissionDecisionApproveForLocationApprovalReadKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalRead': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalReadKind(obj.get("kind")) + return PermissionDecisionApproveForSessionApprovalRead(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalReadKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForLocationApprovalWrite: + kind: PermissionDecisionApproveForLocationApprovalWriteKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocationApprovalWrite': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind")) + return PermissionDecisionApproveForLocationApprovalWrite(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveForSessionApprovalWrite: + kind: PermissionDecisionApproveForLocationApprovalWriteKind + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSessionApprovalWrite': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveForLocationApprovalWriteKind(obj.get("kind")) + return PermissionDecisionApproveForSessionApprovalWrite(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveForLocationApprovalWriteKind, self.kind) + return result + +@dataclass +class PermissionDecisionApproveOnce: + kind: PermissionDecisionApproveOnceKind + """The permission request was approved for this one instance""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveOnce': + assert isinstance(obj, dict) + kind = PermissionDecisionApproveOnceKind(obj.get("kind")) + return PermissionDecisionApproveOnce(kind) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionApproveOnceKind, self.kind) + return result + +@dataclass +class PermissionDecisionReject: + kind: PermissionDecisionRejectKind """Denied by the user during an interactive prompt""" feedback: str | None = None """Optional feedback from the user explaining the denial""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionDeniedInteractivelyByUser': + def from_dict(obj: Any) -> 'PermissionDecisionReject': assert isinstance(obj, dict) - kind = PermissionDecisionDeniedInteractivelyByUserKind(obj.get("kind")) + kind = PermissionDecisionRejectKind(obj.get("kind")) feedback = from_union([from_str, from_none], obj.get("feedback")) - return PermissionDecisionDeniedInteractivelyByUser(kind, feedback) + return PermissionDecisionReject(kind, feedback) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionDeniedInteractivelyByUserKind, self.kind) + result["kind"] = to_enum(PermissionDecisionRejectKind, self.kind) if self.feedback is not None: result["feedback"] = from_union([from_str, from_none], self.feedback) return result @dataclass -class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser: - kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind - """Denied because no approval rule matched and user confirmation was unavailable""" +class PermissionDecisionUserNotAvailable: + kind: PermissionDecisionUserNotAvailableKind + """Denied because user confirmation was unavailable""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser': + def from_dict(obj: Any) -> 'PermissionDecisionUserNotAvailable': assert isinstance(obj, dict) - kind = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(obj.get("kind")) - return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser(kind) + kind = PermissionDecisionUserNotAvailableKind(obj.get("kind")) + return PermissionDecisionUserNotAvailable(kind) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, self.kind) + result["kind"] = to_enum(PermissionDecisionUserNotAvailableKind, self.kind) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -3290,23 +3769,94 @@ def to_dict(self) -> dict: return result @dataclass -class PermissionDecisionRequest: - request_id: str - """Request ID of the pending permission request""" +class PermissionDecision: + kind: PermissionDecisionKind + """The permission request was approved for this one instance - result: PermissionDecision + Approved and remembered for the rest of the session + + Approved and persisted for this project location + + Denied by the user during an interactive prompt + + Denied because user confirmation was unavailable + """ + approval: PermissionDecisionApproveForIonApproval | None = None + """The approval to add as a session-scoped rule + + The approval to persist for this location + """ + location_key: str | None = None + """The location key (git root or cwd) to persist the approval to""" + + feedback: str | None = None + """Optional feedback from the user explaining the denial""" @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionRequest': + def from_dict(obj: Any) -> 'PermissionDecision': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = PermissionDecision.from_dict(obj.get("result")) - return PermissionDecisionRequest(request_id, result) + kind = PermissionDecisionKind(obj.get("kind")) + approval = from_union([PermissionDecisionApproveForIonApproval.from_dict, from_none], obj.get("approval")) + location_key = from_union([from_str, from_none], obj.get("locationKey")) + feedback = from_union([from_str, from_none], obj.get("feedback")) + return PermissionDecision(kind, approval, location_key, feedback) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionDecision, self.result) + result["kind"] = to_enum(PermissionDecisionKind, self.kind) + if self.approval is not None: + result["approval"] = from_union([lambda x: to_class(PermissionDecisionApproveForIonApproval, x), from_none], self.approval) + if self.location_key is not None: + result["locationKey"] = from_union([from_str, from_none], self.location_key) + if self.feedback is not None: + result["feedback"] = from_union([from_str, from_none], self.feedback) + return result + +@dataclass +class PermissionDecisionApproveForLocation: + approval: PermissionDecisionApproveForLocationApproval + """The approval to persist for this location""" + + kind: PermissionDecisionApproveForLocationKind + """Approved and persisted for this project location""" + + location_key: str + """The location key (git root or cwd) to persist the approval to""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForLocation': + assert isinstance(obj, dict) + approval = PermissionDecisionApproveForLocationApproval.from_dict(obj.get("approval")) + kind = PermissionDecisionApproveForLocationKind(obj.get("kind")) + location_key = from_str(obj.get("locationKey")) + return PermissionDecisionApproveForLocation(approval, kind, location_key) + + def to_dict(self) -> dict: + result: dict = {} + result["approval"] = to_class(PermissionDecisionApproveForLocationApproval, self.approval) + result["kind"] = to_enum(PermissionDecisionApproveForLocationKind, self.kind) + result["locationKey"] = from_str(self.location_key) + return result + +@dataclass +class PermissionDecisionApproveForSession: + approval: PermissionDecisionApproveForSessionApproval + """The approval to add as a session-scoped rule""" + + kind: PermissionDecisionApproveForSessionKind + """Approved and remembered for the rest of the session""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApproveForSession': + assert isinstance(obj, dict) + approval = PermissionDecisionApproveForSessionApproval.from_dict(obj.get("approval")) + kind = PermissionDecisionApproveForSessionKind(obj.get("kind")) + return PermissionDecisionApproveForSession(approval, kind) + + def to_dict(self) -> dict: + result: dict = {} + result["approval"] = to_class(PermissionDecisionApproveForSessionApproval, self.approval) + result["kind"] = to_enum(PermissionDecisionApproveForSessionKind, self.kind) return result @dataclass @@ -3660,6 +4210,26 @@ def to_dict(self) -> dict: result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result +@dataclass +class PermissionDecisionRequest: + request_id: str + """Request ID of the pending permission request""" + + result: PermissionDecision + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = PermissionDecision.from_dict(obj.get("result")) + return PermissionDecisionRequest(request_id, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionDecision, self.result) + return result + @dataclass class UIElicitationSchema: """JSON Schema describing the form fields to present to the user""" @@ -3831,6 +4401,7 @@ def to_dict(self) -> dict: @dataclass class RPC: + account_get_quota_request: AccountGetQuotaRequest account_get_quota_result: AccountGetQuotaResult account_quota_snapshot: AccountQuotaSnapshot agent_get_current_result: AgentGetCurrentResult @@ -3839,6 +4410,7 @@ class RPC: agent_reload_result: AgentReloadResult agent_select_request: AgentSelectRequest agent_select_result: AgentSelectResult + auth_info_type: AuthInfoType commands_handle_pending_command_request: CommandsHandlePendingCommandRequest commands_handle_pending_command_result: CommandsHandlePendingCommandResult current_model: CurrentModel @@ -3868,6 +4440,8 @@ class RPC: log_request: LogRequest log_result: LogResult mcp_config_add_request: MCPConfigAddRequest + mcp_config_disable_request: MCPConfigDisableRequest + mcp_config_enable_request: MCPConfigEnableRequest mcp_config_list: MCPConfigList mcp_config_remove_request: MCPConfigRemoveRequest mcp_config_update_request: MCPConfigUpdateRequest @@ -3875,6 +4449,8 @@ class RPC: mcp_discover_request: MCPDiscoverRequest mcp_discover_result: MCPDiscoverResult mcp_enable_request: MCPEnableRequest + mcp_oauth_login_request: MCPOauthLoginRequest + mcp_oauth_login_result: MCPOauthLoginResult mcp_server: MCPServer mcp_server_config: MCPServerConfig mcp_server_config_http: MCPServerConfigHTTP @@ -3896,20 +4472,40 @@ class RPC: model_capabilities_supports: ModelCapabilitiesSupports model_list: ModelList model_policy: ModelPolicy + models_list_request: ModelsListRequest model_switch_to_request: ModelSwitchToRequest model_switch_to_result: ModelSwitchToResult mode_set_request: ModeSetRequest name_get_result: NameGetResult name_set_request: NameSetRequest permission_decision: PermissionDecision - permission_decision_approved: PermissionDecisionApproved - permission_decision_denied_by_content_exclusion_policy: PermissionDecisionDeniedByContentExclusionPolicy - permission_decision_denied_by_permission_request_hook: PermissionDecisionDeniedByPermissionRequestHook - permission_decision_denied_by_rules: PermissionDecisionDeniedByRules - permission_decision_denied_interactively_by_user: PermissionDecisionDeniedInteractivelyByUser - permission_decision_denied_no_approval_rule_and_could_not_request_from_user: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + permission_decision_approve_for_location: PermissionDecisionApproveForLocation + permission_decision_approve_for_location_approval: PermissionDecisionApproveForLocationApproval + permission_decision_approve_for_location_approval_commands: PermissionDecisionApproveForLocationApprovalCommands + permission_decision_approve_for_location_approval_custom_tool: PermissionDecisionApproveForLocationApprovalCustomTool + permission_decision_approve_for_location_approval_mcp: PermissionDecisionApproveForLocationApprovalMCP + permission_decision_approve_for_location_approval_mcp_sampling: PermissionDecisionApproveForLocationApprovalMCPSampling + permission_decision_approve_for_location_approval_memory: PermissionDecisionApproveForLocationApprovalMemory + permission_decision_approve_for_location_approval_read: PermissionDecisionApproveForLocationApprovalRead + permission_decision_approve_for_location_approval_write: PermissionDecisionApproveForLocationApprovalWrite + permission_decision_approve_for_session: PermissionDecisionApproveForSession + permission_decision_approve_for_session_approval: PermissionDecisionApproveForSessionApproval + permission_decision_approve_for_session_approval_commands: PermissionDecisionApproveForSessionApprovalCommands + permission_decision_approve_for_session_approval_custom_tool: PermissionDecisionApproveForSessionApprovalCustomTool + permission_decision_approve_for_session_approval_mcp: PermissionDecisionApproveForSessionApprovalMCP + permission_decision_approve_for_session_approval_mcp_sampling: PermissionDecisionApproveForSessionApprovalMCPSampling + permission_decision_approve_for_session_approval_memory: PermissionDecisionApproveForSessionApprovalMemory + permission_decision_approve_for_session_approval_read: PermissionDecisionApproveForSessionApprovalRead + permission_decision_approve_for_session_approval_write: PermissionDecisionApproveForSessionApprovalWrite + permission_decision_approve_once: PermissionDecisionApproveOnce + permission_decision_reject: PermissionDecisionReject permission_decision_request: PermissionDecisionRequest + permission_decision_user_not_available: PermissionDecisionUserNotAvailable permission_request_result: PermissionRequestResult + permissions_reset_session_approvals_request: PermissionsResetSessionApprovalsRequest + permissions_reset_session_approvals_result: PermissionsResetSessionApprovalsResult + permissions_set_approve_all_request: PermissionsSetApproveAllRequest + permissions_set_approve_all_result: PermissionsSetApproveAllResult ping_request: PingRequest ping_result: PingResult plan_read_result: PlanReadResult @@ -3918,6 +4514,7 @@ class RPC: plugin_list: PluginList server_skill: ServerSkill server_skill_list: ServerSkillList + session_auth_status: SessionAuthStatus session_fs_append_file_request: SessionFSAppendFileRequest session_fs_error: SessionFSError session_fs_error_code: SessionFSErrorCode @@ -3997,6 +4594,7 @@ class RPC: @staticmethod def from_dict(obj: Any) -> 'RPC': assert isinstance(obj, dict) + account_get_quota_request = AccountGetQuotaRequest.from_dict(obj.get("AccountGetQuotaRequest")) account_get_quota_result = AccountGetQuotaResult.from_dict(obj.get("AccountGetQuotaResult")) account_quota_snapshot = AccountQuotaSnapshot.from_dict(obj.get("AccountQuotaSnapshot")) agent_get_current_result = AgentGetCurrentResult.from_dict(obj.get("AgentGetCurrentResult")) @@ -4005,6 +4603,7 @@ def from_dict(obj: Any) -> 'RPC': agent_reload_result = AgentReloadResult.from_dict(obj.get("AgentReloadResult")) agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest")) agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult")) + auth_info_type = AuthInfoType(obj.get("AuthInfoType")) commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest")) commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult")) current_model = CurrentModel.from_dict(obj.get("CurrentModel")) @@ -4034,6 +4633,8 @@ def from_dict(obj: Any) -> 'RPC': log_request = LogRequest.from_dict(obj.get("LogRequest")) log_result = LogResult.from_dict(obj.get("LogResult")) mcp_config_add_request = MCPConfigAddRequest.from_dict(obj.get("McpConfigAddRequest")) + mcp_config_disable_request = MCPConfigDisableRequest.from_dict(obj.get("McpConfigDisableRequest")) + mcp_config_enable_request = MCPConfigEnableRequest.from_dict(obj.get("McpConfigEnableRequest")) mcp_config_list = MCPConfigList.from_dict(obj.get("McpConfigList")) mcp_config_remove_request = MCPConfigRemoveRequest.from_dict(obj.get("McpConfigRemoveRequest")) mcp_config_update_request = MCPConfigUpdateRequest.from_dict(obj.get("McpConfigUpdateRequest")) @@ -4041,6 +4642,8 @@ def from_dict(obj: Any) -> 'RPC': mcp_discover_request = MCPDiscoverRequest.from_dict(obj.get("McpDiscoverRequest")) mcp_discover_result = MCPDiscoverResult.from_dict(obj.get("McpDiscoverResult")) mcp_enable_request = MCPEnableRequest.from_dict(obj.get("McpEnableRequest")) + mcp_oauth_login_request = MCPOauthLoginRequest.from_dict(obj.get("McpOauthLoginRequest")) + mcp_oauth_login_result = MCPOauthLoginResult.from_dict(obj.get("McpOauthLoginResult")) mcp_server = MCPServer.from_dict(obj.get("McpServer")) mcp_server_config = MCPServerConfig.from_dict(obj.get("McpServerConfig")) mcp_server_config_http = MCPServerConfigHTTP.from_dict(obj.get("McpServerConfigHttp")) @@ -4062,20 +4665,40 @@ def from_dict(obj: Any) -> 'RPC': model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) model_list = ModelList.from_dict(obj.get("ModelList")) model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) + models_list_request = ModelsListRequest.from_dict(obj.get("ModelsListRequest")) model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) mode_set_request = ModeSetRequest.from_dict(obj.get("ModeSetRequest")) name_get_result = NameGetResult.from_dict(obj.get("NameGetResult")) name_set_request = NameSetRequest.from_dict(obj.get("NameSetRequest")) permission_decision = PermissionDecision.from_dict(obj.get("PermissionDecision")) - permission_decision_approved = PermissionDecisionApproved.from_dict(obj.get("PermissionDecisionApproved")) - permission_decision_denied_by_content_exclusion_policy = PermissionDecisionDeniedByContentExclusionPolicy.from_dict(obj.get("PermissionDecisionDeniedByContentExclusionPolicy")) - permission_decision_denied_by_permission_request_hook = PermissionDecisionDeniedByPermissionRequestHook.from_dict(obj.get("PermissionDecisionDeniedByPermissionRequestHook")) - permission_decision_denied_by_rules = PermissionDecisionDeniedByRules.from_dict(obj.get("PermissionDecisionDeniedByRules")) - permission_decision_denied_interactively_by_user = PermissionDecisionDeniedInteractivelyByUser.from_dict(obj.get("PermissionDecisionDeniedInteractivelyByUser")) - permission_decision_denied_no_approval_rule_and_could_not_request_from_user = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj.get("PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser")) + permission_decision_approve_for_location = PermissionDecisionApproveForLocation.from_dict(obj.get("PermissionDecisionApproveForLocation")) + permission_decision_approve_for_location_approval = PermissionDecisionApproveForLocationApproval.from_dict(obj.get("PermissionDecisionApproveForLocationApproval")) + permission_decision_approve_for_location_approval_commands = PermissionDecisionApproveForLocationApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCommands")) + permission_decision_approve_for_location_approval_custom_tool = PermissionDecisionApproveForLocationApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalCustomTool")) + permission_decision_approve_for_location_approval_mcp = PermissionDecisionApproveForLocationApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcp")) + permission_decision_approve_for_location_approval_mcp_sampling = PermissionDecisionApproveForLocationApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMcpSampling")) + permission_decision_approve_for_location_approval_memory = PermissionDecisionApproveForLocationApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalMemory")) + permission_decision_approve_for_location_approval_read = PermissionDecisionApproveForLocationApprovalRead.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalRead")) + permission_decision_approve_for_location_approval_write = PermissionDecisionApproveForLocationApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForLocationApprovalWrite")) + permission_decision_approve_for_session = PermissionDecisionApproveForSession.from_dict(obj.get("PermissionDecisionApproveForSession")) + permission_decision_approve_for_session_approval = PermissionDecisionApproveForSessionApproval.from_dict(obj.get("PermissionDecisionApproveForSessionApproval")) + permission_decision_approve_for_session_approval_commands = PermissionDecisionApproveForSessionApprovalCommands.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCommands")) + permission_decision_approve_for_session_approval_custom_tool = PermissionDecisionApproveForSessionApprovalCustomTool.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalCustomTool")) + permission_decision_approve_for_session_approval_mcp = PermissionDecisionApproveForSessionApprovalMCP.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcp")) + permission_decision_approve_for_session_approval_mcp_sampling = PermissionDecisionApproveForSessionApprovalMCPSampling.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMcpSampling")) + permission_decision_approve_for_session_approval_memory = PermissionDecisionApproveForSessionApprovalMemory.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalMemory")) + permission_decision_approve_for_session_approval_read = PermissionDecisionApproveForSessionApprovalRead.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalRead")) + permission_decision_approve_for_session_approval_write = PermissionDecisionApproveForSessionApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalWrite")) + permission_decision_approve_once = PermissionDecisionApproveOnce.from_dict(obj.get("PermissionDecisionApproveOnce")) + permission_decision_reject = PermissionDecisionReject.from_dict(obj.get("PermissionDecisionReject")) permission_decision_request = PermissionDecisionRequest.from_dict(obj.get("PermissionDecisionRequest")) + permission_decision_user_not_available = PermissionDecisionUserNotAvailable.from_dict(obj.get("PermissionDecisionUserNotAvailable")) permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) + permissions_reset_session_approvals_request = PermissionsResetSessionApprovalsRequest.from_dict(obj.get("PermissionsResetSessionApprovalsRequest")) + permissions_reset_session_approvals_result = PermissionsResetSessionApprovalsResult.from_dict(obj.get("PermissionsResetSessionApprovalsResult")) + permissions_set_approve_all_request = PermissionsSetApproveAllRequest.from_dict(obj.get("PermissionsSetApproveAllRequest")) + permissions_set_approve_all_result = PermissionsSetApproveAllResult.from_dict(obj.get("PermissionsSetApproveAllResult")) ping_request = PingRequest.from_dict(obj.get("PingRequest")) ping_result = PingResult.from_dict(obj.get("PingResult")) plan_read_result = PlanReadResult.from_dict(obj.get("PlanReadResult")) @@ -4084,6 +4707,7 @@ def from_dict(obj: Any) -> 'RPC': plugin_list = PluginList.from_dict(obj.get("PluginList")) server_skill = ServerSkill.from_dict(obj.get("ServerSkill")) server_skill_list = ServerSkillList.from_dict(obj.get("ServerSkillList")) + session_auth_status = SessionAuthStatus.from_dict(obj.get("SessionAuthStatus")) session_fs_append_file_request = SessionFSAppendFileRequest.from_dict(obj.get("SessionFsAppendFileRequest")) session_fs_error = SessionFSError.from_dict(obj.get("SessionFsError")) session_fs_error_code = SessionFSErrorCode(obj.get("SessionFsErrorCode")) @@ -4159,10 +4783,11 @@ 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_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approved, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_request, permission_request_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_fs_append_file_request, session_fs_error, session_fs_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, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) + return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) def to_dict(self) -> dict: result: dict = {} + result["AccountGetQuotaRequest"] = to_class(AccountGetQuotaRequest, self.account_get_quota_request) result["AccountGetQuotaResult"] = to_class(AccountGetQuotaResult, self.account_get_quota_result) result["AccountQuotaSnapshot"] = to_class(AccountQuotaSnapshot, self.account_quota_snapshot) result["AgentGetCurrentResult"] = to_class(AgentGetCurrentResult, self.agent_get_current_result) @@ -4171,6 +4796,7 @@ def to_dict(self) -> dict: result["AgentReloadResult"] = to_class(AgentReloadResult, self.agent_reload_result) result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request) result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result) + result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type) result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request) result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result) result["CurrentModel"] = to_class(CurrentModel, self.current_model) @@ -4200,6 +4826,8 @@ def to_dict(self) -> dict: result["LogRequest"] = to_class(LogRequest, self.log_request) result["LogResult"] = to_class(LogResult, self.log_result) result["McpConfigAddRequest"] = to_class(MCPConfigAddRequest, self.mcp_config_add_request) + result["McpConfigDisableRequest"] = to_class(MCPConfigDisableRequest, self.mcp_config_disable_request) + result["McpConfigEnableRequest"] = to_class(MCPConfigEnableRequest, self.mcp_config_enable_request) result["McpConfigList"] = to_class(MCPConfigList, self.mcp_config_list) result["McpConfigRemoveRequest"] = to_class(MCPConfigRemoveRequest, self.mcp_config_remove_request) result["McpConfigUpdateRequest"] = to_class(MCPConfigUpdateRequest, self.mcp_config_update_request) @@ -4207,6 +4835,8 @@ def to_dict(self) -> dict: result["McpDiscoverRequest"] = to_class(MCPDiscoverRequest, self.mcp_discover_request) result["McpDiscoverResult"] = to_class(MCPDiscoverResult, self.mcp_discover_result) result["McpEnableRequest"] = to_class(MCPEnableRequest, self.mcp_enable_request) + result["McpOauthLoginRequest"] = to_class(MCPOauthLoginRequest, self.mcp_oauth_login_request) + result["McpOauthLoginResult"] = to_class(MCPOauthLoginResult, self.mcp_oauth_login_result) result["McpServer"] = to_class(MCPServer, self.mcp_server) result["McpServerConfig"] = to_class(MCPServerConfig, self.mcp_server_config) result["McpServerConfigHttp"] = to_class(MCPServerConfigHTTP, self.mcp_server_config_http) @@ -4228,20 +4858,40 @@ def to_dict(self) -> dict: result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) result["ModelList"] = to_class(ModelList, self.model_list) result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) + result["ModelsListRequest"] = to_class(ModelsListRequest, self.models_list_request) result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) result["ModeSetRequest"] = to_class(ModeSetRequest, self.mode_set_request) result["NameGetResult"] = to_class(NameGetResult, self.name_get_result) result["NameSetRequest"] = to_class(NameSetRequest, self.name_set_request) result["PermissionDecision"] = to_class(PermissionDecision, self.permission_decision) - result["PermissionDecisionApproved"] = to_class(PermissionDecisionApproved, self.permission_decision_approved) - result["PermissionDecisionDeniedByContentExclusionPolicy"] = to_class(PermissionDecisionDeniedByContentExclusionPolicy, self.permission_decision_denied_by_content_exclusion_policy) - result["PermissionDecisionDeniedByPermissionRequestHook"] = to_class(PermissionDecisionDeniedByPermissionRequestHook, self.permission_decision_denied_by_permission_request_hook) - result["PermissionDecisionDeniedByRules"] = to_class(PermissionDecisionDeniedByRules, self.permission_decision_denied_by_rules) - result["PermissionDecisionDeniedInteractivelyByUser"] = to_class(PermissionDecisionDeniedInteractivelyByUser, self.permission_decision_denied_interactively_by_user) - result["PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"] = to_class(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, self.permission_decision_denied_no_approval_rule_and_could_not_request_from_user) + result["PermissionDecisionApproveForLocation"] = to_class(PermissionDecisionApproveForLocation, self.permission_decision_approve_for_location) + result["PermissionDecisionApproveForLocationApproval"] = to_class(PermissionDecisionApproveForLocationApproval, self.permission_decision_approve_for_location_approval) + result["PermissionDecisionApproveForLocationApprovalCommands"] = to_class(PermissionDecisionApproveForLocationApprovalCommands, self.permission_decision_approve_for_location_approval_commands) + result["PermissionDecisionApproveForLocationApprovalCustomTool"] = to_class(PermissionDecisionApproveForLocationApprovalCustomTool, self.permission_decision_approve_for_location_approval_custom_tool) + result["PermissionDecisionApproveForLocationApprovalMcp"] = to_class(PermissionDecisionApproveForLocationApprovalMCP, self.permission_decision_approve_for_location_approval_mcp) + result["PermissionDecisionApproveForLocationApprovalMcpSampling"] = to_class(PermissionDecisionApproveForLocationApprovalMCPSampling, self.permission_decision_approve_for_location_approval_mcp_sampling) + result["PermissionDecisionApproveForLocationApprovalMemory"] = to_class(PermissionDecisionApproveForLocationApprovalMemory, self.permission_decision_approve_for_location_approval_memory) + result["PermissionDecisionApproveForLocationApprovalRead"] = to_class(PermissionDecisionApproveForLocationApprovalRead, self.permission_decision_approve_for_location_approval_read) + result["PermissionDecisionApproveForLocationApprovalWrite"] = to_class(PermissionDecisionApproveForLocationApprovalWrite, self.permission_decision_approve_for_location_approval_write) + result["PermissionDecisionApproveForSession"] = to_class(PermissionDecisionApproveForSession, self.permission_decision_approve_for_session) + result["PermissionDecisionApproveForSessionApproval"] = to_class(PermissionDecisionApproveForSessionApproval, self.permission_decision_approve_for_session_approval) + result["PermissionDecisionApproveForSessionApprovalCommands"] = to_class(PermissionDecisionApproveForSessionApprovalCommands, self.permission_decision_approve_for_session_approval_commands) + result["PermissionDecisionApproveForSessionApprovalCustomTool"] = to_class(PermissionDecisionApproveForSessionApprovalCustomTool, self.permission_decision_approve_for_session_approval_custom_tool) + result["PermissionDecisionApproveForSessionApprovalMcp"] = to_class(PermissionDecisionApproveForSessionApprovalMCP, self.permission_decision_approve_for_session_approval_mcp) + result["PermissionDecisionApproveForSessionApprovalMcpSampling"] = to_class(PermissionDecisionApproveForSessionApprovalMCPSampling, self.permission_decision_approve_for_session_approval_mcp_sampling) + result["PermissionDecisionApproveForSessionApprovalMemory"] = to_class(PermissionDecisionApproveForSessionApprovalMemory, self.permission_decision_approve_for_session_approval_memory) + result["PermissionDecisionApproveForSessionApprovalRead"] = to_class(PermissionDecisionApproveForSessionApprovalRead, self.permission_decision_approve_for_session_approval_read) + result["PermissionDecisionApproveForSessionApprovalWrite"] = to_class(PermissionDecisionApproveForSessionApprovalWrite, self.permission_decision_approve_for_session_approval_write) + result["PermissionDecisionApproveOnce"] = to_class(PermissionDecisionApproveOnce, self.permission_decision_approve_once) + result["PermissionDecisionReject"] = to_class(PermissionDecisionReject, self.permission_decision_reject) result["PermissionDecisionRequest"] = to_class(PermissionDecisionRequest, self.permission_decision_request) + result["PermissionDecisionUserNotAvailable"] = to_class(PermissionDecisionUserNotAvailable, self.permission_decision_user_not_available) result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) + result["PermissionsResetSessionApprovalsRequest"] = to_class(PermissionsResetSessionApprovalsRequest, self.permissions_reset_session_approvals_request) + result["PermissionsResetSessionApprovalsResult"] = to_class(PermissionsResetSessionApprovalsResult, self.permissions_reset_session_approvals_result) + result["PermissionsSetApproveAllRequest"] = to_class(PermissionsSetApproveAllRequest, self.permissions_set_approve_all_request) + result["PermissionsSetApproveAllResult"] = to_class(PermissionsSetApproveAllResult, self.permissions_set_approve_all_result) result["PingRequest"] = to_class(PingRequest, self.ping_request) result["PingResult"] = to_class(PingResult, self.ping_result) result["PlanReadResult"] = to_class(PlanReadResult, self.plan_read_result) @@ -4250,6 +4900,7 @@ def to_dict(self) -> dict: result["PluginList"] = to_class(PluginList, self.plugin_list) result["ServerSkill"] = to_class(ServerSkill, self.server_skill) result["ServerSkillList"] = to_class(ServerSkillList, self.server_skill_list) + result["SessionAuthStatus"] = to_class(SessionAuthStatus, self.session_auth_status) result["SessionFsAppendFileRequest"] = to_class(SessionFSAppendFileRequest, self.session_fs_append_file_request) result["SessionFsError"] = to_class(SessionFSError, self.session_fs_error) result["SessionFsErrorCode"] = to_enum(SessionFSErrorCode, self.session_fs_error_code) @@ -4366,8 +5017,9 @@ class ServerModelsApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def list(self, *, timeout: float | None = None) -> ModelList: - return ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list", {}, **_timeout_kwargs(timeout)))) + async def list(self, params: ModelsListRequest | None = None, *, timeout: float | None = None) -> ModelList: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} + return ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list", params_dict, **_timeout_kwargs(timeout)))) class ServerToolsApi: @@ -4383,8 +5035,9 @@ class ServerAccountApi: def __init__(self, client: "JsonRpcClient"): self._client = client - async def get_quota(self, *, timeout: float | None = None) -> AccountGetQuotaResult: - return AccountGetQuotaResult.from_dict(await self._client.request("account.getQuota", {}, **_timeout_kwargs(timeout))) + async def get_quota(self, params: AccountGetQuotaRequest | None = None, *, timeout: float | None = None) -> AccountGetQuotaResult: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {} + return AccountGetQuotaResult.from_dict(await self._client.request("account.getQuota", params_dict, **_timeout_kwargs(timeout))) class ServerMcpConfigApi: @@ -4406,6 +5059,14 @@ async def remove(self, params: MCPConfigRemoveRequest, *, timeout: float | None params_dict = {k: v for k, v in params.to_dict().items() if v is not None} await self._client.request("mcp.config.remove", params_dict, **_timeout_kwargs(timeout)) + async def enable(self, params: MCPConfigEnableRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("mcp.config.enable", params_dict, **_timeout_kwargs(timeout)) + + async def disable(self, params: MCPConfigDisableRequest, *, timeout: float | None = None) -> None: + params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + await self._client.request("mcp.config.disable", params_dict, **_timeout_kwargs(timeout)) + class ServerMcpApi: def __init__(self, client: "JsonRpcClient"): @@ -4472,6 +5133,15 @@ async def ping(self, params: PingRequest, *, timeout: float | None = None) -> Pi return PingResult.from_dict(await self._client.request("ping", params_dict, **_timeout_kwargs(timeout))) +class AuthApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def get_status(self, *, timeout: float | None = None) -> SessionAuthStatus: + return SessionAuthStatus.from_dict(await self._client.request("session.auth.getStatus", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + + class ModelApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client @@ -4481,7 +5151,7 @@ async def get_current(self, *, timeout: float | None = None) -> CurrentModel: return CurrentModel.from_dict(await self._client.request("session.model.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def switch_to(self, params: ModelSwitchToRequest, *, timeout: float | None = None) -> ModelSwitchToResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 ModelSwitchToResult.from_dict(await self._client.request("session.model.switchTo", params_dict, **_timeout_kwargs(timeout))) @@ -4495,7 +5165,7 @@ 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 set(self, params: ModeSetRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.mode.set", params_dict, **_timeout_kwargs(timeout)) @@ -4509,7 +5179,7 @@ async def get(self, *, timeout: float | None = None) -> NameGetResult: return NameGetResult.from_dict(await self._client.request("session.name.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def set(self, params: NameSetRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.name.set", params_dict, **_timeout_kwargs(timeout)) @@ -4523,7 +5193,7 @@ async def read(self, *, timeout: float | None = None) -> PlanReadResult: return PlanReadResult.from_dict(await self._client.request("session.plan.read", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def update(self, params: PlanUpdateRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.plan.update", params_dict, **_timeout_kwargs(timeout)) @@ -4543,12 +5213,12 @@ async def list_files(self, *, timeout: float | None = None) -> WorkspacesListFil return WorkspacesListFilesResult.from_dict(await self._client.request("session.workspaces.listFiles", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def read_file(self, params: WorkspacesReadFileRequest, *, timeout: float | None = None) -> WorkspacesReadFileResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 WorkspacesReadFileResult.from_dict(await self._client.request("session.workspaces.readFile", params_dict, **_timeout_kwargs(timeout))) async def create_file(self, params: WorkspacesCreateFileRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.workspaces.createFile", params_dict, **_timeout_kwargs(timeout)) @@ -4569,7 +5239,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def start(self, params: FleetStartRequest, *, timeout: float | None = None) -> FleetStartResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 FleetStartResult.from_dict(await self._client.request("session.fleet.start", params_dict, **_timeout_kwargs(timeout))) @@ -4587,7 +5257,7 @@ async def get_current(self, *, timeout: float | None = None) -> AgentGetCurrentR return AgentGetCurrentResult.from_dict(await self._client.request("session.agent.getCurrent", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def select(self, params: AgentSelectRequest, *, timeout: float | None = None) -> AgentSelectResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 AgentSelectResult.from_dict(await self._client.request("session.agent.select", params_dict, **_timeout_kwargs(timeout))) @@ -4608,12 +5278,12 @@ async def list(self, *, timeout: float | None = None) -> SkillList: return SkillList.from_dict(await self._client.request("session.skills.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def enable(self, params: SkillsEnableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.skills.enable", params_dict, **_timeout_kwargs(timeout)) async def disable(self, params: SkillsDisableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.skills.disable", params_dict, **_timeout_kwargs(timeout)) @@ -4621,22 +5291,35 @@ async def reload(self, *, timeout: float | None = None) -> None: 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. +class McpOauthApi: + def __init__(self, client: "JsonRpcClient", session_id: str): + self._client = client + self._session_id = session_id + + async def login(self, params: MCPOauthLoginRequest, *, timeout: float | None = None) -> MCPOauthLoginResult: + 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 MCPOauthLoginResult.from_dict(await self._client.request("session.mcp.oauth.login", params_dict, **_timeout_kwargs(timeout))) + + # Experimental: this API group is experimental and may change or be removed. class McpApi: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + self.oauth = McpOauthApi(client, session_id) async def list(self, *, timeout: float | None = None) -> MCPServerList: return MCPServerList.from_dict(await self._client.request("session.mcp.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def enable(self, params: MCPEnableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.mcp.enable", params_dict, **_timeout_kwargs(timeout)) async def disable(self, params: MCPDisableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.mcp.disable", params_dict, **_timeout_kwargs(timeout)) @@ -4664,12 +5347,12 @@ async def list(self, *, timeout: float | None = None) -> ExtensionList: return ExtensionList.from_dict(await self._client.request("session.extensions.list", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def enable(self, params: ExtensionsEnableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.extensions.enable", params_dict, **_timeout_kwargs(timeout)) async def disable(self, params: ExtensionsDisableRequest, *, timeout: float | None = None) -> None: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 await self._client.request("session.extensions.disable", params_dict, **_timeout_kwargs(timeout)) @@ -4683,7 +5366,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def handle_pending_tool_call(self, params: ToolsHandlePendingToolCallRequest, *, timeout: float | None = None) -> HandleToolCallResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id return HandleToolCallResult.from_dict(await self._client.request("session.tools.handlePendingToolCall", params_dict, **_timeout_kwargs(timeout))) @@ -4694,7 +5377,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def handle_pending_command(self, params: CommandsHandlePendingCommandRequest, *, timeout: float | None = None) -> CommandsHandlePendingCommandResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 CommandsHandlePendingCommandResult.from_dict(await self._client.request("session.commands.handlePendingCommand", params_dict, **_timeout_kwargs(timeout))) @@ -4705,12 +5388,12 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def elicitation(self, params: UIElicitationRequest, *, timeout: float | None = None) -> UIElicitationResponse: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 UIElicitationResponse.from_dict(await self._client.request("session.ui.elicitation", params_dict, **_timeout_kwargs(timeout))) async def handle_pending_elicitation(self, params: UIHandlePendingElicitationRequest, *, timeout: float | None = None) -> UIElicitationResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 UIElicitationResult.from_dict(await self._client.request("session.ui.handlePendingElicitation", params_dict, **_timeout_kwargs(timeout))) @@ -4721,10 +5404,18 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def handle_pending_permission_request(self, params: PermissionDecisionRequest, *, timeout: float | None = None) -> PermissionRequestResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 PermissionRequestResult.from_dict(await self._client.request("session.permissions.handlePendingPermissionRequest", params_dict, **_timeout_kwargs(timeout))) + async def set_approve_all(self, params: PermissionsSetApproveAllRequest, *, timeout: float | None = None) -> PermissionsSetApproveAllResult: + 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 PermissionsSetApproveAllResult.from_dict(await self._client.request("session.permissions.setApproveAll", params_dict, **_timeout_kwargs(timeout))) + + async def reset_session_approvals(self, *, timeout: float | None = None) -> PermissionsResetSessionApprovalsResult: + return PermissionsResetSessionApprovalsResult.from_dict(await self._client.request("session.permissions.resetSessionApprovals", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + class ShellApi: def __init__(self, client: "JsonRpcClient", session_id: str): @@ -4732,12 +5423,12 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._session_id = session_id async def exec(self, params: ShellExecRequest, *, timeout: float | None = None) -> ShellExecResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 ShellExecResult.from_dict(await self._client.request("session.shell.exec", params_dict, **_timeout_kwargs(timeout))) async def kill(self, params: ShellKillRequest, *, timeout: float | None = None) -> ShellKillResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 ShellKillResult.from_dict(await self._client.request("session.shell.kill", params_dict, **_timeout_kwargs(timeout))) @@ -4752,7 +5443,7 @@ async def compact(self, *, timeout: float | None = None) -> HistoryCompactResult return HistoryCompactResult.from_dict(await self._client.request("session.history.compact", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def truncate(self, params: HistoryTruncateRequest, *, timeout: float | None = None) -> HistoryTruncateResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 HistoryTruncateResult.from_dict(await self._client.request("session.history.truncate", params_dict, **_timeout_kwargs(timeout))) @@ -4772,6 +5463,7 @@ class SessionRpc: def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id + self.auth = AuthApi(client, session_id) self.model = ModelApi(client, session_id) self.mode = ModeApi(client, session_id) self.name = NameApi(client, session_id) @@ -4793,7 +5485,7 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self.usage = UsageApi(client, session_id) async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogResult: - params_dict = {k: v for k, v in params.to_dict().items() if v is not None} + 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 LogResult.from_dict(await self._client.request("session.log", params_dict, **_timeout_kwargs(timeout))) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 1b3452bd4..bebed90d9 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -167,6 +167,8 @@ class SessionEventType(Enum): COMMAND_QUEUED = "command.queued" COMMAND_EXECUTE = "command.execute" COMMAND_COMPLETED = "command.completed" + AUTO_MODE_SWITCH_REQUESTED = "auto_mode_switch.requested" + AUTO_MODE_SWITCH_COMPLETED = "auto_mode_switch.completed" COMMANDS_CHANGED = "commands.changed" CAPABILITIES_CHANGED = "capabilities.changed" EXIT_PLAN_MODE_REQUESTED = "exit_plan_mode.requested" @@ -744,6 +746,53 @@ def to_dict(self) -> dict: return result +@dataclass +class AutoModeSwitchCompletedData: + "Auto mode switch completion notification" + request_id: str + response: str + + @staticmethod + def from_dict(obj: Any) -> "AutoModeSwitchCompletedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + response = from_str(obj.get("response")) + return AutoModeSwitchCompletedData( + request_id=request_id, + response=response, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["response"] = from_str(self.response) + return result + + +@dataclass +class AutoModeSwitchRequestedData: + "Auto mode switch request notification requiring user approval" + request_id: str + error_code: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "AutoModeSwitchRequestedData": + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + error_code = from_union([from_none, from_str], obj.get("errorCode")) + return AutoModeSwitchRequestedData( + request_id=request_id, + error_code=error_code, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.error_code is not None: + result["errorCode"] = from_union([from_none, from_str], self.error_code) + return result + + @dataclass class CapabilitiesChangedData: "Session capability change notification" @@ -901,28 +950,105 @@ def to_dict(self) -> dict: @dataclass class CompactionCompleteCompactionTokensUsed: - "Token usage breakdown for the compaction LLM call" - cached_input: float - input: float - output: float + "Token usage breakdown for the compaction LLM call (aligned with assistant.usage format)" + cache_read_tokens: float | None = None + cache_write_tokens: float | None = None + copilot_usage: CompactionCompleteCompactionTokensUsedCopilotUsage | None = None + duration: float | None = None + input_tokens: float | None = None + model: str | None = None + output_tokens: float | None = None @staticmethod def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": assert isinstance(obj, dict) - cached_input = from_float(obj.get("cachedInput")) - input = from_float(obj.get("input")) - output = from_float(obj.get("output")) + cache_read_tokens = from_union([from_none, from_float], obj.get("cacheReadTokens")) + cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens")) + copilot_usage = from_union([from_none, CompactionCompleteCompactionTokensUsedCopilotUsage.from_dict], obj.get("copilotUsage")) + duration = from_union([from_none, from_float], obj.get("duration")) + input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) + model = from_union([from_none, from_str], obj.get("model")) + output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) return CompactionCompleteCompactionTokensUsed( - cached_input=cached_input, - input=input, - output=output, + cache_read_tokens=cache_read_tokens, + cache_write_tokens=cache_write_tokens, + copilot_usage=copilot_usage, + duration=duration, + input_tokens=input_tokens, + model=model, + output_tokens=output_tokens, + ) + + def to_dict(self) -> dict: + result: dict = {} + 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: + result["cacheWriteTokens"] = from_union([from_none, to_float], self.cache_write_tokens) + if self.copilot_usage is not None: + result["copilotUsage"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsedCopilotUsage, x)], self.copilot_usage) + if self.duration is not None: + result["duration"] = from_union([from_none, to_float], self.duration) + if self.input_tokens is not None: + result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) + if self.model is not None: + result["model"] = from_union([from_none, from_str], self.model) + if self.output_tokens is not None: + result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) + return result + + +@dataclass +class CompactionCompleteCompactionTokensUsedCopilotUsage: + "Per-request cost and usage data from the CAPI copilot_usage response field" + token_details: list[CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail] + total_nano_aiu: float + + @staticmethod + def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsedCopilotUsage": + assert isinstance(obj, dict) + token_details = from_list(CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail.from_dict, obj.get("tokenDetails")) + total_nano_aiu = from_float(obj.get("totalNanoAiu")) + return CompactionCompleteCompactionTokensUsedCopilotUsage( + token_details=token_details, + total_nano_aiu=total_nano_aiu, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["tokenDetails"] = from_list(lambda x: to_class(CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail, x), self.token_details) + result["totalNanoAiu"] = to_float(self.total_nano_aiu) + return result + + +@dataclass +class CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail: + "Token usage detail for a single billing category" + batch_size: float + cost_per_batch: float + token_count: float + token_type: str + + @staticmethod + def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail": + assert isinstance(obj, dict) + batch_size = from_float(obj.get("batchSize")) + cost_per_batch = from_float(obj.get("costPerBatch")) + token_count = from_float(obj.get("tokenCount")) + token_type = from_str(obj.get("tokenType")) + return CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail( + batch_size=batch_size, + cost_per_batch=cost_per_batch, + token_count=token_count, + token_type=token_type, ) def to_dict(self) -> dict: result: dict = {} - result["cachedInput"] = to_float(self.cached_input) - result["input"] = to_float(self.input) - result["output"] = to_float(self.output) + result["batchSize"] = to_float(self.batch_size) + result["costPerBatch"] = to_float(self.cost_per_batch) + result["tokenCount"] = to_float(self.token_count) + result["tokenType"] = from_str(self.token_type) return result @@ -1488,21 +1614,26 @@ class PermissionCompletedData: "Permission request completion notification signaling UI dismissal" request_id: str result: PermissionCompletedResult + tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "PermissionCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) result = PermissionCompletedResult.from_dict(obj.get("result")) + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionCompletedData( request_id=request_id, result=result, + tool_call_id=tool_call_id, ) def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) result["result"] = to_class(PermissionCompletedResult, self.result) + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result @@ -1525,6 +1656,155 @@ def to_dict(self) -> dict: return result +@dataclass +class PermissionPromptRequest: + "Derived user-facing permission prompt details for UI consumers" + kind: PermissionPromptRequestKind + access_kind: PermissionPromptRequestPathAccessKind | None = None + action: PermissionPromptRequestMemoryAction | None = None + args: Any | None = None + can_offer_session_approval: bool | None = None + citations: str | None = None + command_identifiers: list[str] | None = None + diff: str | None = None + direction: PermissionPromptRequestMemoryDirection | None = None + fact: str | None = None + file_name: str | None = None + full_command_text: str | None = None + hook_message: str | None = None + intention: str | None = None + new_file_contents: str | None = None + path: str | None = None + paths: list[str] | None = None + reason: str | None = None + server_name: str | None = None + subject: str | None = None + tool_args: Any = None + tool_call_id: str | None = None + tool_description: str | None = None + tool_name: str | None = None + tool_title: str | None = None + url: str | None = None + warning: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionPromptRequest": + assert isinstance(obj, dict) + kind = parse_enum(PermissionPromptRequestKind, obj.get("kind")) + access_kind = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestPathAccessKind, x)], obj.get("accessKind")) + action = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestMemoryAction, x)], obj.get("action", "store")) + args = from_union([from_none, lambda x: x], obj.get("args")) + can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) + citations = from_union([from_none, from_str], obj.get("citations")) + command_identifiers = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("commandIdentifiers")) + diff = from_union([from_none, from_str], obj.get("diff")) + direction = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestMemoryDirection, x)], obj.get("direction")) + fact = from_union([from_none, from_str], obj.get("fact")) + file_name = from_union([from_none, from_str], obj.get("fileName")) + full_command_text = from_union([from_none, from_str], obj.get("fullCommandText")) + hook_message = from_union([from_none, from_str], obj.get("hookMessage")) + intention = from_union([from_none, from_str], obj.get("intention")) + new_file_contents = from_union([from_none, from_str], obj.get("newFileContents")) + path = from_union([from_none, from_str], obj.get("path")) + paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("paths")) + reason = from_union([from_none, from_str], obj.get("reason")) + server_name = from_union([from_none, from_str], obj.get("serverName")) + subject = from_union([from_none, from_str], obj.get("subject")) + tool_args = obj.get("toolArgs") + tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) + tool_description = from_union([from_none, from_str], obj.get("toolDescription")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + tool_title = from_union([from_none, from_str], obj.get("toolTitle")) + url = from_union([from_none, from_str], obj.get("url")) + warning = from_union([from_none, from_str], obj.get("warning")) + return PermissionPromptRequest( + kind=kind, + access_kind=access_kind, + action=action, + args=args, + can_offer_session_approval=can_offer_session_approval, + citations=citations, + command_identifiers=command_identifiers, + diff=diff, + direction=direction, + fact=fact, + file_name=file_name, + full_command_text=full_command_text, + hook_message=hook_message, + intention=intention, + new_file_contents=new_file_contents, + path=path, + paths=paths, + reason=reason, + server_name=server_name, + subject=subject, + tool_args=tool_args, + tool_call_id=tool_call_id, + tool_description=tool_description, + tool_name=tool_name, + tool_title=tool_title, + url=url, + warning=warning, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionPromptRequestKind, self.kind) + if self.access_kind is not None: + result["accessKind"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestPathAccessKind, x)], self.access_kind) + if self.action is not None: + result["action"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestMemoryAction, x)], self.action) + if self.args is not None: + result["args"] = from_union([from_none, lambda x: x], self.args) + if self.can_offer_session_approval is not None: + result["canOfferSessionApproval"] = from_union([from_none, from_bool], self.can_offer_session_approval) + if self.citations is not None: + result["citations"] = from_union([from_none, from_str], self.citations) + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([from_none, lambda x: from_list(from_str, x)], self.command_identifiers) + if self.diff is not None: + result["diff"] = from_union([from_none, from_str], self.diff) + if self.direction is not None: + result["direction"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestMemoryDirection, x)], self.direction) + if self.fact is not None: + result["fact"] = from_union([from_none, from_str], self.fact) + if self.file_name is not None: + result["fileName"] = from_union([from_none, from_str], self.file_name) + if self.full_command_text is not None: + result["fullCommandText"] = from_union([from_none, from_str], self.full_command_text) + if self.hook_message is not None: + result["hookMessage"] = from_union([from_none, from_str], self.hook_message) + if self.intention is not None: + result["intention"] = from_union([from_none, from_str], self.intention) + if self.new_file_contents is not None: + result["newFileContents"] = from_union([from_none, from_str], self.new_file_contents) + if self.path is not None: + result["path"] = from_union([from_none, from_str], self.path) + if self.paths is not None: + result["paths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.paths) + if self.reason is not None: + result["reason"] = from_union([from_none, from_str], self.reason) + if self.server_name is not None: + result["serverName"] = from_union([from_none, from_str], self.server_name) + if self.subject is not None: + result["subject"] = from_union([from_none, from_str], self.subject) + if self.tool_args is not None: + result["toolArgs"] = self.tool_args + if self.tool_call_id is not None: + result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) + if self.tool_description is not None: + result["toolDescription"] = from_union([from_none, from_str], self.tool_description) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + if self.tool_title is not None: + result["toolTitle"] = from_union([from_none, from_str], self.tool_title) + if self.url is not None: + result["url"] = from_union([from_none, from_str], self.url) + if self.warning is not None: + result["warning"] = from_union([from_none, from_str], self.warning) + return result + + @dataclass class PermissionRequest: "Details of the permission being requested" @@ -1729,6 +2009,7 @@ class PermissionRequestedData: "Permission request notification requiring client approval with request details" permission_request: PermissionRequest request_id: str + prompt_request: PermissionPromptRequest | None = None resolved_by_hook: bool | None = None @staticmethod @@ -1736,10 +2017,12 @@ def from_dict(obj: Any) -> "PermissionRequestedData": assert isinstance(obj, dict) permission_request = PermissionRequest.from_dict(obj.get("permissionRequest")) request_id = from_str(obj.get("requestId")) + prompt_request = from_union([from_none, PermissionPromptRequest.from_dict], obj.get("promptRequest")) resolved_by_hook = from_union([from_none, from_bool], obj.get("resolvedByHook")) return PermissionRequestedData( permission_request=permission_request, request_id=request_id, + prompt_request=prompt_request, resolved_by_hook=resolved_by_hook, ) @@ -1747,6 +2030,8 @@ def to_dict(self) -> dict: result: dict = {} result["permissionRequest"] = to_class(PermissionRequest, self.permission_request) result["requestId"] = from_str(self.request_id) + if self.prompt_request is not None: + result["promptRequest"] = from_union([from_none, lambda x: to_class(PermissionPromptRequest, x)], self.prompt_request) if self.resolved_by_hook is not None: result["resolvedByHook"] = from_union([from_none, from_bool], self.resolved_by_hook) return result @@ -3997,6 +4282,8 @@ class McpServersLoadedServerStatus(Enum): class PermissionCompletedKind(Enum): "The outcome of the permission request" APPROVED = "approved" + APPROVED_FOR_SESSION = "approved-for-session" + APPROVED_FOR_LOCATION = "approved-for-location" DENIED_BY_RULES = "denied-by-rules" DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" @@ -4004,6 +4291,38 @@ class PermissionCompletedKind(Enum): DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" +class PermissionPromptRequestKind(Enum): + "Derived user-facing permission prompt details for UI consumers discriminator" + COMMANDS = "commands" + WRITE = "write" + READ = "read" + MCP = "mcp" + URL = "url" + MEMORY = "memory" + CUSTOM_TOOL = "custom-tool" + PATH = "path" + HOOK = "hook" + + +class PermissionPromptRequestMemoryAction(Enum): + "Whether this is a store or vote memory operation" + STORE = "store" + VOTE = "vote" + + +class PermissionPromptRequestMemoryDirection(Enum): + "Vote direction (vote only)" + UPVOTE = "upvote" + DOWNVOTE = "downvote" + + +class PermissionPromptRequestPathAccessKind(Enum): + "Underlying permission kind that needs path approval" + READ = "read" + SHELL = "shell" + WRITE = "write" + + class PermissionRequestKind(Enum): "Details of the permission being requested discriminator" SHELL = "shell" @@ -4114,7 +4433,7 @@ class WorkspaceFileChangedOperation(Enum): UPDATE = "update" -SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data +SessionEventData = SessionStartData | SessionResumeData | SessionRemoteSteerableChangedData | SessionErrorData | SessionIdleData | SessionTitleChangedData | SessionInfoData | SessionWarningData | SessionModelChangeData | SessionModeChangedData | SessionPlanChangedData | SessionWorkspaceFileChangedData | SessionHandoffData | SessionTruncationData | SessionSnapshotRewindData | SessionShutdownData | SessionContextChangedData | SessionUsageInfoData | SessionCompactionStartData | SessionCompactionCompleteData | SessionTaskCompleteData | UserMessageData | PendingMessagesModifiedData | AssistantTurnStartData | AssistantIntentData | AssistantReasoningData | AssistantReasoningDeltaData | AssistantStreamingDeltaData | AssistantMessageData | AssistantMessageDeltaData | AssistantTurnEndData | AssistantUsageData | AbortData | ToolUserRequestedData | ToolExecutionStartData | ToolExecutionPartialResultData | ToolExecutionProgressData | ToolExecutionCompleteData | SkillInvokedData | SubagentStartedData | SubagentCompletedData | SubagentFailedData | SubagentSelectedData | SubagentDeselectedData | HookStartData | HookEndData | SystemMessageData | SystemNotificationData | PermissionRequestedData | PermissionCompletedData | UserInputRequestedData | UserInputCompletedData | ElicitationRequestedData | ElicitationCompletedData | SamplingRequestedData | SamplingCompletedData | McpOauthRequiredData | McpOauthCompletedData | ExternalToolRequestedData | ExternalToolCompletedData | CommandQueuedData | CommandExecuteData | CommandCompletedData | AutoModeSwitchRequestedData | AutoModeSwitchCompletedData | CommandsChangedData | CapabilitiesChangedData | ExitPlanModeRequestedData | ExitPlanModeCompletedData | SessionToolsUpdatedData | SessionBackgroundTasksChangedData | SessionSkillsLoadedData | SessionCustomAgentsUpdatedData | SessionMcpServersLoadedData | SessionMcpServerStatusChangedData | SessionExtensionsLoadedData | RawSessionEventData | Data @dataclass @@ -4201,6 +4520,8 @@ def from_dict(obj: Any) -> "SessionEvent": case SessionEventType.COMMAND_QUEUED: data = CommandQueuedData.from_dict(data_obj) case SessionEventType.COMMAND_EXECUTE: data = CommandExecuteData.from_dict(data_obj) case SessionEventType.COMMAND_COMPLETED: data = CommandCompletedData.from_dict(data_obj) + case SessionEventType.AUTO_MODE_SWITCH_REQUESTED: data = AutoModeSwitchRequestedData.from_dict(data_obj) + case SessionEventType.AUTO_MODE_SWITCH_COMPLETED: data = AutoModeSwitchCompletedData.from_dict(data_obj) case SessionEventType.COMMANDS_CHANGED: data = CommandsChangedData.from_dict(data_obj) case SessionEventType.CAPABILITIES_CHANGED: data = CapabilitiesChangedData.from_dict(data_obj) case SessionEventType.EXIT_PLAN_MODE_REQUESTED: data = ExitPlanModeRequestedData.from_dict(data_obj) diff --git a/python/copilot/session.py b/python/copilot/session.py index 88f742afb..3e1d40931 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -221,11 +221,9 @@ class SystemMessageCustomizeConfig(TypedDict, total=False): # ============================================================================ PermissionRequestResultKind = Literal[ - "approved", - "denied-by-rules", - "denied-by-content-exclusion-policy", - "denied-no-approval-rule-and-could-not-request-from-user", - "denied-interactively-by-user", + "approve-once", + "reject", + "user-not-available", "no-result", ] @@ -234,11 +232,7 @@ class SystemMessageCustomizeConfig(TypedDict, total=False): class PermissionRequestResult: """Result of a permission request.""" - kind: PermissionRequestResultKind = "denied-no-approval-rule-and-could-not-request-from-user" - rules: list[Any] | None = None - feedback: str | None = None - message: str | None = None - path: str | None = None + kind: PermissionRequestResultKind = "user-not-available" _PermissionHandlerFn = Callable[ @@ -252,7 +246,7 @@ class PermissionHandler: def approve_all( request: PermissionRequest, invocation: dict[str, str] ) -> PermissionRequestResult: - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") # ============================================================================ @@ -1455,10 +1449,6 @@ async def _execute_permission_and_respond( perm_result = PermissionDecision( kind=PermissionDecisionKind(result.kind), - rules=result.rules, - feedback=result.feedback, - message=result.message, - path=result.path, ) await self.rpc.permissions.handle_pending_permission_request( @@ -1473,7 +1463,7 @@ async def _execute_permission_and_respond( PermissionDecisionRequest( request_id=request_id, result=PermissionDecision( - kind=PermissionDecisionKind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, + kind=PermissionDecisionKind.USER_NOT_AVAILABLE, ), ) ) diff --git a/python/e2e/test_multi_client.py b/python/e2e/test_multi_client.py index 2d866e8aa..3b3b75d0a 100644 --- a/python/e2e/test_multi_client.py +++ b/python/e2e/test_multi_client.py @@ -234,7 +234,7 @@ async def test_one_client_approves_permission_and_both_see_the_result( # Client 1 creates a session and manually approves permission requests session1 = await mctx.client1.create_session( on_permission_request=lambda request, invocation: ( - permission_requests.append(request) or PermissionRequestResult(kind="approved") + permission_requests.append(request) or PermissionRequestResult(kind="approve-once") ), ) @@ -280,7 +280,7 @@ async def test_one_client_rejects_permission_and_both_see_the_result( # Client 1 creates a session and denies all permission requests session1 = await mctx.client1.create_session( on_permission_request=lambda request, invocation: PermissionRequestResult( - kind="denied-interactively-by-user" + kind="reject" ), ) diff --git a/python/e2e/test_per_session_auth.py b/python/e2e/test_per_session_auth.py new file mode 100644 index 000000000..670236f59 --- /dev/null +++ b/python/e2e/test_per_session_auth.py @@ -0,0 +1,115 @@ +"""E2E Per-session GitHub auth tests""" + +import pytest + +from copilot.session import PermissionHandler + +from .testharness import E2ETestContext + +pytestmark = pytest.mark.asyncio(loop_scope="module") + + +@pytest.fixture(scope="module") +async def auth_ctx(ctx: E2ETestContext): + """Configure per-token user responses on the proxy before tests run.""" + proxy_url = ctx.proxy_url + + # Redirect GitHub API calls to the proxy so per-session auth token + # resolution (fetchCopilotUser) is intercepted. Must be set before the + # CLI subprocess is spawned (i.e., before the first create_session call). + ctx.client._config.env["COPILOT_DEBUG_GITHUB_API_URL"] = proxy_url + + await ctx.set_copilot_user_by_token( + "token-alice", + { + "login": "alice", + "copilot_plan": "individual_pro", + "endpoints": { + "api": proxy_url, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "alice-tracking-id", + }, + ) + + await ctx.set_copilot_user_by_token( + "token-bob", + { + "login": "bob", + "copilot_plan": "business", + "endpoints": { + "api": proxy_url, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "bob-tracking-id", + }, + ) + + return ctx + + +class TestPerSessionAuth: + async def test_should_create_session_with_github_token_and_check_auth_status( + self, auth_ctx: E2ETestContext + ): + session = await auth_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + github_token="token-alice", + ) + + auth_status = await session.rpc.auth.get_status() + assert auth_status.is_authenticated is True + assert auth_status.login == "alice" + assert auth_status.copilot_plan == "individual_pro" + + await session.disconnect() + + async def test_should_isolate_auth_between_sessions_with_different_tokens( + self, auth_ctx: E2ETestContext + ): + session_a = await auth_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + github_token="token-alice", + ) + session_b = await auth_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + github_token="token-bob", + ) + + status_a = await session_a.rpc.auth.get_status() + status_b = await session_b.rpc.auth.get_status() + + assert status_a.is_authenticated is True + assert status_a.login == "alice" + assert status_a.copilot_plan == "individual_pro" + + assert status_b.is_authenticated is True + assert status_b.login == "bob" + assert status_b.copilot_plan == "business" + + await session_a.disconnect() + await session_b.disconnect() + + async def test_should_return_unauthenticated_when_no_token_provided( + self, auth_ctx: E2ETestContext + ): + session = await auth_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) + + auth_status = await session.rpc.auth.get_status() + # Without a per-session token, there is no per-session identity. + # In CI the process-level fake token may still authenticate globally, + # so we check login rather than is_authenticated. + assert auth_status.login is None + + await session.disconnect() + + async def test_should_error_when_creating_session_with_invalid_token( + self, auth_ctx: E2ETestContext + ): + with pytest.raises(Exception): + await auth_ctx.client.create_session( + on_permission_request=PermissionHandler.approve_all, + github_token="invalid-token-12345", + ) diff --git a/python/e2e/test_permissions.py b/python/e2e/test_permissions.py index 86beb3a5c..b4e02ddb9 100644 --- a/python/e2e/test_permissions.py +++ b/python/e2e/test_permissions.py @@ -29,7 +29,7 @@ def on_permission_request( ) -> PermissionRequestResult: permission_requests.append(request) assert invocation["session_id"] == session.session_id - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") session = await ctx.client.create_session(on_permission_request=on_permission_request) @@ -52,7 +52,7 @@ async def test_should_deny_permission_when_handler_returns_denied(self, ctx: E2E def on_permission_request( request: PermissionRequest, invocation: dict ) -> PermissionRequestResult: - return PermissionRequestResult(kind="denied-interactively-by-user") + return PermissionRequestResult(kind="reject") session = await ctx.client.create_session(on_permission_request=on_permission_request) @@ -167,7 +167,7 @@ async def on_permission_request( permission_requests.append(request) # Simulate async permission check (e.g., user prompt) await asyncio.sleep(0.01) - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") session = await ctx.client.create_session(on_permission_request=on_permission_request) @@ -193,7 +193,7 @@ def on_permission_request( request: PermissionRequest, invocation: dict ) -> PermissionRequestResult: permission_requests.append(request) - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") session2 = await ctx.client.resume_session( session_id, on_permission_request=on_permission_request @@ -237,7 +237,7 @@ def on_permission_request( received_tool_call_id = True assert isinstance(request.tool_call_id, str) assert len(request.tool_call_id) > 0 - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") session = await ctx.client.create_session(on_permission_request=on_permission_request) diff --git a/python/e2e/test_tools.py b/python/e2e/test_tools.py index 4bb853976..df3835445 100644 --- a/python/e2e/test_tools.py +++ b/python/e2e/test_tools.py @@ -191,7 +191,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str: def on_permission_request(request, invocation): permission_requests.append(request) - return PermissionRequestResult(kind="approved") + return PermissionRequestResult(kind="approve-once") session = await ctx.client.create_session( on_permission_request=on_permission_request, tools=[encrypt_string] @@ -219,7 +219,7 @@ def encrypt_string(params: EncryptParams, invocation: ToolInvocation) -> str: return params.input.upper() def on_permission_request(request, invocation): - return PermissionRequestResult(kind="denied-interactively-by-user") + return PermissionRequestResult(kind="reject") session = await ctx.client.create_session( on_permission_request=on_permission_request, tools=[encrypt_string] diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index 6a4bac6d2..c2028c14a 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -9,6 +9,7 @@ import shutil import tempfile from pathlib import Path +from typing import Any from copilot import CopilotClient from copilot.client import SubprocessConfig @@ -145,6 +146,12 @@ def client(self) -> CopilotClient: raise RuntimeError("Context not set up. Call setup() first.") return self._client + async def set_copilot_user_by_token(self, token: str, response: dict[str, Any]) -> None: + """Register a per-token response for the /copilot_internal/user endpoint.""" + if not self._proxy: + raise RuntimeError("Proxy not started") + await self._proxy.set_copilot_user_by_token(token, response) + async def get_exchanges(self): """Retrieve the captured HTTP exchanges from the proxy.""" if not self._proxy: diff --git a/python/e2e/testharness/proxy.py b/python/e2e/testharness/proxy.py index 65dd8bda9..e125375e0 100644 --- a/python/e2e/testharness/proxy.py +++ b/python/e2e/testharness/proxy.py @@ -106,6 +106,18 @@ async def get_exchanges(self) -> list[dict[str, Any]]: resp = await client.get(f"{self._proxy_url}/exchanges") return resp.json() + async def set_copilot_user_by_token(self, token: str, response: dict[str, Any]) -> None: + """Register a per-token response for /copilot_internal/user.""" + if not self._proxy_url: + raise RuntimeError("Proxy not started") + + async with httpx.AsyncClient() as client: + resp = await client.post( + f"{self._proxy_url}/copilot-user-config", + json={"token": token, "response": response}, + ) + assert resp.status_code == 200 + @property def url(self) -> str | None: """Return the proxy URL, or None if not started.""" diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 8416d4e40..4baf8061c 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -405,6 +405,17 @@ function findDiscriminator(variants: JSONSchema7[]): { property: string; mapping return null; } +/** Callback that resolves the C# type for a property schema within a polymorphic class. */ +type PropertyTypeResolver = ( + propSchema: JSONSchema7, + parentClassName: string, + propName: string, + isRequired: boolean, + knownTypes: Map, + nestedClasses: Map, + enumOutput: string[] +) => string; + /** * Generate a polymorphic base class and derived classes for a discriminated union. */ @@ -415,8 +426,10 @@ function generatePolymorphicClasses( knownTypes: Map, nestedClasses: Map, enumOutput: string[], - description?: string + description?: string, + propertyResolver?: PropertyTypeResolver ): string { + const resolver = propertyResolver ?? resolveSessionPropertyType; const lines: string[] = []; const discriminatorInfo = findDiscriminator(variants)!; const renamedBase = applyTypeRename(baseClassName); @@ -441,7 +454,7 @@ function generatePolymorphicClasses( for (const [constValue, variant] of discriminatorInfo.mapping) { const derivedClassName = applyTypeRename(`${baseClassName}${toPascalCase(constValue)}`); - const derivedCode = generateDerivedClass(derivedClassName, renamedBase, discriminatorProperty, constValue, variant, knownTypes, nestedClasses, enumOutput); + const derivedCode = generateDerivedClass(derivedClassName, renamedBase, discriminatorProperty, constValue, variant, knownTypes, nestedClasses, enumOutput, resolver); nestedClasses.set(derivedClassName, derivedCode); } @@ -459,7 +472,8 @@ function generateDerivedClass( schema: JSONSchema7, knownTypes: Map, nestedClasses: Map, - enumOutput: string[] + enumOutput: string[], + propertyResolver: PropertyTypeResolver ): string { const lines: string[] = []; const required = new Set(schema.required || []); @@ -480,7 +494,7 @@ function generateDerivedClass( const isReq = required.has(propName); const csharpName = toPascalCase(propName); - const csharpType = resolveSessionPropertyType(propSchema as JSONSchema7, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); + const csharpType = propertyResolver(propSchema as JSONSchema7, className, csharpName, isReq, knownTypes, nestedClasses, enumOutput); lines.push(...xmlDocPropertyComment((propSchema as JSONSchema7).description, propName, " ")); lines.push(...emitDataAnnotations(propSchema as JSONSchema7, " ")); @@ -901,7 +915,15 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam if (!emittedRpcClassSchemas.has(baseClassName)) { emittedRpcClassSchemas.set(baseClassName, "polymorphic"); const nestedMap = new Map(); - const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, rpcKnownTypes, nestedMap, rpcEnumOutput, schema.description); + const rpcPropertyResolver: PropertyTypeResolver = (propSchema, parentClass, pName, isReq, _kt, nestedCls, enumOut) => { + const nestedRpcClasses: string[] = []; + const result = resolveRpcType(propSchema, isReq, parentClass, pName, nestedRpcClasses); + for (const cls of nestedRpcClasses) { + nestedCls.set(cls.match(/class (\w+)/)?.[1] ?? cls.slice(0, 40), cls); + } + return result; + }; + const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, rpcKnownTypes, nestedMap, rpcEnumOutput, schema.description, rpcPropertyResolver); classes.push(polymorphicCode); for (const nested of nestedMap.values()) classes.push(nested); } diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index bb7d85319..c1acc4980 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -250,6 +250,12 @@ function schemaSourceForNamedDefinition( if (schema?.$ref && resolvedSchema) { return resolvedSchema; } + // When the schema is an anyOf/oneOf wrapper (e.g., Zod optional params producing + // `anyOf: [{ not: {} }, { $ref }]`), use the resolved object schema to avoid + // generating self-referential type aliases that crash quicktype. + if ((schema?.anyOf || schema?.oneOf) && resolvedSchema?.properties) { + return resolvedSchema; + } return schema ?? resolvedSchema ?? { type: "object" }; } diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 6fe931994..6a3fe3b7d 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -408,6 +408,12 @@ function schemaSourceForNamedDefinition( if (schema?.$ref && resolvedSchema) { return resolvedSchema; } + // When the schema is an anyOf/oneOf wrapper (e.g., Zod optional params producing + // `anyOf: [{ not: {} }, { $ref }]`), use the resolved object schema to avoid + // generating self-referential type aliases that crash quicktype. + if ((schema?.anyOf || schema?.oneOf) && resolvedSchema?.properties) { + return resolvedSchema; + } return schema ?? resolvedSchema ?? { type: "object" }; } @@ -438,6 +444,19 @@ function pythonResultTypeName(method: RpcMethod, schemaOverride?: JSONSchema7): return getRpcSchemaTypeName(schema, toPascalCase(method.rpcMethod) + "Result"); } +/** Detect the Zod optional params pattern: `anyOf: [{ not: {} }, { $ref }]` */ +function isParamsOptional(method: RpcMethod): boolean { + const schema = method.params; + if (!schema?.anyOf) return false; + return schema.anyOf.some( + (item) => + typeof item === "object" && + (item as JSONSchema7).not !== undefined && + typeof (item as JSONSchema7).not === "object" && + Object.keys((item as JSONSchema7).not as object).length === 0 + ); +} + function pythonParamsTypeName(method: RpcMethod): string { const fallback = pythonRequestFallbackName(method); if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { @@ -1813,8 +1832,9 @@ def _patch_model_capabilities(data: dict) -> dict: `ModelList.from_dict(_patch_model_capabilities(await self._client.request("models.list"`, ); // Close the extra paren opened by _patch_model_capabilities( + // Match everything from _patch_model_capabilities( up to the end of the return statement finalCode = finalCode.replace( - /(_patch_model_capabilities\(await self\._client\.request\("models\.list",\s*\{[^)]*\)[^)]*\))/, + /(_patch_model_capabilities\(await self\._client\.request\("models\.list"[^)]*\)[^)]*\))/, "$1)", ); finalCode = unwrapRedundantPythonLambdas(finalCode); @@ -1943,10 +1963,13 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: const nonSessionParams = Object.keys(paramProps).filter((k) => k !== "sessionId"); const hasParams = isSession ? nonSessionParams.length > 0 : hasSchemaPayload(effectiveParams); const paramsType = resolveType(pythonParamsTypeName(method)); + const paramsOptional = isParamsOptional(method); // Build signature with typed params + optional timeout const sig = hasParams - ? ` async def ${methodName}(self, params: ${paramsType}, *, timeout: float | None = None) -> ${resultType}:` + ? paramsOptional + ? ` async def ${methodName}(self, params: ${paramsType} | None = None, *, timeout: float | None = None) -> ${resultType}:` + : ` async def ${methodName}(self, params: ${paramsType}, *, timeout: float | None = None) -> ${resultType}:` : ` async def ${methodName}(self, *, timeout: float | None = None) -> ${resultType}:`; lines.push(sig); @@ -1986,7 +2009,11 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: if (isSession) { if (hasParams) { - lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); + if (paramsOptional) { + lines.push(` 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 {}`); + } else { + lines.push(` params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}`); + } lines.push(` params_dict["sessionId"] = self._session_id`); emitRequestCall("params_dict"); } else { @@ -1994,7 +2021,11 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: } } else { if (hasParams) { - lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); + if (paramsOptional) { + lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {}`); + } else { + lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); + } emitRequestCall("params_dict"); } else { emitRequestCall("{}"); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 208e05941..d032c34fd 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -235,6 +235,12 @@ function schemaSourceForNamedDefinition( if (schema?.$ref && resolvedSchema) { return resolvedSchema; } + // When the schema is an anyOf/oneOf wrapper (e.g., Zod optional params producing + // `anyOf: [{ not: {} }, { $ref }]`), use the resolved object schema to avoid + // generating self-referential type aliases. + if ((schema?.anyOf || schema?.oneOf) && resolvedSchema?.properties) { + return resolvedSchema; + } return schema ?? resolvedSchema ?? { type: "object" }; } @@ -251,6 +257,19 @@ function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { ); } +/** True when the raw params schema uses `anyOf: [{ not: {} }, …]` — Zod's pattern for `.optional()`. */ +function isParamsOptional(method: RpcMethod): boolean { + const schema = method.params; + if (!schema?.anyOf) return false; + return schema.anyOf.some( + (item) => + typeof item === "object" && + (item as JSONSchema7).not !== undefined && + typeof (item as JSONSchema7).not === "object" && + Object.keys((item as JSONSchema7).not as object).length === 0 + ); +} + function resultTypeName(method: RpcMethod): string { return getRpcSchemaTypeName( getMethodResultSchema(method), @@ -460,14 +479,18 @@ function emitGroup(node: Record, indent: string, isSession: boo if (isSession) { if (hasNonSessionParams) { - sigParams.push(`params: Omit<${paramsType}, "sessionId">`); + const optMark = isParamsOptional(value) ? "?" : ""; + // sessionId is already stripped from the generated type definition, + // so no need for Omit<..., "sessionId"> + sigParams.push(`params${optMark}: ${paramsType}`); bodyArg = "{ sessionId, ...params }"; } else { bodyArg = "{ sessionId }"; } } else { if (hasParams) { - sigParams.push(`params: ${paramsType}`); + const optMark = isParamsOptional(value) ? "?" : ""; + sigParams.push(`params${optMark}: ${paramsType}`); bodyArg = "params"; } else { bodyArg = "{}"; diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 9abc9c8fb..4a4c31f3f 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -482,7 +482,14 @@ export function resolveObjectSchema( } const singleBranch = (resolved.anyOf ?? resolved.oneOf) - ?.filter((item): item is JSONSchema7 => typeof item === "object" && (item as JSONSchema7).type !== "null"); + ?.filter((item): item is JSONSchema7 => { + if (!item || typeof item !== "object") return false; + 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; + return true; + }); if (singleBranch && singleBranch.length === 1) { return resolveObjectSchema(singleBranch[0], definitions); } diff --git a/test/harness/replayingCapiProxy.ts b/test/harness/replayingCapiProxy.ts index a63c5b123..967c18084 100644 --- a/test/harness/replayingCapiProxy.ts +++ b/test/harness/replayingCapiProxy.ts @@ -56,6 +56,14 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { { toolName: "*", normalizer: normalizeLargeOutputFilepaths }, ]; + /** + * Per-token responses for `/copilot_internal/user` endpoint. + * Key is the Bearer token (without "Bearer " prefix), value is the response body. + * When a request arrives with `Authorization: Bearer `, the matching response is returned. + * If no match is found, a 401 Unauthorized response is returned. + */ + private copilotUserByToken = new Map(); + /** * If true, cached responses are played back slowly (~ 2KiB/sec). Otherwise streaming responses are sent as fast as possible. */ @@ -139,6 +147,14 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { this.state.toolResultNormalizers.push({ toolName, normalizer }); } + /** + * Register a per-token response for the `/copilot_internal/user` endpoint. + * When a request with `Authorization: Bearer ` arrives, the matching response is returned. + */ + setCopilotUserByToken(token: string, response: CopilotUserResponse): void { + this.copilotUserByToken.set(token, response); + } + override performRequest(options: PerformRequestOptions): void { void iife(async () => { const commonResponseHeaders = { @@ -146,6 +162,18 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { }; try { + // Handle /copilot-user-config endpoint for configuring per-token user responses + if ( + options.requestOptions.path === "/copilot-user-config" && + options.requestOptions.method === "POST" + ) { + const config = JSON.parse(options.body!) as { token: string; response: CopilotUserResponse }; + this.copilotUserByToken.set(config.token, config.response); + options.onResponseStart(200, {}); + options.onResponseEnd(); + return; + } + // Handle /config endpoint for updating proxy configuration if ( options.requestOptions.path === "/config" && @@ -217,6 +245,27 @@ export class ReplayingCapiProxy extends CapturingHttpProxy { return; } + // Handle /copilot_internal/user endpoint for per-session auth + if (options.requestOptions.path === "/copilot_internal/user") { + const authHeader = options.requestOptions.headers?.["authorization"] as string | undefined; + const token = authHeader?.replace("Bearer ", ""); + const userResponse = token ? this.copilotUserByToken.get(token) : undefined; + if (userResponse) { + const headers = { + "content-type": "application/json", + ...commonResponseHeaders, + }; + options.onResponseStart(200, headers); + options.onData(Buffer.from(JSON.stringify(userResponse))); + options.onResponseEnd(); + } else { + options.onResponseStart(401, commonResponseHeaders); + options.onData(Buffer.from(JSON.stringify({ message: "Bad credentials" }))); + options.onResponseEnd(); + } + return; + } + // Handle memory endpoints - return stub responses in tests // Matches: /agents/*/memory/*/enabled, /agents/*/memory/*/recent, etc. if (options.requestOptions.path?.match(/\/agents\/.*\/memory\//)) { @@ -1113,6 +1162,20 @@ export type ToolResultNormalizer = { normalizer: (result: string) => string; }; +/** + * Response shape for the `/copilot_internal/user` endpoint. + * Used by per-session auth tests to mock GitHub identity resolution. + */ +export type CopilotUserResponse = { + login: string; + copilot_plan?: string; + endpoints?: { + api?: string; + telemetry?: string; + }; + analytics_tracking_id?: string; +}; + export type ParsedHttpExchange = { request: ChatCompletionCreateParamsBase; response: ChatCompletion | undefined; diff --git a/test/snapshots/session/should_set_model_with_reasoningeffort.yaml b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml new file mode 100644 index 000000000..0e019bdad --- /dev/null +++ b/test/snapshots/session/should_set_model_with_reasoningeffort.yaml @@ -0,0 +1,8 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: Run 'sleep 2 && echo done'