From 9873e6f94e95ce5c55784ca64ae6af76800ad380 Mon Sep 17 00:00:00 2001 From: jmoseley Date: Mon, 26 Jan 2026 10:42:14 -0800 Subject: [PATCH 1/3] Improve csharp handling of schemas --- .../scripts/generate-csharp-session-types.ts | 180 ++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/nodejs/scripts/generate-csharp-session-types.ts b/nodejs/scripts/generate-csharp-session-types.ts index 722737b6..1bc90e64 100644 --- a/nodejs/scripts/generate-csharp-session-types.ts +++ b/nodejs/scripts/generate-csharp-session-types.ts @@ -345,6 +345,166 @@ function generateNestedClass( return lines.join("\n"); } +/** + * Find a discriminator property shared by all variants in an anyOf. + * Returns the property name and the mapping of const values to variant schemas. + */ +function findDiscriminator(variants: JSONSchema7[]): { property: string; mapping: Map } | null { + if (variants.length === 0) return null; + + // Look for a property with a const value in all variants + const firstVariant = variants[0]; + if (!firstVariant.properties) return null; + + for (const [propName, propSchema] of Object.entries(firstVariant.properties)) { + if (typeof propSchema !== "object") continue; + const schema = propSchema as JSONSchema7; + if (schema.const === undefined) continue; + + // Check if all variants have this property with a const value + const mapping = new Map(); + let isValidDiscriminator = true; + + for (const variant of variants) { + if (!variant.properties) { + isValidDiscriminator = false; + break; + } + const variantProp = variant.properties[propName]; + if (typeof variantProp !== "object") { + isValidDiscriminator = false; + break; + } + const variantSchema = variantProp as JSONSchema7; + if (variantSchema.const === undefined) { + isValidDiscriminator = false; + break; + } + mapping.set(String(variantSchema.const), variant); + } + + if (isValidDiscriminator && mapping.size === variants.length) { + return { property: propName, mapping }; + } + } + + return null; +} + +/** + * Generate a polymorphic base class and derived classes for a discriminated union. + */ +function generatePolymorphicClasses( + baseClassName: string, + discriminatorProperty: string, + variants: JSONSchema7[], + knownTypes: Map, + nestedClasses: Map, + enumOutput: string[] +): string { + const lines: string[] = []; + const discriminatorInfo = findDiscriminator(variants)!; + + // Generate base class with JsonPolymorphic attribute + lines.push(`[JsonPolymorphic(`); + lines.push(` TypeDiscriminatorPropertyName = "${discriminatorProperty}",`); + lines.push(` UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]`); + + // Add JsonDerivedType attributes for each variant + for (const [constValue, variant] of discriminatorInfo.mapping) { + const derivedClassName = `${baseClassName}${toPascalCase(constValue)}`; + lines.push(`[JsonDerivedType(typeof(${derivedClassName}), "${constValue}")]`); + } + + lines.push(`public partial class ${baseClassName}`); + lines.push(`{`); + lines.push(` [JsonPropertyName("${discriminatorProperty}")]`); + lines.push(` public virtual string ${toPascalCase(discriminatorProperty)} { get; set; } = string.Empty;`); + lines.push(`}`); + lines.push(""); + + // Generate derived classes + for (const [constValue, variant] of discriminatorInfo.mapping) { + const derivedClassName = `${baseClassName}${toPascalCase(constValue)}`; + const derivedCode = generateDerivedClass( + derivedClassName, + baseClassName, + discriminatorProperty, + constValue, + variant, + knownTypes, + nestedClasses, + enumOutput + ); + nestedClasses.set(derivedClassName, derivedCode); + } + + return lines.join("\n"); +} + +/** + * Generate a derived class for a discriminated union variant. + */ +function generateDerivedClass( + className: string, + baseClassName: string, + discriminatorProperty: string, + discriminatorValue: string, + schema: JSONSchema7, + knownTypes: Map, + nestedClasses: Map, + enumOutput: string[] +): string { + const lines: string[] = []; + const required = new Set(schema.required || []); + + lines.push(`public partial class ${className} : ${baseClassName}`); + lines.push(`{`); + + // Override the discriminator property + lines.push(` [JsonIgnore]`); + lines.push(` public override string ${toPascalCase(discriminatorProperty)} => "${discriminatorValue}";`); + lines.push(""); + + if (schema.properties) { + for (const [propName, propSchema] of Object.entries(schema.properties)) { + if (typeof propSchema !== "object") continue; + // Skip the discriminator property (already in base class) + if (propName === discriminatorProperty) continue; + + const isRequired = required.has(propName); + const csharpName = toPascalCase(propName); + const csharpType = resolvePropertyType( + propSchema as JSONSchema7, + className, + csharpName, + isRequired, + knownTypes, + nestedClasses, + enumOutput + ); + + if (!isRequired) { + lines.push(` [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]`); + } + lines.push(` [JsonPropertyName("${propName}")]`); + + const isNullableType = csharpType.endsWith("?"); + const requiredModifier = isRequired && !isNullableType ? "required " : ""; + lines.push(` public ${requiredModifier}${csharpType} ${csharpName} { get; set; }`); + lines.push(""); + } + } + + // Remove trailing empty line + if (lines[lines.length - 1] === "") { + lines.pop(); + } + + lines.push(`}`); + return lines.join("\n"); +} + /** * Resolve the C# type for a property, generating nested classes as needed. * Handles objects and arrays of objects. @@ -411,6 +571,26 @@ function resolvePropertyType( 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 = `${parentClassName}${propName}Item`; + const polymorphicCode = generatePolymorphicClasses( + baseClassName, + discriminatorInfo.property, + variants, + knownTypes, + nestedClasses, + enumOutput + ); + nestedClasses.set(baseClassName, polymorphicCode); + return isRequired ? `${baseClassName}[]` : `${baseClassName}[]?`; + } + } + // Array of objects with properties if (items.type === "object" && items.properties) { const itemClassName = `${parentClassName}${propName}Item`; From 7cb18069cb8abe0ad933bcb2a5758cbffae3b263 Mon Sep 17 00:00:00 2001 From: jmoseley Date: Mon, 26 Jan 2026 10:42:38 -0800 Subject: [PATCH 2/3] Update generated session events. --- dotnet/src/Generated/SessionEvents.cs | 129 +++++++++++++++-- go/generated_session_events.go | 35 ++++- nodejs/src/generated/session-events.ts | 49 ++++++- python/copilot/generated/session_events.py | 136 +++++++++++++++--- .../session/should_abort_a_session.yaml | 13 +- 5 files changed, 311 insertions(+), 51 deletions(-) diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index ea9b3e2d..4e059b70 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -6,7 +6,7 @@ // // Generated from: @github/copilot/session-events.schema.json // Generated by: scripts/generate-session-types.ts -// Generated at: 2026-01-22T14:30:57.371Z +// Generated at: 2026-01-26T18:08:34.014Z // // To update these types: // 1. Update the schema in copilot-agent-runtime @@ -43,6 +43,7 @@ namespace GitHub.Copilot.SDK; [JsonDerivedType(typeof(SessionInfoEvent), "session.info")] [JsonDerivedType(typeof(SessionModelChangeEvent), "session.model_change")] [JsonDerivedType(typeof(SessionResumeEvent), "session.resume")] +[JsonDerivedType(typeof(SessionSnapshotRewindEvent), "session.snapshot_rewind")] [JsonDerivedType(typeof(SessionStartEvent), "session.start")] [JsonDerivedType(typeof(SessionTruncationEvent), "session.truncation")] [JsonDerivedType(typeof(SessionUsageInfoEvent), "session.usage_info")] @@ -181,6 +182,18 @@ public partial class SessionTruncationEvent : SessionEvent public required SessionTruncationData Data { get; set; } } +/// +/// Event: session.snapshot_rewind +/// +public partial class SessionSnapshotRewindEvent : SessionEvent +{ + [JsonIgnore] + public override string Type => "session.snapshot_rewind"; + + [JsonPropertyName("data")] + public required SessionSnapshotRewindData Data { get; set; } +} + /// /// Event: session.usage_info /// @@ -620,6 +633,15 @@ public partial class SessionTruncationData public required string PerformedBy { get; set; } } +public partial class SessionSnapshotRewindData +{ + [JsonPropertyName("upToEventId")] + public required string UpToEventId { get; set; } + + [JsonPropertyName("eventsRemoved")] + public required double EventsRemoved { get; set; } +} + public partial class SessionUsageInfoData { [JsonPropertyName("tokenLimit")] @@ -844,6 +866,14 @@ public partial class ToolExecutionStartData [JsonPropertyName("arguments")] public object? Arguments { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpServerName")] + public string? McpServerName { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("mcpToolName")] + public string? McpToolName { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("parentToolCallId")] public string? ParentToolCallId { get; set; } @@ -1055,10 +1085,22 @@ public partial class SessionCompactionCompleteDataCompactionTokensUsed public required double CachedInput { get; set; } } -public partial class UserMessageDataAttachmentsItem +public partial class UserMessageDataAttachmentsItemFile : UserMessageDataAttachmentsItem { - [JsonPropertyName("type")] - public required UserMessageDataAttachmentsItemType Type { get; set; } + [JsonIgnore] + public override string Type => "file"; + + [JsonPropertyName("path")] + public required string Path { get; set; } + + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } +} + +public partial class UserMessageDataAttachmentsItemDirectory : UserMessageDataAttachmentsItem +{ + [JsonIgnore] + public override string Type => "directory"; [JsonPropertyName("path")] public required string Path { get; set; } @@ -1067,6 +1109,64 @@ public partial class UserMessageDataAttachmentsItem public required string DisplayName { get; set; } } +public partial class UserMessageDataAttachmentsItemSelectionSelectionStart +{ + [JsonPropertyName("line")] + public required double Line { get; set; } + + [JsonPropertyName("character")] + public required double Character { get; set; } +} + +public partial class UserMessageDataAttachmentsItemSelectionSelectionEnd +{ + [JsonPropertyName("line")] + public required double Line { get; set; } + + [JsonPropertyName("character")] + public required double Character { get; set; } +} + +public partial class UserMessageDataAttachmentsItemSelectionSelection +{ + [JsonPropertyName("start")] + public required UserMessageDataAttachmentsItemSelectionSelectionStart Start { get; set; } + + [JsonPropertyName("end")] + public required UserMessageDataAttachmentsItemSelectionSelectionEnd End { get; set; } +} + +public partial class UserMessageDataAttachmentsItemSelection : UserMessageDataAttachmentsItem +{ + [JsonIgnore] + public override string Type => "selection"; + + [JsonPropertyName("filePath")] + public required string FilePath { get; set; } + + [JsonPropertyName("displayName")] + public required string DisplayName { get; set; } + + [JsonPropertyName("text")] + public required string Text { get; set; } + + [JsonPropertyName("selection")] + public required UserMessageDataAttachmentsItemSelectionSelection Selection { get; set; } +} + +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "type", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(UserMessageDataAttachmentsItemFile), "file")] +[JsonDerivedType(typeof(UserMessageDataAttachmentsItemDirectory), "directory")] +[JsonDerivedType(typeof(UserMessageDataAttachmentsItemSelection), "selection")] +public partial class UserMessageDataAttachmentsItem +{ + [JsonPropertyName("type")] + public virtual string Type { get; set; } = string.Empty; +} + + public partial class AssistantMessageDataToolRequestsItem { [JsonPropertyName("toolCallId")] @@ -1088,6 +1188,10 @@ public partial class ToolExecutionCompleteDataResult { [JsonPropertyName("content")] public required string Content { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("detailedContent")] + public string? DetailedContent { get; set; } } public partial class ToolExecutionCompleteDataError @@ -1130,15 +1234,6 @@ public enum SessionHandoffDataSourceType Local, } -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum UserMessageDataAttachmentsItemType -{ - [JsonStringEnumMemberName("file")] - File, - [JsonStringEnumMemberName("directory")] - Directory, -} - [JsonConverter(typeof(JsonStringEnumConverter))] public enum AssistantMessageDataToolRequestsItemType { @@ -1208,6 +1303,8 @@ public enum SystemMessageDataRole [JsonSerializable(typeof(SessionResumeData))] [JsonSerializable(typeof(SessionResumeDataContext))] [JsonSerializable(typeof(SessionResumeEvent))] +[JsonSerializable(typeof(SessionSnapshotRewindData))] +[JsonSerializable(typeof(SessionSnapshotRewindEvent))] [JsonSerializable(typeof(SessionStartData))] [JsonSerializable(typeof(SessionStartDataContext))] [JsonSerializable(typeof(SessionStartEvent))] @@ -1240,5 +1337,11 @@ public enum SystemMessageDataRole [JsonSerializable(typeof(ToolUserRequestedEvent))] [JsonSerializable(typeof(UserMessageData))] [JsonSerializable(typeof(UserMessageDataAttachmentsItem))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemDirectory))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemFile))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelection))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelection))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionEnd))] +[JsonSerializable(typeof(UserMessageDataAttachmentsItemSelectionSelectionStart))] [JsonSerializable(typeof(UserMessageEvent))] 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 64feeade..98af62b5 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -2,7 +2,7 @@ // // Generated from: @github/copilot/session-events.schema.json // Generated by: scripts/generate-session-types.ts -// Generated at: 2026-01-22T04:11:05.365Z +// Generated at: 2026-01-26T18:08:33.950Z // // To update these types: // 1. Update the schema in copilot-agent-runtime @@ -70,6 +70,8 @@ type Data struct { PreTruncationTokensInMessages *float64 `json:"preTruncationTokensInMessages,omitempty"` TokenLimit *float64 `json:"tokenLimit,omitempty"` TokensRemovedDuringTruncation *float64 `json:"tokensRemovedDuringTruncation,omitempty"` + EventsRemoved *float64 `json:"eventsRemoved,omitempty"` + UpToEventID *string `json:"upToEventId,omitempty"` CurrentTokens *float64 `json:"currentTokens,omitempty"` MessagesLength *float64 `json:"messagesLength,omitempty"` CompactionTokensUsed *CompactionTokensUsed `json:"compactionTokensUsed,omitempty"` @@ -108,6 +110,8 @@ type Data struct { Arguments interface{} `json:"arguments"` ToolCallID *string `json:"toolCallId,omitempty"` ToolName *string `json:"toolName,omitempty"` + MCPServerName *string `json:"mcpServerName,omitempty"` + MCPToolName *string `json:"mcpToolName,omitempty"` PartialOutput *string `json:"partialOutput,omitempty"` ProgressMessage *string `json:"progressMessage,omitempty"` IsUserRequested *bool `json:"isUserRequested,omitempty"` @@ -127,9 +131,27 @@ type Data struct { } type Attachment struct { - DisplayName string `json:"displayName"` - Path string `json:"path"` - Type AttachmentType `json:"type"` + DisplayName string `json:"displayName"` + Path *string `json:"path,omitempty"` + Type AttachmentType `json:"type"` + FilePath *string `json:"filePath,omitempty"` + Selection *SelectionClass `json:"selection,omitempty"` + Text *string `json:"text,omitempty"` +} + +type SelectionClass struct { + End End `json:"end"` + Start Start `json:"start"` +} + +type End struct { + Character float64 `json:"character"` + Line float64 `json:"line"` +} + +type Start struct { + Character float64 `json:"character"` + Line float64 `json:"line"` } type CompactionTokensUsed struct { @@ -174,7 +196,8 @@ type Repository struct { } type Result struct { - Content string `json:"content"` + Content string `json:"content"` + DetailedContent *string `json:"detailedContent,omitempty"` } type ToolRequest struct { @@ -189,6 +212,7 @@ type AttachmentType string const ( Directory AttachmentType = "directory" File AttachmentType = "file" + Selection AttachmentType = "selection" ) type Role string @@ -235,6 +259,7 @@ const ( SessionInfo SessionEventType = "session.info" SessionModelChange SessionEventType = "session.model_change" SessionResume SessionEventType = "session.resume" + SessionSnapshotRewind SessionEventType = "session.snapshot_rewind" SessionStart SessionEventType = "session.start" SessionTruncation SessionEventType = "session.truncation" SessionUsageInfo SessionEventType = "session.usage_info" diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index b86e97d5..7b799f8a 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -3,7 +3,7 @@ * * Generated from: @github/copilot/session-events.schema.json * Generated by: scripts/generate-session-types.ts - * Generated at: 2026-01-22T04:11:04.988Z + * Generated at: 2026-01-26T18:08:33.710Z * * To update these types: * 1. Update the schema in copilot-agent-runtime @@ -127,6 +127,17 @@ export type SessionEvent = performedBy: string; }; } + | { + id: string; + timestamp: string; + parentId: string | null; + ephemeral: true; + type: "session.snapshot_rewind"; + data: { + upToEventId: string; + eventsRemoved: number; + }; + } | { id: string; timestamp: string; @@ -178,11 +189,34 @@ export type SessionEvent = data: { content: string; transformedContent?: string; - attachments?: { - type: "file" | "directory"; - path: string; - displayName: string; - }[]; + attachments?: ( + | { + type: "file"; + path: string; + displayName: string; + } + | { + type: "directory"; + path: string; + displayName: string; + } + | { + type: "selection"; + filePath: string; + displayName: string; + text: string; + selection: { + start: { + line: number; + character: number; + }; + end: { + line: number; + character: number; + }; + }; + } + )[]; source?: string; }; } @@ -340,6 +374,8 @@ export type SessionEvent = toolCallId: string; toolName: string; arguments?: unknown; + mcpServerName?: string; + mcpToolName?: string; parentToolCallId?: string; }; } @@ -377,6 +413,7 @@ export type SessionEvent = isUserRequested?: boolean; result?: { content: string; + detailedContent?: string; }; error?: { message: string; diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index f8d8f4f2..ba473c7f 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -3,16 +3,16 @@ Generated from: @github/copilot/session-events.schema.json Generated by: scripts/generate-session-types.ts -Generated at: 2026-01-22T04:11:05.267Z +Generated at: 2026-01-26T18:08:33.907Z To update these types: 1. Update the schema in copilot-agent-runtime 2. Run: npm run generate:session-types """ -from enum import Enum from dataclasses import dataclass -from typing import Any, Optional, Dict, Union, List, TypeVar, Type, Callable, cast +from typing import Any, Optional, Dict, Union, List, TypeVar, Type, cast, Callable +from enum import Enum from datetime import datetime from uuid import UUID import dateutil.parser @@ -22,16 +22,6 @@ EnumT = TypeVar("EnumT", bound=Enum) -def from_str(x: Any) -> str: - assert isinstance(x, str) - return x - - -def to_enum(c: Type[EnumT], x: Any) -> EnumT: - assert isinstance(x, c) - return x.value - - def from_float(x: Any) -> float: assert isinstance(x, (float, int)) and not isinstance(x, bool) return float(x) @@ -42,6 +32,16 @@ def to_float(x: Any) -> float: return x +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + def from_none(x: Any) -> Any: assert x is None return x @@ -56,6 +56,11 @@ def from_union(fs, x): assert False +def to_enum(c: Type[EnumT], x: Any) -> EnumT: + assert isinstance(x, c) + return 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() } @@ -75,35 +80,101 @@ def from_list(f: Callable[[Any], T], x: Any) -> List[T]: 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() +@dataclass +class End: + character: float + line: float + + @staticmethod + def from_dict(obj: Any) -> 'End': + assert isinstance(obj, dict) + character = from_float(obj.get("character")) + line = from_float(obj.get("line")) + return End(character, line) + + def to_dict(self) -> dict: + result: dict = {} + result["character"] = to_float(self.character) + result["line"] = to_float(self.line) + return result + + +@dataclass +class Start: + character: float + line: float + + @staticmethod + def from_dict(obj: Any) -> 'Start': + assert isinstance(obj, dict) + character = from_float(obj.get("character")) + line = from_float(obj.get("line")) + return Start(character, line) + + def to_dict(self) -> dict: + result: dict = {} + result["character"] = to_float(self.character) + result["line"] = to_float(self.line) + return result + + +@dataclass +class Selection: + end: End + start: Start + + @staticmethod + def from_dict(obj: Any) -> 'Selection': + assert isinstance(obj, dict) + end = End.from_dict(obj.get("end")) + start = Start.from_dict(obj.get("start")) + return Selection(end, start) + + def to_dict(self) -> dict: + result: dict = {} + result["end"] = to_class(End, self.end) + result["start"] = to_class(Start, self.start) + return result class AttachmentType(Enum): DIRECTORY = "directory" FILE = "file" + SELECTION = "selection" @dataclass class Attachment: display_name: str - path: str type: AttachmentType + path: Optional[str] = None + file_path: Optional[str] = None + selection: Optional[Selection] = None + text: Optional[str] = None @staticmethod def from_dict(obj: Any) -> 'Attachment': assert isinstance(obj, dict) display_name = from_str(obj.get("displayName")) - path = from_str(obj.get("path")) type = AttachmentType(obj.get("type")) - return Attachment(display_name, path, type) + 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")) + text = from_union([from_str, from_none], obj.get("text")) + return Attachment(display_name, type, path, file_path, selection, text) def to_dict(self) -> dict: result: dict = {} result["displayName"] = from_str(self.display_name) - result["path"] = from_str(self.path) result["type"] = to_enum(AttachmentType, self.type) + 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) + if self.text is not None: + result["text"] = from_union([from_str, from_none], self.text) return result @@ -266,16 +337,20 @@ def to_dict(self) -> dict: @dataclass class Result: content: str + detailed_content: Optional[str] = None @staticmethod def from_dict(obj: Any) -> 'Result': assert isinstance(obj, dict) content = from_str(obj.get("content")) - return Result(content) + detailed_content = from_union([from_str, from_none], obj.get("detailedContent")) + return Result(content, detailed_content) 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_str, from_none], self.detailed_content) return result @@ -351,6 +426,8 @@ class Data: pre_truncation_tokens_in_messages: Optional[float] = None token_limit: Optional[float] = None tokens_removed_during_truncation: Optional[float] = None + events_removed: Optional[float] = None + up_to_event_id: Optional[str] = None current_tokens: Optional[float] = None messages_length: Optional[float] = None compaction_tokens_used: Optional[CompactionTokensUsed] = None @@ -389,6 +466,8 @@ class Data: arguments: Any = None tool_call_id: Optional[str] = None tool_name: Optional[str] = None + mcp_server_name: Optional[str] = None + mcp_tool_name: Optional[str] = None partial_output: Optional[str] = None progress_message: Optional[str] = None is_user_requested: Optional[bool] = None @@ -437,6 +516,8 @@ def from_dict(obj: Any) -> 'Data': 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")) current_tokens = from_union([from_float, from_none], obj.get("currentTokens")) messages_length = from_union([from_float, from_none], obj.get("messagesLength")) compaction_tokens_used = from_union([CompactionTokensUsed.from_dict, from_none], obj.get("compactionTokensUsed")) @@ -475,6 +556,8 @@ def from_dict(obj: Any) -> 'Data': 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")) @@ -491,7 +574,7 @@ def from_dict(obj: Any) -> 'Data': metadata = from_union([Metadata.from_dict, from_none], obj.get("metadata")) name = from_union([from_str, from_none], obj.get("name")) role = from_union([Role, from_none], obj.get("role")) - return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, stack, info_type, new_model, previous_model, handoff_time, 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, current_tokens, messages_length, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, success, summary_content, tokens_removed, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, message_id, parent_tool_call_id, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, provider_call_id, quota_snapshots, reason, arguments, tool_call_id, tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, name, role) + return Data(context, copilot_version, producer, selected_model, session_id, start_time, version, event_count, resume_time, error_type, message, stack, info_type, new_model, previous_model, handoff_time, 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, current_tokens, messages_length, compaction_tokens_used, error, messages_removed, post_compaction_tokens, pre_compaction_messages_length, pre_compaction_tokens, success, summary_content, tokens_removed, attachments, content, source, transformed_content, turn_id, intent, reasoning_id, delta_content, message_id, parent_tool_call_id, tool_requests, total_response_size_bytes, api_call_id, cache_read_tokens, cache_write_tokens, cost, duration, initiator, input_tokens, model, output_tokens, provider_call_id, quota_snapshots, reason, arguments, tool_call_id, tool_name, mcp_server_name, mcp_tool_name, partial_output, progress_message, is_user_requested, result, tool_telemetry, agent_description, agent_display_name, agent_name, tools, hook_invocation_id, hook_type, input, output, metadata, name, role) def to_dict(self) -> dict: result: dict = {} @@ -551,6 +634,10 @@ def to_dict(self) -> dict: 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.current_tokens is not None: result["currentTokens"] = from_union([to_float, from_none], self.current_tokens) if self.messages_length is not None: @@ -627,6 +714,10 @@ def to_dict(self) -> dict: 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: @@ -683,6 +774,7 @@ class SessionEventType(Enum): SESSION_INFO = "session.info" SESSION_MODEL_CHANGE = "session.model_change" SESSION_RESUME = "session.resume" + SESSION_SNAPSHOT_REWIND = "session.snapshot_rewind" SESSION_START = "session.start" SESSION_TRUNCATION = "session.truncation" SESSION_USAGE_INFO = "session.usage_info" diff --git a/test/snapshots/session/should_abort_a_session.yaml b/test/snapshots/session/should_abort_a_session.yaml index 4c7093ad..5f524344 100644 --- a/test/snapshots/session/should_abort_a_session.yaml +++ b/test/snapshots/session/should_abort_a_session.yaml @@ -5,7 +5,9 @@ conversations: - role: system content: ${system} - role: user - content: run the shell command 'sleep 100' (works on bash and PowerShell) + content: run the shell command 'sleep 100' (note this works on both bash and PowerShell) + - role: assistant + content: I'll run the sleep command for 100 seconds. - role: assistant tool_calls: - id: toolcall_0 @@ -19,13 +21,14 @@ conversations: type: function function: name: ${shell} - arguments: '{"command":"sleep 100","description":"Run sleep 100 command","mode":"sync","initial_wait":30}' + arguments: '{"command":"sleep 100","description":"Run sleep 100 command","mode":"sync","initial_wait":105}' - messages: - role: system content: ${system} - role: user - content: run the shell command 'sleep 100' (works on bash and PowerShell) + content: run the shell command 'sleep 100' (note this works on both bash and PowerShell) - role: assistant + content: I'll run the sleep command for 100 seconds. tool_calls: - id: toolcall_0 type: function @@ -36,7 +39,7 @@ conversations: type: function function: name: ${shell} - arguments: '{"command":"sleep 100","description":"Run sleep 100 command","mode":"sync","initial_wait":30}' + arguments: '{"command":"sleep 100","description":"Run sleep 100 command","mode":"sync","initial_wait":105}' - role: tool tool_call_id: toolcall_0 content: Intent logged @@ -46,4 +49,4 @@ conversations: - role: user content: What is 2+2? - role: assistant - content: 2 + 2 = 4 + content: 2+2 equals 4. From d51cbfa9035178b55f3315edb245d09e92b6fee4 Mon Sep 17 00:00:00 2001 From: jmoseley Date: Mon, 26 Jan 2026 11:38:21 -0800 Subject: [PATCH 3/3] fix: remove unused variable in C# generator loop --- nodejs/scripts/generate-csharp-session-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nodejs/scripts/generate-csharp-session-types.ts b/nodejs/scripts/generate-csharp-session-types.ts index 1bc90e64..cf295117 100644 --- a/nodejs/scripts/generate-csharp-session-types.ts +++ b/nodejs/scripts/generate-csharp-session-types.ts @@ -411,7 +411,7 @@ function generatePolymorphicClasses( lines.push(` UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]`); // Add JsonDerivedType attributes for each variant - for (const [constValue, variant] of discriminatorInfo.mapping) { + for (const [constValue] of discriminatorInfo.mapping) { const derivedClassName = `${baseClassName}${toPascalCase(constValue)}`; lines.push(`[JsonDerivedType(typeof(${derivedClassName}), "${constValue}")]`); }