Rename conversation to thread in codex exec (#4482)
This commit is contained in:
@@ -4,7 +4,7 @@ import { createInterface } from "node:readline/promises";
|
||||
import { stdin as input, stdout as output } from "node:process";
|
||||
|
||||
import { Codex } from "@openai/codex-sdk";
|
||||
import type { ConversationEvent, ConversationItem } from "@openai/codex-sdk";
|
||||
import type { ThreadEvent, ThreadItem } from "@openai/codex-sdk";
|
||||
import path from "node:path";
|
||||
|
||||
const executablePath =
|
||||
@@ -15,7 +15,7 @@ const codex = new Codex({ executablePath });
|
||||
const thread = codex.startThread();
|
||||
const rl = createInterface({ input, output });
|
||||
|
||||
const handleItemCompleted = (item: ConversationItem): void => {
|
||||
const handleItemCompleted = (item: ThreadItem): void => {
|
||||
switch (item.item_type) {
|
||||
case "assistant_message":
|
||||
console.log(`Assistant: ${item.text}`);
|
||||
@@ -37,7 +37,7 @@ const handleItemCompleted = (item: ConversationItem): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleItemUpdated = (item: ConversationItem): void => {
|
||||
const handleItemUpdated = (item: ThreadItem): void => {
|
||||
switch (item.item_type) {
|
||||
case "todo_list": {
|
||||
console.log(`Todo:`);
|
||||
@@ -49,7 +49,7 @@ const handleItemUpdated = (item: ConversationItem): void => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEvent = (event: ConversationEvent): void => {
|
||||
const handleEvent = (event: ThreadEvent): void => {
|
||||
switch (event.type) {
|
||||
case "item.completed":
|
||||
handleItemCompleted(event.item);
|
||||
@@ -63,6 +63,9 @@ const handleEvent = (event: ConversationEvent): void => {
|
||||
`Used ${event.usage.input_tokens} input tokens, ${event.usage.cached_input_tokens} cached input tokens, ${event.usage.output_tokens} output tokens.`,
|
||||
);
|
||||
break;
|
||||
case "turn.failed":
|
||||
console.error(`Turn failed: ${event.error.message}`);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// based on event types from codex-rs/exec/src/exec_events.rs
|
||||
|
||||
import type { ConversationItem } from "./items";
|
||||
import type { ThreadItem } from "./items";
|
||||
|
||||
export type SessionCreatedEvent = {
|
||||
type: "session.created";
|
||||
session_id: string;
|
||||
export type ThreadStartedEvent = {
|
||||
type: "thread.started";
|
||||
thread_id: string;
|
||||
};
|
||||
|
||||
export type TurnStartedEvent = {
|
||||
@@ -22,31 +22,41 @@ export type TurnCompletedEvent = {
|
||||
usage: Usage;
|
||||
};
|
||||
|
||||
export type TurnFailedEvent = {
|
||||
type: "turn.failed";
|
||||
error: ThreadError;
|
||||
};
|
||||
|
||||
export type ItemStartedEvent = {
|
||||
type: "item.started";
|
||||
item: ConversationItem;
|
||||
item: ThreadItem;
|
||||
};
|
||||
|
||||
export type ItemUpdatedEvent = {
|
||||
type: "item.updated";
|
||||
item: ConversationItem;
|
||||
item: ThreadItem;
|
||||
};
|
||||
|
||||
export type ItemCompletedEvent = {
|
||||
type: "item.completed";
|
||||
item: ConversationItem;
|
||||
item: ThreadItem;
|
||||
};
|
||||
|
||||
export type ConversationErrorEvent = {
|
||||
export type ThreadError = {
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ThreadErrorEvent = {
|
||||
type: "error";
|
||||
message: string;
|
||||
};
|
||||
|
||||
export type ConversationEvent =
|
||||
| SessionCreatedEvent
|
||||
export type ThreadEvent =
|
||||
| ThreadStartedEvent
|
||||
| TurnStartedEvent
|
||||
| TurnCompletedEvent
|
||||
| TurnFailedEvent
|
||||
| ItemStartedEvent
|
||||
| ItemUpdatedEvent
|
||||
| ItemCompletedEvent
|
||||
| ConversationErrorEvent;
|
||||
| ThreadErrorEvent;
|
||||
|
||||
@@ -6,7 +6,7 @@ export type CodexExecArgs = {
|
||||
|
||||
baseUrl?: string;
|
||||
apiKey?: string;
|
||||
sessionId?: string | null;
|
||||
threadId?: string | null;
|
||||
};
|
||||
|
||||
export class CodexExec {
|
||||
@@ -17,8 +17,8 @@ export class CodexExec {
|
||||
|
||||
async *run(args: CodexExecArgs): AsyncGenerator<string> {
|
||||
const commandArgs: string[] = ["exec", "--experimental-json"];
|
||||
if (args.sessionId) {
|
||||
commandArgs.push("resume", args.sessionId, args.input);
|
||||
if (args.threadId) {
|
||||
commandArgs.push("resume", args.threadId, args.input);
|
||||
} else {
|
||||
commandArgs.push(args.input);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
export type {
|
||||
ConversationEvent,
|
||||
SessionCreatedEvent,
|
||||
ThreadEvent,
|
||||
ThreadStartedEvent,
|
||||
TurnStartedEvent,
|
||||
TurnCompletedEvent,
|
||||
TurnFailedEvent,
|
||||
ItemStartedEvent,
|
||||
ItemUpdatedEvent,
|
||||
ItemCompletedEvent,
|
||||
ConversationErrorEvent,
|
||||
ThreadError,
|
||||
ThreadErrorEvent,
|
||||
} from "./events";
|
||||
export type {
|
||||
ConversationItem,
|
||||
ThreadItem,
|
||||
AssistantMessageItem,
|
||||
ReasoningItem,
|
||||
CommandExecutionItem,
|
||||
|
||||
@@ -78,17 +78,7 @@ export type SessionItem = {
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
export type ConversationItem =
|
||||
| AssistantMessageItem
|
||||
| ReasoningItem
|
||||
| CommandExecutionItem
|
||||
| FileChangeItem
|
||||
| McpToolCallItem
|
||||
| WebSearchItem
|
||||
| TodoListItem
|
||||
| ErrorItem;
|
||||
|
||||
export type ConversationItemDetails =
|
||||
export type ThreadItem =
|
||||
| AssistantMessageItem
|
||||
| ReasoningItem
|
||||
| CommandExecutionItem
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { CodexOptions } from "./codexOptions";
|
||||
import { ConversationEvent } from "./events";
|
||||
import { ThreadEvent } from "./events";
|
||||
import { CodexExec } from "./exec";
|
||||
import { ConversationItem } from "./items";
|
||||
import { ThreadItem } from "./items";
|
||||
|
||||
export type RunResult = {
|
||||
items: ConversationItem[];
|
||||
items: ThreadItem[];
|
||||
finalResponse: string;
|
||||
};
|
||||
|
||||
export type RunStreamedResult = {
|
||||
events: AsyncGenerator<ConversationEvent>;
|
||||
events: AsyncGenerator<ThreadEvent>;
|
||||
};
|
||||
|
||||
export type Input = string;
|
||||
@@ -29,17 +29,17 @@ export class Thread {
|
||||
return { events: this.runStreamedInternal(input) };
|
||||
}
|
||||
|
||||
private async *runStreamedInternal(input: string): AsyncGenerator<ConversationEvent> {
|
||||
private async *runStreamedInternal(input: string): AsyncGenerator<ThreadEvent> {
|
||||
const generator = this.exec.run({
|
||||
input,
|
||||
baseUrl: this.options.baseUrl,
|
||||
apiKey: this.options.apiKey,
|
||||
sessionId: this.id,
|
||||
threadId: this.id,
|
||||
});
|
||||
for await (const item of generator) {
|
||||
const parsed = JSON.parse(item) as ConversationEvent;
|
||||
if (parsed.type === "session.created") {
|
||||
this.id = parsed.session_id;
|
||||
const parsed = JSON.parse(item) as ThreadEvent;
|
||||
if (parsed.type === "thread.started") {
|
||||
this.id = parsed.thread_id;
|
||||
}
|
||||
yield parsed;
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class Thread {
|
||||
|
||||
async run(input: string): Promise<RunResult> {
|
||||
const generator = this.runStreamedInternal(input);
|
||||
const items: ConversationItem[] = [];
|
||||
const items: ThreadItem[] = [];
|
||||
let finalResponse: string = "";
|
||||
for await (const event of generator) {
|
||||
if (event.type === "item.completed") {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
||||
|
||||
describe("Codex", () => {
|
||||
it("returns session events", async () => {
|
||||
it("returns thread events", async () => {
|
||||
const { url, close } = await startResponsesTestProxy({
|
||||
statusCode: 200,
|
||||
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
||||
@@ -65,7 +65,7 @@ describe("Codex", () => {
|
||||
await thread.run("first input");
|
||||
await thread.run("second input");
|
||||
|
||||
// Check second request continues conversation
|
||||
// Check second request continues the same thread
|
||||
expect(requests.length).toBeGreaterThanOrEqual(2);
|
||||
const secondRequest = requests[1];
|
||||
expect(secondRequest).toBeDefined();
|
||||
|
||||
@@ -3,7 +3,7 @@ import path from "path";
|
||||
import { describe, expect, it } from "@jest/globals";
|
||||
|
||||
import { Codex } from "../src/codex";
|
||||
import { ConversationEvent } from "../src/index";
|
||||
import { ThreadEvent } from "../src/index";
|
||||
|
||||
import {
|
||||
assistantMessage,
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
const codexExecPath = path.join(process.cwd(), "..", "..", "codex-rs", "target", "debug", "codex");
|
||||
|
||||
describe("Codex", () => {
|
||||
it("returns session events", async () => {
|
||||
it("returns thread events", async () => {
|
||||
const { url, close } = await startResponsesTestProxy({
|
||||
statusCode: 200,
|
||||
responseBodies: [sse(responseStarted(), assistantMessage("Hi!"), responseCompleted())],
|
||||
@@ -28,15 +28,15 @@ describe("Codex", () => {
|
||||
const thread = client.startThread();
|
||||
const result = await thread.runStreamed("Hello, world!");
|
||||
|
||||
const events: ConversationEvent[] = [];
|
||||
const events: ThreadEvent[] = [];
|
||||
for await (const event of result.events) {
|
||||
events.push(event);
|
||||
}
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
type: "session.created",
|
||||
session_id: expect.any(String),
|
||||
type: "thread.started",
|
||||
thread_id: expect.any(String),
|
||||
},
|
||||
{
|
||||
type: "turn.started",
|
||||
@@ -91,7 +91,7 @@ describe("Codex", () => {
|
||||
const second = await thread.runStreamed("second input");
|
||||
await drainEvents(second.events);
|
||||
|
||||
// Check second request continues conversation
|
||||
// Check second request continues the same thread
|
||||
expect(requests.length).toBeGreaterThanOrEqual(2);
|
||||
const secondRequest = requests[1];
|
||||
expect(secondRequest).toBeDefined();
|
||||
@@ -159,7 +159,7 @@ describe("Codex", () => {
|
||||
});
|
||||
});
|
||||
|
||||
async function drainEvents(events: AsyncGenerator<ConversationEvent>): Promise<void> {
|
||||
async function drainEvents(events: AsyncGenerator<ThreadEvent>): Promise<void> {
|
||||
let done = false;
|
||||
do {
|
||||
done = (await events.next()).done ?? false;
|
||||
|
||||
Reference in New Issue
Block a user