Skip to content

Replace Go RPC quicktype generation#1234

Merged
stephentoub merged 8 commits intogithub:mainfrom
qmuntal:go-custom-rpc-codegen
May 9, 2026
Merged

Replace Go RPC quicktype generation#1234
stephentoub merged 8 commits intogithub:mainfrom
qmuntal:go-custom-rpc-codegen

Conversation

@qmuntal
Copy link
Copy Markdown
Contributor

@qmuntal qmuntal commented May 8, 2026

Motivation

Go RPC type generation currently depends on quicktype plus several post-processing passes. That breaks down when distinct schema definitions have the same structural shape: quicktype can merge those definitions into one Go type, which caused separate protocol concepts such as MCPServerSource and DiscoveredMCPServerSource to be deduplicated. Quicktype does not expose a suitable Go option to disable that same-layout merging, so the generated API cannot reliably preserve schema-level type identity.

The quicktype output also made casing harder to keep consistent with this repository's Go naming policy. Owning the Go generator lets us apply one explicit initialism list and avoid a mix of incidental quicktype casing plus local post-processing fixes.

This change follows the custom-generator approach used by other SDK codegen and replaces quicktype for Go RPC types with a schema-aware Go generator. The generator preserves named definitions, keeps generated declarations sorted, emits quicktype-like wrapped comments for RPC output, preserves enum ordering, and flattens object unions so the generated Go surface stays close to the existing quicktype shape where that behavior was desirable.

Dropping quicktype also makes the Go codegen easier to reason about: the generator now owns the schema-to-Go mapping directly instead of generating quicktype output and then repairing it with a pile of post-processing hacks. The end result is better-controlled codegen with an equivalent number of lines, but with the behavior expressed in the generator instead of scattered after-the-fact rewrites.

Python codegen still uses quicktype; this only removes quicktype from the Go RPC generation path.

Breaking changes

This changes the generated Go API surface. The breaking changes are intentionally limited to generated Go types, constants, fields, and method signatures that reference those generated types.

The main breaking-change patterns are:

  • Generated names now follow the Go generator's current initialism list exactly. Since mcp, fs, and sse are intentionally not initialisms in this PR, generated MCP... / SessionFS... names become Mcp... / SessionFs.... The same casing rule also applies to generated field names such as PID and MS suffixes.
  • The synthetic RPCTypes root type is removed. It existed only as a quicktype input artifact and was not a real RPC schema concept.
  • Distinct schema definitions that happen to have identical structure now remain distinct Go types instead of being collapsed into shared quicktype helper types. As a result, some previously shared helper types become schema-specific names, especially around models/plugins, workspace metadata, task status/execution mode, permission approval kinds, and filter mapping values.
  • Discriminated object unions are flattened into Go structs, matching the useful quicktype shape while preserving schema identity. This can produce schema-specific discriminator fields/types instead of reusing broad merged enum names.
  • Non-discriminated object unions are also flattened where quicktype previously produced a useful value struct shape, such as MCP server config and UI elicitation schema property unions.
  • Fields that quicktype previously emitted as any or broad primitive types are now generated as typed structs, typed maps, or schema-specific enum/string aliases when the schema provides that information. This affects areas such as embedded resources, UI elicitation values, and session-event payload details.

Call sites and tests in the Go SDK were updated for the renamed generated RPC types, renamed fields, schema-specific helper types, and typed generated fields.

Why the breaks are done

The generated RPC package is derived from the protocol schema, so preserving the schema's named concepts is more important than preserving quicktype's incidental same-layout merging. Keeping separate named types avoids accidental aliasing between unrelated protocol concepts, makes future schema evolution safer, and removes fragile post-generation rewrites that were compensating for quicktype behavior.

The casing changes are done so generated names consistently follow the generator's explicit initialism list. The Mcp and SessionFs casing is intentional for this PR: it keeps the current initialism policy unchanged, so adding mcp, fs, or sse can be reviewed separately if desired.

Validation

  • npm run generate:go
  • go test ./... -run '^$'

Copilot AI review requested due to automatic review settings May 8, 2026 14:25
@qmuntal qmuntal requested a review from a team as a code owner May 8, 2026 14:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces Go RPC type generation from quicktype + post-processing with a schema-aware Go generator to preserve schema-level type identity (preventing structural deduplication collisions) and to keep the generated surface stable/sorted.

Changes:

  • Reworked scripts/codegen/go.ts to generate Go RPC types directly from schema definitions (no quicktype), including comment wrapping and union/object-flattening logic.
  • Updated Go SDK code and E2E tests for renamed generated types (e.g., SessionFs..., Mcp...) and updated shapes.
  • Adjusted schema post-processing documentation and added a local TS module type shim for wordwrap.
Show a summary per file
File Description
scripts/codegen/utils.ts Updates schema post-processing comments to reflect broader generator usage (not just quicktype Go).
scripts/codegen/types.d.ts Adds a local TS module declaration for wordwrap to satisfy TS tooling.
scripts/codegen/go.ts Replaces quicktype-based Go RPC generation with a custom generator; adds comment wrapping and new type-shape logic.
nodejs/package.json Adds @types/wordwrap (currently appears misplaced relative to where wordwrap is used).
nodejs/package-lock.json Lockfile updates for the added typings dependency.
go/types.go Updates references to renamed generated RPC types (SessionFs...).
go/session.go Updates UI elicitation defaults to use toRPCContent with new generated types.
go/session_fs_provider.go Updates handler and types to renamed generated RPC SessionFs request/result/error types.
go/rpc/generated_rpc.go Regenerated Go RPC types and wrappers using the new generator (major surface changes).
go/internal/e2e/session_fs_e2e_test.go Updates SessionFs-related test references and messages for renamed types.
go/internal/e2e/rpc_session_state_e2e_test.go Updates MCP OAuth request type name.
go/internal/e2e/rpc_server_e2e_test.go Updates MCP discover request type name.
go/internal/e2e/rpc_mcp_config_e2e_test.go Updates MCP config types/enums to new generated names and shapes.
go/internal/e2e/rpc_mcp_and_skills_e2e_test.go Updates MCP enable/disable request type names.
go/generated_session_events.go Regenerated session event types with more specific field types (notably elicitation/tool resource fields).
go/client.go Updates SessionFs provider registration request type name.
go/client_test.go Updates SessionFs conventions enum type name in tests.

Copilot's findings

Files not reviewed (1)
  • nodejs/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)

nodejs/package.json:70

  • @types/wordwrap is added to the Node SDK package devDependencies, but there are no imports/usages of wordwrap under nodejs/. The only usage is in scripts/codegen/go.ts, which is a different npm package (scripts/codegen/package.json). This dependency should be moved to the codegen package (or removed if scripts/codegen/types.d.ts is sufficient).
    "devDependencies": {
        "@platformatic/vfs": "^0.3.0",
        "@types/node": "^25.2.0",
        "@types/wordwrap": "^1.0.3",
        "@typescript-eslint/eslint-plugin": "^8.54.0",
        "@typescript-eslint/parser": "^8.54.0",
        "esbuild": "^0.27.2",
        "eslint": "^9.0.0",
  • Files reviewed: 14/17 changed files
  • Comments generated: 3

Comment thread scripts/codegen/go.ts
Comment thread scripts/codegen/go.ts
Comment thread scripts/codegen/go.ts Outdated
@qmuntal qmuntal marked this pull request as draft May 8, 2026 14:32
@qmuntal qmuntal marked this pull request as ready for review May 8, 2026 14:51
@qmuntal qmuntal requested a review from Copilot May 8, 2026 14:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

Files not reviewed (2)
  • nodejs/package-lock.json: Language not supported
  • scripts/codegen/package-lock.json: Language not supported
Comments suppressed due to low confidence (1)

scripts/codegen/go.ts:1584

  • The generated RPC union structs (e.g., from emitGoUnionStruct) call errors.New in UnmarshalJSON, but the import list only adds "errors" when schema.clientSession is present. If the schema ever omits clientSession (or it’s filtered out), the generated file will fail to compile due to missing errors import. Consider adding "errors" when generatedTypeCode references errors.New / "errors." (similar to the existing time.Time include check), rather than tying it to clientSession.
    const imports = [`"context"`, `"encoding/json"`];
    if (generatedTypeCode.includes("time.Time")) {
        imports.push(`"time"`);
    }
    if (schema.clientSession) {
        imports.push(`"errors"`, `"fmt"`);
    }
  • Files reviewed: 16/20 changed files
  • Comments generated: 0 new

Comment thread go/rpc/generated_rpc.go
Comment thread go/rpc/generated_rpc.go
Comment thread go/rpc/generated_rpc.go Outdated
@SteveSandersonMS
Copy link
Copy Markdown
Contributor

Thanks @qmuntal! This looks like a great improvements. Just had a couple of comments but I'm sure we can get this merged soon.

@qmuntal qmuntal force-pushed the go-custom-rpc-codegen branch from 62e5a3a to 39a4786 Compare May 9, 2026 12:16
@qmuntal
Copy link
Copy Markdown
Contributor Author

qmuntal commented May 9, 2026

@stephentoub this can be merged now, thanks!

@stephentoub stephentoub added this pull request to the merge queue May 9, 2026
Merged via the queue into github:main with commit 30a76a5 May 9, 2026
18 of 21 checks passed
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.

4 participants