Files
paseo/public-docs/sdk/provider-options.md
Mohamed Boudra 29c198f955 fix(providers): apply configured options to every agent provider (#5780)
* fix(providers): apply configured options at session startup

Treat provider options as one opaque record, with provider-owned validation and per-agent overrides over configured defaults. Keep agent overrides separate from defaults so configuration changes apply on the next launch or resume.

* test(providers): verify provider-owned validation through Hub

* fix(providers): preserve the complete wrapped session interface
2026-09-30 19:37:14 +01:00

210 lines
8.1 KiB
Markdown

---
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
Provider options are an opaque `Record<string, unknown>` passed to the provider.
The provider decides how to validate and apply it. Claude, Codex, and OpenCode
use strict schemas: an unknown or misspelled key fails agent creation with a
message naming the bad key. Providers without option handling ignore the record.
Pi, OMP, ACP, and plugin providers interpret their own keys.
You set options in two places:
- **Provider defaults:** `config.json` → `agents.providers.<id>.options`.
- **One agent:** `config.options` in `client.agents.create()`. Its wire name is
`providerOptions`.
The daemon deep-merges the agent's options over the provider defaults at session
launch or resume. Plain objects merge recursively; arrays, scalars, and `null`
replace the base value. Only the agent's own options are stored. Changing
`config.json` defaults takes effect on the next launch or resume.
For example, set Codex defaults once:
```json
{
"agents": {
"providers": {
"codex": {
"options": {
"sandbox_mode": "read-only",
"approval_policy": "never",
"sandbox_workspace_write": { "network_access": false }
}
}
}
}
}
```
Then create one agent with `config.options: { sandbox_mode: "workspace-write" }`.
It receives `workspace-write`, retains `approval_policy: "never"`, and retains
`sandbox_workspace_write.network_access: false`. With no per-agent options it
receives the defaults above. Keys are provider-native; use the sections below
for the options each provider accepts.
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.