Provide a standalone agent client for l-ai-chat with service-token auth, safe at-least-once subscriptions, ordered cumulative ACK tracking, and self-contained gRPC fake tests. Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.7 KiB
Markdown
109 lines
3.7 KiB
Markdown
# l-ai-chat-client-go
|
|
|
|
Reusable Go SDK for l-ai-chat agent/service clients. It uses the authoritative
|
|
`l-ai-chat-proto` generated Go API through the `proto/l-ai-chat-proto`
|
|
submodule and does not copy protobuf files manually.
|
|
|
|
## Install / Build
|
|
|
|
Clone with submodules:
|
|
|
|
```bash
|
|
git clone --recurse-submodules <repo-url> l-ai-chat-client-go
|
|
cd l-ai-chat-client-go
|
|
go test ./...
|
|
```
|
|
|
|
On this workstation the server currently consumes proto commit
|
|
`f1f44e4ed276316ef17f8deccac54da457654312`. The local sibling
|
|
`../l-ai-chat-proto` checkout is older, so the SDK submodule is pinned to the
|
|
same proto worktree commit as `../l-ai-chat`.
|
|
|
|
## Transport and Auth
|
|
|
|
`New` uses TLS by default and automatically adds service auth metadata to
|
|
unary RPCs, `Subscribe`, reconnects, and `Acknowledge`:
|
|
|
|
```text
|
|
authorization: Bearer <token>
|
|
```
|
|
|
|
Use `WithInsecureTransport()` only for trusted/internal networks. The service
|
|
token travels over this gRPC transport; plaintext mode is suitable only for
|
|
trusted/internal network until service-to-service TLS/mTLS is deployed.
|
|
|
|
Tokens come from a `TokenProvider`; `StaticToken` is the simple provider. Empty
|
|
tokens and provider errors fail closed. Tokens are not included in SDK errors.
|
|
|
|
## Delivery Guarantee
|
|
|
|
Agent delivery is durable at-least-once:
|
|
|
|
- receiving a message does not mean processing succeeded;
|
|
- the server durable cursor changes only after successful `Acknowledge`;
|
|
- the SDK acknowledges only after `MarkProcessed`;
|
|
- unacked messages replay after reconnect;
|
|
- handlers with external side effects must be idempotent.
|
|
|
|
The server derives the durable consumer identity from the authenticated
|
|
agent/service token. The SDK does not accept an arbitrary consumer id.
|
|
|
|
## Cumulative ACK Warning
|
|
|
|
`Acknowledge(last_ack_sequence=N)` is cumulative. If deliveries are:
|
|
|
|
```text
|
|
100, 104, 111
|
|
```
|
|
|
|
and `104` finishes before `100`, do not acknowledge `104` yet: that would mark
|
|
`100` processed too. Use `Subscription.MarkProcessed`; the SDK tracks actual
|
|
delivery order, not `seq+1`, so global sequence gaps inside a topic are safe.
|
|
|
|
Expected ACK behavior:
|
|
|
|
```text
|
|
104 done -> no ACK
|
|
100 done -> ACK 104
|
|
111 done -> ACK 111
|
|
```
|
|
|
|
## Subscription Lifecycle
|
|
|
|
`Subscribe` opens one server stream for one topic and waits for the first
|
|
`ready=true` marker before returning. READY is not exposed as a chat message.
|
|
Each successful stream registration is a new SDK generation. Deliveries carry
|
|
internal generation/subscription fencing, so an old `Delivery` cannot ACK a new
|
|
stream after reconnect.
|
|
|
|
Transient stream failures (`Unavailable`, `ResourceExhausted`,
|
|
`DeadlineExceeded`, EOF/transport reset) reconnect with bounded exponential
|
|
backoff and jitter. Reconnect does not send a local receive cursor; the server
|
|
starts from the durable ACK cursor and replays unacked messages.
|
|
|
|
`Aborted` means the server intentionally replaced this active subscription
|
|
because another stream for the same `(consumer, topic)` connected. The SDK
|
|
treats that as terminal by default to avoid reconnect ping-pong between two
|
|
replicas of one logical consumer. HA/leader election belongs above this SDK.
|
|
|
|
First-time `(consumer, topic)` subscriptions start at the current topic head.
|
|
Use `History` for historical context. Existing consumers resume from the
|
|
durable ACK cursor. `Delivery.Replay` means redelivery/backlog context, not an
|
|
error.
|
|
|
|
## gRPC Surface
|
|
|
|
The SDK wraps implemented read/agent RPCs:
|
|
|
|
- `ListTopics`
|
|
- `GetTopic`
|
|
- `GetMessage`
|
|
- `History`
|
|
- `PostMessage` using server-side `AgentPostMessage`
|
|
- `Subscribe`
|
|
- `Subscription.MarkProcessed`
|
|
|
|
Generic `PostMessage`, `CreateTopic`, and `UpdateTopic` are intentionally not
|
|
exposed as ergonomic helpers because the current server returns
|
|
`Unimplemented` for those fail-closed gRPC paths.
|