Skip to content

Commit 937983c

Browse files
mattsp1290claude
andcommitted
feat: Add Go SDK encoding and error handling packages
- Add comprehensive encoding package with JSON support - Implement content negotiation for Accept headers - Add buffer sizing utilities and encoder pool - Add error types and utilities package - Include unit tests for negotiation functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 88b80fe commit 937983c

File tree

15 files changed

+5014
-0
lines changed

15 files changed

+5014
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package encoding
2+
3+
import (
4+
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
5+
)
6+
7+
// Buffer size constants optimized for different event types
8+
const (
9+
// Small events (typically 100-500 bytes)
10+
SmallEventBufferSize = 512
11+
12+
// Medium events (typically 500-2KB)
13+
MediumEventBufferSize = 2048
14+
15+
// Large events (typically 2KB-8KB)
16+
LargeEventBufferSize = 8192
17+
18+
// Very large events (8KB+)
19+
VeryLargeEventBufferSize = 16384
20+
21+
// Default buffer size for unknown events
22+
DefaultEventBufferSize = 1024
23+
24+
// Array processing buffer size per event
25+
ArrayProcessingBufferSize = 1024
26+
)
27+
28+
// GetOptimalBufferSize returns the optimal buffer size for a given event type
29+
func GetOptimalBufferSize(eventType events.EventType) int {
30+
switch eventType {
31+
case events.EventTypeTextMessageStart:
32+
return SmallEventBufferSize // Simple metadata
33+
case events.EventTypeTextMessageContent:
34+
return MediumEventBufferSize // Text content can vary
35+
case events.EventTypeTextMessageEnd:
36+
return SmallEventBufferSize // Simple metadata
37+
case events.EventTypeToolCallStart:
38+
return SmallEventBufferSize // Tool metadata
39+
case events.EventTypeToolCallArgs:
40+
return LargeEventBufferSize // Tool arguments can be large
41+
case events.EventTypeToolCallEnd:
42+
return SmallEventBufferSize // Simple metadata
43+
case events.EventTypeStateSnapshot:
44+
return VeryLargeEventBufferSize // State snapshots can be very large
45+
case events.EventTypeStateDelta:
46+
return MediumEventBufferSize // Delta operations are usually medium
47+
case events.EventTypeMessagesSnapshot:
48+
return VeryLargeEventBufferSize // Message snapshots can be very large
49+
case events.EventTypeRaw:
50+
return LargeEventBufferSize // Raw events are unpredictable
51+
case events.EventTypeCustom:
52+
return MediumEventBufferSize // Custom events are usually medium
53+
case events.EventTypeRunStarted:
54+
return SmallEventBufferSize // Simple metadata
55+
case events.EventTypeRunFinished:
56+
return SmallEventBufferSize // Simple metadata
57+
case events.EventTypeRunError:
58+
return MediumEventBufferSize // Error details can be medium
59+
case events.EventTypeStepStarted:
60+
return SmallEventBufferSize // Simple metadata
61+
case events.EventTypeStepFinished:
62+
return SmallEventBufferSize // Simple metadata
63+
default:
64+
return DefaultEventBufferSize
65+
}
66+
}
67+
68+
// GetOptimalBufferSizeForEvent returns the optimal buffer size for a specific event instance
69+
func GetOptimalBufferSizeForEvent(event events.Event) int {
70+
if event == nil {
71+
return DefaultEventBufferSize
72+
}
73+
74+
baseSize := GetOptimalBufferSize(event.Type())
75+
76+
// For certain event types, we can make more precise estimates
77+
switch e := event.(type) {
78+
case *events.TextMessageContentEvent:
79+
// Estimate based on delta length
80+
if len(e.Delta) > 0 {
81+
// Add some overhead for JSON encoding
82+
return max(baseSize, len(e.Delta)*2)
83+
}
84+
case *events.ToolCallArgsEvent:
85+
// Estimate based on delta length
86+
if len(e.Delta) > 0 {
87+
// Add some overhead for JSON encoding
88+
return max(baseSize, len(e.Delta)*2)
89+
}
90+
case *events.StateSnapshotEvent:
91+
// State snapshots can be very large, but we can't easily estimate
92+
// without serializing first, so stick with the base size
93+
return baseSize
94+
case *events.StateDeltaEvent:
95+
// Estimate based on number of operations
96+
if len(e.Delta) > 0 {
97+
// Rough estimate: 100 bytes per operation
98+
return max(baseSize, len(e.Delta)*100)
99+
}
100+
case *events.MessagesSnapshotEvent:
101+
// Estimate based on number of messages
102+
if len(e.Messages) > 0 {
103+
// Rough estimate: 500 bytes per message
104+
return max(baseSize, len(e.Messages)*500)
105+
}
106+
case *events.CustomEvent:
107+
// For custom events, we can't easily estimate without knowing the value
108+
return baseSize
109+
}
110+
111+
return baseSize
112+
}
113+
114+
// GetOptimalBufferSizeForMultiple returns the optimal buffer size for encoding multiple events
115+
func GetOptimalBufferSizeForMultiple(events []events.Event) int {
116+
if len(events) == 0 {
117+
return DefaultEventBufferSize
118+
}
119+
120+
totalSize := 0
121+
for _, event := range events {
122+
totalSize += GetOptimalBufferSizeForEvent(event)
123+
}
124+
125+
// Add some overhead for array structure
126+
arrayOverhead := 50 * len(events) // Rough estimate for JSON array overhead
127+
return totalSize + arrayOverhead
128+
}
129+
130+
// max returns the maximum of two integers
131+
func max(a, b int) int {
132+
if a > b {
133+
return a
134+
}
135+
return b
136+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package encoder
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/core/events"
8+
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/encoding"
9+
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/encoding/json"
10+
"github.com/ag-ui-protocol/ag-ui/sdks/community/go/pkg/encoding/negotiation"
11+
)
12+
13+
// EventEncoder provides a high-level interface for encoding AG-UI events
14+
// This adapter bridges the Go SDK encoding package with example server needs
15+
type EventEncoder struct {
16+
negotiator *negotiation.ContentNegotiator
17+
jsonCodec encoding.Codec
18+
}
19+
20+
// NewEventEncoder creates a new event encoder with content negotiation support
21+
func NewEventEncoder() *EventEncoder {
22+
// Create content negotiator with JSON as preferred type
23+
negotiator := negotiation.NewContentNegotiator("application/json")
24+
25+
return &EventEncoder{
26+
negotiator: negotiator,
27+
jsonCodec: json.NewCodec(),
28+
}
29+
}
30+
31+
// EncodeEvent encodes a single event using the specified content type
32+
func (e *EventEncoder) EncodeEvent(ctx context.Context, event events.Event, contentType string) ([]byte, error) {
33+
if event == nil {
34+
return nil, fmt.Errorf("event cannot be nil")
35+
}
36+
37+
// Validate the event before encoding
38+
if err := event.Validate(); err != nil {
39+
return nil, fmt.Errorf("event validation failed: %w", err)
40+
}
41+
42+
// For now, we only support JSON encoding as specified in the task
43+
// Protobuf support can be added later
44+
switch contentType {
45+
case "application/json", "":
46+
return e.jsonCodec.Encode(ctx, event)
47+
default:
48+
// Try to negotiate to a supported type
49+
supportedType, err := e.negotiator.Negotiate(contentType)
50+
if err != nil {
51+
return nil, fmt.Errorf("unsupported content type %q: %w", contentType, err)
52+
}
53+
54+
// For now, fallback to JSON
55+
if supportedType == "application/json" {
56+
return e.jsonCodec.Encode(ctx, event)
57+
}
58+
59+
return nil, fmt.Errorf("content type %q not implemented yet", supportedType)
60+
}
61+
}
62+
63+
// NegotiateContentType performs content negotiation based on Accept header
64+
func (e *EventEncoder) NegotiateContentType(acceptHeader string) (string, error) {
65+
if acceptHeader == "" {
66+
return "application/json", nil // Default to JSON
67+
}
68+
69+
contentType, err := e.negotiator.Negotiate(acceptHeader)
70+
if err != nil {
71+
// If negotiation fails, fallback to JSON with a clear message
72+
return "application/json", fmt.Errorf("content negotiation failed, falling back to JSON: %w", err)
73+
}
74+
75+
return contentType, nil
76+
}
77+
78+
// SupportedContentTypes returns the list of supported content types
79+
func (e *EventEncoder) SupportedContentTypes() []string {
80+
return e.negotiator.SupportedTypes()
81+
}
82+
83+
// GetContentType returns the content type that this encoder will produce
84+
func (e *EventEncoder) GetContentType(acceptHeader string) string {
85+
contentType, err := e.NegotiateContentType(acceptHeader)
86+
if err != nil {
87+
// Log the error but continue with fallback
88+
return "application/json"
89+
}
90+
return contentType
91+
}

0 commit comments

Comments
 (0)