Agents APIReference
TypeScript SDK
@latentcode/sandbox-agents 0.1.1: a typed client for hosted agent sessions. It has no runtime dependencies and runs anywhere fetch does.
Install
The package is distributed by LatentStack rather than npm. Installing from the URL records it in package.json:
$
npm install https://latentstack.dev/dl/latentcode-sandbox-agents-0.1.1.tgzbun add, pnpm add and yarn add take the same URL. To pin a copy, download the tarball and install the file (npm install ./latentcode-sandbox-agents-0.1.1.tgz).
| Runtime | Supported |
|---|---|
| Node.js | 22 or newer |
| Bun · Deno | Yes |
| Browsers | Technically yes, but never ship an ls- key to a browser. Call from your server. |
Create a client
TypeScript
import { LatentStack } from "@latentcode/sandbox-agents"
const client = new LatentStack({ apiKey: process.env.LS_API_KEY! })
const sessions = client.agents.sessionsnew LatentStack(options: LatentStackOptions)| Option | Type | |
|---|---|---|
| apiKey | string | Required. Your ls-… key. |
| baseUrl | string | Your LatentStack URL, if it isn't https://latentstack.dev. |
| fetch | typeof fetch | Your own fetch, for proxies, tracing or tests. |
client.agents.sessions
Lifecycle
create(params: CreateSessionParams): Promise<Session>Starts a session.
params holds the session settings (agent, environment, input, metadata) and an optional idempotencyKeythat makes the call safe to retry.returns
Sessionget(sessionId: string): Promise<Session>Reads a session.
returns
Sessionlist(params?: { limit?: number; after?: string }): Promise<SessionList>Your sessions, newest first. Pass
next_after as after for the next page.returns
{ data, has_more, next_after }delete(sessionId: string): Promise<Session>Stops the session for good. Its events and patches stay readable.
returns
Session (stopping or stopped)pause(sessionId: string): Promise<Session>Saves its state and releases the sandbox.
returns
Session (pausing)resume(sessionId: string): Promise<Session>Continues a paused session in a new sandbox.
message() does this on its own.returns
Session (provisioning)Input
message(sessionId: string, text: string,
options?: { delivery?: "steer" | "queue" }): Promise<InputAccepted>Sends a message.
steer (default) folds into the current run; queue runs after it.returns
{ accepted, queued?, resumed? }approve(sessionId: string, requestId: string,
reply: "once" | "always" | "reject", message?: string): Promise<InputAccepted>Answers an
approval.requested frame.cancel(sessionId: string): Promise<InputAccepted>Interrupts the current run. The session stays open.
send(sessionId: string, input: SessionInput): Promise<InputAccepted>Any of the above as a typed object, e.g.
{ type: "message", text, delivery }.Following
events(sessionId: string, options?: EventsOptions): AsyncGenerator<StreamFrame>The session's frames, live. It reconnects with
after=<last seq> when the connection drops and ends when the session is stopped or failed. It throws a LatentStackError if the session can't be read (e.g. not_found) or the stream keeps closing (5 attempts in a row).| Option | Type | |
|---|---|---|
| after | number | Start after this seq, e.g. a stored session.last_seq. Default: from the beginning. |
| signal | AbortSignal | Ends the loop when aborted. |
| retryDelayMs | number | Fixed wait between reconnects. Default: 1 s, 2 s, 3 s … per failed attempt. |
wait(sessionId: string, options?: { until?: SessionStatusName[];
timeoutMs?: number; pollMs?: number }): Promise<Session>Polls until the status is in
until (default SETTLED: idle, waiting, paused, stopped, failed). timeoutMs defaults to 10 minutes and pollMs to 2 seconds. On timeout it throws LatentStackError with type timeout. Right after create() a session is briefly idle before its first message starts, so follow events() to know a task is done.returns
SessionArtifacts
artifacts(sessionId: string): Promise<ArtifactList>One record per repository, with a per-file summary.
returns
{ data: Artifact[] }artifact(sessionId: string, name = "patch", options?: { repo?: string }): Promise<string>The patch as text. Pass
repo when the session has several repositories.returns
string (unified diff)Working with frames
StreamFrame is a union of SessionStatus, ApprovalRequested and Event. A durable event's type is an open string, so narrow on "seq" in frame first. data may be missing; read it with ?..
TypeScript
for await (const frame of sessions.events(id)) {
if ("seq" in frame) {
// Event: { seq, type, data: Record<string, unknown>, time }
if (frame.type === "text.ended") console.log(frame.data?.text)
if (frame.type === "run.completed") break
} else if (frame.type === "session.status") {
// SessionStatus: status, error, instance_number, pending_approvals
} else if (frame.type === "approval.requested") {
// ApprovalRequested: request_id, action, resources, metadata
await sessions.approve(id, frame.request_id, "once")
}
}Errors
Every failed call throws LatentStackError, with status (0 if no response arrived), type and message. See Errors for the list of types.
TypeScript
import { LatentStackError } from "@latentcode/sandbox-agents"
try {
await sessions.create({ agent: { model: "bedrock/us.anthropic.claude-sonnet-5" }, input: "hi" })
} catch (e) {
if (e instanceof LatentStackError && e.type === "model_not_allowed") { /* pick another model */ }
else throw e
}Exports
| Export | What |
|---|---|
| LatentStack | The client. |
| Sessions | The class behind client.agents.sessions. |
| LatentStackError | The error class. |
| SETTLED | ["idle", "waiting", "paused", "stopped", "failed"] |
| DEFAULT_BASE_URL | https://latentstack.dev |
| types | Session, SessionList, SessionStatusName, Agent, Rule, Event, StreamFrame, SessionStatus, ApprovalRequested, PendingApproval, InputAccepted, SessionInput, Artifact, ArtifactList, FileChange, ApiError, CreateSessionParams, ListSessionsParams, WaitOptions, EventsOptions, LatentStackOptions |
Upgrading
- Each new version has its own install URL; see Install above. Update the URL in
package.jsonand reinstall. 0.1.1added several repositories per session and therepooption onartifact().