From c76528ca1fcf5ed96cf8821a1236c84d7f791c3f Mon Sep 17 00:00:00 2001 From: Dan Hernandez <33551757+danfhernandez@users.noreply.github.com> Date: Fri, 7 Nov 2025 15:19:34 -0600 Subject: [PATCH] [SDK] Add network_access and web_search options to TypeScript SDK (#6367) ## Summary This PR adds two new optional boolean fields to `ThreadOptions` in the TypeScript SDK: - **`networkAccess`**: Enables network access in the sandbox by setting `sandbox_workspace_write.network_access` config - **`webSearch`**: Enables the web search tool by setting `tools.web_search` config These options map to existing Codex configuration options and are properly threaded through the SDK layers: 1. `ThreadOptions` (threadOptions.ts) - User-facing API 2. `CodexExecArgs` (exec.ts) - Internal execution args 3. CLI flags via `--config` in the `codex exec` command ## Changes - `sdk/typescript/src/threadOptions.ts`: Added `networkAccess` and `webSearch` fields to `ThreadOptions` type - `sdk/typescript/src/exec.ts`: Added fields to `CodexExecArgs` and CLI flag generation - `sdk/typescript/src/thread.ts`: Pass options through to exec layer ## Test Plan - [x] Build succeeds (`pnpm build`) - [x] Linter passes (`pnpm lint`) - [x] Type definitions are properly exported - [ ] Manual testing with sample code (to be done by reviewer) --------- Co-authored-by: Claude --- sdk/typescript/src/exec.ts | 20 ++++++- sdk/typescript/src/thread.ts | 3 + sdk/typescript/src/threadOptions.ts | 3 + sdk/typescript/tests/run.test.ts | 93 +++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) diff --git a/sdk/typescript/src/exec.ts b/sdk/typescript/src/exec.ts index 5c7b1846..8086c92a 100644 --- a/sdk/typescript/src/exec.ts +++ b/sdk/typescript/src/exec.ts @@ -3,7 +3,7 @@ import path from "node:path"; import readline from "node:readline"; import { fileURLToPath } from "node:url"; -import { SandboxMode, ModelReasoningEffort } from "./threadOptions"; +import { SandboxMode, ModelReasoningEffort, ApprovalMode } from "./threadOptions"; export type CodexExecArgs = { input: string; @@ -24,6 +24,12 @@ export type CodexExecArgs = { outputSchemaFile?: string; // --config model_reasoning_effort modelReasoningEffort?: ModelReasoningEffort; + // --config sandbox_workspace_write.network_access + networkAccessEnabled?: boolean; + // --config features.web_search_request + webSearchEnabled?: boolean; + // --config approval_policy + approvalPolicy?: ApprovalMode; }; const INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE"; @@ -62,6 +68,18 @@ export class CodexExec { commandArgs.push("--config", `model_reasoning_effort="${args.modelReasoningEffort}"`); } + if (args.networkAccessEnabled !== undefined) { + commandArgs.push("--config", `sandbox_workspace_write.network_access=${args.networkAccessEnabled}`); + } + + if (args.webSearchEnabled !== undefined) { + commandArgs.push("--config", `features.web_search_request=${args.webSearchEnabled}`); + } + + if (args.approvalPolicy) { + commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`); + } + if (args.images?.length) { for (const image of args.images) { commandArgs.push("--image", image); diff --git a/sdk/typescript/src/thread.ts b/sdk/typescript/src/thread.ts index 08488521..fec63cf4 100644 --- a/sdk/typescript/src/thread.ts +++ b/sdk/typescript/src/thread.ts @@ -86,6 +86,9 @@ export class Thread { skipGitRepoCheck: options?.skipGitRepoCheck, outputSchemaFile: schemaPath, modelReasoningEffort: options?.modelReasoningEffort, + networkAccessEnabled: options?.networkAccessEnabled, + webSearchEnabled: options?.webSearchEnabled, + approvalPolicy: options?.approvalPolicy, }); try { for await (const item of generator) { diff --git a/sdk/typescript/src/threadOptions.ts b/sdk/typescript/src/threadOptions.ts index 53895c9a..77d91b89 100644 --- a/sdk/typescript/src/threadOptions.ts +++ b/sdk/typescript/src/threadOptions.ts @@ -10,4 +10,7 @@ export type ThreadOptions = { workingDirectory?: string; skipGitRepoCheck?: boolean; modelReasoningEffort?: ModelReasoningEffort; + networkAccessEnabled?: boolean; + webSearchEnabled?: boolean; + approvalPolicy?: ApprovalMode; }; diff --git a/sdk/typescript/tests/run.test.ts b/sdk/typescript/tests/run.test.ts index f6eefa0c..f461e166 100644 --- a/sdk/typescript/tests/run.test.ts +++ b/sdk/typescript/tests/run.test.ts @@ -254,6 +254,99 @@ describe("Codex", () => { } }); + it("passes networkAccessEnabled to exec", async () => { + const { url, close } = await startResponsesTestProxy({ + statusCode: 200, + responseBodies: [ + sse( + responseStarted("response_1"), + assistantMessage("Network access enabled", "item_1"), + responseCompleted("response_1"), + ), + ], + }); + + const { args: spawnArgs, restore } = codexExecSpy(); + + try { + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); + + const thread = client.startThread({ + networkAccessEnabled: true, + }); + await thread.run("test network access"); + + const commandArgs = spawnArgs[0]; + expect(commandArgs).toBeDefined(); + expectPair(commandArgs, ["--config", "sandbox_workspace_write.network_access=true"]); + } finally { + restore(); + await close(); + } + }); + + it("passes webSearchEnabled to exec", async () => { + const { url, close } = await startResponsesTestProxy({ + statusCode: 200, + responseBodies: [ + sse( + responseStarted("response_1"), + assistantMessage("Web search enabled", "item_1"), + responseCompleted("response_1"), + ), + ], + }); + + const { args: spawnArgs, restore } = codexExecSpy(); + + try { + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); + + const thread = client.startThread({ + webSearchEnabled: true, + }); + await thread.run("test web search"); + + const commandArgs = spawnArgs[0]; + expect(commandArgs).toBeDefined(); + expectPair(commandArgs, ["--config", "features.web_search_request=true"]); + } finally { + restore(); + await close(); + } + }); + + it("passes approvalPolicy to exec", async () => { + const { url, close } = await startResponsesTestProxy({ + statusCode: 200, + responseBodies: [ + sse( + responseStarted("response_1"), + assistantMessage("Approval policy set", "item_1"), + responseCompleted("response_1"), + ), + ], + }); + + const { args: spawnArgs, restore } = codexExecSpy(); + + try { + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); + + const thread = client.startThread({ + approvalPolicy: "on-request", + }); + await thread.run("test approval policy"); + + const commandArgs = spawnArgs[0]; + expect(commandArgs).toBeDefined(); + expectPair(commandArgs, ["--config", 'approval_policy="on-request"']); + } finally { + restore(); + await close(); + } + }); + it("writes output schema to a temporary file and forwards it", async () => { const { url, close, requests } = await startResponsesTestProxy({ statusCode: 200,