Skip to content

[go] Use types to encode and decode jsonrpc queries#372

Open
qmuntal wants to merge 16 commits intomainfrom
dev/qmuntal/sdkpriv
Open

[go] Use types to encode and decode jsonrpc queries#372
qmuntal wants to merge 16 commits intomainfrom
dev/qmuntal/sdkpriv

Conversation

@qmuntal
Copy link
Member

@qmuntal qmuntal commented Feb 4, 2026

This pull request refactors the JSON-RPC 2.0 client and session code to use strongly-typed request and response structs instead of generic map[string]any types. It introduces generic helpers for request and notification handlers, improves type safety, and simplifies marshaling and unmarshaling of parameters and results. The changes also remove manual parsing logic in favor of direct JSON unmarshaling into typed structs.

@qmuntal qmuntal requested a review from a team as a code owner February 4, 2026 15:43
Copilot AI review requested due to automatic review settings February 4, 2026 15:43
Copy link
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 pull request refactors the Go SDK's JSON-RPC 2.0 implementation to use strongly-typed request and response structs instead of generic map[string]any types. The refactoring introduces generic helper functions for type-safe marshaling/unmarshaling, removes manual parsing logic, and consolidates notification and request handling.

Changes:

  • Introduced typed request/response structs for all JSON-RPC methods (session.create, session.send, etc.)
  • Added generic helper functions NotificationHandlerFor and RequestHandlerFor in the jsonrpc2 package
  • Removed manual map-based parameter construction and parsing throughout client.go and session.go
  • Simplified response handling by directly unmarshaling into typed structs

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
go/types.go Added internal request/response types; removed JSON tags from public UserInputRequest/Response; changed public response types to unexported; added JSON tags to InfiniteSessionConfig and Tool
go/session.go Refactored Send, GetMessages, Destroy, Abort to use typed structs; replaced manual hook input parsing with direct unmarshaling
go/internal/jsonrpc2/jsonrpc2.go Added generic helpers for type-safe handlers; unified request/notification handling; removed separate Notification type
go/client_test.go Updated test to use new typed toolCallRequest/Response structs
go/client.go Refactored all API methods to use typed requests; fixed copy-paste bug in ResumeSessionWithOptions; renamed dispatchLifecycleEvent to handleLifecycleEvent
Comments suppressed due to low confidence (2)

go/internal/jsonrpc2/jsonrpc2.go:125

  • The reflection logic to detect pointer types has the same issue as in NotificationHandlerFor: reflect.TypeOf(in) operates on a zero value which could be problematic for interface types. Consider using reflect.TypeFor[In]() (Go 1.22+) to get the type parameter's type directly, which would be more robust.
func RequestHandlerFor[In, Out any](handler func(params In) (Out, *Error)) RequestHandler {
	return func(params json.RawMessage) (json.RawMessage, *Error) {
		var in In
		// If In is a pointer type, allocate the underlying value and unmarshal into it directly
		var target any = &in
		if t := reflect.TypeOf(in); t != nil && t.Kind() == reflect.Pointer {
			in = reflect.New(t.Elem()).Interface().(In)
			target = in
		}

go/client.go:875

  • The method documentation (line 865) references the old exported type name PingResponse, but the actual return type has been changed to the unexported *pingResponse. This documentation should be updated to either describe the response without referencing a specific type name (e.g., "Returns a response containing the message and server timestamp") or the type should remain exported to match the documentation and maintain API compatibility.
func (c *Client) Ping(ctx context.Context, message string) (*pingResponse, error) {

qmuntal and others added 2 commits February 4, 2026 16:51
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link

github-actions bot commented Feb 4, 2026

Cross-SDK Consistency Review ✅

I've reviewed this PR for consistency across all SDK implementations. Good news: This refactoring improves cross-SDK consistency rather than introducing inconsistencies.

Summary of Changes

This PR refactors the Go SDK's JSON-RPC handling from generic map[string]any structures to strongly-typed request/response structs (e.g., createSessionRequest, createSessionResponse).

Consistency Analysis

Current state across SDKs:

SDK JSON-RPC Request/Response Handling Type Safety
Node.js/TypeScript Typed objects with TypeScript interfaces ✅ Strong
.NET Strongly-typed classes with source-generated serialization ✅ Strong
Go (after this PR) Strongly-typed structs with native Go types ✅ Strong
Python Dictionary structures with TypedDict validation at API level ⚠️ Medium (idiomatic for Python)

Verdict: ✅ No Action Needed

This PR aligns the Go SDK with the type-safe patterns already established in Node.js and .NET. Python's dictionary-based approach is the outlier, but that's intentional—it's idiomatic for Python and still provides type safety through TypedDict annotations.

Key benefits of this change:

  • ✅ Compile-time type checking (catches errors earlier)
  • ✅ Better code maintainability (less manual map construction)
  • ✅ Consistent architecture with TypeScript and .NET SDKs
  • ✅ No public API changes (purely internal refactoring)

No follow-up work needed in other SDKs. The change is internal to Go and brings it into alignment with existing patterns.

AI generated by SDK Consistency Review Agent

@github-actions
Copy link

github-actions bot commented Feb 4, 2026

SDK Consistency Review

I've reviewed PR #372 for cross-SDK consistency. The refactoring to use strongly-typed request/response structs is excellent and aligns with how Node.js and .NET already handle JSON-RPC. However, I found one consistency issue regarding public API design:

⚠️ Inconsistency: Ping() Return Type Visibility

Current state across SDKs:

SDK Ping() return type Is type exported?
Python PingResponse ✅ Yes (public dataclass)
.NET PingResponse ✅ Yes (public class)
Node.js Inline type N/A (inline: { message: string; timestamp: number; ... })
Go (this PR) pingResponse No (changed from public to private)

Issue: The Go SDK previously exported PingResponse (matching Python/.NET), but this PR makes it private (pingResponse). This creates an inconsistency where Python and .NET users can reference PingResponse in their code (e.g., for type annotations or variable declarations), but Go users cannot.

Example use case: A user might want to store or pass around the ping response:

// Before this PR (works):
var response *copilot.PingResponse
response, err := client.Ping(ctx, "hello")

// After this PR (breaks):
var response *copilot.pingResponse  // ❌ Cannot reference unexported type

Recommendation: Consider keeping PingResponse as a public type alias or re-exporting it:

// Option 1: Type alias
type PingResponse = pingResponse

// Option 2: Re-export with documentation
// PingResponse is the response from a ping request
type PingResponse struct {
    Message         string `json:"message"`
    Timestamp       int64  `json:"timestamp"`
    ProtocolVersion *int   `json:"protocolVersion,omitempty"`
}

This would maintain backward compatibility and consistency with Python/.NET while keeping the internal types private.

Note: The same concern applies to any other public methods that previously returned exported types (if applicable). I also noticed the documentation comment still references "Returns a PingResponse" which should be updated to match the actual return type.


Overall assessment: The internal refactoring is well-done and brings Go closer to Node.js/.NET's type-safe approach. The visibility change is the only consistency concern I found.

AI generated by SDK Consistency Review Agent

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

✅ Cross-SDK Consistency Review

I've reviewed PR #372 for consistency across the Node.js, Python, Go, and .NET SDK implementations.

Summary

This PR maintains cross-SDK consistency. The refactoring to use strongly-typed JSON-RPC structs in Go is a language-appropriate optimization that aligns with idiomatic Go patterns and doesn't create feature parity issues.

Analysis

What changed:

  • Internal refactoring of Go SDK's JSON-RPC 2.0 client to use typed request/response structs instead of map[string]any
  • Added generic helpers (RequestHandlerFor, NotificationHandlerFor) for type-safe handler registration
  • Simplified marshaling by eliminating manual parameter building code

Cross-language comparison:

Each SDK appropriately uses typing patterns idiomatic to its language:

SDK JSON-RPC Approach Typing Strategy
TypeScript vscode-jsonrpc Already strongly-typed via TypeScript's type system
.NET StreamJsonRpc Already uses strongly-typed C# classes (CreateSessionRequest, etc.)
Go Custom implementation Moving from map[string]any to typed structs ✨
Python Custom implementation Uses dynamic dict (idiomatic for Python)

Key points:

  1. Public API unchanged - External method signatures (CreateSession, Ping, etc.) remain consistent across SDKs
  2. No feature disparity - This doesn't add functionality that other SDKs lack
  3. Language-appropriate - Go's move toward typed structs mirrors .NET's existing approach and is more idiomatic than using maps for structured data
  4. Internal implementation - This is exactly the type of difference that should exist between SDKs

Recommendation

No action needed. This refactoring improves Go SDK code quality without affecting cross-SDK consistency. The change makes the Go implementation more aligned with statically-typed best practices, similar to how .NET already operates, while appropriately leaving Python's dynamic approach unchanged.

AI generated by SDK Consistency Review Agent

@qmuntal qmuntal marked this pull request as draft February 5, 2026 16:00
@github-actions
Copy link

github-actions bot commented Feb 5, 2026

✅ Cross-SDK Consistency Review

I've reviewed this PR for consistency across the four SDK implementations. No concerns found - this change actually improves cross-SDK consistency.

Summary

This PR refactors the Go SDK's JSON-RPC handling from generic map[string]any types to strongly-typed structs. This brings Go into alignment with how the other SDKs handle JSON-RPC protocols:

Current State:

  • Node.js/TypeScript: Uses strongly-typed interfaces throughout (e.g., CreateSessionParams, ResumeSessionParams)
  • .NET: Uses strongly-typed C# records with nullable fields (e.g., CreateSessionRequest, ResumeSessionRequest)
  • Go (before this PR): Used map[string]any with manual field building via helper functions like buildProviderParams
  • Go (after this PR): ✅ Uses strongly-typed structs with pointer fields, matching the Node.js/.NET pattern
  • Python: Uses generic dict objects at the transport layer - this appears to be an intentional design choice for Python's dynamic nature

Why This Improves Consistency

The refactoring eliminates ~468 lines of manual map-building code in favor of direct struct marshaling, which:

  1. Matches the type-safety approach used by Node.js and .NET
  2. Makes the Go SDK's internal structure more similar to the other statically-typed SDKs
  3. Improves maintainability by removing error-prone manual parameter construction

Note on Python

Python's use of generic dicts at the JSON-RPC transport layer (in jsonrpc.py) differs from the other SDKs, but this is appropriate for Python's ecosystem and doesn't represent an inconsistency that needs fixing. The higher-level Python SDK types (in types.py) are still properly typed using TypedDict.

Recommendation: ✅ Approve - this PR brings Go closer to the established patterns in Node.js and .NET.

AI generated by SDK Consistency Review Agent

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

✅ Cross-SDK Consistency Review

This PR maintains cross-SDK consistency. Here's my analysis:

Summary

This PR refactors the Go SDK's internal JSON-RPC handling to use strongly-typed structs instead of map[string]any. This is an internal implementation improvement with no public API changes.

Key Findings

1. No Public API Changes

  • All exported types and methods (Client, Session, etc.) retain their existing signatures
  • The strongly-typed JSON-RPC structs (e.g., sessionSendRequest, sessionEventRequest) are unexported (lowercase), so SDK users never interact with them directly
  • This is purely an internal refactoring for better type safety and maintainability

2. Language-Specific Optimization

This refactoring doesn't need to be ported to other SDKs because:

  • Node.js/TypeScript: Already uses TypeScript interfaces for type safety
  • Python: Already uses dataclasses/Pydantic models for structured data
  • .NET: Already uses C# classes with strong typing
  • Go: This PR brings Go up to the same level of internal type safety that other SDKs already enjoy

3. Test Helper Alignment

The PR removes the Go-specific testharness.GetFinalAssistantMessage helper in favor of Session.SendAndWait(), which aligns with existing cross-SDK patterns:

  • Go: Session.SendAndWait() ✅ (already exists, now used in tests)
  • Python: session.send_and_wait()
  • .NET: session.SendAndWaitAsync()
  • Node.js: session.sendAndWait()

All SDKs already provide this public method, so removing the redundant test helper actually improves consistency by using the public API that users would use.

Conclusion

No consistency issues found. This is a backward-compatible internal improvement that brings the Go SDK's implementation quality in line with other language implementations without affecting the public API surface.

AI generated by SDK Consistency Review Agent

@qmuntal qmuntal marked this pull request as ready for review February 5, 2026 16:48
@github-actions
Copy link

github-actions bot commented Feb 5, 2026

Cross-SDK Consistency Review ✅

I've reviewed this PR for cross-language SDK consistency. This change improves consistency across the SDK implementations.

Summary

This PR refactors the Go SDK's JSON-RPC handling from using map[string]any to strongly-typed structs. This brings the Go SDK into alignment with the type-safety patterns already used by the other language implementations:

  • Node.js/TypeScript: Uses strongly-typed interfaces (ToolCallRequestPayload, PermissionRequest, etc.)
  • Python: Uses TypedDict classes (ToolInvocation, PermissionRequest, etc.)
  • .NET: Uses strongly-typed records/classes throughout
  • Go (after this PR): Now uses strongly-typed structs (toolCallRequest, permissionRequestRequest, etc.)

No Consistency Issues Found

  1. Internal refactoring only - No public API changes that would affect SDK consumers
  2. Aligns with best practices - All SDKs now use type-safe patterns appropriate for their languages
  3. No feature gaps - This is purely architectural improvement, not new functionality
  4. Test coverage maintained - E2E tests updated to use the new patterns (e.g., Session.SendAndWait instead of testharness.GetFinalAssistantMessage, which aligns with Node SDK approach)

This refactoring is language-appropriate (Go's static typing naturally benefits from explicit structs) and makes the Go SDK's internals more maintainable while preserving feature parity.

AI generated by SDK Consistency Review Agent

@qmuntal qmuntal requested a review from friggeri February 5, 2026 16:57
@qmuntal
Copy link
Member Author

qmuntal commented Feb 5, 2026

Sorry for the noise. This is ready for review.

@friggeri
Copy link
Collaborator

friggeri commented Feb 5, 2026

Sorry about this, you have a merge conflict!

@qmuntal
Copy link
Member Author

qmuntal commented Feb 5, 2026

Sorry about this, you have a merge conflict!

Fixed 👍

@github-actions
Copy link

github-actions bot commented Feb 5, 2026

SDK Consistency Review

I've reviewed this PR for cross-SDK consistency. The changes are focused on the Go SDK and include two main aspects:

1. ✅ Strongly-typed JSON-RPC structs (Good for consistency)

The PR refactors Go SDK internals to use strongly-typed structs (e.g., createSessionRequest, createSessionResponse) instead of map[string]any for JSON-RPC encoding/decoding.

Cross-SDK status:

  • .NET: Already uses strongly-typed records (CreateSessionRequest, CreateSessionResponse)
  • Go (this PR): Moving to strongly-typed structs
  • ⚠️ Python: Still builds dictionaries dynamically in client.py (line 404-498)
  • ⚠️ Node.js: Still creates object literals inline in client.ts (line 470-494)

Recommendation: This change improves Go's type safety and brings it closer to .NET's approach. Consider documenting this as a best practice, though it's less critical for dynamically-typed Python and acceptable for TypeScript which already has structural typing.

2. ⚠️ Removal of GetFinalAssistantMessage test helper (Inconsistency)

The PR removes testharness.GetFinalAssistantMessage from the Go test harness in favor of using Session.SendAndWait exclusively. The PR description states this "is aligned with the Node SDK," but this creates an inconsistency:

Current usage across SDKs:

  • Node.js: Has BOTH getFinalAssistantMessage (4 uses in tests) AND sendAndWait (17 uses) - primarily uses sendAndWait
  • Python: Has BOTH get_final_assistant_message (11 uses) AND send_and_wait (9 uses) - uses both equally
  • .NET: Has BOTH GetFinalAssistantMessage (13 uses) AND SendAndWaitAsync (5 uses) - primarily uses the helper
  • Go (this PR): Removes GetFinalAssistantMessage entirely, only provides SendAndWait

Issue: While Node.js does prefer sendAndWait, it still exports and uses getFinalAssistantMessage in its test harness. Removing it entirely from Go creates an asymmetry where:

  • Developers moving from Python/.NET/Node to Go won't find the familiar test helper
  • The Go test harness is less feature-complete than others

Suggestions:

  1. Option A (Preferred): Keep GetFinalAssistantMessage in the Go test harness for consistency, but update examples/docs to prefer SendAndWait as the primary approach
  2. Option B: If removing it from Go, consider removing it from all SDKs' test harnesses and updating all tests to use SendAndWait/send_and_wait/SendAndWaitAsync exclusively
  3. Option C: Document why Go diverges here (e.g., if there's a Go-specific reason it's problematic)

Overall Assessment

The strongly-typed struct refactoring is a great improvement. The test helper removal creates a minor inconsistency but is not blocking if there's a deliberate decision to simplify the Go test API. Consider whether other SDKs should follow suit or if Go should retain the helper for consistency.


Note: This is a consistency review only - the code changes themselves appear well-implemented.

AI generated by SDK Consistency Review Agent

@github-actions
Copy link

github-actions bot commented Feb 6, 2026

Cross-SDK Consistency Review

Thank you for improving the Go SDK's type safety and test reliability! I've reviewed this PR for cross-SDK consistency.

✅ Internal Implementation Changes (Go-specific)

The JSON-RPC 2.0 refactoring to use strongly-typed structs instead of map[string]any is an internal implementation detail that's appropriate for Go. Other SDKs may have different internal architectures, so this change doesn't require mirroring.

⚠️ Test Pattern Inconsistency Detected

The PR removes testharness.GetFinalAssistantMessage from the Go SDK and aligns with the Node.js SDK (which also removed it in commit 7959629). The PR description notes:

"remove testharness.GetFinalAssistantMessage and use Session.SendAndWait instead. The former is more robust to races and is aligned with the Node SDK."

However, Python and .NET SDKs still have this helper and are still using it extensively:

  • Python: get_final_assistant_message() in python/e2e/testharness/helper.py (~18 usages across tests)
  • .NET: GetFinalAssistantMessageAsync() in dotnet/test/Harness/TestHelper.cs (~34 usages across tests)

Recommendation

For cross-SDK consistency and test robustness, consider:

  1. Python: Remove get_final_assistant_message() from python/e2e/testharness/helper.py and update tests to use session.send_and_wait() instead
  2. .NET: Remove GetFinalAssistantMessageAsync() from dotnet/test/Harness/TestHelper.cs and update tests to use session.SendAndWaitAsync() instead

This would align all four SDKs with the same, more race-resistant test pattern. If there's a reason to keep these helpers in Python/.NET (e.g., different threading models), that's worth documenting.

Note: This doesn't block the current PR since it only touches Go and Node.js, but it's worth tracking as a follow-up for consistency.

AI generated by SDK Consistency Review Agent

@qmuntal qmuntal force-pushed the dev/qmuntal/sdkpriv branch from 42ad3b2 to 31f5f81 Compare February 6, 2026 12:28
@github-actions
Copy link

github-actions bot commented Feb 6, 2026

✅ Cross-SDK Consistency Review: No Issues Found

I've reviewed PR #372 for consistency across the four SDK implementations (Node.js/TypeScript, Python, Go, .NET), and I'm happy to report that this change maintains and actually improves cross-SDK consistency.

Summary

This PR refactors the Go SDK's JSON-RPC client to use strongly-typed request/response structs instead of map[string]any. This is a positive change that brings Go closer to the patterns used in the .NET SDK while maintaining compatibility with the Node.js and Python approaches.

Cross-SDK Comparison

JSON-RPC Request/Response Type Handling

Aspect Node.js Python .NET Go (After PR)
Session RPC Types Public? ❌ No (inlined) ❌ No (inline dicts) ❌ No (internal records) No (private structs)
Explicit Types for RPC? ❌ No ❌ No ✅ Yes Yes
Type Safety Low Low High High

Consistency Finding: ✅ All four SDKs now treat session JSON-RPC request/response types as internal implementation details, not public API surface.

  • Node.js: Builds requests inline as plain objects
  • Python: Builds requests inline as dictionaries
  • .NET: Uses explicit typed records marked internal
  • Go (After this PR): Uses explicit typed structs with lowercase names (unexported/private)

Public API Consistency

The following types remain public in Go (as they should, since they're returned by public methods):

PingResponse - returned by Client.Ping()
GetStatusResponse - returned by Client.GetStatus()
GetAuthStatusResponse - returned by Client.GetAuthStatus()

These are also public in all other SDKs:

  • Node.js: export interface GetStatusResponse, export interface GetAuthStatusResponse
  • Python: class GetStatusResponse, class GetAuthStatusResponse, class PingResponse
  • .NET: public class GetStatusResponse, public class GetAuthStatusResponse, public class PingResponse

Benefits of This Change

  1. 🎯 Type Safety: Move from map[string]any to strongly-typed structs
  2. 🔒 Better Encapsulation: Internal RPC types are now private (consistent with .NET's internal pattern)
  3. 🐛 Easier to Maintain: Explicit types make the code more maintainable and reduce marshaling errors
  4. 📐 Architectural Alignment: Go now follows the same architectural pattern as .NET

Conclusion

This PR improves internal code quality without breaking the public API and aligns Go's JSON-RPC handling with .NET's best practices. The public API surface remains consistent across all four SDKs.

No action needed from other SDK maintainers. ✨

AI generated by SDK Consistency Review Agent

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.

2 participants