Skip to content

Possible naming improvements via codegen changes#1043

Draft
SteveSandersonMS wants to merge 11 commits intomainfrom
stevesa/rpc-naming-improvments
Draft

Possible naming improvements via codegen changes#1043
SteveSandersonMS wants to merge 11 commits intomainfrom
stevesa/rpc-naming-improvments

Conversation

@SteveSandersonMS
Copy link
Copy Markdown
Contributor

@SteveSandersonMS SteveSandersonMS commented Apr 8, 2026

Draft of possible changes to RPC naming convention changes to fix #1035

This PR improves generated public type names across the SDKs by moving naming decisions into the shared schema/codegen pipeline instead of relying on language-specific quirks.

The goal is to make generated names:

  • shorter when the old names were just path concatenation noise
  • more specific when the old names were too generic to be useful
  • more consistent across TypeScript, C#, Go, and Python
  • more stable, because naming now follows a common set of rules plus explicit schema-local overrides where the API really wants a specific public name

Main naming rules

The naming is now mostly mechanical rather than hand-curated:

  1. Derive names from the RPC method / event identity first

    • Example: account.getQuota becomes an AccountQuota root instead of AccountGetQuotaResult.
  2. Drop generic namespace and filler words

    • Strip prefixes like session.
    • Strip terminal filler verbs like get, read, and list when they are only structural
  3. Singularize list entities

    • Example: models.list uses Model for nested item types and ModelList for the top-level result
  4. Keep top-level wrapper words only when they are actually useful

    • Request / Result remain for top-level params/results
    • nested helper types stop inheriting repeated ...Request...Result...Value...Item noise
  5. Drop purely structural wrapper property names in nested helpers

    • Words like data, result, request, response, kind, value, etc. are removed when they are just containers, not meaningful domain terms
  6. Normalize overlapping words

    • Avoid awkward duplication like requested + request
  7. Use explicit schema-local names for true API surface decisions

    • When the API really wants a specific public name, that name is declared in the schema instead of in generator-side override maps

Before / after examples

Previously too long

Before After
AccountGetQuotaResultQuotaSnapshotsValue AccountQuotaSnapshot
SessionPermissionsHandlePendingPermissionRequestResult PermissionRequestResult
SessionToolsHandlePendingToolCallResult HandleToolCallResult
SessionUiElicitationRequestRequestedSchema UiElicitationSchema
SessionUiHandlePendingElicitationRequestResult UiElicitationResponse
SessionUiHandlePendingElicitationResult UiElicitationResult
ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme ToolExecutionCompleteContentResourceLinkIconTheme
UserMessageDataAttachmentsItemSelectionSelectionEnd UserMessageAttachmentSelectionDetailsEnd

Previously too short / too generic

Before After
Entry SessionFsReaddirWithTypesEntry / SessionFSReaddirWithTypesEntry
End UserMessageAttachmentSelectionDetailsEnd
Mode SessionMode
SessionModeGetResultMode SessionMode

A few especially notable cases

  • The worst "path explosion" example was the old icon theme helper name:

    • ToolExecutionCompleteDataResultContentsItemResourceLinkIconsItemTheme
    • now: ToolExecutionCompleteContentResourceLinkIconTheme
  • The worst "too generic to understand in isolation" examples were names like:

    • Entry
    • End
    • Mode

    Those now carry enough domain context to be understandable in generated SDKs without needing to inspect the surrounding method/property path.

API-specific naming that is now explicit

A few names are intentionally chosen as API surface names rather than just synthesized mechanically, for example:

  • UiElicitationResponse
  • UiElicitationResult
  • HandlePendingElicitationRequest
  • PermissionDecision
  • PermissionDecisionRequest
  • PermissionRequestResult
  • ModeGetResult
  • ModeSetResult
  • HandleToolCallResult

That keeps the generator logic generic while still letting the schema declare clear public names where needed.

TypeScript note

TypeScript still avoids exploding the surface area with every synthesized helper as a named export. Explicit public names are preserved where intended, while synthetic helper naming is mainly used to improve the non-TS SDKs and shared generation consistency.

Co-Authored-By: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1043

/**
* The elicitation response (accept with form values, decline, or cancel)
*/
export interface UiElicitationResponse {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate interface declaration

UiElicitationResponse is declared twice in this file (also at line 1073). TypeScript's declaration merging means this compiles, but it's a codegen artifact indicating that the same schema structure appears in two places without a shared $ref. The first occurrence (line 1073) is the result of session.ui.elicitation, and this second occurrence (line 1204) is the type for the result field in HandlePendingElicitationRequest.

In Go and Python, these two occurrences generate separate types (UIElicitationResponse and ResultClass). The duplicate here suggests the two schema locations should share the same definition reference, which would also fix the ResultClass naming in Go and Python.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in add04df. TypeScript now deduplicates repeated exported declarations emitted by per-method json-schema-to-typescript compiles, so this no longer produces a second UiElicitationResponse declaration.

… side

Co-Authored-By: SteveSandersonMS <1101362+SteveSandersonMS@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1043

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS
Copy link
Copy Markdown
Contributor Author

Addressed the review findings in add04df. Specifically: (1) TS now deduplicates repeated named export blocks from per-method json-schema-to-typescript compiles, which removes the duplicate SessionMode and duplicate UiElicitationResponse declarations; and (2) the Go/Python quicktype-based generators now collapse placeholder *Class duplicates onto the explicit titled type when the structures are identical, so HandlePendingElicitationRequest.result now uses UIElicitationResponse instead of ResultClass.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1043



class HostType(Enum):
class ContextChangedHostType(Enum):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency: This enum is named ContextChangedHostType here but StartContextHostType in Go and .NET. Consider adding a title/titleSuggestion to this schema node so quicktype produces StartContextHostType to match.



class Operation(Enum):
class ChangedOperation(Enum):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency: Python collapses both the plan.changed and workspace.fileChanged operation enums into a single ChangedOperation, while Go and .NET produce two separate enums PlanChangedOperation and WorkspaceFileChangedOperation. If they should be distinct, schema-level title/titleSuggestion overrides on each individual enum schema would resolve this.



class PermissionRequestKind(Enum):
class Kind(Enum):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming inconsistency: Kind is too generic and lacks context. Go names this PermissionRequestKind (renamed from PermissionRequestedDataPermissionRequestKind in this PR). Adding a titleSuggestion: "PermissionRequestKind" to this schema node would align the Python output with Go.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #1043

ExtensionsLoadedExtensionStatusStarting ExtensionsLoadedExtensionStatus = "starting"
)

// Type aliases for convenience.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Go compilation break: TYPE_ALIASES and CONST_ALIASES reference old type names

The convenience type/const aliases below this line still reference the old generated type names that no longer exist in this file after the renames. For example:

// TYPE_ALIASES in scripts/codegen/go.ts — still points to OLD names:
PermissionRequest        = PermissionRequestedDataPermissionRequest        // ❌ undefined; PermissionRequest is now a struct directly
PermissionRequestKind    = PermissionRequestedDataPermissionRequestKind    // ❌ redeclared (PermissionRequestKind is now a string type at line ~2227)
PermissionRequestCommand = PermissionRequestedDataPermissionRequestCommandsItem  // ❌ undefined
PossibleURL              = PermissionRequestedDataPermissionRequestPossibleUrlsItem // ❌ undefined
Attachment               = UserMessageDataAttachmentsItem                  // ❌ undefined
AttachmentType           = UserMessageDataAttachmentsItemType              // ❌ undefined

And the CONST_ALIASES block similarly references UserMessageDataAttachmentsItemTypeFile, PermissionRequestedDataPermissionRequestKindShell, etc. — all of which have been renamed.

The TYPE_ALIASES and CONST_ALIASES maps in scripts/codegen/go.ts (around line 800) were not updated as part of this PR. They need to be updated to reflect the new generated names, e.g.:

// Suggested fixes:
PossibleURL      = PermissionRequestShellPossibleUrl
Attachment       = UserMessageAttachment
AttachmentType   = UserMessageAttachmentType

// Const aliases:
AttachmentTypeFile = UserMessageAttachmentTypeFile
// etc.

Types like PermissionRequest and PermissionRequestKind are now direct struct/type declarations, so their alias entries can simply be removed (or the alias should map to the new canonical name if needed for backward compat).

type SessionPermissionsHandlePendingPermissionRequestParams struct {
RequestID string `json:"requestId"`
Result SessionPermissionsHandlePendingPermissionRequestParamsResult `json:"result"`
type PermissionDecisionRequest struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Go compilation break: non-generated SDK code references old type names

This PR renames SessionPermissionsHandlePendingPermissionRequestParamsPermissionDecisionRequest (and similarly for other types), but the non-generated Go SDK code in go/session.go and go/types.go still references the old names. A full list of ~43 identifiers from the rpc package that are missing includes:

Old name (used in non-generated code) New name (in this PR)
rpc.SessionCommandsHandlePendingCommandParams rpc.CommandsHandlePendingCommandRequest
rpc.SessionUIHandlePendingElicitationParams rpc.HandlePendingElicitationRequest
rpc.SessionUIHandlePendingElicitationParamsResult rpc.UIElicitationResponse
rpc.SessionPermissionsHandlePendingPermissionRequestParams rpc.PermissionDecisionRequest
rpc.ActionAccept / rpc.ActionCancel rpc.UIElicitationActionAccept / rpc.UIElicitationActionCancel
rpc.PropertyTypeBoolean / rpc.PropertyTypeString rpc.UIElicitationSchemaPropertyNumberTypeBoolean / ...String
rpc.SessionUIElicitationParams (structure changed)
rpc.ConventionsPosix / rpc.ConventionsWindows rpc.SessionFSSetProviderConventionsPosix / ...Windows
rpc.EntryTypeDirectory / rpc.EntryTypeFile rpc.SessionFSReaddirWithTypesEntryTypeDirectory / ...File
rpc.ModeInteractive / rpc.ModePlan rpc.SessionModeInteractive / rpc.SessionModePlan
rpc.LevelError etc. rpc.LogLevelError etc.
All rpc.SessionFS*Params rpc.SessionFS*Request

go/session.go, go/types.go, and other non-generated files will need to be updated to use the new names. This is expected to be a significant follow-up change to make the Go SDK compile.

SteveSandersonMS and others added 2 commits April 9, 2026 19:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

SteveSandersonMS and others added 4 commits April 9, 2026 19:38
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

Cross-SDK Consistency Review

This PR does thorough work improving generated type names across all four SDKs. Previous review runs identified several issues (duplicate SessionMode, ResultClass naming, Go compilation errors, TYPE_ALIASES stale references) — all of which have been addressed in the author's follow-up commits. The remaining open inline threads on python/copilot/generated/session_events.py (lines 453, 856, 902) for ContextChangedHostType, ChangedOperation, and Kind naming are still applicable.

I found two additional inconsistencies not flagged in prior runs:


⚠️ session.title_changedtitle field removed from TypeScript but kept in Go and .NET

This PR changed the TypeScript session.title_changed event data from:

data: { title: string; }

to:

data: {};

However, the corresponding Go struct SessionTitleChangedData (line 704 of go/generated_session_events.go) still has:

type SessionTitleChangedData struct {
    Title string `json:"title"`
}

And .NET's SessionTitleChangedData (dotnet/src/Generated/SessionEvents.cs:1222) still has required string Title.

If this reflects an underlying schema change (the title field is no longer emitted), Go and .NET should also drop it. If it was accidentally removed from the TypeScript definition, the TS file should be reverted. Either way, all three SDKs should agree.


⚠️ session.import_legacy event — added to TypeScript only

The PR adds a new session.import_legacy event to nodejs/src/generated/session-events.ts (line 523) with a large inline schema. This event type is absent from go/generated_session_events.go, dotnet/src/Generated/SessionEvents.cs, and python/copilot/generated/session_events.py.

All other session event types are defined consistently across all four SDKs. If session.import_legacy is an event that the CLI can emit at runtime, SDK consumers on Go, .NET, and Python will receive unrecognized events with no typed representation. If this event is internal/TypeScript-only (e.g., only used within the CLI runtime itself), please add a note in the PR explaining why it's excluded from the other SDKs.


Generated by SDK Consistency Review Agent for PR #1043 ·

Generated by SDK Consistency Review Agent for issue #1043 · ● 3.1M ·

/// <summary>RPC data type for SessionModeGet operations.</summary>
public class SessionModeGetResult
/// <summary>RPC data type for ModeGet operations.</summary>
public class ModeGetResult
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect there to be additional data that's returned from this operation in the future? If not, could it just return a SessionMode instead of a wrapper around one?

/// <summary>RPC data type for SessionModeSet operations.</summary>
public class SessionModeSetResult
/// <summary>RPC data type for ModeSet operations.</summary>
public class ModeSetResult
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed (rather than void) because the set operation might fail?

/// <summary>RPC data type for SessionPlanUpdate operations.</summary>
public class SessionPlanUpdateResult
/// <summary>RPC data type for PlanUpdate operations.</summary>
public class PlanUpdateResult
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these namings sound a little odd to my ears, e.g. PlanUpdateResult vs UpdatePlanResult. But not a big deal.

[Experimental(Diagnostics.Experimental)]
public class SessionAgentListResult
public class AgentList
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For C#, we could consider nesting types, having the request/response types for an RPC API be nested within the type that API is defined on, e.g. have AgentList be nested inside of AgentApi. That way, we minimize chances of conflict by giving them a namespace, e.g. when we add an API for getting a list of all subagents, if we were to just call them "agent", we'd have a conflict, but not if that listing method and the one for defined custom agents were on different types.

[Experimental(Diagnostics.Experimental)]
public class SessionAgentGetCurrentResult
public class AgentCurrent
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another case where I'm wondering if the wrapper is adding any value. For cases where we already need to return multiple pieces of data, of course, and for cases where we think we will, makes sense... but in a case like this, I struggle to think what else we might need to return, in which case this needn't be in the public API surface area and the relevant method can just return an AgentCurrentAgent (which itself is a questionable name)

@@ -711,13 +747,29 @@ internal class SessionAgentDeselectRequest
public string SessionId { get; set; } = string.Empty;
}

/// <summary>RPC data type for SessionAgentReload operations.</summary>
/// <summary>RPC data type for AgentReloadAgent operations.</summary>
public class AgentReloadAgent
Copy link
Copy Markdown
Collaborator

@stephentoub stephentoub Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We seem to have multiple types that are identical other than in name, e.g. select agent, current agent, reload agent... can we do something to dedup those in the SDK? That would then also help with naming, as we wouldn't need to have multiple names for the exact same thing.

[Experimental(Diagnostics.Experimental)]
public class SessionMcpDisableResult
public class McpDisableResult
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless we plan to add more to a type like this, can we model that in the SDK by just having the method return void / Task instead of an empty dummy object?


/// <summary>Per-model token and request metrics, keyed by model identifier.</summary>
[JsonPropertyName("modelMetrics")]
public Dictionary<string, UsageMetricsModelMetric> ModelMetrics { get => field ??= []; set; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate from the naming, but seeing here makes me think of it, I think we'll want to update the generator and the manually-written models to use interfaces rather than these concrete types. It gives us more flexibility for what we choose to implement in the future (neither Dictionary or List allows for derivation, overriding, etc., which means we can't do things like modification tracking, throwing on things we consider misuse, etc.) and it allows consumers when setting these to use other types without needing to do a translation.

{
/// <summary>Entry name.</summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;

/// <summary>Entry type.</summary>
[JsonPropertyName("type")]
public EntryType Type { get; set; }
public SessionFsReaddirWithTypesEntryType Type { get; set; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For .NET, I expect we'd want "Readdir" to be "ReadDir" or "ReadDirectory".

Plugin,
/// <summary>The <c>builtin</c> variant.</summary>
[JsonStringEnumMemberName("builtin")]
Builtin,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builtin or BuiltIn ?

@edburns
Copy link
Copy Markdown
Contributor

edburns commented Apr 10, 2026

Full disclosure 01: using my human brain Java subject matter expertise, Opus 4.6, and existing context, I arrived at these comments, in light of the comments Stephen posted to Slack.

Full disclosure 02: The current agentic workflow derives the Java SDK directly from the post-code-generated parts of the reference implementation. I have an outstanding work item ( https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2751554 ) to make it so the Java SDK also uses the Zod schema, in addition to the agentic workflow to reduce toil in keeping the Java SDK in synch with the .NET and TypeScript reference implementations (See agentic workflow). Therefore, my comments are to be taken in light of what I would like to see before I undertake the work to implement dd-2751554-do-code-gen-for-java.

Disclosures aside, I stand by the following comments.

1. Reinforce @stephentoub 's point on empty types — with a Java-specific angle

+1 on eliminating empty result types. From the Java SDK perspective, the 13 empty types (PlanUpdateResult, AgentDeselect, SkillsEnableResult, McpReload, etc.) present an awkward ergonomics issue. Java doesn't have Task vs Task<T> — we use CompletableFuture<Void> for void results. Generating an empty record like public record McpReload() {} would feel unnatural to Java developers and force consumers to handle a meaningless return type. If the schema expressed these as null/void results, each language generator could map to its natural void representation (Task in C#, CompletableFuture<Void> in Java, None in Python, empty struct or no return in Go).

Question: Would it make sense for empty result schemas to be represented as null in the API schema rather than { "type": "object", "properties": {} }? That way generators could choose void/Void without needing per-language special-casing. Happy to discuss if there's a reason the current shape is preferred.

2. Reinforce the duplicate types point — with specific examples

+1 on consolidating identical structures. The Agent group stood out to me: Agent, AgentCurrentAgent, AgentSelectAgent, and AgentReloadAgent are structurally identical (all have name, displayName, description). Similarly, ModelCurrent and ModelSwitchToResult both wrap a single modelId: string?. And four types (UiElicitationResult, PermissionRequestResult, HandleToolCallResult, CommandsHandlePendingCommandResult) are all { success: boolean }.

In Java, each of these becomes a separate record class. The naming machinery in PR 5908 does a nice job producing clean names for them, but I wonder if we could go a step further: would it be feasible to define a shared Agent schema (with $ref) and have agent.select, agent.current, and agent.reload all reference it? I noticed that hoistTitledSchemas() already deduplicates schemas that share the same title — so maybe it's just a matter of giving these inline definitions a shared title? I might be missing a reason they need to be separate.

3. Namespacing: Java has a different approach than C# nested types

On the namespacing suggestion: For context on how this lands in Java — our natural namespacing mechanism is packages, not nested classes. Where C# would nest Agent.SelectResult inside a class Agent, Java would typically use com.github.copilot.sdk.rpc.agents.SelectResult. This tends to work well in practice because:

  • It avoids the Agent.Agent pattern (outer class and inner class sharing a name, which is a Java code smell).
  • It aligns with how Jackson deserialization resolves types.
  • It enables per-package package-info.java for group documentation.

One thing I'd love to explore: package-based namespacing would benefit from the schema carrying some grouping information so generators know which package/namespace a type belongs to. Today, the schema titles are flat strings (AgentSelectResult, McpConfigServerLocal). I'm wondering whether it might be worth considering an optional group or namespace annotation alongside title — that way each language generator could map to its natural grouping mechanism (packages in Java, nested types in C#, module prefixes in Python) without parsing the group from the type name string. This may be out of scope for this PR, but wanted to plant the seed.

4. Java-specific observation: the MCP filter mapping type explosion

From the Java SDK perspective, the MCP config types are a notable hotspot. There are 18 FilterMapping variant types across 6 contexts (server local, server http, add local, add http, update local, update http) — structurally identical in groups of 3. In Java, each would become a separate sealed interface + record hierarchy, with identical Jackson @JsonTypeInfo/@JsonSubTypes annotations multiplied 6×.

This hasn't been an issue in practice because the Java SDK hasn't implemented MCP config types yet (it was easy to skip when hand-writing). With codegen, they'd all materialize automatically. I'm curious — is there a reason these can't share a single McpConfigFilterMapping definition with $ref? There may be a forward-compatibility reason I'm not seeing for keeping them separate.

5. Stability guarantee + Java sealed types interaction

The stability guarantee from PR 5908 (new schemas never rename existing types) is very welcome. One subtlety for Java: with sealed interfaces/classes, adding a new event type requires updating the permits clause on the base type. This means the generated base type changes on every new event — which is fine since it's generated. But it also means consumers who do exhaustive switch on event types will get compile warnings when new events are added. This is actually desirable behavior (it's the point of sealed types), but it's worth documenting as an expected contract: adding a new event type in the runtime is a source-compatible but not pattern-exhaustiveness-compatible change.

6. Single-property wrappers: a different perspective from the Java side

On single-property wrapper types: I wanted to offer a slightly different perspective from the Java side. Stephen's suggestion to return the raw value directly (e.g., string instead of ShellExecResult { processId: string }) makes a lot of sense for .NET ergonomics. In Java, there's a tension: CompletableFuture<String> is less self-documenting than CompletableFuture<ShellExecResult>, and unwrapping removes the ability to add fields later without a breaking change.

One possible approach: Keep the wrapper types in the schema (for extensibility), but mark single-property types with a schema annotation (e.g., "unwrappable": true). Generators that prefer unwrapping (C#) could do so; generators that prefer wrapper types (Java, for future-proofing) could ignore it. This would avoid baking an unwrapping decision into the shared schema that all languages must follow. That said, I recognize this adds complexity to the schema — I'm open to simpler alternatives if others have ideas.

Comment on lines 66 to 67
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't naming-related, but lots of APIs use double when they would be better represented as int, TimeSpan, etc. It would be nice if we could support a richer set of numeric types in generated code.

Comment on lines +158 to +159
/// <summary>RPC data type for ModelList operations.</summary>
public class ModelList
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be a good time to make these classes sealed?

@@ -2297,17 +2513,17 @@ internal UiApi(JsonRpc rpc, string sessionId)
}

/// <summary>Calls "session.ui.elicitation".</summary>
public async Task<SessionUiElicitationResult> ElicitationAsync(string message, SessionUiElicitationRequestRequestedSchema requestedSchema, CancellationToken cancellationToken = default)
public async Task<UiElicitationResponse> ElicitationAsync(string message, UiElicitationSchema requestedSchema, CancellationToken cancellationToken = default)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ElicitAsync might be a better name for this.

Comment on lines 814 to 819
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is a wrapper type that probably doesn't have to exist (could we just use List<Skill> directly)?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see similar patterns elsewhere (like McpList)

Comment on lines +905 to 910
public class McpList
{
/// <summary>Configured MCP servers.</summary>
[JsonPropertyName("servers")]
public List<Server> Servers { get => field ??= []; set; }
public List<McpServer> Servers { get => field ??= []; set; }
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if we decide to keep these wrapper types, it seems like this one should be McpServerList.

/// <summary>RPC data type for SessionUiElicitation operations.</summary>
public class SessionUiElicitationResult
/// <summary>The elicitation response (accept with form values, decline, or cancel).</summary>
public class UiElicitationResponse
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a nit, but I think UI feels more natural than Ui.

}

/// <summary>Token usage metrics for this model.</summary>
public class UsageMetricsModelMetricUsage
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these names still seem a bit unwieldy (though for cases like this I anticipate we could just use .withTypeName and override it).

Comment on lines +1436 to +1445
public class UsageMetricsModelMetric
{
/// <summary>Request count and cost metrics for this model.</summary>
[JsonPropertyName("requests")]
public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; }

/// <summary>Token usage metrics for this model.</summary>
[JsonPropertyName("usage")]
public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; }
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if some of these would make sense as nested types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Review all codegenerated type names; provide better names in many cases

4 participants