diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d65067a3f..0f4465a220 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: ts-jest: specifier: ^29.3.4 version: 29.4.4(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.10)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2) + ts-jest-mock-import-meta: + specifier: ^1.3.1 + version: 1.3.1(ts-jest@29.4.4(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.10)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2)) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.19.18)(typescript@5.9.2) @@ -1979,6 +1982,11 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-jest-mock-import-meta@1.3.1: + resolution: {integrity: sha512-KGrp9Nh/SdyrQs5hZvtkp0CFPOgAh3DL57NZgFRbtlvMyEo7XuXLbeyylmxFZGGu30pL338h9KxwSxrNDndygw==} + peerDependencies: + ts-jest: '>=20.0.0' + ts-jest@29.4.4: resolution: {integrity: sha512-ccVcRABct5ZELCT5U0+DZwkXMCcOCLi2doHRrKy1nK/s7J7bch6TzJMsrY09WxgUUIP/ITfmcDS8D2yl63rnXw==} engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} @@ -4291,6 +4299,10 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-jest-mock-import-meta@1.3.1(ts-jest@29.4.4(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.10)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2)): + dependencies: + ts-jest: 29.4.4(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.10)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2) + ts-jest@29.4.4(@babel/core@7.28.4)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.4))(esbuild@0.25.10)(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.18)(ts-node@10.9.2(@types/node@20.19.18)(typescript@5.9.2)))(typescript@5.9.2): dependencies: bs-logger: 0.2.6 diff --git a/sdk/typescript/jest.config.cjs b/sdk/typescript/jest.config.cjs index 55d25cd842..05d51f832c 100644 --- a/sdk/typescript/jest.config.cjs +++ b/sdk/typescript/jest.config.cjs @@ -3,14 +3,29 @@ module.exports = { preset: "ts-jest/presets/default-esm", testEnvironment: "node", extensionsToTreatAsEsm: [".ts"], - globals: { - "ts-jest": { - useESM: true, - tsconfig: "tsconfig.json", - }, - }, moduleNameMapper: { "^(\\.{1,2}/.*)\\.js$": "$1", }, testMatch: ["**/tests/**/*.test.ts"], + transform: { + "^.+\\.tsx?$": [ + "ts-jest", + { + useESM: true, + tsconfig: "tsconfig.json", + diagnostics: { + ignoreCodes: [1343], + }, + astTransformers: { + before: [ + { + path: "ts-jest-mock-import-meta", + // Workaround for meta.url not working in jest + options: { metaObjectReplacement: { url: "file://" + __dirname + "/dist/index.js" } }, + }, + ], + }, + }, + ], + }, }; diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index b68f939c12..dac7a89d6e 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -42,15 +42,16 @@ "devDependencies": { "@types/jest": "^29.5.14", "@types/node": "^20.19.18", - "typescript-eslint": "^8.45.0", "eslint": "^9.36.0", "eslint-config-prettier": "^9.1.2", + "eslint-plugin-jest": "^29.0.1", "jest": "^29.7.0", "prettier": "^3.6.2", "ts-jest": "^29.3.4", "ts-node": "^10.9.2", "tsup": "^8.5.0", "typescript": "^5.9.2", - "eslint-plugin-jest": "^29.0.1" + "typescript-eslint": "^8.45.0", + "ts-jest-mock-import-meta": "^1.3.1" } } diff --git a/sdk/typescript/src/codex.ts b/sdk/typescript/src/codex.ts index 36275bc7d0..bb4b713550 100644 --- a/sdk/typescript/src/codex.ts +++ b/sdk/typescript/src/codex.ts @@ -7,11 +7,7 @@ export class Codex { private options: CodexOptions; constructor(options: CodexOptions) { - if (!options.executablePath) { - throw new Error("executablePath is required"); - } - - this.exec = new CodexExec(options.executablePath); + this.exec = new CodexExec(options.codexPathOverride); this.options = options; } diff --git a/sdk/typescript/src/codexOptions.ts b/sdk/typescript/src/codexOptions.ts index 16113bc2de..2d22bcf227 100644 --- a/sdk/typescript/src/codexOptions.ts +++ b/sdk/typescript/src/codexOptions.ts @@ -1,7 +1,5 @@ export type CodexOptions = { - // TODO: remove - executablePath: string; - // TODO: remove + codexPathOverride?: string; baseUrl?: string; apiKey?: string; }; diff --git a/sdk/typescript/src/exec.ts b/sdk/typescript/src/exec.ts index 010f723fa7..6ae5b441a7 100644 --- a/sdk/typescript/src/exec.ts +++ b/sdk/typescript/src/exec.ts @@ -1,7 +1,10 @@ import { spawn } from "child_process"; + import readline from "node:readline"; import { SandboxMode } from "./turnOptions"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; export type CodexExecArgs = { input: string; @@ -15,8 +18,8 @@ export type CodexExecArgs = { export class CodexExec { private executablePath: string; - constructor(executablePath: string) { - this.executablePath = executablePath; + constructor(executablePath: string | null = null) { + this.executablePath = executablePath || findCodexPath(); } async *run(args: CodexExecArgs): AsyncGenerator { @@ -92,3 +95,64 @@ export class CodexExec { } } } + +const scriptFileName = fileURLToPath(import.meta.url); +const scriptDirName = path.dirname(scriptFileName); + +function findCodexPath() { + const { platform, arch } = process; + + let targetTriple = null; + switch (platform) { + case "linux": + case "android": + switch (arch) { + case "x64": + targetTriple = "x86_64-unknown-linux-musl"; + break; + case "arm64": + targetTriple = "aarch64-unknown-linux-musl"; + break; + default: + break; + } + break; + case "darwin": + switch (arch) { + case "x64": + targetTriple = "x86_64-apple-darwin"; + break; + case "arm64": + targetTriple = "aarch64-apple-darwin"; + break; + default: + break; + } + break; + case "win32": + switch (arch) { + case "x64": + targetTriple = "x86_64-pc-windows-msvc"; + break; + case "arm64": + targetTriple = "aarch64-pc-windows-msvc"; + break; + default: + break; + } + break; + default: + break; + } + + if (!targetTriple) { + throw new Error(`Unsupported platform: ${platform} (${arch})`); + } + + const vendorRoot = path.join(scriptDirName, "..", "vendor"); + const archRoot = path.join(vendorRoot, targetTriple); + const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex"; + const binaryPath = path.join(archRoot, "codex", codexBinaryName); + + return binaryPath; +} diff --git a/sdk/typescript/src/index.ts b/sdk/typescript/src/index.ts index 93557fb65d..5fa6d5bc4e 100644 --- a/sdk/typescript/src/index.ts +++ b/sdk/typescript/src/index.ts @@ -22,9 +22,9 @@ export type { ErrorItem, } from "./items"; -export type { Thread, RunResult, RunStreamedResult, Input } from "./thread"; +export { Thread, RunResult, RunStreamedResult, Input } from "./thread"; -export type { Codex } from "./codex"; +export { Codex } from "./codex"; export type { CodexOptions } from "./codexOptions"; diff --git a/sdk/typescript/tests/run.test.ts b/sdk/typescript/tests/run.test.ts index 67b1f3d2cf..8c7dff7725 100644 --- a/sdk/typescript/tests/run.test.ts +++ b/sdk/typescript/tests/run.test.ts @@ -23,7 +23,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); const result = await thread.run("Hello, world!"); @@ -60,7 +60,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); await thread.run("first input"); @@ -103,7 +103,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); await thread.run("first input"); @@ -149,7 +149,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const originalThread = client.startThread(); await originalThread.run("first input"); @@ -193,7 +193,7 @@ describe("Codex", () => { const { args: spawnArgs, restore } = codexExecSpy(); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); await thread.run("apply options", { diff --git a/sdk/typescript/tests/runStreamed.test.ts b/sdk/typescript/tests/runStreamed.test.ts index 85b5cf38df..9f60cef02e 100644 --- a/sdk/typescript/tests/runStreamed.test.ts +++ b/sdk/typescript/tests/runStreamed.test.ts @@ -23,7 +23,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); const result = await thread.runStreamed("Hello, world!"); @@ -82,7 +82,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const thread = client.startThread(); const first = await thread.runStreamed("first input"); @@ -128,7 +128,7 @@ describe("Codex", () => { }); try { - const client = new Codex({ executablePath: codexExecPath, baseUrl: url, apiKey: "test" }); + const client = new Codex({ codexPathOverride: codexExecPath, baseUrl: url, apiKey: "test" }); const originalThread = client.startThread(); const first = await originalThread.runStreamed("first input"); diff --git a/sdk/typescript/tsconfig.json b/sdk/typescript/tsconfig.json index 7dd88b00f4..cdbc4f30c2 100644 --- a/sdk/typescript/tsconfig.json +++ b/sdk/typescript/tsconfig.json @@ -16,7 +16,8 @@ "declaration": true, "declarationMap": true, "noImplicitAny": true, - "outDir": "dist" + "outDir": "dist", + "stripInternal": true }, "include": ["src", "tests", "tsup.config.ts"], "exclude": ["dist", "node_modules"]