Runnable browser-safe boundary
Consume a hosted run from a remote client
Install the independent remote package when run events cross an untrusted transport boundary or a client can disconnect while work continues.
Customization depth
Client protocol
Hosting depth
Browser or remote JavaScript runtime
Documentation status
Runnable reference
Assumptions
- The server exposes an authorized run lifecycle and a documented public payload contract.
- The client can persist the last fully handled sequence when reload recovery is required.
- Your application still owns timers, UI state, retry presentation, and product result handling.
- Runtime validation of the run envelope and host-supplied activity and result schemas.
- Cursor advancement, duplicate suppression, sequence-gap failure, terminal detection, and bounded retry calculation.
- Optional fetch lifecycle, incremental SSE parsing, response validation, identity checks, and reader cleanup.
- Which fields are authorized for remote users and the schemas that enforce that allowlist.
- Authentication headers, transport timers, subscription handles, cursor persistence, online/offline policy, and retry UX.
- Messages, tool rendering, optimistic state, notifications, errors, and application of terminal results.
Install only the browser-safe package
npm install @roackb2/heddle-remote zodDo not bundle @roackb2/heddle into a browser. The independent remote package excludes the Node agent runtime, model providers, server, CLI, and control plane. This tutorial uses Zod; substitute another synchronous Standard Schema validator only if you install it directly and adjust the imports.
Define the public event contract
import { z } from 'zod'
import { ConversationRunProtocolCodec } from '@roackb2/heddle-remote'
const protocol = new ConversationRunProtocolCodec({
activity: z.object({
type: z.string().min(1),
text: z.string().optional(),
}),
result: z.object({
outcome: z.string().min(1),
summary: z.string(),
}),
})Validators use the Standard Schema interface. Zod 3.24+, Zod 4, Valibot, ArkType, and compatible validators work without Heddle-specific adapters. Validation must be synchronous because parsing and serialization happen within the stream.
The schema output is the public projection. The codec validates your choice; it does not decide authorization or sanitize secrets for you. With Zod, ordinary object schemas strip unknown fields. Configure equivalent allowlist behavior explicitly in another validator.
The codec validates a non-empty runId, positive safe-integer sequence, ISO timestamp, one of activity, result, cancelled, or error, the host payload, and JSON-safe values. It rejects bigint, functions, symbols, non-finite numbers, and undefined that cannot safely cross JSON.
Let the consumer own cursor correctness
The following composition excerpt assumes runId came from an accepted start response and restoredSequence came from product-owned persisted UI state. The linked runnable example shows the complete lifecycle.
import { ConversationRunConsumerService } from '@roackb2/heddle-remote'
const consumer = new ConversationRunConsumerService<{ runId: string }>({
retry: { maxAttempts: 6, baseDelayMs: 500, maxDelayMs: 4_000 },
})
consumer.select({ runId }, { afterSequence: restoredSequence })Before subscribing, call consumer.subscriptionInput(). For each validated event, call consumer.accept(event) before rendering it. An accepted event advances the cursor; a duplicate is ignored; an event for another run is ignored; a sequence gap throws; a result, cancellation, or error marks terminal; a later post-terminal event is rejected.
Persist subscriptionInput().afterSequence only after your application has successfully handled the corresponding event. Otherwise a reload can skip data the user never saw.
If the stream disconnects before terminal, call consumer.nextRetry(). Heddle calculates the bounded delay and canonical next cursor. Your application owns the actual timer, abort controller, online/offline behavior, and visible retry state. Accepted progress resets the attempt budget.
Opt into the REST/SSE client
When the server uses the conventional run resource, import the preset:
This is a composition excerpt. StartRunResultSchema and CancelRunResultSchema must be the same public schemas used by the server; accessToken, sessionId, prompt, and the AbortController named subscription come from your authenticated product flow. Use the linked runnable browser client for a complete file.
import { ConversationRunHttpSseClient } from '@roackb2/heddle-remote/http-sse'
const client = new ConversationRunHttpSseClient({
baseUrl: '/api/agent',
protocol,
accepted: StartRunResultSchema,
cancellation: CancelRunResultSchema,
getHeaders: () => ({ Authorization: `Bearer ${accessToken}` }),
})
const accepted = await client.start({ sessionId, prompt })
await client.subscribe({
runId: accepted.runId,
afterSequence: consumer.subscriptionInput()?.afterSequence,
signal: subscription.signal,
onEvent(event) {
const acceptance = consumer.accept(event)
if (acceptance.accepted) renderProductEvent(event)
},
})The client owns URL encoding, header composition, HTTP error decoding, response validation, incremental SSE parsing, event identity checks, reader cleanup, and abort propagation. It does not own reconnect timers or UI state.
Bearer-authenticated browser streams use fetch because native EventSource cannot attach an Authorization header. Cookie-authenticated products may use EventSource and let the browser send Last-Event-ID during reconnect.
Scope of support
The maintained package targets JavaScript and TypeScript. A non-JavaScript client may implement the documented wire envelope and lifecycle, but Heddle does not currently ship an official client for other languages. Treat those integrations as adaptations, not drop-in support.