Misc SDK fixes (#4752)

Remove codex-level workingDirectory
Throw on turn.failed in `run()`
Cleanup readme
This commit is contained in:
pakrym-oai
2025-10-04 19:55:33 -07:00
committed by GitHub
parent 4764fc1ee7
commit 356ea6ea34
6 changed files with 47 additions and 16 deletions

View File

@@ -10,7 +10,7 @@ npm install @openai/codex-sdk
## Usage
Call `startThread()` and `run()` to start a thead with Codex.
Call `startThread()` and `run()` to start a thread with Codex.
```typescript
import { Codex } from "@openai/codex-sdk";
@@ -32,17 +32,21 @@ console.log(result);
### Streaming
The `await run()` method completes when a thread turn is complete and agent is prepared the final response.
The `run()` method completes when a thread turn is complete and the agent has produced the final response.
You can thread items while they are being produced by calling `await runStreamed()`.
You can stream events while they are being produced by calling `runStreamed()` and iterating the returned generator.
```typescript
const result = thread.runStreamed("Diagnose the test failure and propose a fix");
const { events } = await thread.runStreamed("Diagnose the test failure and propose a fix");
for await (const event of events) {
console.log(event);
}
```
### 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.
If you don't have the original `Thread` instance to continue the thread, you can resume by calling `resumeThread()` and providing the thread identifier.
```typescript
const threadId = "...";
@@ -54,7 +58,7 @@ console.log(result);
### Working directory
By default, Codex will run in the current working directory. You can change the working directory by passing the `workingDirectory` option to the when creating a thread.
By default, Codex will run in the current working directory. You can change the working directory by passing the `workingDirectory` option when creating a thread.
```typescript
const thread = codex.startThread({
@@ -62,7 +66,7 @@ const thread = codex.startThread({
});
```
To avoid unrecoverable errors, Codex requires the working directory to be a git repository. You can skip the git repository check by passing the `skipGitRepoCheck` option to the when creating a thread.
To avoid unrecoverable errors, Codex requires the working directory to be a Git repository. You can skip the Git repository check by passing the `skipGitRepoCheck` option when creating a thread.
```typescript
const thread = codex.startThread({

View File

@@ -2,5 +2,4 @@ export type CodexOptions = {
codexPathOverride?: string;
baseUrl?: string;
apiKey?: string;
workingDirectory?: string;
};

View File

@@ -30,4 +30,4 @@ export { Codex } from "./codex";
export type { CodexOptions } from "./codexOptions";
export type { ThreadOptions as TheadOptions, ApprovalMode, SandboxMode } from "./threadOptions";
export type { ThreadOptions, ApprovalMode, SandboxMode } from "./threadOptions";

View File

@@ -1,5 +1,5 @@
import { CodexOptions } from "./codexOptions";
import { ThreadEvent, Usage } from "./events";
import { ThreadEvent, ThreadError, Usage } from "./events";
import { CodexExec } from "./exec";
import { ThreadItem } from "./items";
import { ThreadOptions } from "./threadOptions";
@@ -87,6 +87,7 @@ export class Thread {
const items: ThreadItem[] = [];
let finalResponse: string = "";
let usage: Usage | null = null;
let turnFailure: ThreadError | null = null;
for await (const event of generator) {
if (event.type === "item.completed") {
if (event.item.type === "agent_message") {
@@ -95,8 +96,14 @@ export class Thread {
items.push(event.item);
} else if (event.type === "turn.completed") {
usage = event.usage;
} else if (event.type === "turn.failed") {
turnFailure = event.error;
break;
}
}
if (turnFailure) {
throw new Error(turnFailure.message);
}
return { items, finalResponse, usage };
}
}

View File

@@ -173,16 +173,19 @@ export function assistantMessage(text: string, itemId: string = DEFAULT_MESSAGE_
};
}
export function responseFailed(errorMessage: string): SseEvent {
return {
type: "error",
error: { code: "rate_limit_exceeded", message: errorMessage },
};
}
export function responseCompleted(
responseId: string = DEFAULT_RESPONSE_ID,
usage: ResponseCompletedUsage = DEFAULT_COMPLETED_USAGE,
): SseEvent {
const inputDetails = usage.input_tokens_details
? { ...usage.input_tokens_details }
: null;
const outputDetails = usage.output_tokens_details
? { ...usage.output_tokens_details }
: null;
const inputDetails = usage.input_tokens_details ? { ...usage.input_tokens_details } : null;
const outputDetails = usage.output_tokens_details ? { ...usage.output_tokens_details } : null;
return {
type: "response.completed",
response: {

View File

@@ -12,6 +12,7 @@ import {
responseCompleted,
responseStarted,
sse,
responseFailed,
startResponsesTestProxy,
} from "./responsesProxy";
@@ -287,6 +288,23 @@ describe("Codex", () => {
await close();
}
});
it("throws ThreadRunError on turn failures", async () => {
const { url, close } = await startResponsesTestProxy({
statusCode: 200,
responseBodies: [
sse(responseStarted("response_1")),
sse(responseFailed("rate limit exceeded")),
],
});
try {
const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" });
const thread = client.startThread();
await expect(thread.run("fail")).rejects.toThrow("stream disconnected before completion:");
} finally {
await close();
}
}, 10000); // TODO(pakrym): remove timeout
});
function expectPair(args: string[] | undefined, pair: [string, string]) {
if (!args) {