Add initial set of doc comments to the SDK (#4513)

Also perform minor code cleanup.
This commit is contained in:
pakrym-oai
2025-10-01 13:12:59 -07:00
committed by GitHub
parent 5d78c1edd3
commit 31102af54b
8 changed files with 202 additions and 34 deletions

View File

@@ -2,34 +2,45 @@ use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use ts_rs::TS; use ts_rs::TS;
/// Top-level events emitted on the Codex Exec thread stream. /// Top-level JSONL events emitted by codex exec
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum ThreadEvent { pub enum ThreadEvent {
/// Emitted when a new thread is started as the first event.
#[serde(rename = "thread.started")] #[serde(rename = "thread.started")]
ThreadStarted(ThreadStartedEvent), ThreadStarted(ThreadStartedEvent),
/// Emitted when a turn is started by sending a new prompt to the model.
/// A turn encompasses all events that happen while agent is processing the prompt.
#[serde(rename = "turn.started")] #[serde(rename = "turn.started")]
TurnStarted(TurnStartedEvent), TurnStarted(TurnStartedEvent),
/// Emitted when a turn is completed. Typically right after the assistant's response.
#[serde(rename = "turn.completed")] #[serde(rename = "turn.completed")]
TurnCompleted(TurnCompletedEvent), TurnCompleted(TurnCompletedEvent),
/// Indicates that a turn failed with an error.
#[serde(rename = "turn.failed")] #[serde(rename = "turn.failed")]
TurnFailed(TurnFailedEvent), TurnFailed(TurnFailedEvent),
/// Emitted when a new item is added to the thread. Typically the item will be in an "in progress" state.
#[serde(rename = "item.started")] #[serde(rename = "item.started")]
ItemStarted(ItemStartedEvent), ItemStarted(ItemStartedEvent),
/// Emitted when an item is updated.
#[serde(rename = "item.updated")] #[serde(rename = "item.updated")]
ItemUpdated(ItemUpdatedEvent), ItemUpdated(ItemUpdatedEvent),
/// Signals that an item has reached a terminal state—either success or failure.
#[serde(rename = "item.completed")] #[serde(rename = "item.completed")]
ItemCompleted(ItemCompletedEvent), ItemCompleted(ItemCompletedEvent),
/// Represents an unrecoverable error emitted directly by the event stream.
#[serde(rename = "error")] #[serde(rename = "error")]
Error(ThreadErrorEvent), Error(ThreadErrorEvent),
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct ThreadStartedEvent { pub struct ThreadStartedEvent {
/// The identified of the new thread. Can be used to resume the thread later.
pub thread_id: String, pub thread_id: String,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)]
pub struct TurnStartedEvent {} pub struct TurnStartedEvent {}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
@@ -42,11 +53,14 @@ pub struct TurnFailedEvent {
pub error: ThreadErrorEvent, pub error: ThreadErrorEvent,
} }
/// Minimal usage summary for a turn. /// Describes the usage of tokens during a turn.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS, Default)]
pub struct Usage { pub struct Usage {
/// The number of input tokens used during the turn.
pub input_tokens: u64, pub input_tokens: u64,
/// The number of cached input tokens used during the turn.
pub cached_input_tokens: u64, pub cached_input_tokens: u64,
/// The number of output tokens used during the turn.
pub output_tokens: u64, pub output_tokens: u64,
} }
@@ -83,34 +97,44 @@ pub struct ThreadItem {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
#[serde(tag = "item_type", rename_all = "snake_case")] #[serde(tag = "item_type", rename_all = "snake_case")]
pub enum ThreadItemDetails { pub enum ThreadItemDetails {
/// Response from the agent.
/// Either a natural-language response or a JSON string when structured output is requested.
AssistantMessage(AssistantMessageItem), AssistantMessage(AssistantMessageItem),
/// Agent's reasoning summary.
Reasoning(ReasoningItem), Reasoning(ReasoningItem),
/// Tracks a command executed by the agent. The item starts when the command is
/// spawned, and completes when the process exits with an exit code.
CommandExecution(CommandExecutionItem), CommandExecution(CommandExecutionItem),
/// Represents a set of file changes by the agent. The item is emitted only as a
/// completed event once the patch succeeds or fails.
FileChange(FileChangeItem), FileChange(FileChangeItem),
/// Represents a call to an MCP tool. The item starts when the invocation is
/// dispatched and completes when the MCP server reports success or failure.
McpToolCall(McpToolCallItem), McpToolCall(McpToolCallItem),
/// Captures a web search request. It starts when the search is kicked off
/// and completes when results are returned to the agent.
WebSearch(WebSearchItem), WebSearch(WebSearchItem),
/// Tracks the agent's running to-do list. It starts when the plan is first
/// issued, updates as steps change state, and completes when the turn ends.
TodoList(TodoListItem), TodoList(TodoListItem),
/// Describes a non-fatal error surfaced as an item.
Error(ErrorItem), Error(ErrorItem),
} }
/// Session metadata. /// Response from the agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] /// Either a natural-language response or a JSON string when structured output is requested.
pub struct SessionItem {
pub session_id: String,
}
/// Assistant message payload.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct AssistantMessageItem { pub struct AssistantMessageItem {
pub text: String, pub text: String,
} }
/// Model reasoning summary payload. /// Agent's reasoning summary.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct ReasoningItem { pub struct ReasoningItem {
pub text: String, pub text: String,
} }
/// The status of a command execution.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, TS)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum CommandExecutionStatus { pub enum CommandExecutionStatus {
@@ -120,7 +144,7 @@ pub enum CommandExecutionStatus {
Failed, Failed,
} }
/// Local shell command execution payload. /// A command executed by the agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct CommandExecutionItem { pub struct CommandExecutionItem {
pub command: String, pub command: String,
@@ -130,13 +154,14 @@ pub struct CommandExecutionItem {
pub status: CommandExecutionStatus, pub status: CommandExecutionStatus,
} }
/// Single file change summary for a patch. /// A set of file changes by the agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct FileUpdateChange { pub struct FileUpdateChange {
pub path: String, pub path: String,
pub kind: PatchChangeKind, pub kind: PatchChangeKind,
} }
/// The status of a file change.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum PatchApplyStatus { pub enum PatchApplyStatus {
@@ -144,14 +169,14 @@ pub enum PatchApplyStatus {
Failed, Failed,
} }
/// Patch application payload. /// A set of file changes by the agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct FileChangeItem { pub struct FileChangeItem {
pub changes: Vec<FileUpdateChange>, pub changes: Vec<FileUpdateChange>,
pub status: PatchApplyStatus, pub status: PatchApplyStatus,
} }
/// Known change kinds for a patch. /// Indicates the type of the file change.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum PatchChangeKind { pub enum PatchChangeKind {
@@ -160,6 +185,7 @@ pub enum PatchChangeKind {
Update, Update,
} }
/// The status of an MCP tool call.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, TS)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum McpToolCallStatus { pub enum McpToolCallStatus {
@@ -169,6 +195,7 @@ pub enum McpToolCallStatus {
Failed, Failed,
} }
/// A call to an MCP tool.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct McpToolCallItem { pub struct McpToolCallItem {
pub server: String, pub server: String,
@@ -176,16 +203,19 @@ pub struct McpToolCallItem {
pub status: McpToolCallStatus, pub status: McpToolCallStatus,
} }
/// A web search request.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct WebSearchItem { pub struct WebSearchItem {
pub query: String, pub query: String,
} }
/// An error notification.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct ErrorItem { pub struct ErrorItem {
pub message: String, pub message: String,
} }
/// An item in agent's to-do list.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, TS)]
pub struct TodoItem { pub struct TodoItem {
pub text: String, pub text: String,

View File

@@ -1 +1,53 @@
# Codex SDK # Codex SDK
Bring the power of the best coding agent to your application.
## Installation
```bash
npm install @openai/codex-sdk
```
## Usage
Call `startThread()` and `run()` to start a thead with Codex.
```typescript
import { Codex } from "@openai/codex-sdk";
const codex = new Codex();
const thread = codex.startThread();
const result = await thread.run("Diagnose the test failure and propose a fix");
console.log(result);
```
You can call `run()` again to continue the same thread.
```typescript
const result = await thread.run("Implement the fix");
console.log(result);
```
### Streaming
The `await run()` method completes when a thread turn is complete and agent is prepared the final response.
You can thread items while they are being produced by calling `await runStreamed()`.
```typescript
const result = thread.runStreamed("Diagnose the test failure and propose a fix");
```
### Resuming a thread
If you don't have the original `Thread` instance to continue the thread, you can resume a thread by calling `resumeThread()` and providing the thread.
```typescript
const threadId = "...";
const thread = codex.resumeThread(threadId);
const result = await thread.run("Implement the fix");
console.log(result);
```

View File

@@ -2,6 +2,11 @@ import { CodexOptions } from "./codexOptions";
import { CodexExec } from "./exec"; import { CodexExec } from "./exec";
import { Thread } from "./thread"; import { Thread } from "./thread";
/**
* Codex is the main class for interacting with the Codex agent.
*
* Use the `startThread()` method to start a new thread or `resumeThread()` to resume a previously started thread.
*/
export class Codex { export class Codex {
private exec: CodexExec; private exec: CodexExec;
private options: CodexOptions; private options: CodexOptions;
@@ -11,10 +16,21 @@ export class Codex {
this.options = options; this.options = options;
} }
/**
* Starts a new conversation with an agent.
* @returns A new thread instance.
*/
startThread(): Thread { startThread(): Thread {
return new Thread(this.exec, this.options); return new Thread(this.exec, this.options);
} }
/**
* Resumes a conversation with an agent based on the thread id.
* Threads are persisted in ~/.codex/sessions.
*
* @param id The id of the thread to resume.
* @returns A new thread instance.
*/
resumeThread(id: string): Thread { resumeThread(id: string): Thread {
return new Thread(this.exec, this.options, id); return new Thread(this.exec, this.options, id);
} }

View File

@@ -2,55 +2,73 @@
import type { ThreadItem } from "./items"; import type { ThreadItem } from "./items";
/** Emitted when a new thread is started as the first event. */
export type ThreadStartedEvent = { export type ThreadStartedEvent = {
type: "thread.started"; type: "thread.started";
/** The identifier of the new thread. Can be used to resume the thread later. */
thread_id: string; thread_id: string;
}; };
/**
* Emitted when a turn is started by sending a new prompt to the model.
* A turn encompasses all events that happen while the agent is processing the prompt.
*/
export type TurnStartedEvent = { export type TurnStartedEvent = {
type: "turn.started"; type: "turn.started";
}; };
/** Describes the usage of tokens during a turn. */
export type Usage = { export type Usage = {
/** The number of input tokens used during the turn. */
input_tokens: number; input_tokens: number;
/** The number of cached input tokens used during the turn. */
cached_input_tokens: number; cached_input_tokens: number;
/** The number of output tokens used during the turn. */
output_tokens: number; output_tokens: number;
}; };
/** Emitted when a turn is completed. Typically right after the assistant's response. */
export type TurnCompletedEvent = { export type TurnCompletedEvent = {
type: "turn.completed"; type: "turn.completed";
usage: Usage; usage: Usage;
}; };
/** Indicates that a turn failed with an error. */
export type TurnFailedEvent = { export type TurnFailedEvent = {
type: "turn.failed"; type: "turn.failed";
error: ThreadError; error: ThreadError;
}; };
/** Emitted when a new item is added to the thread. Typically the item is initially "in progress". */
export type ItemStartedEvent = { export type ItemStartedEvent = {
type: "item.started"; type: "item.started";
item: ThreadItem; item: ThreadItem;
}; };
/** Emitted when an item is updated. */
export type ItemUpdatedEvent = { export type ItemUpdatedEvent = {
type: "item.updated"; type: "item.updated";
item: ThreadItem; item: ThreadItem;
}; };
/** Signals that an item has reached a terminal state—either success or failure. */
export type ItemCompletedEvent = { export type ItemCompletedEvent = {
type: "item.completed"; type: "item.completed";
item: ThreadItem; item: ThreadItem;
}; };
/** Fatal error emitted by the stream. */
export type ThreadError = { export type ThreadError = {
message: string; message: string;
}; };
/** Represents an unrecoverable error emitted directly by the event stream. */
export type ThreadErrorEvent = { export type ThreadErrorEvent = {
type: "error"; type: "error";
message: string; message: string;
}; };
/** Top-level JSONL events emitted by codex exec. */
export type ThreadEvent = export type ThreadEvent =
| ThreadStartedEvent | ThreadStartedEvent
| TurnStartedEvent | TurnStartedEvent

View File

@@ -22,7 +22,8 @@ export type {
ErrorItem, ErrorItem,
} from "./items"; } from "./items";
export { Thread, RunResult, RunStreamedResult, Input } from "./thread"; export { Thread } from "./thread";
export type { RunResult, RunStreamedResult, Input } from "./thread";
export { Codex } from "./codex"; export { Codex } from "./codex";

View File

@@ -1,71 +1,101 @@
// based on item types from codex-rs/exec/src/exec_events.rs // based on item types from codex-rs/exec/src/exec_events.rs
/** The status of a command execution. */
export type CommandExecutionStatus = "in_progress" | "completed" | "failed"; export type CommandExecutionStatus = "in_progress" | "completed" | "failed";
/** A command executed by the agent. */
export type CommandExecutionItem = { export type CommandExecutionItem = {
id: string; id: string;
item_type: "command_execution"; item_type: "command_execution";
/** The command line executed by the agent. */
command: string; command: string;
/** Aggregated stdout and stderr captured while the command was running. */
aggregated_output: string; aggregated_output: string;
/** Set when the command exits; omitted while still running. */
exit_code?: number; exit_code?: number;
/** Current status of the command execution. */
status: CommandExecutionStatus; status: CommandExecutionStatus;
}; };
/** Indicates the type of the file change. */
export type PatchChangeKind = "add" | "delete" | "update"; export type PatchChangeKind = "add" | "delete" | "update";
/** A set of file changes by the agent. */
export type FileUpdateChange = { export type FileUpdateChange = {
path: string; path: string;
kind: PatchChangeKind; kind: PatchChangeKind;
}; };
/** The status of a file change. */
export type PatchApplyStatus = "completed" | "failed"; export type PatchApplyStatus = "completed" | "failed";
/** A set of file changes by the agent. Emitted once the patch succeeds or fails. */
export type FileChangeItem = { export type FileChangeItem = {
id: string; id: string;
item_type: "file_change"; item_type: "file_change";
/** Individual file changes that comprise the patch. */
changes: FileUpdateChange[]; changes: FileUpdateChange[];
/** Whether the patch ultimately succeeded or failed. */
status: PatchApplyStatus; status: PatchApplyStatus;
}; };
/** The status of an MCP tool call. */
export type McpToolCallStatus = "in_progress" | "completed" | "failed"; export type McpToolCallStatus = "in_progress" | "completed" | "failed";
/**
* Represents a call to an MCP tool. The item starts when the invocation is dispatched
* and completes when the MCP server reports success or failure.
*/
export type McpToolCallItem = { export type McpToolCallItem = {
id: string; id: string;
item_type: "mcp_tool_call"; item_type: "mcp_tool_call";
/** Name of the MCP server handling the request. */
server: string; server: string;
/** The tool invoked on the MCP server. */
tool: string; tool: string;
/** Current status of the tool invocation. */
status: McpToolCallStatus; status: McpToolCallStatus;
}; };
/** Response from the agent. Either natural-language text or JSON when structured output is requested. */
export type AssistantMessageItem = { export type AssistantMessageItem = {
id: string; id: string;
item_type: "assistant_message"; item_type: "assistant_message";
/** Either natural-language text or JSON when structured output is requested. */
text: string; text: string;
}; };
/** Agent's reasoning summary. */
export type ReasoningItem = { export type ReasoningItem = {
id: string; id: string;
item_type: "reasoning"; item_type: "reasoning";
text: string; text: string;
}; };
/** Captures a web search request. Completes when results are returned to the agent. */
export type WebSearchItem = { export type WebSearchItem = {
id: string; id: string;
item_type: "web_search"; item_type: "web_search";
query: string; query: string;
}; };
/** Describes a non-fatal error surfaced as an item. */
export type ErrorItem = { export type ErrorItem = {
id: string; id: string;
item_type: "error"; item_type: "error";
message: string; message: string;
}; };
/** An item in the agent's to-do list. */
export type TodoItem = { export type TodoItem = {
text: string; text: string;
completed: boolean; completed: boolean;
}; };
/**
* Tracks the agent's running to-do list. Starts when the plan is issued, updates as steps change,
* and completes when the turn ends.
*/
export type TodoListItem = { export type TodoListItem = {
id: string; id: string;
item_type: "todo_list"; item_type: "todo_list";
@@ -78,6 +108,7 @@ export type SessionItem = {
session_id: string; session_id: string;
}; };
/** Canonical union of thread items and their type-specific payloads. */
export type ThreadItem = export type ThreadItem =
| AssistantMessageItem | AssistantMessageItem
| ReasoningItem | ReasoningItem

View File

@@ -4,29 +4,45 @@ import { CodexExec } from "./exec";
import { ThreadItem } from "./items"; import { ThreadItem } from "./items";
import { TurnOptions } from "./turnOptions"; import { TurnOptions } from "./turnOptions";
export type RunResult = { /** Completed turn. */
export type Turn = {
items: ThreadItem[]; items: ThreadItem[];
finalResponse: string; finalResponse: string;
}; };
export type RunStreamedResult = { /** Alias for `Turn` to describe the result of `run()`. */
export type RunResult = Turn;
/** The result of the `runStreamed` method. */
export type StreamedTurn = {
events: AsyncGenerator<ThreadEvent>; events: AsyncGenerator<ThreadEvent>;
}; };
/** Alias for `StreamedTurn` to describe the result of `runStreamed()`. */
export type RunStreamedResult = StreamedTurn;
/** An input to send to the agent. */
export type Input = string; export type Input = string;
/** Respesent a thread of conversation with the agent. One thread can have multiple consecutive turns. */
export class Thread { export class Thread {
private exec: CodexExec; private _exec: CodexExec;
private options: CodexOptions; private _options: CodexOptions;
public id: string | null; private _id: string | null;
constructor(exec: CodexExec, options: CodexOptions, id: string | null = null) { /** Returns the ID of the thread. Populated after the first turn starts. */
this.exec = exec; public get id(): string | null {
this.options = options; return this._id;
this.id = id;
} }
async runStreamed(input: string, options?: TurnOptions): Promise<RunStreamedResult> { constructor(exec: CodexExec, options: CodexOptions, id: string | null = null) {
this._exec = exec;
this._options = options;
this._id = id;
}
/** Provides the input to the agent and streams events as they are produced during the turn. */
async runStreamed(input: string, options?: TurnOptions): Promise<StreamedTurn> {
return { events: this.runStreamedInternal(input, options) }; return { events: this.runStreamedInternal(input, options) };
} }
@@ -34,26 +50,32 @@ export class Thread {
input: string, input: string,
options?: TurnOptions, options?: TurnOptions,
): AsyncGenerator<ThreadEvent> { ): AsyncGenerator<ThreadEvent> {
const generator = this.exec.run({ const generator = this._exec.run({
input, input,
baseUrl: this.options.baseUrl, baseUrl: this._options.baseUrl,
apiKey: this.options.apiKey, apiKey: this._options.apiKey,
threadId: this.id, threadId: this._id,
model: options?.model, model: options?.model,
sandboxMode: options?.sandboxMode, sandboxMode: options?.sandboxMode,
workingDirectory: options?.workingDirectory, workingDirectory: options?.workingDirectory,
skipGitRepoCheck: options?.skipGitRepoCheck, skipGitRepoCheck: options?.skipGitRepoCheck,
}); });
for await (const item of generator) { for await (const item of generator) {
const parsed = JSON.parse(item) as ThreadEvent; let parsed: ThreadEvent;
try {
parsed = JSON.parse(item) as ThreadEvent;
} catch (error) {
throw new Error(`Failed to parse item: ${item}`, { cause: error });
}
if (parsed.type === "thread.started") { if (parsed.type === "thread.started") {
this.id = parsed.thread_id; this._id = parsed.thread_id;
} }
yield parsed; yield parsed;
} }
} }
async run(input: string, options?: TurnOptions): Promise<RunResult> { /** Provides the input to the agent and returns the completed turn. */
async run(input: string, options?: TurnOptions): Promise<Turn> {
const generator = this.runStreamedInternal(input, options); const generator = this.runStreamedInternal(input, options);
const items: ThreadItem[] = []; const items: ThreadItem[] = [];
let finalResponse: string = ""; let finalResponse: string = "";

View File

@@ -218,7 +218,6 @@ describe("Codex", () => {
await close(); await close();
} }
}); });
it("runs in provided working directory", async () => { it("runs in provided working directory", async () => {
const { url, close } = await startResponsesTestProxy({ const { url, close } = await startResponsesTestProxy({
statusCode: 200, statusCode: 200,
@@ -286,7 +285,6 @@ describe("Codex", () => {
} }
}); });
}); });
function expectPair(args: string[] | undefined, pair: [string, string]) { function expectPair(args: string[] | undefined, pair: [string, string]) {
if (!args) { if (!args) {
throw new Error("Args is undefined"); throw new Error("Args is undefined");