npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@golemcloud/golem-ts-sdk

v2.0.2

Published

```ts import { defineAgent, method } from '@golemcloud/golem-ts-sdk'; import { z } from 'zod';

Readme

Golem TypeScript SDK

import { defineAgent, method } from '@golemcloud/golem-ts-sdk';
import { z } from 'zod';

const counter = defineAgent({
  name: 'Counter',
  id: { name: z.string() },
  methods: {
    increment: method({ input: {}, returns: z.number() }),
  },
});

export const Counter = counter.implement({
  init() {
    return { value: 0 };
  },
  methods: {
    async increment() {
      return ++this.value;
    },
  },
});

The state returned by init() or a snapshot load() must not contain top-level fields named config, getId, getPhantomId, or getPrincipal. These names are reserved for the SDK's configuration accessor and identity/principal helpers. The SDK rejects these fields during initialization or restoration, even if their value is undefined. Rename them or nest them inside another state field; nested fields may use these names.

HTTP routers and files

defineHttpRouter registers a parameterless, ephemeral, snapshot-disabled HTTP router alongside defineAgent. Routers have no ordinary agent client or dependencies option. The mount is a literal public path; authentication and CORS use the ordinary host policies.

import { defineHttpRouter, withRawHeaders } from '@golemcloud/golem-ts-sdk';
import { z } from 'zod';

export const Web = defineHttpRouter('Web', { config: { greeting: z.string() } })
  .mount('/web', { cors: ['https://example.com'] })
  .static('/assets/*', '/assets/$1')
  .openApi(
    ({ config }) => ({
      openapi: '3.1.0',
      info: { title: config.greeting, version: '1' },
      paths: { '/echo': { post: { responses: { '200': { description: 'Echo' } } } } },
    }),
    { methodName: 'describe' },
  )
  .implement(
    (request, context) => {
      // request.body is an incremental ReadableStream<Uint8Array>, not a buffered payload.
      // context.rawRequest preserves original method, target, query, and byte-valued headers.
      return withRawHeaders(new Response(request.body), [
        { name: 'set-cookie', value: new TextEncoder().encode('a=first') },
        { name: 'set-cookie', value: new TextEncoder().encode('a=second') },
      ]);
    },
    { methodName: 'serve' },
  );

export const Assets = defineHttpRouter('Assets')
  .mount('/assets', {
    fileResponseHeaders: { 'content-security-policy': "default-src 'self'" },
  })
  .static('/*', '/assets/$1')
  .implement();
export const Docs = defineHttpRouter('Docs')
  .mount('/docs')
  .openApi(() => ({
    openapi: '3.1.0',
    info: { title: 'Docs', version: '1' },
    paths: {},
  }))
  .implement();

The two method names are arbitrary and must differ. Omitting the handler registers a static-only, provider-only, or empty router without a dummy method. OpenAPI providers run lazily, not during registration. Supply files and configuration through the normal application manifest and list the router in the HTTP API deployment's agents map. Static mappings read immutable deployed files; they do not expose files written by an invocation. Set fileResponseHeaders on either an agent mount or router mount to add those headers when the host serves exposed or static files.

Web Headers normalizes names and combines repeated values; Request normalizes URLs and some standard methods and forbids bodies on GET/HEAD. context.rawRequest exposes the original head without a second body reader. withRawHeaders replaces the entire canonical response header sequence, including independent cookies and non-UTF-8 bytes. Use lowercase field names and Uint8Array values. For methods, targets, statuses or bodies that Web APIs cannot represent, use the canonical API instead:

import { AgentStream, defineHttpRouter } from '@golemcloud/golem-ts-sdk';

export const Raw = defineHttpRouter('Raw')
  .mount('/raw')
  .implementRaw((request) => ({
    status: 200,
    headers: [{ name: 'x-byte', value: new Uint8Array([255]) }],
    body: request.body, // AgentStream<Uint8Array>; extension method spelling is unchanged.
  }));

The adapter owns input/output disposal. It retains input while the response may consume it and releases both on response EOF, error, or local disposal. HEAD and 204/205/304 responses dispose their producers before the native stream pump can poll them. Use highWaterMark: 0 if a Web producer must not prefetch. Do not detach work that outlives the returned stream. The host owns framing, head commitment and session completion; body EOF alone is not session completion.

Cancellation limitation: local stream disposal is tested, but host cancellation currently traps Wasm rather than providing a cooperative JavaScript invocation signal. The pinned native stream pump can also wait in next() before observing a remote reader drop. JavaScript finally execution and prompt disposal during an indefinitely pending pull are not guaranteed on those paths. Request.signal does not represent host invocation cancellation.

Ordinary durable, non-phantom agents expose live files with exposeFiles. Every constructor parameter must be a path-compatible scalar bound exactly once in the mount; the constructor can create the file before the host reads it. Ordinary methods and clients remain available.

import { defineAgent, http } from '@golemcloud/golem-ts-sdk';
import { z } from 'zod';
import * as fs from 'node:fs';

export const Files = defineAgent({
  name: 'Files',
  id: { name: z.string() },
  methods: {},
  http: http.mount('/files/{name}', {
    exposeFiles: [{ route: '/report', path: '/report.txt' }],
  }),
}).implement({
  init({ id }) {
    fs.writeFileSync('/report.txt', `Report for ${id.name}`);
    return {};
  },
  methods: {},
});

Mappings preserve declaration order. Exact routes target absolute files; a terminal /* maps to a target ending in /$1. Public segments are decoded once; filesystem targets are not URI decoded. Identical compiled pairs are rejected, while the same route with distinct targets is allowed. The host applies static/live-file selection and path-security rules.

Public HTTP router entry

Import defineHttpRouter, nodeHttpHandler, and the following contract helpers from @golemcloud/golem-ts-sdk/http-router. This is a component-runtime entry, not a host-free Node module. Build applications with the SDK component plugin so router registration and the HTTP facade share the component's module graph. nodeHttpHandler accepts only servers constructed by the component facade, not native socket-backed Node servers. The facade does not change the HTTP module used by host-side build tooling.

Socket-free Node HTTP servers

Use the familiar request listener API, then register it explicitly as a router:

import { createServer } from 'node:http';
import { defineHttpRouter, nodeHttpHandler } from '@golemcloud/golem-ts-sdk/http-router';

const server = createServer((req, res) => {
  res.writeHead(200, { 'content-type': 'text/plain' });
  res.end('Hello from Golem');
});

export const router = defineHttpRouter('Web').mount('/').implementRaw(nodeHttpHandler(server));

List Web in the HTTP API deployment's agents map in golem.yaml, just as for other routers. The name identifies the deployed router; the mount defines its public prefix. Use .mount('/web', { auth: true, cors: ['https://example.com'] }) to apply the normal host authentication and CORS policies. No listen() call or component port is needed.

Alternatively, opt into initialization-time listen() registration with a component nodeHttpRouters mapping in golem.yaml:

components:
  my-app:main:
    templates: ts
    nodeHttpRouters:
      '3000':
        name: Web
        mount: /web
        auth: true
        cors: [https://example.com]

Then call server.listen(3000, callback) while the application module initializes. The build injects this configuration before application and bundled dependency evaluation, in both discovery and execution instances. Still list Web in the HTTP API deployment. Ports are lookup keys, not sockets. listening and the asynchronous listening event/callback report successful local router registration, not deployment readiness. address() returns null; close() throws because it cannot undeploy a route. Invocation-time registration, duplicate ports or names, and mixing explicit and automatic registration of one server are rejected. Only a numeric port from 1 through 65535 and an optional callback are accepted. Templates and presets can also provide the mapping; each supplied table replaces the inherited table, and {} clears it. Explicit registration remains independently usable.

Component builds resolve named and default imports from both http and node:http to one facade, including statically bundled CommonJS dependency imports. Runtime-computed module loading is not supported. Outgoing request(), get(), client IncomingMessage, and the other original client exports remain the runtime's HTTP implementation.

The server supports createServer() or createServer(listener) and later server.on('request', listener) registration. Constructor options, custom message classes, connection management and upgrades are not supported. It is a local EventEmitter listener container, not a persistent process-wide server: each public request runs in an ephemeral router instance. Closing a local stream cannot undeploy a route. This subset does not imply compatibility with every Node web framework.

Request objects inherit the original IncomingMessage and support readable-stream operations such as on('data'), async iteration, pipe(), pause(), resume(), and destroy(). Response objects use the runtime's Writable with statusCode, setHeader(), getHeader(), getHeaders(), getHeaderNames(), hasHeader(), removeHeader(), writeHead() (header object or raw name/value pairs), headersSent, flushHeaders(), write(), end(), and destroy(). Use writable finish, drain, error, and close events for local stream lifecycle; finish is not confirmation that the remote caller received all bytes.

Incoming Node requests preserve the method, mount prefix, and original query, including absent versus empty queries. Body reads are demand-driven and produce Buffer values. Header values use Latin-1 byte strings. headers uses lowercase names, keeps the first singleton header, joins cookies with ; and other duplicates with , , and retains set-cookie as an array. headersDistinct retains all occurrences. rawHeaders contains the occurrences in Golem's canonical envelope plus host from its authority; it does not recover original wire casing or order. The request has no HTTP version, remote address, or operational socket.

Responses commit their status and headers at writeHead(), flushHeaders(), the first write(), or end(). The handler returns at commitment while the body remains live. Writable callbacks and drain provide cooperative backpressure; callers must pause when write() returns false. Ignoring that result can grow the writable queue without bound. The host, not the adapter, supplies HTTP framing. Repeated set-cookie values remain separate occurrences. Custom reason phrases, trailers, informational responses, upgrades, and listening sockets are unsupported.

Adapter-observed errors, destruction, and output disposal release both sides of the exchange. Normal HEAD/204/205/304 and Content-Length-zero suppression discards body writes without waiting for end() or a first body read. Other early disposal cancels local writes. Request listeners follow EventEmitter semantics: return values are ignored, and rejected promises from async listeners are not automatically caught. Handle them explicitly. Remote disconnects use Golem's existing host cancellation and interruption. There is no handler-context signal that guarantees graceful JavaScript aborted/close events. Adapter-created input sources must cooperate with disposal; an arbitrary pending source promise is not forcibly interrupted.

  • HttpRequest<Body> / HttpResponse<Body> impose no constraint on Body. HttpHeader.value is Uint8Array; header arrays are readonly ordered occurrences. Request query is string | undefined: undefined means absent; '' means a present empty query.
  • compileFileMappings(readonly { route: string; path: string }[]) returns WIT-shaped mappings.
  • compileRouterMount({ mount, auth?, cors?, staticBindings?, handlerMethod?, openapiProviderMethod? }) accepts already compiled mappings and returns { mount, handlerBinding }. An absent handler produces no binding. The helper never rebuilds an adapter's schemas or configuration.
  • copyHttpHeaders validates/copies the canonical sequence without normalizing occurrences.
  • serializeOpenApi(document: unknown): string synchronously emits bounded, canonical JSON (sorted Unicode keys, preserved array order). It rejects non-JSON values, cycles, unsupported root sections, non-3.1.0 documents, depth over 64 and UTF-8 output over 1 MiB. It does not fetch, register, evaluate providers, strip fields, rebase paths, or merge domains. The host performs full OpenAPI semantic validation, reference handling, rebasing and merging.
  • HttpRouterError.category provides a safe diagnostic category without document contents.

The TypeScript and Effect SDKs build these primitives from the private sdks/http-contract source package independently. Both bundle its code and declarations; npm consumers need no private package or sibling SDK checkout. After building the SDK and its template, run pnpm --filter @golemcloud/golem-ts-sdk run check:package from sdks/ts to verify a packed installation, component HTTP imports, and consumer declarations.

The SDK tests execute shared corpus metadata/mapping cases and targeted stream/codec cases; corpus integrity alone does not prove runtime conformance. The CLI deployed fixture typescript_http_router exercises static files, live files and ordinary invocation, OpenAPI, extension methods, empty queries, HEAD, early responses, cookie order, and delayed duplex input.

Unstructured outputs and HTTP responses

s.unstructuredText() and s.unstructuredBinary() declare unstructured values. A method returns them as { tag: 'inline', val, ... } with the data or { tag: 'url', val }. Wrap them in s.option(inner) for an optional value (undefined is none) and in s.result(ok, err) for a typed error:

import { defineAgent, http, method, Result, s } from '@golemcloud/golem-ts-sdk';
import { z } from 'zod';

const Pending = z.object({ tag: z.literal('pending') });

export const Images = defineAgent({
  name: 'Images',
  id: { bucket: z.string() },
  http: http.mount('/images/{bucket}'),
  methods: {
    download: method({
      input: { id: z.string() },
      returns: s.result(s.option(s.unstructuredBinary({ mimeTypes: ['image/png'] })), Pending),
      http: http.get('/{id}'),
    }),
  },
}).implement({
  init: () => ({}),
  methods: {
    download: ({ id }) =>
      id === 'missing'
        ? Result.ok(undefined)
        : id === 'pending'
          ? Result.err({ tag: 'pending' as const })
          : Result.ok({ tag: 'inline' as const, val: new Uint8Array(), mimeType: 'image/png' }),
  },
});

The output schema keeps the unstructured-text or unstructured-binary role on the nested variant { inline, url }. The Golem HTTP host maps the output of an HTTP endpoint method as follows:

| Output | Response | | ---------------------------- | --------------------------------------------------------------- | | element | inline → 200 with the raw body; url → redirect | | option<element> | none → 404; some → as the element | | result<element, E> | ok → as the element; err → 500 with the error as JSON | | result<option<element>, E> | ok(none) → 404; ok(some) → as the element; err → 500 JSON |

Other return schemas use the same option and result rules with a JSON body in place of the raw body.

Streaming methods

Use s.stream(itemSchema) in a method schema and AgentStream<T> in its implementation. Streams can appear at the root or nested inside records, tuples, options, results, variants, and lists.

import { AgentStream, defineAgent, method, s } from '@golemcloud/golem-ts-sdk';

const numbers = defineAgent({
  name: 'Numbers',
  id: {},
  methods: {
    doubled: method({
      input: { values: s.stream(s.u32()) },
      returns: s.stream(s.u32()),
    }),
  },
});

export const Numbers = numbers.implement({
  init() {
    return {};
  },
  methods: {
    doubled({ values }) {
      return AgentStream.from(
        (async function* () {
          for await (const value of values) yield value * 2;
        })(),
      );
    },
  },
});

AgentStream is lazy and single-reader, so await each operation before starting another. Clean EOF is { done: true, value: undefined }. Encoding or forwarding a stream transfers its ownership; do not reuse the original object.

For a stream received through a connected P3 agent invocation, early exit from for await calls return() and closes the readable endpoint. throw(reason) also closes it and rejects locally with reason, which is not sent to the producer. When an AgentStream.from source is sent through P3, accepted writes gate later pulls, providing back-pressure. When a subsequent write observes a remote reader drop, production stops and awaits the source iterator's return() exactly once. P3 does not interrupt an arbitrary pending source next() or guarantee cleanup before a later agent invocation. Producer and cleanup failures fail the active operation or invocation session rather than becoming clean EOF. P3 has no recoverable stream-local terminal error, so model one explicitly in the item type, for example stream<result<T, E>>, when needed.

Agent client approaches and reflection

Choose the narrowest caller surface that owns the information you have:

  1. Normal RPC uses a shared source definition through the SDK's ordinary typed client and owns all lifecycle factories allowed by its mode.
  2. Caller-defined static clients use local schemas without discovery. defineAgentClient({ methods }) binds an existing durable canonical or phantom ParsedAgentId; defineAgentClient({ name, id, methods, mode?, config? }) adds identity construction, lifecycle factories, and declaration-aware config validation.
  3. Discovered clients use reflection.getReflectedAgentType and retain an immutable deployed metadata snapshot for runtime-selected types and methods.
  4. Fully dynamic clients use ParsedAgentId.dynamicClient() to transport schema-native values on an existing durable identity. They do not retain schemas or create a target.

Normal RPC and caller-defined static clients encode through their local schemas before opening RPC. Full-client binding also checks the declared type name and constructor shape. Discovered clients apply the complete snapshot input restrictions and validate output cardinality and shape. The host remains authoritative for visibility, authorization, effective configuration, identity resolution, and deployed-schema validation.

Typed inputs use the schema library's normal optional-field syntax. Canonical reflected JSON records contain every field; use null for an absent option<T>. s64 and u64 are canonical decimal strings, duration is { nanoseconds: "..." }, and quantity uses a decimal-string mantissa; smaller integers remain numbers. Schema-native streams and opaque capabilities require the *Value APIs and transfer ownership once.

import { getReflectedAgentType, isRemoteCallError } from '@golemcloud/golem-ts-sdk';

const type = getReflectedAgentType('Counter');
if (!type || type.mode !== 'durable') throw new Error('Counter is unavailable');
const client = type.client.get({ name: 'main' });
const add = client.method('add');

try {
  const abort = new AbortController();
  const result = await add.invoke({ by: 1, note: null }, abort.signal);
  console.log(result.value, result.metadata.agentId);
  const scheduled = add.schedule({ seconds: 1n, nanoseconds: 0 }, { by: 2, note: null });
  scheduled.cancellationToken.cancel();
} catch (error) {
  if (isRemoteCallError(error)) console.error(error.cause);
  else throw error;
}

Runtime tool reflection

Use reflection.getToolType(name) or reflection.getAllToolTypes() to discover tools visible to the calling component. A ToolType is an immutable metadata snapshot. Resolve command names or aliases with tool.client.command(path); the returned command exposes its canonical path, ordered arguments, input schema, result schema, and child commands. Namespace commands appear in the tree but cannot be invoked until a command body is selected.

import { reflection } from '@golemcloud/golem-ts-sdk';

const tool = reflection.getToolType('weather');
if (tool) {
  const forecast = tool.client.command(['forecast']);
  const result = await forecast.invokeJson({ city: 'Budapest' });
}

Canonical JSON records include every argument key; use null for an absent optional value. invokeValue accepts a schema-native value. Both forms validate against the discovered schema before opening RPC and check declared results after invocation. startJson and startValue expose independent stdout and stderr streams, result, collect, and cancellation for pending calls; use them when either output is required. Drain every declared output concurrently with observing the result, or use collect() to settle all three. Result failures take precedence over output failures. Consume or cancel every started call. DynamicToolClient accepts a caller-packed typed schema value without pretending to know the deployed schema. Reflected output failures reject with ToolRemoteOutputError.

Retrying user code

retry interprets the same semantic policies used by Golem in ordinary user space. The callback may return synchronously or return a Promise. Use properties when a filtered policy needs values from the current failure, and use an AbortSignal to cancel an attempt or delay.

import { Duration, Policy, Predicate, retry } from '@golemcloud/golem-ts-sdk';

const policy = Policy.exponential(Duration.milliseconds(100), 2)
  .maxRetries(4)
  .onlyWhen(Predicate.eq('transient', true));

const response = await retry(policy, () => callUnreliableService(), {
  properties: (error) => ({ transient: error instanceof TransientError }),
  signal,
});

Aborting stops waiting for an in-flight callback but cannot stop arbitrary user code. Pass the same signal into the underlying operation when it supports cancellation.

Local prop-matches predicates translate * to JavaScript regex .* and ? to ., without flags; all other characters are literal. Wildcards exclude line terminators, and ? matches one UTF-16 code unit. This does not implement the host's full glob syntax. Use host-driven retries when authoritative platform matching is required.

These are local function calls, not executor-managed retry attempts. They do not create executor retry-attempt oplog entries or survive suspension and recovery as one host-managed retry sequence.

External Durable Streams

The SDK reads and appends existing external Durable Streams through reader and writer resources in golem:agent/[email protected]. Each SDK factory synchronously constructs one journaled resource, capturing its immutable URL, mode or producer identity, timeout and optional secret identity without HTTP. Subsequent asynchronous reads record only checkpoint, transport and content type; appends record only payload, sequence and close flag. Retries reuse that resource. The SDK does not create remote streams or send HTTP itself. Custom component worlds using these APIs must import that interface.

import {
  readDurableJsonStream,
  readDurableByteStream,
  createDurableJsonWriter,
  createDurableByteWriter,
  s,
} from '@golemcloud/golem-ts-sdk';

const url = 'https://streams.example/events';
const events = readDurableJsonStream(s.u32(), { url, offset: '-1', live: 'sse' });
for await (const event of events) {
  if (event === 42) break;
}

const writer = createDurableJsonWriter(s.u32(), { url, producerId: 'my-producer' });
await writer.append([11, 23]);
await writer.append([47], { close: true }); // one atomic append-and-close

const bytes = readDurableByteStream({ url: 'https://streams.example/bytes' });
const byteWriter = createDurableByteWriter({ url: 'https://streams.example/other-bytes' });
await byteWriter.append(new Uint8Array([3, 241, 27]));
await byteWriter.close(); // close-only consumes a producer sequence too

Readers return ordinary AgentStream<T> values. Byte readers yield individual number bytes for s.stream(s.u8()); external append boundaries are not message boundaries. They can be forwarded as native method inputs or outputs, including nested streams, with the ownership, failure and backpressure rules above. A reader retains one complete batch and an index; it drains that batch before requesting the next opaque offset/cursor. Final data is delivered before closed EOF. Empty responses and up-to-date markers are not closure. offset: 'now' is resolved once by catch-up; subsequent reads use that concrete checkpoint and the original content type, including for SSE. EOF and return() release the reader resource, including when forwarded through a native stream. Use return() to release an unused reader or one abandoned after a local decode or host failure.

JSON uses the SDK's canonical schema representation by default. The canonical codec rejects 64-bit integers outside JavaScript's safe-number range rather than rounding them. For an external representation needing exact application conversion, provide deterministic decode/encode callbacks; the SDK still checks the application value against the compiled schema. decode receives each original complete JSON value as text, with numeric lexemes preserved. encode returns one complete value; the host supplies the outer batch array. An array-valued message therefore stays one message, rather than being flattened. For example, an unquoted u64 stream:

const integers = readDurableJsonStream(s.u64(), { url, decode: (text) => BigInt(text) });
const integerWriter = createDurableJsonWriter(s.u64(), {
  url,
  encode: (value) => value.toString(),
});
await integerWriter.append([18446744073709551615n]);

The default attempt timeout is 30000 ms (allowed: 1–300000). Each batch operation automatically retries timeout, transport, rate-limited and unavailable failures at most five times, using the runtime's durable timers, exponential backoff starting at 100 ms and capped at 30000 ms, and never waiting less than Retry-After. maxRetries and retryDelayMs configure this policy. Reconstruction replays failed attempts and waits; it does not restart the retry budget. An explicit later operation or retryPending() starts a new bounded resolution attempt. Empty live responses wait idleDelayMs (default 100 ms) before another pull. Active HTTP awaits remain resident until their bounded deadline; only idle durable timer waits can unload. Do not enclose these waits in custom durability scopes.

Client deadline expiry counts against the failure budget even for a quiet live stream. For long-poll, set timeoutMs above the server's polling window. SSE peers that send no completed control event before the deadline may exhaust the retry budget during prolonged silence. The SDK does not turn timeouts into fabricated successful checkpoints. Retry-After can exceed the exponential backoff cap; the SDK honors it without imposing a total elapsed-time limit.

Writers serialize operations per instance, while independent writers can overlap. They copy and retain one assigned producer tuple, exact encoded payload and close flag before the host append. Any host failure or cancellation retains this request, including non-retryable failures such as payload-too-large. retryPending() resolves it without new data; append and close also resolve it before assigning a new sequence. This prevents replacing a body that may have committed during an earlier uncertain attempt. A duplicate acknowledgement advances exactly once. Acknowledged sequences greater than the submitted sequence raise DurableStreamError with kind producer-diverged. The SDK never steals epochs, renumbers pending data, or splits oversized requests. Epoch and sequence are bounded by 2^53-1; an explicit new epoch starts at sequence zero. Empty appends require close: true. An acknowledged closed receipt releases the writer resource. await writer.dispose() releases it without closing the remote stream, after resolving any pending request; if resolution fails, the handle and pending data remain available for another attempt. Disposal is serialized with appends and is idempotent. Appending after disposal fails with closed.

Append receipts contain epoch, sequence, closed, and an optional nextOffset. A duplicate acknowledgement may omit Stream-Next-Offset; the SDK preserves that absence as undefined, not an empty string. There is no duplicate flag: HTTP 204 can acknowledge either a duplicate append or a fresh close-only operation. Producer progress advances from the acknowledged tuple, regardless of whether an offset was supplied.

DurableStreamError.kind preserves the host's typed failure classification and exposes retryAfterMs, producerEpoch and expectedSequence when supplied. Missing streams (not-found), retention loss/deletion (gone), fencing, conflicts and oversized payloads are errors, not EOF. The host enforces its configured batch-size limit; retrying an oversized read with the same limit cannot make progress. Local decode failures do not consume the failed item. Forwarded failures fail the active producer/session; use an explicitly result-valued stream for recoverable application errors. Closing a reader prevents new pulls, but the existing native stream API cannot interrupt an arbitrary pending async import; deadlines bound that interval.

Pass a borrowed raw secret capability (for example an s.secret(s.string()) method argument) as auth. It goes directly to the host; do not call Secret.get() or reveal a bearer token in the SDK. The configuration Secret<T> wrapper is not a raw capability and cannot be passed as auth. The host enforces reveal/network permissions and pins the secret revision. HTTPS is required except for localhost/literal loopback development URLs; URL credentials and redirects are rejected.

Durability uses ordinary Golem host-call replay, without a custom wrapper. Completed replay performs no external HTTP. Interrupted writes retain the platform's fail-closed recovery behavior. Reader/writer buffers reconstruct from the oplog and deterministic execution; no snapshot export is provided, and existing restrictions on snapshots of active native streams remain.

A copied-oplog Golem fork/revert retains the same external URL, producer ID, epoch and checkpoint. It does not create an external stream fork or independent producer. Divergent branches may reuse the same tuple; the server may deduplicate without comparing bodies. Exactly-once effects depend on the peer atomically persisting data and producer state and retaining deduplication records. Construct a writer with a different ID, or an explicitly higher epoch and sequence zero, only when that is the intended external identity; never discard uncertain data to do so.

The SDK uses Standard Schema-compatible schemas to define agent identities, method inputs, and method results.

From sdks/ts, run pnpm build to build all TypeScript packages and pnpm test to run their tests.

Tool middleware

Typed middleware declares installation parameters with parameterSchema; the decoded static type is available as context.parameters. Omit it for the normalized empty-record {} schema. Universal middleware uses the same option. The compile-backed forms are exercised in tests/tool.test-d.ts and tests/tool-middleware.test.ts.

Typed underlying methods retain the convenient awaited call and add .start(args). A started call exposes an independent result promise, optional stdout and stderr, and cancel(), so calls may overlap and their results and streams may be consumed in any order. Universal middleware uses await underlying.invoke(...) for the same started shape, or underlying.invokeAndAwait(...) for the convenience form.

The underlying is revoked when the middleware handler returns: no new calls may start, but already admitted calls are not implicitly cancelled. Releasing an invocation result observer is disposal, not cancellation; call cancel() explicitly when intended. The SDK disposes abandoned observers and streams.

For commands declaring stdout or stderr, the guest receives the matching host writers. The SDK forwards each middleware-selected output to its writer concurrently with the other output and the result, finishes it on clean EOF, and fails it on forwarding errors. Authors return or select the readable output streams and do not write to or finish the host writers directly.

Component bundling

All TypeScript components use the same full agent-guest WIT world. The component Rollup configuration infers registration capabilities from the TypeScript program and generates static imports for agent, tool, middleware, and snapshot exports. Absent capabilities use small empty discovery/error implementations; no role or alternate world is selected.

The application bundles the SDK's preserved runtime modules, rather than importing a complete SDK embedded in the wrapper. Rollup can therefore remove absent registries and agent snapshot, principal serialization, and lifecycle code when they are unreachable. Builder methods are not rewritten or removed by the build. Opaque registration helpers and computed SDK access conservatively retain capabilities. Host modules remain external.

Agent, configuration, schema, and tool definitions are evaluated normally at runtime, including metadata computed by application functions. Component builds do not statically evaluate or rewrite these definitions.

After building the SDK and agent template, run node scripts/measure-components.mjs from this package to build the five capability fixtures, check their full-world ABI, and record JS, release/stripped, and preinitialized component sizes in .component-measurements/sizes.json. Pass a baseline full-SDK template WASM as the first argument to compare the old external-SDK build. The recorded timing is median end-to-end QuickJS preinitialization time (three runs), including Wasmtime compilation; it is not deployed invocation latency. The harness requires wasm-rquickjs and wasm-tools on PATH and is separate from the process-free unit suite.

Sagas

infallibleSaga returns Result<Out, SagaRetryFailure>. It compensates returned step failures and caught exceptions in reverse order, then resolves the saga retry policy for the agent URI. The host receives the same attempt and error-kind context for selection and evaluation. It owns the opaque continuation, jitter and finite retry bounds; the SDK does not interpret the policy tree. No selected policy uses config.retry. Policy changes are errors, not a new retry budget. Exhaustion and policy errors return a typed failure rather than trapping.

Elapsed time includes work, compensation and durable timer waits from before the first attempt. Attempts append fresh calls; they do not rewind local state. Compensate local changes explicitly and do not reuse a compensated external operation's idempotency key.

Partial compensation returns a failure with the original error and compensation failure, without retrying. fallibleSaga returns complete or partial rollback metadata for returned failures. Caught exceptions run compensation and are rethrown unchanged after complete rollback. Uncatchable guest traps cannot run guest compensation.