# HotPlex Worker Go Client

> Go client SDK for HotPlex Worker Gateway — AEP v1 WebSocket protocol

[![Go Reference](https://pkg.go.dev/badge/github.com/hrygo/hotplex/client.svg)](https://pkg.go.dev/github.com/hrygo/hotplex/client)

## Installation

```bash
go get github.com/hrygo/hotplex/client
```

## Quick Start

```go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    client "github.com/hrygo/hotplex/client"
)

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

    c, err := client.New(ctx,
        client.URL("ws://localhost:8888"),
        client.WorkerType("claude_code"),
        client.APIKey(os.Getenv("HOTPLEX_API_KEY")),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer c.Close()

    // 1. Connect
    ack, err := c.Connect(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Connected | Session: %s\n", ack.SessionID)

    // 2. Listen for streaming deltas in background
    go func() {
        for evt := range c.Events() {
            if evt.Type == client.EventMessageDelta {
                if d, ok := evt.AsMessageDeltaData(); ok {
                    fmt.Print(d.Content)
                }
            }
        }
    }()

    // 3. Send input and wait for completion (async task)
    done, err := c.SendInputAsync(ctx, "What is 2+2?")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("\n--- done (success: %v) ---\n", done.Success)
}
```

## API

### Options

Functional options pattern, passed to `New`:

```go
client.URL("ws://localhost:8888")           // required
client.WorkerType("claude_code")            // required
client.AuthToken("jwt-token")               // JWT bearer token
client.APIKey("sk-xxx")                     // API key header
client.PingInterval(30 * time.Second)       // heartbeat (default 54s)
client.ClientSessionID("my-session-001")    // client-managed session ID (UUIDv5 mapped)
client.AutoReconnect(true)                  // enable automatic reconnection
client.Logger(slog.Default())               // custom logger
client.Metadata(map[string]any{"p": "v"})   // init handshake metadata
```

### Connection

```go
// New session
ack, err := c.Connect(ctx)                  // returns *InitAckData

// Resume existing session
ack, err := c.Resume(ctx, "sess_xxx")       // returns *InitAckData
```

### Sending

```go
// Fire and forget
c.SendInput(ctx, "your message", metadata)               // user input + opt. metadata

// Send and wait for completion
done, err := c.SendInputAsync(ctx, "your message")       // returns *DoneData

c.SendPermissionResponse(ctx, "id", true, "approved")    // approve tool
c.SendQuestionResponse(ctx, "id", answers)               // answer question
c.SendElicitationResponse(ctx, "id", "accept", content)  // respond to elicit
c.SendControl(ctx, "terminate")                           // terminate session
c.SendReset(ctx, "user_requested")                       // clear context, restart worker
c.SendGC(ctx, "user_idle")                               // archive session, terminate worker
```

### Events

```go
// Subscribe to all subsequent events
events := c.Events() 

// Unsubscribe to stop receiving and free resources
defer c.Unsubscribe(events)

for evt := range events {
    // evt.Type    — event type string (see constants below)
    // evt.Seq     — monotonic sequence number
    // evt.Session — session ID
    // evt.Data    — event payload (use helpers below)

    if done, ok := evt.AsDoneData(); ok { /* ... */ }
    if err, ok := evt.AsErrorData(); ok { /* ... */ }
    if tc, ok := evt.AsToolCallData(); ok { /* ... */ }
}
```

### Lifecycle

```go
c.SessionID()     // current session ID
c.State()         // current SessionState
c.Close()         // graceful shutdown
```

## Event Kinds

| Constant | Description |
|----------|-------------|
| `EventMessageStart` | Streaming message begins |
| `EventMessageDelta` | Streaming content chunk |
| `EventMessageEnd` | Streaming message ends |
| `EventToolCall` | Worker requests tool execution |
| `EventToolResult` | Tool execution result |
| `EventPermissionRequest` | Worker asks for permission |
| `EventState` | Session state changed |
| `EventDone` | Session completed |
| `EventError` | Error occurred |
| `EventControl` | Control event |
| `EventPing` | Heartbeat probe |
| `EventPong` | Heartbeat response |
| `EventInitAck` | Connection established |
| `EventRaw` | Passthrough agent data |
| `EventReasoning` | Agent "thinking" tokens |
| `EventStep` | Higher-level task step |
| `EventQuestionRequest` | Worker asks a question |
| `EventElicitationRequest` | MCP elicitation request |

## Data Types

### InitAckData

```go
type InitAckData struct {
    SessionID  string
    State      SessionState
    ServerCaps ServerCaps
    Error      string
}
```

### ServerCaps

```go
type ServerCaps struct {
    ProtocolVersion string
    WorkerType      string
    SupportsResume  bool
    SupportsDelta   bool
    SupportsTool    bool
    SupportsPing    bool
    MaxFrameSize    int
    MaxTurns        int
    Tools           []string
}
```

### Session States

```go
StateCreated    // session initialized
StateRunning    // worker active
StateIdle       // waiting for input
StateTerminated // worker exited
StateDeleted    // GC'd
```

## Token Generation

```go
gen, err := client.NewTokenGenerator(signingKey)
if err != nil { /* ... */ }

// Key formats: PEM file path, 64-char hex, or 44-char base64
token, err := gen.Generate("user-id", []string{"read", "write"}, 1*time.Hour)

// Custom audience (default "gateway")
gen.WithAudience("custom-aud")
```

## Examples

| File | Description |
|------|-------------|
| [`examples/quickstart.go`](examples/quickstart.go) | Minimal connect & chat |
| [`examples/complete.go`](examples/complete.go) | Full features: permissions, stats, resume |
| [`scripts/gen-token/main.go`](scripts/gen-token/main.go) | JWT token generator CLI |

Run an example:

```bash
cd client
HOTPLEX_SIGNING_KEY=<key> go run examples/quickstart.go
```

## Related

- **Protocol Spec**: `docs/architecture/AEP-v1-Protocol.md`
- **Python Client**: `examples/python-client/`
- **TypeScript Client**: `examples/typescript-client/`
- **Java Client**: `examples/java-client/`
