@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:

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:

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