diff --git a/.github/workflows/rust-release.yml b/.github/workflows/rust-release.yml index c5b45071b0..792c574796 100644 --- a/.github/workflows/rust-release.yml +++ b/.github/workflows/rust-release.yml @@ -601,6 +601,9 @@ jobs: "codex-npm-win32-*-${version}.tgz" "codex-responses-api-proxy-npm-${version}.tgz" "codex-sdk-npm-${version}.tgz" + "codex-sdk-npm-linux-*-${version}.tgz" + "codex-sdk-npm-darwin-*-${version}.tgz" + "codex-sdk-npm-win32-*-${version}.tgz" ) for pattern in "${patterns[@]}"; do gh release download "$tag" \ @@ -638,6 +641,11 @@ jobs: platform="${platform%-${VERSION}.tgz}" tag="${prefix}${platform}" ;; + codex-sdk-npm-linux-*-"${VERSION}".tgz|codex-sdk-npm-darwin-*-"${VERSION}".tgz|codex-sdk-npm-win32-*-"${VERSION}".tgz) + platform="${filename#codex-sdk-npm-}" + platform="${platform%-${VERSION}.tgz}" + tag="${prefix}${platform}" + ;; codex-npm-"${VERSION}".tgz|codex-responses-api-proxy-npm-"${VERSION}".tgz|codex-sdk-npm-"${VERSION}".tgz) tag="${NPM_TAG}" ;; diff --git a/codex-cli/scripts/README.md b/codex-cli/scripts/README.md index ca0d54b544..70119104e2 100644 --- a/codex-cli/scripts/README.md +++ b/codex-cli/scripts/README.md @@ -18,6 +18,11 @@ When `--package codex` is provided, the staging helper builds the lightweight `@openai/codex` meta package plus all platform-native `@openai/codex` variants that are later published under platform-specific dist-tags. +When `--package codex-sdk` is provided, the staging helper now follows the same +pattern: it builds a lightweight `@openai/codex-sdk` meta package plus +platform-native `@openai/codex-sdk` variants published under platform-specific +dist-tags. + If you need to invoke `build_npm_package.py` directly, run `codex-cli/scripts/install_native_deps.py` first and pass `--vendor-src` pointing to the directory that contains the populated `vendor/` tree. diff --git a/codex-cli/scripts/build_npm_package.py b/codex-cli/scripts/build_npm_package.py index 6e1c677b37..af8f53b0b3 100755 --- a/codex-cli/scripts/build_npm_package.py +++ b/codex-cli/scripts/build_npm_package.py @@ -15,6 +15,7 @@ REPO_ROOT = CODEX_CLI_ROOT.parent RESPONSES_API_PROXY_NPM_ROOT = REPO_ROOT / "codex-rs" / "responses-api-proxy" / "npm" CODEX_SDK_ROOT = REPO_ROOT / "sdk" / "typescript" CODEX_NPM_NAME = "@openai/codex" +CODEX_SDK_NPM_NAME = "@openai/codex-sdk" # `npm_name` is the local optional-dependency alias consumed by `bin/codex.js`. # The underlying package published to npm is always `@openai/codex`. @@ -63,8 +64,56 @@ CODEX_PLATFORM_PACKAGES: dict[str, dict[str, str]] = { }, } +# `npm_name` is the local optional-dependency alias consumed by `sdk/typescript/src/exec.ts`. +# The underlying package published to npm is always `@openai/codex-sdk`. +CODEX_SDK_PLATFORM_PACKAGES: dict[str, dict[str, str]] = { + "codex-sdk-linux-x64": { + "npm_name": "@openai/codex-sdk-linux-x64", + "npm_tag": "linux-x64", + "target_triple": "x86_64-unknown-linux-musl", + "os": "linux", + "cpu": "x64", + }, + "codex-sdk-linux-arm64": { + "npm_name": "@openai/codex-sdk-linux-arm64", + "npm_tag": "linux-arm64", + "target_triple": "aarch64-unknown-linux-musl", + "os": "linux", + "cpu": "arm64", + }, + "codex-sdk-darwin-x64": { + "npm_name": "@openai/codex-sdk-darwin-x64", + "npm_tag": "darwin-x64", + "target_triple": "x86_64-apple-darwin", + "os": "darwin", + "cpu": "x64", + }, + "codex-sdk-darwin-arm64": { + "npm_name": "@openai/codex-sdk-darwin-arm64", + "npm_tag": "darwin-arm64", + "target_triple": "aarch64-apple-darwin", + "os": "darwin", + "cpu": "arm64", + }, + "codex-sdk-win32-x64": { + "npm_name": "@openai/codex-sdk-win32-x64", + "npm_tag": "win32-x64", + "target_triple": "x86_64-pc-windows-msvc", + "os": "win32", + "cpu": "x64", + }, + "codex-sdk-win32-arm64": { + "npm_name": "@openai/codex-sdk-win32-arm64", + "npm_tag": "win32-arm64", + "target_triple": "aarch64-pc-windows-msvc", + "os": "win32", + "cpu": "arm64", + }, +} + PACKAGE_EXPANSIONS: dict[str, list[str]] = { "codex": ["codex", *CODEX_PLATFORM_PACKAGES], + "codex-sdk": ["codex-sdk", *CODEX_SDK_PLATFORM_PACKAGES], } PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = { @@ -76,13 +125,25 @@ PACKAGE_NATIVE_COMPONENTS: dict[str, list[str]] = { "codex-win32-x64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"], "codex-win32-arm64": ["codex", "rg", "codex-windows-sandbox-setup", "codex-command-runner"], "codex-responses-api-proxy": ["codex-responses-api-proxy"], - "codex-sdk": ["codex"], + "codex-sdk": [], + "codex-sdk-linux-x64": ["codex"], + "codex-sdk-linux-arm64": ["codex"], + "codex-sdk-darwin-x64": ["codex"], + "codex-sdk-darwin-arm64": ["codex"], + "codex-sdk-win32-x64": ["codex", "codex-windows-sandbox-setup", "codex-command-runner"], + "codex-sdk-win32-arm64": ["codex", "codex-windows-sandbox-setup", "codex-command-runner"], } PACKAGE_TARGET_FILTERS: dict[str, str] = { package_name: package_config["target_triple"] for package_name, package_config in CODEX_PLATFORM_PACKAGES.items() } +PACKAGE_TARGET_FILTERS.update( + { + package_name: package_config["target_triple"] + for package_name, package_config in CODEX_SDK_PLATFORM_PACKAGES.items() + } +) PACKAGE_CHOICES = tuple(PACKAGE_NATIVE_COMPONENTS) @@ -194,7 +255,7 @@ def main() -> int: "Verify the responses API proxy:\n" f" node {staging_dir_str}/bin/codex-responses-api-proxy.js --help\n\n" ) - elif package in CODEX_PLATFORM_PACKAGES: + elif package in CODEX_PLATFORM_PACKAGES or package in CODEX_SDK_PLATFORM_PACKAGES: print( f"Staged version {version} for release in {staging_dir_str}\n\n" "Verify native payload contents:\n" @@ -205,7 +266,6 @@ def main() -> int: f"Staged version {version} for release in {staging_dir_str}\n\n" "Verify the SDK contents:\n" f" ls {staging_dir_str}/dist\n" - f" ls {staging_dir_str}/vendor\n" " node -e \"import('./dist/index.js').then(() => console.log('ok'))\"\n\n" ) else: @@ -280,6 +340,39 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None: package_manager = codex_package_json.get("packageManager") if isinstance(package_manager, str): package_json["packageManager"] = package_manager + elif package in CODEX_SDK_PLATFORM_PACKAGES: + platform_package = CODEX_SDK_PLATFORM_PACKAGES[package] + platform_npm_tag = platform_package["npm_tag"] + platform_version = compute_platform_package_version(version, platform_npm_tag) + + readme_src = CODEX_SDK_ROOT / "README.md" + if readme_src.exists(): + shutil.copy2(readme_src, staging_dir / "README.md") + + license_src = REPO_ROOT / "LICENSE" + if license_src.exists(): + shutil.copy2(license_src, staging_dir / "LICENSE") + + with open(CODEX_SDK_ROOT / "package.json", "r", encoding="utf-8") as fh: + codex_sdk_package_json = json.load(fh) + + package_json = { + "name": CODEX_SDK_NPM_NAME, + "version": platform_version, + "license": codex_sdk_package_json.get("license", "Apache-2.0"), + "os": [platform_package["os"]], + "cpu": [platform_package["cpu"]], + "files": ["vendor"], + "repository": codex_sdk_package_json.get("repository"), + } + + engines = codex_sdk_package_json.get("engines") + if isinstance(engines, dict): + package_json["engines"] = engines + + package_manager = codex_sdk_package_json.get("packageManager") + if isinstance(package_manager, str): + package_json["packageManager"] = package_manager elif package == "codex-responses-api-proxy": bin_dir = staging_dir / "bin" bin_dir.mkdir(parents=True, exist_ok=True) @@ -318,12 +411,15 @@ def stage_sources(staging_dir: Path, version: str, package: str) -> None: if isinstance(scripts, dict): scripts.pop("prepare", None) - files = package_json.get("files") - if isinstance(files, list): - if "vendor" not in files: - files.append("vendor") - else: - package_json["files"] = ["dist", "vendor"] + package_json["files"] = ["dist"] + package_json["optionalDependencies"] = { + CODEX_SDK_PLATFORM_PACKAGES[platform_package]["npm_name"]: ( + f"npm:{CODEX_SDK_NPM_NAME}@" + f"{compute_platform_package_version(version, CODEX_SDK_PLATFORM_PACKAGES[platform_package]['npm_tag'])}" + ) + for platform_package in PACKAGE_EXPANSIONS["codex-sdk"] + if platform_package != "codex-sdk" + } with open(staging_dir / "package.json", "w", encoding="utf-8") as out: json.dump(package_json, out, indent=2) diff --git a/scripts/stage_npm_packages.py b/scripts/stage_npm_packages.py index 5bbee755e9..9a8343e462 100755 --- a/scripts/stage_npm_packages.py +++ b/scripts/stage_npm_packages.py @@ -27,6 +27,7 @@ _SPEC.loader.exec_module(_BUILD_MODULE) PACKAGE_NATIVE_COMPONENTS = getattr(_BUILD_MODULE, "PACKAGE_NATIVE_COMPONENTS", {}) PACKAGE_EXPANSIONS = getattr(_BUILD_MODULE, "PACKAGE_EXPANSIONS", {}) CODEX_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_PLATFORM_PACKAGES", {}) +CODEX_SDK_PLATFORM_PACKAGES = getattr(_BUILD_MODULE, "CODEX_SDK_PLATFORM_PACKAGES", {}) def parse_args() -> argparse.Namespace: @@ -134,6 +135,9 @@ def tarball_name_for_package(package: str, version: str) -> str: if package in CODEX_PLATFORM_PACKAGES: platform = package.removeprefix("codex-") return f"codex-npm-{platform}-{version}.tgz" + if package in CODEX_SDK_PLATFORM_PACKAGES: + platform = package.removeprefix("codex-sdk-") + return f"codex-sdk-npm-{platform}-{version}.tgz" return f"{package}-npm-{version}.tgz" diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index ae87b63fab..70b8586689 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -11,6 +11,7 @@ npm install @openai/codex-sdk ``` Requires Node.js 18+. +The package installs a platform-specific native Codex binary via optional dependencies. ## Quickstart diff --git a/sdk/typescript/src/exec.ts b/sdk/typescript/src/exec.ts index 6f8048e5e4..a01065c345 100644 --- a/sdk/typescript/src/exec.ts +++ b/sdk/typescript/src/exec.ts @@ -1,4 +1,6 @@ import { spawn } from "node:child_process"; +import { existsSync } from "node:fs"; +import { createRequire } from "node:module"; import path from "node:path"; import readline from "node:readline"; import { fileURLToPath } from "node:url"; @@ -41,6 +43,7 @@ export type CodexExecArgs = { const INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE"; const TYPESCRIPT_SDK_ORIGINATOR = "codex_sdk_ts"; +const nodeRequire = createRequire(import.meta.url); export class CodexExec { private executablePath: string; @@ -300,6 +303,14 @@ function isPlainObject(value: unknown): value is CodexConfigObject { const scriptFileName = fileURLToPath(import.meta.url); const scriptDirName = path.dirname(scriptFileName); +const PLATFORM_PACKAGE_BY_TARGET = { + "x86_64-unknown-linux-musl": "@openai/codex-sdk-linux-x64", + "aarch64-unknown-linux-musl": "@openai/codex-sdk-linux-arm64", + "x86_64-apple-darwin": "@openai/codex-sdk-darwin-x64", + "aarch64-apple-darwin": "@openai/codex-sdk-darwin-arm64", + "x86_64-pc-windows-msvc": "@openai/codex-sdk-win32-x64", + "aarch64-pc-windows-msvc": "@openai/codex-sdk-win32-arm64", +}; function findCodexPath() { const { platform, arch } = process; @@ -351,10 +362,30 @@ function findCodexPath() { 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); + const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple as keyof typeof PLATFORM_PACKAGE_BY_TARGET]; + if (!platformPackage) { + throw new Error(`Unsupported target triple: ${targetTriple}`); + } - return binaryPath; + const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex"; + const localVendorRoot = path.join(scriptDirName, "..", "vendor"); + const localBinaryPath = path.join(localVendorRoot, targetTriple, "codex", codexBinaryName); + + let vendorRoot: string | null = null; + try { + const packageJsonPath = nodeRequire.resolve(`${platformPackage}/package.json`); + vendorRoot = path.join(path.dirname(packageJsonPath), "vendor"); + } catch { + if (existsSync(localBinaryPath)) { + vendorRoot = localVendorRoot; + } + } + + if (!vendorRoot) { + throw new Error( + `Missing optional dependency ${platformPackage}. Reinstall @openai/codex-sdk: npm install @openai/codex-sdk`, + ); + } + + return path.join(vendorRoot, targetTriple, "codex", codexBinaryName); }