Let Claude sessions pass structured launch arguments (#5206)

Expose Claude Agent SDK's documented extraArgs map through providerOptions so Claude-specific flags such as --chrome reach the CLI.

Document boolean and valued flags, link the upstream contract, and validate through schema tests, the public session interface, and a real Claude launch.
This commit is contained in:
paseo-bot[bot]
2026-09-23 22:04:20 +02:00
committed by GitHub
parent 48384cafdf
commit 829cc5e173
4 changed files with 59 additions and 6 deletions
+14 -5
View File
@@ -8,7 +8,8 @@ This guide walks through adding a new agent provider end-to-end. There are two i
names and nesting are the provider's native contract; options are not portable between providers.
Paseo validates the object with the selected provider's strict schema before constructing a session.
Unknown keys fail with their `providerOptions.*` path. Paseo-owned controls such as cwd, model,
prompt, environment, session identity, MCP transport, callbacks, and hooks cannot be passed here.
prompt, environment, session identity, MCP transport, callbacks, and hooks are not accepted as
top-level provider options.
This Paseo version accepts these keys:
@@ -19,10 +20,18 @@ This Paseo version accepts these keys:
`allow_local_binding`, `allow_upstream_proxy`, `dangerously_allow_all_unix_sockets`,
`dangerously_allow_non_loopback_proxy`, `domains`, and `unix_sockets`. See the
[Codex configuration reference](https://developers.openai.com/codex/config-reference).
- **Claude:** `allowedTools`, `disallowedTools`, `additionalDirectories`, `sandbox`, and `settings`.
The accepted sandbox fields cover enablement, fail-if-unavailable behavior, excluded and
unsandboxed commands, filesystem read/write rules, network domain/socket/local-binding rules,
weaker nested sandboxing, ignored violations, and the ripgrep command. `settings` accepts native
- **Claude:** `allowedTools`, `disallowedTools`, `additionalDirectories`, `extraArgs`, `sandbox`, and
`settings`. `providerOptions.extraArgs` passes the SDK's documented
[`Options.extraArgs`](https://platform.claude.com/docs/en/agent-sdk/typescript#options) map
unchanged: keys omit the leading `--`, string values supply an argument value, and `null`
supplies a boolean flag. For example, `providerOptions: { extraArgs: { chrome: null } }`
passes `--chrome`, and `providerOptions: { extraArgs: { model: "x" } }` passes `--model x`.
Set it in session configuration or a plugin's `server.before("agent.create", ...)` hook; see
[plugin configuration hooks](../public-docs/plugins/reference.md#change-configuration-and-inject-an-mcp-server). Values are literal;
shell expressions such as `$(command)` are not evaluated. The accepted sandbox
fields cover enablement, fail-if-unavailable behavior, excluded and unsandboxed commands,
filesystem read/write rules, network domain/socket/local-binding rules, weaker nested
sandboxing, ignored violations, and the ripgrep command. `settings` accepts native
`permissions.{allow,ask,deny}` and sandbox settings. See the
[Claude Agent SDK TypeScript reference](https://platform.claude.com/docs/en/agent-sdk/typescript)
and [Claude settings reference](https://code.claude.com/docs/en/settings).
@@ -51,6 +51,7 @@ describe("provider-owned option schemas", () => {
ClaudeProviderOptionsSchema.parse({
allowedTools: ["Read"],
disallowedTools: ["Bash(rm *)"],
extraArgs: { chrome: null, model: "x" },
sandbox: {
enabled: true,
failIfUnavailable: true,
@@ -63,7 +64,26 @@ describe("provider-owned option schemas", () => {
},
settings: { permissions: { ask: ["Bash(*)"], deny: ["Edit(.env)"] } },
}),
).toMatchObject({ sandbox: { enabled: true, failIfUnavailable: true } });
).toMatchObject({
extraArgs: { chrome: null, model: "x" },
sandbox: { enabled: true, failIfUnavailable: true },
});
});
test.each([true, 42, ["x"]])("rejects invalid Claude extra argument values: %j", (value) => {
expect(() =>
validateProviderOptions("claude", ClaudeProviderOptionsSchema, {
extraArgs: { chrome: value },
}),
).toThrow("providerOptions.extraArgs.chrome");
});
test("rejects a raw argument list for the SDK argument map", () => {
expect(() =>
validateProviderOptions("claude", ClaudeProviderOptionsSchema, {
extraArgs: ["--chrome"],
}),
).toThrow("providerOptions.extraArgs");
});
test("reports the exact invalid Claude option path", () => {
@@ -785,6 +785,29 @@ describe("ClaudeAgentSession features", () => {
await session.close();
});
test("passes extra Claude Code CLI arguments to the SDK", async () => {
const { queryFactory, launches } = createQueryMock();
const client = new ClaudeAgentClient({
logger,
queryFactory,
resolveBinary: async () => "/test/claude/bin",
});
const session = await client.createSession({
provider: "claude",
cwd: process.cwd(),
providerOptions: {
extraArgs: { chrome: null, model: "x" },
},
});
await expect(session.startTurn("hello")).resolves.toEqual({
turnId: expect.stringMatching(/^foreground-turn-/),
});
expect(launches[0]?.options.extraArgs).toEqual({ chrome: null, model: "x" });
await session.close();
});
test("lists fast mode only for supported Opus models", async () => {
const client = new ClaudeAgentClient({ logger, resolveBinary: async () => "/test/claude/bin" });
@@ -48,6 +48,7 @@ export const ClaudeProviderOptionsSchema = z
allowedTools: z.array(z.string()).optional(),
disallowedTools: z.array(z.string()).optional(),
additionalDirectories: z.array(z.string()).optional(),
extraArgs: z.record(z.string(), z.string().nullable()).optional(),
sandbox: z
.object({
enabled: z.boolean().optional(),