Publish the TypeScript SDK and integration guides (#3141)
* feat(client): publish the TypeScript SDK surface Make the high-level client API usable for external integrations and document complete agent, workspace, provider, event, and recipe flows. * fix(client): correlate provider snapshots by daemon cwd Return the daemon's canonical snapshot identity so waitForReady can match updates for relative, tilde, and otherwise non-canonical paths without duplicating filesystem rules in the SDK. * test(server): make snapshot cwd assertion portable * fix(client): gate canonical provider snapshot waits Older daemons do not expose the canonical snapshot identity needed to correlate workspace updates safely. Require the advertised capability and fail with an update-host message instead of guessing across workspaces.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
---
|
||||
title: Agents with the SDK
|
||||
description: Create, run, find, reuse, parent, and archive coding agents from TypeScript.
|
||||
nav: Agents
|
||||
order: 52
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# Agents with the SDK
|
||||
|
||||
An agent handle keeps a stable agent ID and exposes the turn lifecycle without exposing daemon RPCs.
|
||||
|
||||
## Run an initial prompt
|
||||
|
||||
```ts
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "claude/claude-sonnet-5",
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Review the checkout flow and propose one focused fix.",
|
||||
labels: { source: "checkout-review" },
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
console.log(result.status, result.lastMessage);
|
||||
```
|
||||
|
||||
`waitForFinish()` returns one of four statuses:
|
||||
|
||||
| Status | Meaning |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `idle` | The turn completed and the agent can accept another prompt. |
|
||||
| `permission` | The agent needs a person to answer a permission request in Paseo. |
|
||||
| `error` | The provider ended the turn with an error. |
|
||||
| `timeout` | The wait deadline elapsed; the agent may still be running. |
|
||||
|
||||
A timeout does not cancel the agent.
|
||||
|
||||
## Keep a session alive for follow-ups
|
||||
|
||||
Create an idle session when prompts arrive later:
|
||||
|
||||
```ts
|
||||
const reviewer = await client.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
title: "Checkout reviewer",
|
||||
});
|
||||
|
||||
const first = await reviewer.run("Review the current diff.");
|
||||
|
||||
if (first.status === "idle") {
|
||||
const second = await reviewer.run("Now focus on failure recovery.");
|
||||
console.log(second.lastMessage);
|
||||
}
|
||||
```
|
||||
|
||||
Use `send()` for fire-and-forget delivery. Use `run()` when the caller needs the outcome of that turn.
|
||||
|
||||
## Find agents by label
|
||||
|
||||
Set `labels` at creation, then filter on them. The daemon does the matching:
|
||||
|
||||
```ts
|
||||
const page = await client.agents.list({
|
||||
filter: { labels: { "issue-provider": "my-tracker" } },
|
||||
});
|
||||
|
||||
for (const { agent } of page.entries) {
|
||||
console.log(agent.id, agent.title, agent.status);
|
||||
}
|
||||
```
|
||||
|
||||
## Continue an agent by ID
|
||||
|
||||
```ts
|
||||
const agent = client.agents.ref("agent_01H8X...");
|
||||
|
||||
const result = await agent.run("Now write the fix.");
|
||||
console.log(result.lastMessage);
|
||||
```
|
||||
|
||||
`ref()` does not contact the daemon. Call `refresh()` first when you need to know whether the agent still exists; it returns `null` if it does not.
|
||||
|
||||
## Create a subagent
|
||||
|
||||
Create a child through its workspace. The handle owns placement, so the caller does not repeat its directory:
|
||||
|
||||
```ts
|
||||
if (!parent.workspaceId) throw new Error("Parent has no workspace");
|
||||
|
||||
const workspace = client.workspaces.ref(parent.workspaceId);
|
||||
const child = await workspace.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
},
|
||||
parent,
|
||||
title: "Implement checkout fix",
|
||||
prompt: "Implement the accepted checkout plan and run focused tests.",
|
||||
});
|
||||
```
|
||||
|
||||
`parent` establishes parentage. Archiving a parent cascade-archives its children. Call `detach()` first when a child should continue independently.
|
||||
|
||||
## Request structured output
|
||||
|
||||
```ts
|
||||
const schema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
summary: { type: "string" },
|
||||
risk: { type: "string", enum: ["low", "medium", "high"] },
|
||||
},
|
||||
required: ["summary", "risk"],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
outputSchema: schema,
|
||||
prompt: "Assess the release risk of the current diff.",
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
if (result.status !== "idle" || !result.lastMessage) {
|
||||
throw new Error(result.error ?? "Agent returned no structured output");
|
||||
}
|
||||
|
||||
const assessment = JSON.parse(result.lastMessage) as {
|
||||
summary: string;
|
||||
risk: "low" | "medium" | "high";
|
||||
};
|
||||
```
|
||||
|
||||
Validate the parsed value in your application before using it as trusted input.
|
||||
|
||||
## Archive or detach
|
||||
|
||||
```ts
|
||||
await agent.archive(); // Soft-deletes the agent and closes its runtime.
|
||||
await child.detach(); // Keeps the child alive but removes its parent relationship.
|
||||
```
|
||||
|
||||
Closing the SDK connection does not archive agents. Archive temporary agents explicitly, preferably in `finally`.
|
||||
@@ -0,0 +1,102 @@
|
||||
---
|
||||
title: SDK events
|
||||
description: Subscribe to agent status, timeline, workspace, and provider updates without maintaining a second state model.
|
||||
nav: Events
|
||||
order: 56
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# SDK events
|
||||
|
||||
Subscriptions report changes after they happen. Fetch an initial snapshot first, then apply updates to it.
|
||||
|
||||
Every `subscribe()` method returns a local unsubscribe function. It removes your callback; it does not stop or archive the underlying resource.
|
||||
|
||||
## Follow one agent's status
|
||||
|
||||
The daemon sends agent-directory updates only after the connection opens an agent-list subscription:
|
||||
|
||||
```ts
|
||||
await client.agents.list({
|
||||
filter: { includeArchived: false },
|
||||
subscribe: { subscriptionId: "issue-board-agents" },
|
||||
});
|
||||
|
||||
const agent = client.agents.ref(agentId);
|
||||
await agent.refresh();
|
||||
|
||||
const unsubscribe = agent.subscribe((update) => {
|
||||
if (update.kind === "upsert") {
|
||||
console.log(update.agent.status);
|
||||
} else {
|
||||
console.log("Agent removed from the active directory");
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
The handle updates its properties and `current()` value before it calls your handler.
|
||||
|
||||
## Follow timeline events
|
||||
|
||||
```ts
|
||||
const unsubscribe = agent.timeline.subscribe(({ event, timestamp }) => {
|
||||
if (event.type === "timeline" && event.item.type === "assistant_message") {
|
||||
process.stdout.write(event.item.text);
|
||||
}
|
||||
|
||||
if (event.type === "turn_completed") {
|
||||
console.log(`\nCompleted at ${timestamp}`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Assistant messages can arrive in pieces. Concatenate their text when you need a complete message, or use `run()` and read `lastMessage` when you only need the final reply.
|
||||
|
||||
Turn completion comes from `turn_completed`, `turn_failed`, or `turn_canceled`. Do not infer turn completion from an `agent_update` transition to `idle`.
|
||||
|
||||
## Fetch timeline history
|
||||
|
||||
```ts
|
||||
const page = await agent.timeline.refetch({
|
||||
direction: "before",
|
||||
limit: 100,
|
||||
projection: "projected",
|
||||
});
|
||||
|
||||
for (const entry of page.entries) {
|
||||
console.log(entry.seq, entry.event.type);
|
||||
}
|
||||
```
|
||||
|
||||
Use `startCursor`, `endCursor`, `hasOlder`, and `hasNewer` from the result to page without inventing offsets.
|
||||
|
||||
## Follow workspace updates
|
||||
|
||||
Workspace updates also require a directory subscription:
|
||||
|
||||
```ts
|
||||
await client.workspaces.list({
|
||||
subscribe: { subscriptionId: "issue-board-workspaces" },
|
||||
});
|
||||
|
||||
const workspace = client.workspaces.ref(workspaceId);
|
||||
const unsubscribe = workspace.subscribe((update) => {
|
||||
if (update.kind === "upsert") {
|
||||
console.log(update.workspace.status);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Follow provider catalog changes
|
||||
|
||||
```ts
|
||||
const unsubscribe = client.providers.subscribe((update) => {
|
||||
const ready = update.entries.filter((entry) => entry.status === "ready");
|
||||
console.log(
|
||||
"Ready providers:",
|
||||
ready.map((entry) => entry.provider),
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
Always call the returned unsubscribe functions before discarding the owning object. Call `client.close()` when the application no longer needs the daemon connection.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: TypeScript SDK
|
||||
description: Run Paseo coding agents from a TypeScript program.
|
||||
nav: Overview
|
||||
order: 50
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# TypeScript SDK
|
||||
|
||||
`@getpaseo/client` is a TypeScript library that drives a Paseo daemon from your own program. You pick a provider and model, give an agent a prompt and a directory, and wait for the answer.
|
||||
|
||||
The daemon does the work: it launches the provider CLI, keeps the session alive, and streams it to the Paseo app. Your program is a client. Agents you create show up in Paseo next to the ones you started by hand, and they stay there after your program exits.
|
||||
|
||||
Use it to:
|
||||
|
||||
- turn an issue, alert, or webhook into a coding task;
|
||||
- run several agents in parallel and collect their answers;
|
||||
- hold a session open and send follow-up prompts;
|
||||
- build a dashboard over your agents.
|
||||
|
||||
## Start a daemon
|
||||
|
||||
```bash
|
||||
npx @getpaseo/cli
|
||||
```
|
||||
|
||||
It listens on `ws://127.0.0.1:6767/ws`.
|
||||
|
||||
## Run an agent
|
||||
|
||||
```ts
|
||||
import { createPaseoClient } from "@getpaseo/client";
|
||||
|
||||
const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
|
||||
await client.connect();
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: { provider: "codex/gpt-5.5" },
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Review the current diff and name the riskiest change.",
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
console.log(result.lastMessage);
|
||||
|
||||
await client.close();
|
||||
```
|
||||
|
||||
[Quickstart](/docs/sdk/quickstart) walks through this line by line and covers passwords and remote daemons.
|
||||
@@ -0,0 +1,172 @@
|
||||
---
|
||||
title: Provider options
|
||||
description: Sandboxing, permissions, and network rules for Codex, Claude, and OpenCode agents created from the SDK.
|
||||
nav: Provider options
|
||||
order: 55
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# Provider options
|
||||
|
||||
`config.options` passes settings straight through to the provider CLI. Paseo validates the object against that provider's strict schema before starting the agent, so an unknown or misspelled key fails agent creation instead of silently doing nothing.
|
||||
|
||||
Options are provider-native. A Codex sandbox key is not a Claude sandbox key. Codex, Claude, and OpenCode accept options; every other provider rejects a non-empty `options`.
|
||||
|
||||
Provider options are not a host boundary. They constrain the agent CLI, which runs as your user on your machine. For untrusted work, run the daemon in a container or on a separate machine. See [Security](/docs/security).
|
||||
|
||||
## Codex
|
||||
|
||||
Confine writes to one directory, cut off network access, and stop approval prompts so the run is unattended:
|
||||
|
||||
```ts
|
||||
import { createPaseoClient } from "@getpaseo/client";
|
||||
|
||||
const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
|
||||
await client.connect();
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
options: {
|
||||
approval_policy: "never",
|
||||
sandbox_mode: "workspace-write",
|
||||
sandbox_workspace_write: {
|
||||
writable_roots: ["/Users/me/dev/storefront"],
|
||||
network_access: false,
|
||||
exclude_slash_tmp: true,
|
||||
},
|
||||
web_search: "disabled",
|
||||
},
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Fix the failing checkout test.",
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
console.log(result.status, result.lastMessage);
|
||||
|
||||
await client.close();
|
||||
```
|
||||
|
||||
| Option | Values |
|
||||
| ------------------------- | --------------------------------------------------------------------------------- |
|
||||
| `approval_policy` | `untrusted`, `on-request`, `never`, or `{ granular: { … } }` |
|
||||
| `sandbox_mode` | `read-only`, `workspace-write`, `danger-full-access` |
|
||||
| `sandbox_workspace_write` | `writable_roots`, `network_access`, `exclude_slash_tmp`, `exclude_tmpdir_env_var` |
|
||||
| `web_search` | `disabled`, `cached`, `indexed`, `live` |
|
||||
| `features` | `multi_agent_v2`, `network_proxy` (boolean or a proxy/domain policy object) |
|
||||
|
||||
`approval_policy: "never"` only removes the prompts. What the agent is allowed to touch is `sandbox_mode`. Setting `never` without a sandbox mode gives an unattended agent full access.
|
||||
|
||||
## Claude
|
||||
|
||||
Turn on Claude's own sandbox, restrict writes to the project, deny reads of credential directories, and allow only two domains:
|
||||
|
||||
```ts
|
||||
import { createPaseoClient } from "@getpaseo/client";
|
||||
|
||||
const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
|
||||
await client.connect();
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "claude/claude-sonnet-5",
|
||||
options: {
|
||||
disallowedTools: ["WebFetch"],
|
||||
sandbox: {
|
||||
enabled: true,
|
||||
failIfUnavailable: true,
|
||||
allowUnsandboxedCommands: false,
|
||||
filesystem: {
|
||||
allowWrite: ["/Users/me/dev/storefront"],
|
||||
denyRead: ["/Users/me/.ssh", "/Users/me/.aws"],
|
||||
},
|
||||
network: {
|
||||
allowedDomains: ["registry.npmjs.org", "github.com"],
|
||||
strictAllowlist: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Install dependencies and run the focused test.",
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
console.log(result.status, result.lastMessage);
|
||||
|
||||
await client.close();
|
||||
```
|
||||
|
||||
| Option | Values |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| `allowedTools` | Tool names the agent may use without asking |
|
||||
| `disallowedTools` | Tool names the agent may never use |
|
||||
| `additionalDirectories` | Extra directories the agent may access |
|
||||
| `sandbox` | `enabled`, `failIfUnavailable`, `allowUnsandboxedCommands`, `excludedCommands`, `filesystem`, `network` |
|
||||
| `settings` | `permissions` (`allow`/`ask`/`deny` rule lists) and a nested `sandbox` |
|
||||
|
||||
`failIfUnavailable: true` makes agent startup fail when the sandbox cannot be established. Leave it off and Claude runs unsandboxed instead.
|
||||
|
||||
`sandbox.filesystem` takes `allowWrite`, `denyWrite`, `allowRead`, and `denyRead`. `sandbox.network` takes `allowedDomains`, `deniedDomains`, `strictAllowlist`, and proxy settings.
|
||||
|
||||
## OpenCode
|
||||
|
||||
Allow reads and edits, deny anything that reaches outside the project, and gate shell commands by pattern:
|
||||
|
||||
```ts
|
||||
import { createPaseoClient } from "@getpaseo/client";
|
||||
|
||||
const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
|
||||
await client.connect();
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "opencode/opencode/gpt-5.5",
|
||||
options: {
|
||||
permission: {
|
||||
read: "allow",
|
||||
edit: "allow",
|
||||
webfetch: "deny",
|
||||
external_directory: "deny",
|
||||
bash: {
|
||||
"git status": "allow",
|
||||
"git diff*": "allow",
|
||||
"git push*": "deny",
|
||||
"*": "ask",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Implement the requested change and show me the diff.",
|
||||
});
|
||||
|
||||
const result = await agent.waitForFinish();
|
||||
console.log(result.status, result.lastMessage);
|
||||
|
||||
await client.close();
|
||||
```
|
||||
|
||||
Every permission is `ask`, `allow`, or `deny`. Tools that take a target — `read`, `edit`, `glob`, `grep`, `list`, `bash`, `task`, `external_directory`, `repo_clone`, `repo_overview`, `lsp`, `skill` — also accept a pattern map, where later keys act as the fallback. Tools without a target — `todowrite`, `question`, `webfetch`, `websearch`, `codesearch`, `doom_loop` — take a bare action.
|
||||
|
||||
`permission: "deny"` as a bare string applies to everything at once.
|
||||
|
||||
An `ask` resolves to a permission request in Paseo, and `waitForFinish()` returns `permission` while it is pending. Use `allow` and `deny` for unattended runs. See [Events](/docs/sdk/events) for reacting to a pending request.
|
||||
|
||||
## Modes and options
|
||||
|
||||
`modeId` picks a Paseo mode from the provider's published list, which the daemon shows in the app and reports through [provider discovery](/docs/sdk/providers). `options` is the provider's own configuration. They are separate controls and you can set both:
|
||||
|
||||
```ts
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
modeId: "full-access",
|
||||
options: {
|
||||
approval_policy: "never",
|
||||
sandbox_mode: "read-only",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Where the two overlap, `options` wins. Above, Codex runs read-only even though `full-access` would otherwise grant more.
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Providers with the SDK
|
||||
description: Select a provider and model, discover available configurations, and configure an agent session.
|
||||
nav: Providers
|
||||
order: 54
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# Providers with the SDK
|
||||
|
||||
Every agent configuration names both the provider and model:
|
||||
|
||||
```ts
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
}
|
||||
```
|
||||
|
||||
The first `/` separates the provider from the model. Model IDs can contain additional slashes.
|
||||
|
||||
## Configure a session
|
||||
|
||||
```ts
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
modeId: "full-access",
|
||||
thinkingOptionId: "high",
|
||||
featureValues: {
|
||||
web_search: false,
|
||||
},
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
prompt: "Implement the accepted plan and run focused tests.",
|
||||
});
|
||||
```
|
||||
|
||||
| Field | Meaning |
|
||||
| ------------------ | -------------------------------------------------------------- |
|
||||
| `provider` | Required `provider/model` selection. |
|
||||
| `modeId` | Provider operating or permission mode. |
|
||||
| `thinkingOptionId` | Provider reasoning level. |
|
||||
| `featureValues` | Values for features returned by `providers.listFeatures()`. |
|
||||
| `options` | Provider-native settings such as sandbox and permission rules. |
|
||||
| `systemPrompt` | Additional system or developer instructions. |
|
||||
| `mcpServers` | Session-scoped MCP servers. |
|
||||
| `toolPolicy` | Exact preapproval rules for MCP tools. |
|
||||
|
||||
[Provider options](/docs/sdk/provider-options) lists the accepted sandbox and permission settings.
|
||||
|
||||
## Discover installed providers and models
|
||||
|
||||
```ts
|
||||
const snapshot = await client.providers.waitForReady({
|
||||
cwd: process.cwd(),
|
||||
timeoutMs: 60_000,
|
||||
});
|
||||
|
||||
for (const entry of snapshot.entries) {
|
||||
if (entry.status !== "ready") continue;
|
||||
|
||||
for (const model of entry.models ?? []) {
|
||||
console.log(`${entry.provider}/${model.id}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
An entry finishes as `ready`, `unavailable`, or `error`. `snapshot()` returns immediately and can include `loading` entries.
|
||||
|
||||
## Select a discovered model
|
||||
|
||||
```ts
|
||||
const snapshot = await client.providers.waitForReady({ cwd: process.cwd() });
|
||||
const entry = snapshot.entries.find((candidate) => candidate.status === "ready");
|
||||
const model = entry?.models?.find((candidate) => candidate.isDefault) ?? entry?.models?.[0];
|
||||
|
||||
if (!entry || !model) throw new Error("No provider model is ready");
|
||||
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: `${entry.provider}/${model.id}`,
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
prompt: "Summarize this repository.",
|
||||
});
|
||||
```
|
||||
|
||||
## Discover modes, thinking levels, and features
|
||||
|
||||
```ts
|
||||
const models = await client.providers.listModels("codex", { cwd: process.cwd() });
|
||||
const modes = await client.providers.listModes("codex", { cwd: process.cwd() });
|
||||
|
||||
const selectedModel = models.models[0]?.id;
|
||||
const selectedMode = modes.modes[0]?.id;
|
||||
if (!selectedModel) throw new Error("No Codex model is available");
|
||||
|
||||
const features = await client.providers.listFeatures({
|
||||
provider: `codex/${selectedModel}`,
|
||||
cwd: process.cwd(),
|
||||
modeId: selectedMode,
|
||||
});
|
||||
```
|
||||
|
||||
Use IDs returned by the daemon. Provider installations and configured models differ between hosts.
|
||||
|
||||
## Diagnose an unavailable provider
|
||||
|
||||
```ts
|
||||
const result = await client.providers.diagnostic("codex");
|
||||
console.error(result.diagnostic);
|
||||
```
|
||||
|
||||
Host-level profiles, binaries, credentials, and custom providers belong in [Custom providers](/docs/custom-providers).
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
title: SDK quickstart
|
||||
description: Connect to a Paseo daemon, run one coding agent, and read its reply.
|
||||
nav: Quickstart
|
||||
order: 51
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# SDK quickstart
|
||||
|
||||
```bash
|
||||
npm install @getpaseo/client
|
||||
```
|
||||
|
||||
Requires Node.js 22 or newer.
|
||||
|
||||
## Connect
|
||||
|
||||
```ts
|
||||
import { createPaseoClient } from "@getpaseo/client";
|
||||
|
||||
const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" });
|
||||
await client.connect();
|
||||
```
|
||||
|
||||
`connect()` resolves once the daemon has identified itself. If the daemon has a password, pass it:
|
||||
|
||||
```ts
|
||||
const client = createPaseoClient({
|
||||
url: "wss://devbox.example.com/ws",
|
||||
password: "my-secret",
|
||||
});
|
||||
```
|
||||
|
||||
## Create an agent
|
||||
|
||||
```ts
|
||||
const agent = await client.agents.create({
|
||||
config: { provider: "codex/gpt-5.5" },
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Review the current diff and name the riskiest change.",
|
||||
});
|
||||
```
|
||||
|
||||
`config.provider` is always `provider/model`. [Providers](/docs/sdk/providers) lists what the daemon has available and how to discover it at runtime.
|
||||
|
||||
`cwd` is the directory the agent works in. Paseo creates the workspace behind it. [Workspaces](/docs/sdk/workspaces) covers reusing one instead.
|
||||
|
||||
`create()` resolves as soon as the session exists. The prompt is still running.
|
||||
|
||||
## Wait for the reply
|
||||
|
||||
```ts
|
||||
const result = await agent.waitForFinish();
|
||||
|
||||
if (result.status === "idle") {
|
||||
console.log(result.lastMessage);
|
||||
}
|
||||
```
|
||||
|
||||
`waitForFinish()` waits up to 10 minutes by default; pass milliseconds to change it. It returns one of four statuses:
|
||||
|
||||
| Status | Meaning |
|
||||
| ------------ | ----------------------------------------------------------------- |
|
||||
| `idle` | The turn finished and the agent can take another prompt. |
|
||||
| `permission` | The agent needs a person to answer a permission request in Paseo. |
|
||||
| `error` | The provider ended the turn with an error. |
|
||||
| `timeout` | The deadline elapsed. The agent is still running. |
|
||||
|
||||
## Disconnect
|
||||
|
||||
```ts
|
||||
await client.close();
|
||||
```
|
||||
|
||||
The agent keeps running on the daemon and stays visible in Paseo. A later program reaches it again by ID:
|
||||
|
||||
```ts
|
||||
const agent = client.agents.ref("agent_01H8X...");
|
||||
await agent.refresh();
|
||||
await agent.run("Now write the fix.");
|
||||
```
|
||||
|
||||
## Next
|
||||
|
||||
- [Agents](/docs/sdk/agents), follow-up prompts, finding existing agents, archiving.
|
||||
- [Providers](/docs/sdk/providers), discovering models and modes from the daemon.
|
||||
- [Provider options](/docs/sdk/provider-options), sandboxing and provider-native settings.
|
||||
- [Workspaces](/docs/sdk/workspaces), reusing a workspace or creating a worktree.
|
||||
- [Events](/docs/sdk/events), streaming updates instead of waiting.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
title: SDK recipes
|
||||
description: Complete patterns for issue integrations, parallel review, resident agents, and safe temporary-agent cleanup.
|
||||
nav: Recipes
|
||||
order: 57
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# SDK recipes
|
||||
|
||||
Examples use a connected `client` imported from the public package root.
|
||||
|
||||
## Turn an issue into visible work
|
||||
|
||||
After selecting an issue:
|
||||
|
||||
```ts
|
||||
type Issue = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
repositoryPath: string;
|
||||
};
|
||||
|
||||
async function startIssue(issue: Issue) {
|
||||
const workspace = await client.workspaces.open(issue.repositoryPath);
|
||||
const agent = await workspace.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
},
|
||||
title: issue.title,
|
||||
labels: {
|
||||
"issue-provider": "my-tracker",
|
||||
"issue-id": issue.id,
|
||||
},
|
||||
prompt: [
|
||||
`Implement issue ${issue.id}: ${issue.title}`,
|
||||
"",
|
||||
issue.description,
|
||||
"",
|
||||
"Run focused tests and summarize the result.",
|
||||
].join("\n"),
|
||||
});
|
||||
|
||||
return { workspaceId: workspace.id, agentId: agent.id };
|
||||
}
|
||||
```
|
||||
|
||||
Persist the returned IDs in your integration. On the next webhook or page load, recover handles with `workspaces.ref()` and `agents.ref()` instead of creating duplicates.
|
||||
|
||||
## Run parallel reviewers
|
||||
|
||||
```ts
|
||||
const prompts = [
|
||||
"Review the diff for correctness and missed edge cases.",
|
||||
"Review the diff for security and unsafe input handling.",
|
||||
"Review the diff for unnecessary complexity.",
|
||||
];
|
||||
|
||||
const reviewers = await Promise.all(
|
||||
prompts.map((prompt, index) =>
|
||||
client.agents.create({
|
||||
config: {
|
||||
provider: index === 1 ? "claude/claude-sonnet-5" : "codex/gpt-5.5",
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
title: `Review ${index + 1}`,
|
||||
prompt,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const results = await Promise.all(reviewers.map((reviewer) => reviewer.waitForFinish()));
|
||||
|
||||
for (const result of results) {
|
||||
console.log(result.status, result.lastMessage);
|
||||
}
|
||||
```
|
||||
|
||||
## Keep a resident role across process restarts
|
||||
|
||||
```ts
|
||||
async function getPlanner() {
|
||||
const listed = await client.agents.list({
|
||||
filter: { includeArchived: false },
|
||||
page: { limit: 100 },
|
||||
});
|
||||
|
||||
const existing = listed.entries.find(({ agent }) => agent.labels["my-app-role"] === "planner");
|
||||
|
||||
if (existing) return client.agents.ref(existing.agent);
|
||||
|
||||
return client.agents.create({
|
||||
config: {
|
||||
provider: "claude/claude-sonnet-5",
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
title: "Planner",
|
||||
labels: { "my-app-role": "planner" },
|
||||
});
|
||||
}
|
||||
|
||||
const planner = await getPlanner();
|
||||
const plan = await planner.run("Plan the next small, shippable improvement.");
|
||||
```
|
||||
|
||||
Labels are application-owned metadata. Namespace keys when several tools may manage agents on the same daemon.
|
||||
|
||||
## Clean up temporary agents
|
||||
|
||||
```ts
|
||||
const temporaryAgents = [];
|
||||
|
||||
try {
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "codex/gpt-5.5",
|
||||
},
|
||||
cwd: process.cwd(),
|
||||
title: "Temporary smoke test",
|
||||
});
|
||||
temporaryAgents.push(agent);
|
||||
|
||||
const result = await agent.run("Reply with READY and nothing else.", {
|
||||
timeoutMs: 2 * 60_000,
|
||||
});
|
||||
|
||||
if (result.status !== "idle") {
|
||||
throw new Error(result.error ?? result.status);
|
||||
}
|
||||
} finally {
|
||||
await Promise.allSettled(temporaryAgents.map((agent) => agent.archive()));
|
||||
}
|
||||
```
|
||||
|
||||
Do not archive agents your integration did not create.
|
||||
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: SDK API reference
|
||||
description: Public configuration, methods, handles, results, defaults, and lifecycle behavior for @getpaseo/client.
|
||||
nav: API reference
|
||||
order: 58
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# SDK API reference
|
||||
|
||||
Import every supported runtime value and TypeScript type from `@getpaseo/client`.
|
||||
|
||||
## `createPaseoClient(config)`
|
||||
|
||||
Creates a client without opening the connection.
|
||||
|
||||
Required configuration:
|
||||
|
||||
| Field | Type | Meaning |
|
||||
| ----- | -------- | ------------------------------------------- |
|
||||
| `url` | `string` | Daemon WebSocket endpoint, including `/ws`. |
|
||||
|
||||
Common optional configuration:
|
||||
|
||||
| Field | Type | Default | Meaning |
|
||||
| ----------------------- | ------------- | -------------- | ------------------------------------------------ |
|
||||
| `clientId` | `string` | Generated | Stable identifier for logs and subscriptions. |
|
||||
| `password` | `string` | Unset | Daemon password. |
|
||||
| `authHeader` | `string` | Unset | Complete authorization-header value for a proxy. |
|
||||
| `connectTimeoutMs` | `number` | Client default | Connection deadline. |
|
||||
| `reconnect.enabled` | `boolean` | Client default | Reconnect after an unexpected disconnect. |
|
||||
| `reconnect.baseDelayMs` | `number` | Client default | Initial reconnect delay. |
|
||||
| `reconnect.maxDelayMs` | `number` | Client default | Maximum reconnect delay. |
|
||||
| `logger` | `PaseoLogger` | Unset | Debug, info, warning, and error sink. |
|
||||
|
||||
Relay E2EE clients can also pass `e2ee.enabled` and `e2ee.daemonPublicKeyB64`. `appVersion`, `runtimeGeneration`, and runtime-metrics options exist for Paseo client surfaces; ordinary integrations can omit them.
|
||||
|
||||
## Client lifecycle
|
||||
|
||||
| Method | Result | Behavior |
|
||||
| ---------------------- | ----------------- | ------------------------------------------------------------------------- |
|
||||
| `connect()` | `Promise<void>` | Resolves after the daemon sends its server information. |
|
||||
| `close()` | `Promise<void>` | Closes the connection and disposes this client. |
|
||||
| `ensureConnected()` | `void` | Throws unless the client is connected. |
|
||||
| `getConnectionState()` | `ConnectionState` | Returns `idle`, `connecting`, `connected`, `disconnected`, or `disposed`. |
|
||||
|
||||
Create a new client after `close()`.
|
||||
|
||||
## `client.agents`
|
||||
|
||||
| Method | Result | Behavior |
|
||||
| -------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `list(options?)` | `PaseoAgentListResult` | Lists a page of agents. `scope`, `filter`, `sort`, `page`, and `subscribe` match the daemon directory query. |
|
||||
| `create(options)` | `PaseoAgentHandle` | Creates an agent and a fresh workspace for `cwd`. Requires `config`. |
|
||||
| `ref(agentOrId)` | `PaseoAgentHandle` | Creates a local handle without fetching. |
|
||||
| `subscribe(handler)` | Unsubscribe function | Listens for connection-local agent directory updates. Call `list({ subscribe })` first. |
|
||||
|
||||
Creation options include `config`, `cwd`, `parent`, `title`, `prompt`, `env`, `outputSchema`, `images`, `attachments`, `git`, `worktree`, `autoArchive`, and `labels`.
|
||||
|
||||
`config` accepts:
|
||||
|
||||
| Field | Type | Meaning |
|
||||
| ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `provider` | `string` | Required `provider/model` selection. |
|
||||
| `modeId` | `string` | Provider operating or permission mode. |
|
||||
| `thinkingOptionId` | `string` | Provider reasoning level. |
|
||||
| `featureValues` | `Record<string, unknown>` | Values for features discovered through `providers.listFeatures`. |
|
||||
| `options` | JSON object | Provider-native settings, strictly validated. See [Provider options](/docs/sdk/provider-options). |
|
||||
| `systemPrompt` | `string` | Additional system or developer instructions. |
|
||||
| `mcpServers` | MCP server map | Session-scoped MCP servers. |
|
||||
| `toolPolicy` | MCP tool policy | Exact preapproval rules for MCP tools. |
|
||||
|
||||
### Agent handle
|
||||
|
||||
| Member | Result | Behavior |
|
||||
| --------------------------- | --------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `id` | `string` | Stable daemon agent ID. |
|
||||
| `workspaceId` | `string \| null` | Current workspace placement. |
|
||||
| `cwd` | `string \| null` | Current working directory. |
|
||||
| `status` | Agent status or `null` | Current lifecycle status. |
|
||||
| `current()` | `PaseoAgent \| null` | Current detailed value observed by this handle; never fetches. |
|
||||
| `refresh(requestId?)` | `PaseoAgentRefetchResult \| null` | Fetches the current agent and project placement. |
|
||||
| `send(text, options?)` | `Promise<void>` | Resolves when the daemon accepts the prompt. |
|
||||
| `run(text, options?)` | `PaseoAgentRunResult` | Sends a prompt and waits for that turn. `timeoutMs` controls the wait; it defaults to 10 minutes. |
|
||||
| `waitForFinish(timeoutMs?)` | `PaseoAgentRunResult` | Waits for the active turn, including an initial prompt. Default timeout: 10 minutes. |
|
||||
| `subscribe(handler)` | Unsubscribe function | Filters agent-directory updates to this ID and refreshes the handle properties. |
|
||||
| `archive()` | `{ archivedAt }` | Soft-deletes the agent and closes its runtime. |
|
||||
| `detach()` | `Promise<void>` | Removes the parent relationship without stopping the agent. |
|
||||
|
||||
`PaseoAgentRunResult` contains `status`, `final`, `error`, and `lastMessage`. `final` refreshes the handle when present.
|
||||
|
||||
### Timeline handle
|
||||
|
||||
`agent.timeline.refetch(options?)` fetches a page. Options are `direction`, `cursor`, `limit`, `projection`, and `requestId`.
|
||||
|
||||
`agent.timeline.subscribe(handler)` listens for stream events belonging to the agent and returns a local unsubscribe function.
|
||||
|
||||
## `client.workspaces`
|
||||
|
||||
| Method | Result | Behavior |
|
||||
| ------------------------ | ----------------------------- | --------------------------------------------------------------------------------- |
|
||||
| `list(options?)` | `PaseoWorkspaceListResult` | Lists, filters, pages, or subscribes to the workspace directory. |
|
||||
| `open(cwd)` | `PaseoWorkspaceHandle` | Reuses the active workspace for a directory or creates one. |
|
||||
| `create(options)` | `PaseoWorkspaceHandle` | Always creates a fresh directory-backed or Paseo-worktree workspace. |
|
||||
| `ref(workspaceOrId)` | `PaseoWorkspaceHandle` | Creates a local handle. |
|
||||
| `archive(workspaceOrId)` | `PaseoWorkspaceArchiveResult` | Archives without first creating a handle. |
|
||||
| `subscribe(handler)` | Unsubscribe function | Listens for connection-local workspace updates. Call `list({ subscribe })` first. |
|
||||
|
||||
A workspace handle exposes `id`, `projectId`, `directory`, `name`, `status`, `current()`, `refresh()`, `archive()`, and `subscribe()`. Use `workspace.agents.create(options)` to create an agent without repeating the workspace ID or directory.
|
||||
|
||||
## `client.providers`
|
||||
|
||||
| Method | Result | Behavior |
|
||||
| -------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `waitForReady(options?)` | `PaseoProviderSnapshotResult` | Waits until no provider is loading. Default timeout: 60 seconds. Rejects with an update-host error when the daemon cannot correlate workspace snapshots. |
|
||||
| `snapshot(options?)` | `PaseoProviderSnapshotResult` | Returns the current catalog immediately. |
|
||||
| `refresh(options?)` | Acknowledgement | Forces catalog refresh for all or selected providers. |
|
||||
| `listAvailable()` | Availability result | Reports installed provider availability. |
|
||||
| `listModels(provider, options?)` | Models result | Discovers models for one provider and directory. |
|
||||
| `listModes(provider, options?)` | Modes result | Discovers permission or operating modes. |
|
||||
| `listFeatures(draftConfig)` | Features result | Discovers features for the current draft provider configuration. |
|
||||
| `diagnostic(provider)` | Diagnostic result | Returns human-readable setup diagnostics. |
|
||||
| `subscribe(handler)` | Unsubscribe function | Listens for catalog updates. |
|
||||
|
||||
## `client.config`
|
||||
|
||||
`config.get(requestId?)` returns the daemon's mutable configuration.
|
||||
|
||||
`config.patch(patch, requestId?)` validates, persists, and returns an updated configuration. Use this administrative surface for host configuration, not per-agent choices. A patch affects every client and future agent using that daemon.
|
||||
|
||||
## Errors and cleanup
|
||||
|
||||
Connection, validation, rejection, and timeout failures reject their promise. Turn outcomes are returned through `PaseoAgentRunResult.status` because permission and provider errors are expected agent states.
|
||||
|
||||
Always close the client in `finally`. Closing a client removes its local listeners and network connection; it does not stop agents or archive workspaces.
|
||||
@@ -0,0 +1,114 @@
|
||||
---
|
||||
title: Workspaces with the SDK
|
||||
description: Open directories as Paseo workspaces, place agents in them, follow changes, and archive them.
|
||||
nav: Workspaces
|
||||
order: 53
|
||||
category: TypeScript SDK
|
||||
---
|
||||
|
||||
# Workspaces with the SDK
|
||||
|
||||
Use a workspace when an integration needs a durable place in the Paseo app for agents, terminals, browsers, and files related to one task.
|
||||
|
||||
## Open a directory
|
||||
|
||||
```ts
|
||||
const workspace = await client.workspaces.open("/Users/me/dev/storefront");
|
||||
|
||||
console.log(workspace.id);
|
||||
console.log(workspace.directory);
|
||||
```
|
||||
|
||||
`open()` creates the project when needed and reuses the active workspace for that exact directory. Use it when the directory is the identity you care about.
|
||||
|
||||
## Create a fresh workspace
|
||||
|
||||
`create()` always creates a new workspace, even when another workspace already uses the directory:
|
||||
|
||||
```ts
|
||||
const workspace = await client.workspaces.create({
|
||||
source: {
|
||||
kind: "directory",
|
||||
path: "/Users/me/dev/storefront",
|
||||
},
|
||||
title: "Checkout issue 42",
|
||||
});
|
||||
```
|
||||
|
||||
Create a Paseo-owned worktree when concurrent work needs an isolated checkout:
|
||||
|
||||
```ts
|
||||
const workspace = await client.workspaces.create({
|
||||
source: {
|
||||
kind: "worktree",
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
action: "branch-off",
|
||||
refName: "main",
|
||||
branchName: "fix/checkout-42",
|
||||
},
|
||||
title: "Checkout issue 42",
|
||||
});
|
||||
```
|
||||
|
||||
You can pass `projectId` in either source when you already have one. Most integrations should omit it; the daemon finds or creates the project from the directory.
|
||||
|
||||
## Start an agent in a workspace
|
||||
|
||||
Create through the workspace handle:
|
||||
|
||||
```ts
|
||||
const agent = await workspace.agents.create({
|
||||
config: {
|
||||
provider: "claude/claude-sonnet-5",
|
||||
},
|
||||
prompt: "Map the checkout flow before changing anything.",
|
||||
});
|
||||
```
|
||||
|
||||
The handle supplies both the workspace identity and its actual directory. This avoids mismatched placement arguments.
|
||||
|
||||
For a one-off agent, you can skip the workspace call:
|
||||
|
||||
```ts
|
||||
const agent = await client.agents.create({
|
||||
config: {
|
||||
provider: "claude/claude-sonnet-5",
|
||||
},
|
||||
cwd: "/Users/me/dev/storefront",
|
||||
prompt: "Map the checkout flow before changing anything.",
|
||||
});
|
||||
```
|
||||
|
||||
The daemon still creates a project and a fresh workspace. Read `agent.workspaceId` when you need the generated workspace ID.
|
||||
|
||||
## List workspaces
|
||||
|
||||
```ts
|
||||
let cursor: string | undefined;
|
||||
|
||||
do {
|
||||
const page = await client.workspaces.list({
|
||||
filter: { query: "storefront" },
|
||||
page: { limit: 50, cursor },
|
||||
});
|
||||
|
||||
for (const workspace of page.entries) {
|
||||
console.log(workspace.id, workspace.name, workspace.status);
|
||||
}
|
||||
|
||||
cursor = page.pageInfo.nextCursor ?? undefined;
|
||||
} while (cursor);
|
||||
```
|
||||
|
||||
## Refresh and archive a handle
|
||||
|
||||
```ts
|
||||
const workspace = client.workspaces.ref(savedWorkspaceId);
|
||||
const snapshot = await workspace.refresh();
|
||||
|
||||
if (snapshot) {
|
||||
await workspace.archive();
|
||||
}
|
||||
```
|
||||
|
||||
Workspace archive is separate from agent archive. Archive each resource according to the lifecycle your integration owns.
|
||||
Reference in New Issue
Block a user