Skip to content

Go codegen: per-event-type data structs#1037

Merged
stephentoub merged 4 commits intomainfrom
stoub/fixgocodegen
Apr 7, 2026
Merged

Go codegen: per-event-type data structs#1037
stephentoub merged 4 commits intomainfrom
stoub/fixgocodegen

Conversation

@stephentoub
Copy link
Copy Markdown
Collaborator

Fixes #1031.

Problem

The Go codegen for session events used quicktype, which represented the discriminated union of all session event Data payloads as a single flat struct with every property from every event type merged together — all as optional pointers. Adding a new event type with a same-named-but-different-typed property silently changed the shared field's type, breaking all existing consumers.

Solution

Replace quicktype-based session events generation with custom codegen that produces per-event-type data structs, matching the C# codegen approach. Each event type gets its own data struct (e.g., SessionStartData, AssistantMessageData), and SessionEvent.Data is now a SessionEventData interface accessed via type switches:

switch d := event.Data.(type) {
case *copilot.AssistantMessageData:
    fmt.Println(d.Content)
case *copilot.SessionErrorData:
    fmt.Println(d.Message)
}

Adding event type B never changes the struct shape of event type A.

Changes

  • scripts/codegen/go.ts — Replaced generateSessionEvents() with custom codegen producing 74 per-event data structs, SessionEventData interface, custom UnmarshalJSON/MarshalJSON on SessionEvent, and RawSessionEventData for forward-compatible handling of unknown event types.
  • go/generated_session_events.go — Regenerated output with per-event structs, shared nested types, enums, and type/const aliases for backward compatibility (PermissionRequest, Attachment, AttachmentType*, PermissionRequestKind*).
  • go/session.go — Updated handleBroadcastEvent() and SendAndWait() to use idiomatic Go type switches. Updated doc examples.
  • go/samples/chat.go — Updated to use type switches.
  • go/session_test.go — Updated to construct events with per-event data structs.
  • E2E tests (10 files) — Updated all event.Data.* access patterns to use type assertions.
  • go/rpc/generated_rpc.go — Minor renames from adding ui, uri, mime to Go initialisms (UiUI, UriURI — correct Go convention).
  • .github/workflows/codegen-check.yml — Removed the git checkout -- go/generated_session_events.go workaround.
  • .gitattributes — Added eol=lf for generated files to prevent cross-platform line ending diffs.

Breaking changes

  • event.Data is now a SessionEventData interface instead of a flat Data struct. Consumers must use type switches or type assertions to access per-event fields.
  • RPC field renames: SessionRpc.UiSessionRpc.UI, UiApiUIApi, FormatUriFormatURI (correct Go initialism convention).

Testing

  • go build ./... — clean
  • go vet ./... — clean
  • Unit tests — all pass
  • E2E tests — compile clean (runtime requires test harness)

@stephentoub stephentoub requested a review from a team as a code owner April 7, 2026 16:14
Copilot AI review requested due to automatic review settings April 7, 2026 16:14
@github-actions

This comment has been minimized.

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 fixes Go SDK breakages caused by quicktype’s “flat union” Data struct by switching Go session-event codegen to a custom generator that emits per-event-type *XxxData structs behind a SessionEventData interface, aligning Go with the C# approach and making additions of new event types additive-only.

Changes:

  • Replaced Go session-events quicktype generation with custom codegen producing per-event data structs plus custom SessionEvent (un)marshaling and forward-compatible RawSessionEventData.
  • Updated Go SDK runtime code, samples, README docs, and tests/E2E to use type assertions / type switches on event.Data.
  • Updated Go RPC generated types to follow Go initialism casing (UI, URI, MIME) and re-enabled codegen-check determinism (plus .gitattributes LF enforcement).
Show a summary per file
File Description
scripts/codegen/go.ts Implements custom Go session-events codegen (per-event structs, interface payloads, custom JSON (un)marshal).
go/generated_session_events.go Regenerated Go session-events types with per-event payload structs and SessionEventData interface.
go/session.go Updates event handling and examples to use type switches/assertions; updates RPC UI casing usage.
go/session_test.go Updates unit tests to construct events with new per-event data structs.
go/samples/chat.go Updates sample to read event payloads via type assertions.
go/rpc/generated_rpc.go Renames RPC API/types for Go initialisms (UIApi, FormatURI, SessionRpc.UI).
go/README.md Updates documentation examples to use type switches on event.Data.
go/client.go Updates inline usage example to use type assertion on event.Data.
go/internal/e2e/testharness/helper.go Updates harness helpers to interpret event payloads via type assertions.
go/internal/e2e/tools_test.go Updates E2E tool tests to use *AssistantMessageData payloads.
go/internal/e2e/tool_results_test.go Updates tool-results E2E checks to use typed payloads.
go/internal/e2e/streaming_fidelity_test.go Updates streaming delta/message checks to use typed payloads.
go/internal/e2e/skills_test.go Updates skill-marker assertions to use typed payloads.
go/internal/e2e/session_test.go Updates session start/message assertions and helper to extract message from typed payloads.
go/internal/e2e/permissions_test.go Updates permission/tool-complete checks to use typed payloads.
go/internal/e2e/multi_client_test.go Updates multi-client assertions for assistant/perms to use typed payloads.
go/internal/e2e/mcp_and_agents_test.go Updates MCP/agent E2E assertions to use typed payloads.
go/internal/e2e/compaction_test.go Updates compaction-success/message assertions to use typed payloads.
go/internal/e2e/commands_and_elicitation_test.go Updates commands/capabilities checks to use typed payloads.
.github/workflows/codegen-check.yml Removes the prior workaround that excluded go/generated_session_events.go from codegen checks.
.gitattributes Forces LF for generated files to reduce cross-platform diffs and marks them as generated.

Copilot's findings

  • Files reviewed: 20/21 changed files
  • Comments generated: 3

@github-actions

This comment has been minimized.

@stephentoub stephentoub changed the title Go codegen: per-event-type data structs (fixes #1031) Go codegen: per-event-type data structs Apr 7, 2026
@github-actions github-actions bot mentioned this pull request Apr 7, 2026
Replace the flat quicktype-generated Data union struct with per-event-type
data structs, matching the C# codegen approach. Adding a new session event
type can no longer change the struct shape of existing event types.

- Replace generateSessionEvents() in scripts/codegen/go.ts with custom
  codegen that produces 74 per-event data structs, a SessionEventData
  interface, and custom UnmarshalJSON/MarshalJSON on SessionEvent
- Update go/session.go to use idiomatic Go type switches for event dispatch
- Update samples, tests, and doc examples for the new API
- Add type/const aliases for PermissionRequest, Attachment, and related
  types to ease migration
- Add .gitattributes eol=lf for generated files to prevent cross-platform
  line ending diffs
- Re-enable the codegen-check CI workflow (remove go/generated_session_events.go
  revert workaround)
- Add ui, uri, mime to Go initialisms for correct field naming
Prefix all generated type comments with the type name per Go convention
(e.g., '// SessionStartData session initialization metadata.').
@github-actions

This comment has been minimized.

Update all Go code examples in docs/, test/scenarios/, and one E2E
test to use per-event type assertions instead of the old flat
event.Data.FieldName pattern.

Fix staticcheck SA5011 lint warning in tools_test.go (nil check
must precede type assertion).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

Cross-SDK Consistency Review

This PR is Go-specific, addressing a genuine codegen bug by replacing the quicktype-generated flat Data struct with per-event-type data structs. Here's a cross-SDK consistency assessment:

✅ Aligned with .NET

The new Go approach (SessionEventData interface + per-event structs accessed via type switches) aligns well with the .NET SDK's design, which uses [JsonPolymorphic]-decorated derived types per event. Both SDKs now give consumers type-safe access to event-specific fields without a merged flat struct.

✅ Node.js/TypeScript already type-safe

Node.js uses TypeScript discriminated union types for SessionEvent, so event.data is already typed per-event-type. No consistency gap there.

⚠️ Python has the same underlying quicktype issue

Python's generated/session_events.py still uses a quicktype-generated flat Data class with all event properties merged together — the same pattern this PR fixes in Go. This means:

  • SessionEvent.data in Python is a single Data class with every field from every event type (all optional)
  • Adding a new event type with a same-named but differently-typed property would silently break Python consumers in the same way it broke Go

This is a pre-existing inconsistency (not introduced by this PR), but worth tracking as a follow-up. The Python SDK would benefit from a similar codegen improvement — either per-event-type dataclasses or a Union discriminated on type.

Summary

The PR improves Go consistency with .NET and is well-scoped. The Python gap is pre-existing and worth addressing in a follow-up issue.

Generated by SDK Consistency Review Agent for issue #1037 ·

@stephentoub stephentoub added this pull request to the merge queue Apr 7, 2026
Merged via the queue into main with commit 9ef0dac Apr 7, 2026
36 checks passed
@stephentoub stephentoub deleted the stoub/fixgocodegen branch April 7, 2026 18:50
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.

Go session events codegen: flat union type causes breaking changes when adding new event types

3 participants