e2e

package module
v1.58.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 0 Imported by: 0

README ΒΆ

Smoo AI license smoo.ai/th pkg.go.dev

github.com/SmooAI/smooth-operator/go β€” the idiomatic, race-clean Go client for the smooth-operator service.
Streaming agent turns, HITL resume, transport-agnostic. One of five native SDKs over one schema-driven WebSocket protocol.


What is this?

The native Go client for the smooth-operator WebSocket protocol. It connects to a running smooth-operator service (create a session, send a message, stream the agent's events back) β€” not the agent engine itself. Wire types are generated from the language-neutral JSON Schemas in spec/ (committed, so you don't need the generator); the ergonomic layer gives you typed As* event accessors and a streaming MessageTurn that's both range-able and awaitable.


30-second quickstart

go get github.com/SmooAI/smooth-operator/go/protocol
package main

import (
	"context"
	"fmt"

	"github.com/SmooAI/smooth-operator/go/protocol"
)

func main() {
	ctx := context.Background()

	c, _ := protocol.New(protocol.Options{
		Transport: protocol.NewWebSocketTransport("ws://127.0.0.1:8787/ws", nil),
	})
	_ = c.Connect(ctx)
	defer c.Close()

	sess, _ := c.CreateConversationSession(ctx, protocol.CreateConversationSessionParams{
		AgentID:  "11111111-1111-1111-1111-111111111111",
		UserName: "Alice",
	})

	turn := c.SendMessage(protocol.SendMessageParams{SessionID: sess.SessionID, Message: "How long is your return window?"})
	final, _ := turn.Wait(ctx)
	fmt.Println("messageId:", final.Data.Data.MessageID)
}

(Point the transport at your own smooth-operator-server or the hosted endpoint.)


Watch it stream

SendMessage returns a MessageTurn β€” range over Events() for live tokens, then Wait() for the authoritative terminal response. Go has no sum types, so a ServerEvent carries the common envelope fields plus the raw frame; switch on Type and call the matching As* accessor.

turn := c.SendMessage(protocol.SendMessageParams{SessionID: sess.SessionID, Message: "Where's my order?"})

for ev := range turn.Events() {
	switch ev.Type {
	case protocol.EventStreamToken:
		tok, _ := ev.AsStreamToken()
		fmt.Print(tok.Data.Token)                 // tokens, live
	case protocol.EventStreamChunk:
		chunk, _ := ev.AsStreamChunk()
		fmt.Printf("\n  ↳ node: %s\n", chunk.Node) // workflow node boundary
	case protocol.EventWriteConfirmationRequired:
		// HITL: approve and the resumed stream flows back into this same turn.
		c.ConfirmToolAction(protocol.ConfirmToolActionParams{
			SessionID: sess.SessionID, RequestID: turn.RequestID(), Approved: true,
		})
	}
}

final, _ := turn.Wait(ctx)
fmt.Println("\nmessageId:", final.Data.Data.MessageID)

Stop button

turn.Cancel() is the client-initiated "stop" β€” it sends a cancel frame for this turn. The server aborts the LLM + tool work and emits a terminal cancelled event (in place of eventual_response); the turn then settles as a user-stop: Wait resolves with no error, the Events() channel closes cleanly, and turn.Cancelled() reports true so the UI tells a stop apart from a failure. Idempotent β€” a cancel with nothing in flight is a harmless no-op.

turn := c.SendMessage(protocol.SendMessageParams{SessionID: sess.SessionID, Message: "Write me an essay…"})

go func() { <-stopButton; turn.Cancel() }() // user hits stop

if _, err := turn.Wait(ctx); err != nil {
	// a real failure β€” surface it
} else if turn.Cancelled() {
	fmt.Println("stopped by user") // resolved, not errored
}

c.Cancel(protocol.CancelParams{RequestID: turn.RequestID(), SessionID: sess.SessionID}) is the lower-level form when you hold the requestId but not the MessageTurn.

%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','actorBkg':'#0b1426','actorBorder':'#2b3a52','actorTextColor':'#e6edf6','signalColor':'#7c8aa0','signalTextColor':'#e6edf6','noteBkgColor':'#f49f0a','noteTextColor':'#1a0f00','noteBorderColor':'#ff6b6c','fontFamily':'ui-sans-serif, system-ui, sans-serif'}}}%%
sequenceDiagram
  participant App
  participant C as protocol.Client
  participant S as Service
  App->>C: SendMessage(...)
  C->>S: { action: send_message }
  S-->>C: immediate_response (202)
  S-->>C: stream_token / stream_chunk …
  S-->>C: eventual_response (200)
  C-->>App: range Events() Β· Wait() returns final

Layout

File Purpose
protocol/types_gen.go Generated wire types (one struct per schema / $def). Do not edit.
protocol/events.go Ergonomic ServerEvent discrimination + typed As* accessors.
protocol/transport.go Transport interface + default coder/websocket implementation.
protocol/client.go Client with the action methods.
protocol/turn.go MessageTurn (streaming events + awaitable terminal) + ProtocolError.
protocol/validate.go Optional runtime JSON Schema validation against ../spec/.

The Transport interface is mockable, so the test suite drives real client code (correlation, event discrimination, HITL routing) without a network.


Polyglot β€” one spec, five clients

%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart LR
  SPEC["spec/ (JSON Schema)"] --> GO["Go<br/>github.com/SmooAI/smooth-operator/go"]
  SPEC --> TS["TypeScript"]
  SPEC --> NET[".NET (+ MEAI IChatClient facade)"]
  SPEC --> PY["Python"]
  SPEC --> RS["Rust"]

Test-driven by default

Nothing here is vibe-coded β€” it's verified against a real LLM gateway.

%%{init: {'theme':'base','themeVariables':{'background':'#020618','primaryColor':'#0b1426','primaryTextColor':'#e6edf6','primaryBorderColor':'#2b3a52','lineColor':'#7c8aa0','secondaryColor':'#0b1426','tertiaryColor':'#0b1426','fontFamily':'ui-sans-serif, system-ui, sans-serif','clusterBkg':'#0b1426','clusterBorder':'#22304a'}}}%%
flowchart TD
  J["🎯 LLM-as-judge quality evals (Rust harness)"]
  E["🌐 Live cross-language E2E β€” this client boots the real server + drives a real claude-haiku-4-5 turn"]
  C["πŸ§ͺ Conformance fixtures (shared across all 5 clients)"]
  U["⚑ Unit tests (event discrimination, transport, correlation) β€” race-clean"]
  J --> E --> C --> U

26 tests, race-clean (go test -race). In the live cross-language E2E (e2e_live_test.go), this client boots a real smooth-operator-server subprocess (KB seeded) and drives a real claude-haiku-4-5 turn over WebSocket β€” asserting β‰₯1 streamed event, a knowledge-grounded "17", and per-session memory.

The proof story: an LLM-as-judge scored a multi-turn answer 1/5 (the runtime forgot turn 1's context); the failing eval drove a per-session-memory fix; it now scores 5/5 β€” a regression a substring test would have missed. See docs/EVALS.md.

Live tests are gated, never silently skipped β€” they run with SMOOTH_AGENT_E2E=1 + SMOOAI_GATEWAY_KEY and skip cleanly otherwise.

go test -race ./...                                   # no creds
SMOOTH_AGENT_E2E=1 go test -race ./... -run Live      # live cross-language E2E

Regenerating types

protocol/types_gen.go is generated with go-jsonschema (pure Go, offline). Run scripts/generate-go.sh from the repo root after any ../spec change:

go install github.com/atombender/go-jsonschema@latest   # once
./scripts/generate-go.sh

The script documents its own flags. The two that matter: --only-models (plain structs, no generated enum validation, so the client tolerates forward-compatible wire values and the conformance fixtures round-trip cleanly) and --struct-name-from-title (every $def in the spec carries a stable title, so feeding all schemas at once doesn't collide on the shared $defs/Request / $defs/Response keys).

Smoo-powered or bring-your-own

Point the transport at your own self-hosted smooth-operator-server β€” same protocol, same client.

Authenticating to a token-gated server? Pass a connection Token β€” it rides the ?token= query slot of the WS URL (browsers can't set WebSocket headers):

c, _ := protocol.New(protocol.Options{
	Transport: protocol.NewWebSocketTransportWithOptions(
		"wss://your-operator.example.com/ws",
		protocol.WebSocketOptions{Token: connToken},
	),
})

🧩 Part of Smoo AI

This Go client is built and open-sourced by Smoo AI β€” the AI-powered business platform with AI built into every product. It's the Go member of the polyglot SDK set (TypeScript Β· Python Β· Go Β· .NET Β· Rust) for the smooth-operator service.

  • 🌐 The service β€” smooth-operator (protocol, server, the five clients, AWS/k8s deploy)
  • 🧰 More open source from Smoo AI β€” smoo.ai/open-source
  • ☁️ Hosted by Smoo β€” smooth-operator runs the Smoo AI platform in production

πŸ“„ License

MIT Β© 2026 Smoo AI. See LICENSE.


Built by Smoo AI β€” AI built into every product.

Documentation ΒΆ

Overview ΒΆ

Package e2e is the module root for the Go implementation of smooth-operator.

Index ΒΆ

Constants ΒΆ

View Source
const Version = "1.58.4"

Version is the shared, lockstep release version for all smooth-operator language artifacts. The real Go "publish" is a git tag (go/v<Version>); this constant is the anchor that scripts/sync-versions.mjs keeps in sync with the canonical npm version on every changeset release.

Variables ΒΆ

This section is empty.

Functions ΒΆ

This section is empty.

Types ΒΆ

This section is empty.

Directories ΒΆ

Path Synopsis
Package protocol is the Go client for the smooth-operator WebSocket protocol.
Package protocol is the Go client for the smooth-operator WebSocket protocol.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL