# `@zephyrcloudio/harness-client`

Typed Effect Client for the Ze Harness v1 protocol.

Use network Fetch with `baseUrl`, or inject Fetch for an in-process handler:

```ts
import { createClient } from '@zephyrcloudio/harness-client';

const client = createClient({ baseUrl: 'http://127.0.0.1:4317' });
```

Every session call addresses one harness server. Local hosts serve only the default server, so the
option can be omitted there; remote callers pass the target server explicitly:

```ts
import { ServerName, createClient } from '@zephyrcloudio/harness-client';

const remote = createClient({
  baseUrl: 'https://harness.example',
  server: ServerName.make('my-server'),
});
```

Pass `onServerVersion: ({ protocolVersion, harnessVersion }) => { ... }` to observe the
`Ze-Harness-Version` response header, such as `1/0.6.0`. The callback runs for the first valid
value and each subsequent change, on ordinary responses and newly opened SSE streams. No
handshake request is added and no response body is consumed to obtain the version. Missing or
malformed headers are ignored. Callback exceptions and protocol/version differences do not
reject, retry, or cancel API calls; the observer is for application notices, not enforcement.
An existing SSE response keeps its original headers until the next connection.

Client methods return Effects. `watchTurn` and `watchSession` validate SSE progress, reconnect where
safe, apply text deltas and tool-activity replacements, and reconcile gaps from authoritative
snapshots. Tool activity checkpoints must advance monotonically and match the requested tool part.
The Client does not import Core or Server.
`watchSession` resumes transport failures from its last safe activity cursor. Before each retry it
emits `session.reconnecting` with the error and delay, and it emits `session.resumed` once the
resumed stream catches up. Servers repeat the cursor while idle, so the watch also treats three
missed heartbeats (`SESSION_EVENT_HEARTBEAT_MILLIS` × 3) without any event as a lost connection.
When a reconciliation read overtakes queued stream snapshots or deltas, the Client keeps the newer
state and consumes the covered revisions without moving the Turn backwards.
`watchTurn` reconnects after transport and server (5xx) failures and resumes after its last known
revision. While a Turn is quiet, servers send `turn.heartbeat` and never leave a gap longer than
`TURN_EVENT_HEARTBEAT_MILLIS`, so the watch treats three such intervals
(`TURN_EVENT_HEARTBEAT_MILLIS` × 3) without any event as a lost connection. Heartbeats are not
forwarded to `onEvent`.
On both watches, a response body that fails mid-stream, such as a closed or timed-out connection, is
a transport failure and resumes. Malformed events are still protocol failures.
`submitPrompt` requires either queue delivery with a `turnId`, or steer delivery with a `steerId` and
`expectedTurnId`.

`client.watchServer({ afterServerCursor?, onEvent? })` watches every Session on the server from one
stream. It pages no history. Without `afterServerCursor` it starts live: subscribe first, read the
current cursor from `server.ready`, then bootstrap with `listSessions()` and, per Session,
`listDescendantPendingApprovals` and `listDescendantPendingQuestions`; events that arrive during the
bootstrap are upserts over it. Pass `afterServerCursor` instead to replay every stamped row after a
cursor observed earlier (zero replays everything). It emits `server.ready` on the first event, carrying
the cursor the server announced, `session.created` (a Session snapshot, repeatable after a resume,
so upsert by `session.id`), `session.title`, `session.deleted`, `session.turn.updated` (the
authoritative Turn, reconciled through `getTurn` when a durable row names state the watch does not
hold, carrying the `serverCursor` of the event that produced it; a 404 or 409 on that read means
the Session or Turn is gone or closing for deletion, so the Turn is dropped and the stream
continues until its `session.deleted` arrives), `server.activity` when the cursor advances past rows that produced no
event, and the same
`server.reconnecting` and `server.resumed` lifecycle as `watchSession`, with the same idle timeout
(`SERVER_EVENT_HEARTBEAT_MILLIS` × 3) and backoff. Live text deltas are not on this stream; watch a
Session for those.

Use `appendConversation(sessionId, { content })` to add durable user context without creating a
Turn or calling a model. The Client generates an entry ID unless `entryId` is supplied. Work already
admitted before the append does not see the entry. Pass `authorId` to attribute the entry to a
stable human author; receipts and reads echo it back.

Use `getSessionConfiguration` and revision-checked `replaceSessionConfiguration` for ordered Session
rules. Use `replyPermission` with `once`, `always`, or `reject`. `listSavedPermissions` and
`removeSavedPermission` manage the durable allow grants created by `always`.
Use `forkSession(sourceId, { id?, turnId? })` to copy settled history before an optional Turn into
a new Session; an absent `turnId` forks the whole history. Pass `through` instead to copy
through a named message inclusively — a Turn message selects its whole Turn, a conversation
entry selects itself — and `visibility` to restrict the copy to what the forking actor may
observe. The fork inherits the parent workdir,
model, system prompt, and permission rules, and records `forkedFrom` lineage on the new Session.
Use `listSessions()` for the server's Sessions oldest-first, and `deleteServer()` to cancel and
delete them all (workspace files stay). Pass `workdir` to `createSession` for a per-Session
working directory; the server root is the default. Use `listSessionChildren(sessionId)` for a
Session's spawned children, `promoteSessionBackground(sessionId)` to detach its blocking
foreground sub-agent chains, and `capabilities()` to report host feature flags.

## Model discovery and provider management

The client provides typed discovery and connection management:

- `client.getRuntimeModels()` and `client.watchRuntimeModels()`: fetch the runtime model snapshot, or take a single-shot current-revision check.
- `client.getSessionModels(sessionId)` and `client.watchSessionModels(sessionId)`: fetch the snapshot a Session adopted, or take a single-shot current-revision check.
- `client.listProviders()` and `client.getProvider(providerId)`: list and inspect safe provider summaries and connection status.
- `client.connectProviderKey(providerId, { apiKey, label? })`: connect a provider API key atomically.
- `client.updateProviderConnection(providerId, connectionId, { label })`: update a connection's label.
- `client.deleteProviderConnection(providerId, connectionId)`: delete a stored connection.

The `watch*Models` streams are single-shot, not long-lived subscriptions: the server emits the
current revision once and closes. Re-call to re-check, and use the `get*` snapshot calls for fresh
catalog data.


## Public TypeScript exports

- `AddSessionPluginRequest`
- `AgentId`
- `AppendConversationInput`
- `AuthorId`
- `CatalogSnapshot`
- `ClientError`
- `ClientId`
- `ClientWorkdir`
- `ClientWorkdirValue`
- `CompleteMcpCredentialCallbackInput`
- `ContextCompactionId`
- `ContextCompactionOutcome`
- `ContextCompactionReceipt`
- `ContextCompactionSnapshot`
- `ConversationContent`
- `ConversationEntryId`
- `ConversationEntryReceipt`
- `createClient`
- `CreateClientOptions`
- `CreateSessionInput`
- `ForkSessionInput`
- `HARNESS_PROTOCOL_VERSION`
- `HarnessClient`
- `HarnessFetch`
- `imageBase64Prompt`
- `ImageMimeType`
- `ImageReference`
- `imageUrlPrompt`
- `ListPluginJournalOptions`
- `ListSessionCompactionsOptions`
- `ListSessionTurnsOptions`
- `MCP_CREDENTIAL_AUTHORIZATION_REQUIRED_REASONS`
- `MCP_CREDENTIAL_CALLBACK_DENIED_REASONS`
- `MCP_CREDENTIAL_REVOCATION_STATUSES`
- `MCP_PROTOCOL_VERSION`
- `ModelRef`
- `PendingDescendantApproval`
- `PendingDescendantQuestion`
- `PermissionReply`
- `PermissionRequest`
- `PermissionRequestId`
- `PluginJournalCursorInput`
- `ProjectId`
- `PromptContent`
- `PromptContentPart`
- `QuestionAnswer`
- `QuestionInfo`
- `QuestionOption`
- `QuestionRequest`
- `QuestionRequestId`
- `ReasoningEffort`
- `SavedPermission`
- `SavedPermissionId`
- `SERVER_VERSION_HEADER`
- `ServerActivityCursor`
- `ServerName`
- `ServerVersion`
- `ServerWatchEvent`
- `Session`
- `SessionActivityCursor`
- `SessionAgentEntryValue`
- `SessionAgentsResponseValue`
- `SessionCompactionHistoryCursor`
- `SessionCompactionState`
- `SessionConfiguration`
- `SessionConfigurationValue`
- `SessionHistoryCursor`
- `SessionId`
- `SessionWatchEvent`
- `SessionWorkdir`
- `SkillId`
- `SkillListEntryValue`
- `SkillSelection`
- `sniffImageMime`
- `StartMcpCredentialInput`
- `SteerId`
- `SteerReceipt`
- `SubmitContextCompactionInput`
- `SubmitMcpPromptInput`
- `SubmitPromptReceipt`
- `SubmitPromptRequest`
- `textPrompt`
- `ToolPermissionRequest`
- `TurnId`
- `TurnSnapshot`
- `TurnWatchEvent`
- `WatchServerOptions`
- `WatchSessionOptions`
- `WatchTurnOptions`

---

Generated from [`packages/client/README.md`](https://github.com/ZephyrCloudIO/ze-harness/blob/main/packages/client/README.md).
