# SDK What the WAMP Cloud TypeScript client exposes, the drift guard that keeps it honest, and why you may want to call the HTTP API directly instead. Source: https://docs.cloud.vampikez.fun/reference/sdk/ There is a first-party TypeScript client for this API. It is not on npm, so build against the HTTP contract, and read the versioning rule below before you pin anything. ## Availability `@wamp/app-sdk` is maintained in the WAMP repository. A registry check on September 8, 2026 returned `404` for the public npm package. Use the HTTP examples in this site, or obtain a packed package from your platform operator. Do not depend on an unpublished package name in deployment instructions. Working without it costs little: every operation on this site is plain HTTPS against the [OpenAPI contract](/reference/api/), and the only part that is not a `fetch` call is signing the app assertion, which is about ten lines of `jose`. [Authentication](/start/authentication/) shows it. ## What a version number means **The major version is the Cloud API generation the client speaks.** `1.x` speaks Cloud `v1`, the API this site documents; a Cloud `v2` would ship as `2.x`. Minor and patch releases are SDK-only — added methods, added exported types, fixes — and never move you to a different API generation. So pin on the major (`^1.0.0`) if what you care about is the wire contract, and read a major bump as "the API generation moved", not as "the client was rewritten". The corollary is the useful part: a `1.x` upgrade cannot change which endpoints you are calling. ## Why the client is worth using when it ships Authenticated operations in the OpenAPI contract carry an `x-wamp-sdk-method` extension naming their client method. A test checks that mapping in both directions. The two without one are the unauthenticated discovery pair, `GET /v1/openapi.json` and `GET /v1/docs`. So the client cannot quietly fall behind the API, and the API cannot gain an operation the client silently lacks. That is the same reason the reference pages on this site are generated from the contract rather than written: the contract is the one artifact everything else is checked against. The mapping is data, not just a test fixture: `WAMP_CLOUD_V1_SDK_OPERATIONS` is exported as a table of `[method, path, client method]` rows, so a backend that audits its own coverage can read it rather than transcribe it. ## The surface Three classes and two error types. `WampApp` handles identity — the pieces you need before any Cloud call: | Member | Purpose | |---|---| | `generateAppKeypair()` | One-time Ed25519 key generation. Register the public half; the private half never leaves your backend | | `new WampApp(options)` | Holds your app slug and private key | | `issueUserToken(options)` | Sign an end-user session token for a client to send to the AI proxy | | `installationToken()` | Mint the tenant- and resource-exact backend token | | `createInstallationIntent()` / `getInstallationIntent()` | Drive the installation handshake | | `buildAssertion()` | Build the signed assertion the token exchange consumes | `WampCloud` is the API client. Its methods map onto the operations documented under [Operations](/api/operations/tags/sessions/): | Area | Methods | |---|---| | Discovery and input | `capabilities`, `transcribeAudio` | | Native identity | `me` — first-party Desktop Cloud only; an installation credential cannot call it | | Release probes | `activateRuntimeReleaseProbe`, `deactivateRuntimeReleaseProbe` — requires `wamp.cloud.runtime-releases:probe` | | Sessions | `createSession`, `getSession`, `updateSession`, `listSessions`, `archiveSession`, `restoreSessionWorkspace`, `inspectSessionExecution` | | Organization environments | `listEnvironments`, `getEnvironment`, `listEnvironmentRevisions`, `putEnvironment`, `restoreEnvironment`, `archiveEnvironment`, `unarchiveEnvironment`, `putEnvironmentSecret`, `deleteEnvironmentSecret`, `putEnvironmentArchive`, `setDefaultEnvironment` | | Turns and runs | `submitTurn`, `listTurns`, `getRun`, `cancelRun` | | Following work | `listSessionEvents`, `sessionEvents`, `streamSessionEvents`, `streamSessionChanges`, `listOpenInteractions`, `decideInteraction` | | Artifacts | `listArtifacts`, `getArtifact`, `getArtifactText`, `getArtifactContent` | | Repositories | `listRepositories`, `listRepositoryGrants`, `resolveRepositoryGrant`, `reviewRepository` | | Publications | `publishRepository`, `listPublications`, `getPublication`, `mergePublication`, `listPublicationMerges`, `getPublicationMerge` | `transcribeAudio` accepts bounded audio bytes, MIME type, and duration (100 ms to 10 minutes, at most 20 MiB), and returns text. WAMP does not persist the source audio; treat the returned text as ordinary user input and apply your product's normal review/send policy to it. `me` exists on the Node client so the same class can serve the first-party native token. It is not an integration method. `CloudInteraction.request` is typed as `CloudInteractionRequest`: `questions` for `ask_user`, `plan` for `exit_plan_mode`, and `approval` with the complete action for a live approval. The opened event carries approval metadata and deadline, without action arguments; fetch open interactions before presenting a decision. `offeredApprovalChoices(request.approval)` returns the choices Cloud accepts. Pass a caller-owned `idempotencyKey` to `decideInteraction` and reuse it on retry. When another answer already won, the `cloud_interaction_conflict` error's `outcome` holds that answer; without `outcome` the request is no longer waiting. ACP questions expose stable field ids, typed option values, defaults and constraints; submit their answer as a JSON object in the reply Turn's `message`. Each request member is optional, because a caller that renders only one kind still has to compile against an event carrying the other. [Events](/reference/events/#wampinteractionopened) has the payload. `sessionEvents` is the exception in that table: it is an async generator over the event log rather than a single operation, so it drives the cursor loop for you instead of returning one page. Three things stay yours, deliberately, and each is one line: - **The cursor is `event.sequence`.** The generator holds no state between calls, because only your code knows when an event became durable on your side. Persist the last sequence you handled and pass it back as `after`. - **The bound is `break`.** A generator fetches only what is pulled, so leaving the `for await` stops paging. There is no `maxEvents` option: a page cap makes "I stopped early" and "I reached the tail" indistinguishable, and a caller's own `break` does not. - **A hole throws `WampCloudEventGapError`.** Sequences are gapless per session, and nothing prunes an event, so a page that does not start at `after + 1` is a server-side invariant violation rather than a slow write or expired retention. The generator throws and your cursor stays where it was, so retrying asks for the same range again. `error.after` is the cursor you came from and `error.firstSequence` is what the server actually served — report the pair, do not invent a cursor past it. There is deliberately no `resumeAfter`: every cursor on the far side of a hole discards the missing events permanently, and that is an operator's decision to take with the facts in hand, never a client fallback. `restoreSessionWorkspace` brings back an expired sandbox, and `inspectSessionExecution` is an operational inspection aid, not part of the normal follow loop. `streamSessionEvents(sessionId, { after, signal })` follows the same log live as an `AsyncIterable`. It checks sequence contiguity, resumes with `Last-Event-ID`, refreshes an App-mode token after `401`, and spreads reconnects after server-side ends over 0–30 seconds. Save the last sequence your handler committed; the iterator's in-memory cursor cannot replace that durable record. Abort its signal or break the loop to stop following. `isTerminalRunEvent(event, runId)` recognizes the four terminal Run types. `streamSessionChanges({ after, signal })` follows every Session the installation created over one connection, as an `AsyncIterable<{ change?: CloudSessionChange; watermark: number }>`. A `change` names a Session and the sequence its log reached; page that Session from your own cursor. An item without `change` only moves the watermark; every stream opens with one, so the first item also says the stream is open. Save `watermark` after acting on `change` and pass it back as `after`; the iterator reconnects with the newest watermark it holds, under the same retry and spread rules as `streamSessionEvents`. It needs an installation credential. See [Follow many Sessions](/guides/follow-a-run/#follow-many-sessions). For a multi-tenant backend, construct one App-mode `WampCloud` per customer installation and reuse it across that customer's calls. Each client caches its installation token, coalesces exchanges, and refreshes after a rejected token. `WampCloudError` carries the API's error code and status, so the handling described in [Errors](/reference/errors/) applies unchanged. `WampCloudEventGapError` is the second error type and it is not an API error: no request failed, and it carries no code and no status. It is the SDK reporting that the event page is not contiguous with the cursor you supplied. Events are not pruned by a retention window; this is an invariant violation, not an expected expiration outcome. Handle it where you handle your cursor, not where you handle HTTP failures — `instanceof WampCloudEventGapError` before a generic `WampCloudError` branch, since neither is a subclass of the other. `WampApp` and `WampCloud` both apply a 30-second deadline to every network request. Override it once with `requestTimeoutMs`, which must be an integer from 1 to 3 600 000 milliseconds — anything outside that range throws at construction rather than failing later. Provide a constructor-level `AbortSignal` to cancel all in-flight requests when your backend shuts down. Event-page reads also accept a per-call signal. Two audience constants are exported for callers that validate tokens themselves: `END_USER_TOKEN_AUDIENCE` and `WAMP_CLOUD_RESOURCE_AUDIENCE`. ## Forward-compatible event typing Every public Cloud declaration is exported from the package root, including `CloudSessionContinuation`, the retained timeline event types, `KnownCloudSessionEvent`, and `CloudSafeTimelineEvent`. Five value sets are exported as `as const` arrays, so they are enumerable at runtime and not only in the type system: | Export | Union | Additive? | |---|---|---| | `CLOUD_RUN_STOP_REASONS` | `CloudRunStopReason` | yes | | `KNOWN_CLOUD_RUN_ERROR_CODES` | `KnownCloudRunErrorCode` | yes | | `CLOUD_CONTINUATION_REASONS` | `CloudContinuationReason` | yes | | `CLOUD_SESSION_PHASES` | `CloudSessionPhase` | **no — closed** | | `THINKING_LEVELS` | `ThinkingLevel` | **no — closed** | Each native model's `thinkingLevels` lists the levels its route accepts. When the list is absent, that model has no reasoning-level control. For the additive ones, treat an unrecognized value as unknown rather than invalid: use `isKnownCloudSessionEvent` before an exhaustive switch over known event members, and `isKnownCloudRunErrorCode` before branching on a terminal run's `errorCode`. Both let you safely ignore a value newer than your installed SDK. `phase` is the exception. `CLOUD_SESSION_PHASES` is the complete set the server can serve — three producers write it and nothing else does — so an exhaustive `switch` over it is legal, and a new value would be a contract change rather than an additive one. It stays advisory for a different reason: `phase` may lag a run's terminal transition, so derive your state machine from `run.status` and use `phase` to label what a session is doing between runs.