2つの異なるアプローチでCodexバイナリを呼び出す方法の詳細比較
対象: Codex開発者、内部実装の理解を深めたい方
バージョン:
- Codex SDK: @openai/codex-sdk v0.46.0
- codex-viewer: カスタム実装
はじめに
Codexを使ったAI駆動開発を実践する中で、「どうやってCodexバイナリを呼び出すべきか?」という疑問に直面したことはありませんか?
実は、Codexバイナリを呼び出す方法には複数のアプローチがあり、それぞれに明確な設計思想と使い分けがあります。
このドキュメントが生まれた背景
私がcodex-viewerを開発する過程で、公式のCodex TypeScript SDKの実装を調査したところ、興味深い発見がありました:
- SDKは独自にバイナリを内包している(約198MB)
- しかし設定ファイルは共有している(
~/.codex/config.toml) - 入力方式がstdin経由
- 出力はstdout から直接ストリーミング
一方、codex-viewerでは:
- システムのcodexコマンドを使用
- 入力はコマンドライン引数として渡す
- 出力はファイルシステム経由で取得
- タスクキューで複数リクエストを管理
補足: この設計思想はCoding Agent Viewer SDKにも引き継がれており、Codex以外のAIエージェント(Claude Code、Cursor、Gemini、OpenCode)でも同じアプローチが採用されています。
この違いはそれぞれのユースケースに最適化された設計判断の結果となります。
このドキュメントの目的
このドキュメントでは、以下を明らかにします:
-
なぜアプローチが異なっているのか
- Codex TypeScript SDKがバイナリを内包する理由とメリット
- codex-viewerがシステムコマンドを使う理由
-
実装の詳細な違い
- バイナリの場所、コマンド引数、入出力方式
- セッション管理、エラーハンドリング、データ取得方法
-
どちらを選ぶべきか
- それぞれの適用シーン
- 具体的なユースケースと判断基準
誰のためのドキュメントか
- 🔧 Codexの内部実装を理解したい開発者
- 🏗️ Codexを使ったアプリケーションを構築したい方
- 🤔 「SDKを使うべきか、直接バイナリを呼ぶべきか」迷っている方
- 📚 Codexのエコシステムを深く知りたい方
このドキュメントを読むことで、あなたのプロジェクトに最適なアプローチを選択できるようになります。
📖 codex-viewerとは
codex-viewerは、Codexセッションをブラウザで可視化・管理するフルスタックWebアプリケーションです。
~/.codex/sessions/ と ~/.codex/history.jsonl をリアルタイムで監視し、以下の機能を提供します:
- 📊 プロジェクト一覧: 複数のワークスペースを一元管理
- 🔍 セッション詳細: シンタックスハイライト付きログビューア
- ⚡ リアルタイム同期: SSE(Server-Sent Events)によるライブ更新
- 📱 どこからでもアクセス: スマホ、タブレット、別のPCからも確認可能
詳細はこちら:
📦 Coding Agent Viewer SDK
codex-viewerと同じアプローチを複数のAIエージェントに拡張したのが、Coding Agent Viewer SDKです。
対応エージェント:
- 🤖 Codex
- 🧠 Claude Code
- 💬 Cursor
- 💎 Gemini
- 🔓 OpenCode
特徴:
- 統一API: 複数のAIエージェントを同じインターフェースで操作
- 4つの利用レベル: ライブラリ直接利用、REST API、完成Webアプリ、カスタムチャットUI
- システムコマンド方式: codex-viewerと同じく、インストール済みのエージェントコマンドを使用
-
ファイルベース同期:
~/.{agent}/sessions/を監視してログを収集
詳細はこちら:
- 🔗 GitHub リポジトリ
- 📝 Qiita: たった3行でAIエージェントを管理できる!Coding Agent Viewer SDKで開発が10倍速になった話
- 📦 npm: @nogataka/coding-agent-viewer-sdk
Note: このドキュメントでは主にCodex専用の実装を比較していますが、Coding Agent Viewer SDKは同じアーキテクチャを複数のAIエージェントに適用した汎用版です。
📚 目次
概要
このセクションでは、Codex TypeScript SDKとcodex-viewerの基本的な目的と特徴を紹介します。
Codex TypeScript SDK
目的: 汎用的なライブラリとして、任意のNode.jsアプリケーションからCodexを利用可能にする
特徴:
- npm パッケージとして配布
- プラットフォーム別のバイナリを内包
- 同期的なAPI(Promise/AsyncGenerator)
- 1ターン完結型の実行
codex-viewer
目的: Webベースのビューアアプリとして、複数のCodexセッションを管理・可視化する
リンク: GitHub | README (日本語)
特徴:
- Next.jsアプリケーション
- システムにインストール済みの
codexコマンドを使用 - 非同期イベントバス経由の通信
- 長時間実行・複数ターン対応
- リアルタイムファイル監視(SSE)
- ブラウザから直接Codexセッションを操作可能
関連プロジェクト: Coding Agent Viewer SDK は、codex-viewerと同じアーキテクチャを複数のAIエージェント(Codex、Claude Code、Cursor、Gemini、OpenCode)に対応させた統合SDKです。詳細はQiita記事を参照してください。
アーキテクチャの違い
ここでは、両者のシステム構成とデータフローの違いを図解します。
Codex TypeScript SDK のアーキテクチャ
特徴:
- 同期的なフロー
- バイナリが内包されている
- 呼び出し元が直接結果を受け取る
codex-viewer のアーキテクチャ
特徴:
- 非同期的なイベント駆動
- システムコマンドを使用
- EventBus経由でフロントエンドに通知
- タスクキューで複数リクエストを管理
実装方法の比較
このセクションでは、7つの観点から実装の詳細な違いを解説します。
1. バイナリの場所
| 項目 | Codex TypeScript SDK | codex-viewer |
|---|---|---|
| バイナリの場所 | パッケージ内に内包vendor/{target}/codex/codex
|
システムのPATHcodex コマンド |
| プラットフォーム対応 | 6つのプラットフォーム用バイナリを同梱 | ユーザー環境に依存 |
| バイナリサイズ | 大きい(全プラットフォーム分:約197MB) | なし(外部依存) |
| バージョン管理 | パッケージバージョンに固定 | システムの codex バージョンに依存 |
なぜCodex TypeScript SDKは独自バイナリを内包するのか?
Q: システムの codex コマンドを使えばいいのでは?
Codex TypeScript SDKがバイナリを内包する理由は、信頼性とユーザー体験のためです:
| 観点 | 内包バイナリ(Codex TypeScript SDK) | システムコマンド依存 |
|---|---|---|
| インストール |
npm install だけで完結 ✅ |
codex を別途インストール必要 ❌ |
| バージョン整合性 | SDKとバイナリのバージョンが保証される ✅ | バージョン不一致の可能性 ❌ |
| 本番環境 |
package.json で管理可能 ✅ |
システム管理者が別途インストール必要 ❌ |
| CI/CD |
npm ci で自動インストール ✅ |
パイプラインに別途インストール手順追加 ❌ |
| クロスプラットフォーム | 自動でOS判定・適切なバイナリ使用 ✅ | 各OSで別々にインストール ❌ |
| 配布 | npmパッケージで完結 ✅ | エンドユーザーへの説明が複雑 ❌ |
具体例1: バージョン不一致の問題
# 問題のあるシナリオ(システムコマンド依存の場合)
$ codex --version
0.40.0 # 古いバージョンがインストールされている
$ npm install @openai/hypothetical-sdk@1.5.0
# このSDKは codex 0.46.0 以降を要求するが、
# システムには 0.40.0 しかない
# → 実行時エラー ❌
# Codex TypeScript SDKの場合(バイナリ内包)
$ npm install @openai/codex-sdk@1.5.0
# codex 0.46.0 のバイナリが自動的に含まれる
# → そのまま動作 ✅
具体例2: CI/CDパイプライン
# システムコマンド依存の場合
name: Test
jobs:
test:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
# ❌ codexを別途インストールする手順が必要
- run: brew install codex # macOS
# または
- run: curl -fsSL https://... | sh # Linux
- run: npm ci
- run: npm test
# Codex TypeScript SDKの場合(バイナリ内包)
name: Test
jobs:
test:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
# ✅ npm ci だけで完結
- run: npm ci
- run: npm test
具体例3: エンドユーザーへの配布
// システムコマンド依存のREADME
/**
* インストール手順:
* 1. Codex CLIをインストール:
* - macOS: brew install codex
* - Linux: curl -fsSL https://... | sh
* - Windows: Download from ...
* 2. このパッケージをインストール:
* npm install my-app
*
* ❌ 2ステップ、プラットフォーム依存
*/
// Codex TypeScript SDKの場合
/**
* インストール手順:
* npm install my-app
*
* ✅ 1ステップ、すべてのOSで同じ
*/
デメリットとトレードオフ
| デメリット | 影響 | 対策 |
|---|---|---|
| パッケージサイズが大きい | 197MB(6プラットフォーム分) | ダウンロード時間増加 |
| ディスク容量 | プロジェクトごとに197MB |
npm dedupe で共有可能 |
| 最新版への追従 | SDKのリリースを待つ必要 |
codexPathOverride で回避可能 |
結論
Codex TypeScript SDKがバイナリを内包するのは、「npm install だけで動く」という体験を提供するためです。これは特に:
- 🏢 企業での利用: 社内ツールとして配布する際、インストール手順が複雑だと導入の障壁になる
- 🚀 SaaS製品: エンドユーザーに「Codexをインストールしてください」とは言えない
- 🔄 CI/CD: パイプラインがシンプルになり、トラブルシュートしやすい
- 📦 再現性:
package-lock.jsonでバイナリバージョンも固定される
つまり、197MBのコスト(ディスクの容量)を払って(使用して)、開発者体験と信頼性を買っているわけです。
実際の内包バイナリの確認
インストール先:
# npm install @openai/codex-sdk すると以下に配置される
node_modules/@openai/codex-sdk/vendor/
├── aarch64-apple-darwin/codex/codex # macOS Apple Silicon (27MB)
├── x86_64-apple-darwin/codex/codex # macOS Intel (30MB)
├── aarch64-unknown-linux-musl/codex/codex # Linux ARM64 (33MB)
├── x86_64-unknown-linux-musl/codex/codex # Linux x86_64 (39MB)
├── aarch64-pc-windows-msvc/codex/codex.exe # Windows ARM64 (32MB)
└── x86_64-pc-windows-msvc/codex/codex.exe # Windows x86_64 (37MB)
# 合計: 約198MB
確認コマンド:
# 内包バイナリのサイズ確認
$ ls -lh node_modules/@openai/codex-sdk/vendor/aarch64-apple-darwin/codex/codex
-rwxr-xr-x 1 user staff 27M Oct 18 11:37 codex
# バイナリの種類確認
$ file node_modules/@openai/codex-sdk/vendor/aarch64-apple-darwin/codex/codex
codex: Mach-O 64-bit executable arm64
# vendor ディレクトリ全体のサイズ
$ du -sh node_modules/@openai/codex-sdk/vendor/
197M node_modules/@openai/codex-sdk/vendor/
注意: システムにインストールされている
codexコマンド(npm install -g @openai/codexでインストールするもの)は、実はNode.jsスクリプト(codex.js)であり、内部でRustバイナリを呼び出します。一方、TypeScript SDKはRustバイナリを直接呼び出す点が異なります。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:142-198
function findCodexPath() {
const { platform, arch } = process;
// プラットフォームとアーキテクチャから target triple を決定
let targetTriple = null;
switch (platform) {
case "darwin":
targetTriple = arch === "arm64"
? "aarch64-apple-darwin"
: "x86_64-apple-darwin";
break;
// ...
}
// パッケージ内のバイナリパスを構築
const vendorRoot = path.join(scriptDirName, "..", "vendor");
const binaryPath = path.join(vendorRoot, targetTriple, "codex", "codex");
return binaryPath;
}
実行されるバイナリ:
node_modules/@openai/codex-sdk/vendor/aarch64-apple-darwin/codex/codex
設定ファイル:
~/.codex/config.toml # ⭐ 通常のcodexと同じ場所を使用
重要: Codex TypeScript SDKは独自の設定ファイルを持ちません。内包されたバイナリも、システムにインストールされたcodexと同じ
~/.codex/config.tomlを読み込みます。Codex TypeScript SDKとCLIで設定を共有できます。
codex-viewer
// src/server/service/codex/CodexTaskController.ts:228
const child = spawn("codex", args, {
cwd: options.cwd,
env: childEnv,
stdio: ["ignore", "pipe", "pipe"],
});
実行されるバイナリ:
/usr/local/bin/codex # システムのPATHにあるcodex
設定ファイル:
~/.codex/config.toml # Codex TypeScript SDK、CLI、codex-viewerすべてが同じ設定を共有
2. コマンド引数の違い
Codexバイナリに渡すコマンドライン引数の比較です。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:36-60
const commandArgs: string[] = ["exec", "--experimental-json"];
if (args.model) {
commandArgs.push("--model", args.model);
}
if (args.sandboxMode) {
commandArgs.push("--sandbox", args.sandboxMode);
}
if (args.workingDirectory) {
commandArgs.push("--cd", args.workingDirectory);
}
if (args.threadId) {
commandArgs.push("resume", args.threadId);
}
実行例:
codex exec --experimental-json --model gpt-5 --sandbox read-only
特徴:
-
--experimental-jsonを使用(より詳細なJSON出力) - 最小限の引数
- ユーザー指定のオプションのみ
codex-viewer
// src/server/service/codex/CodexTaskController.ts:188-219
const jsonFlag = (() => {
const useExperimental = process.env.CODEX_USE_EXPERIMENTAL_JSON;
if (useExperimental && ["1", "true", "yes"].includes(useExperimental.toLowerCase())) {
return "--experimental-json";
}
return "--json";
})();
const args = [
"exec",
jsonFlag, // --json または --experimental-json
"--sandbox",
"workspace-write",
"-c",
'sandbox_workspace_write={network_access=true,writable_roots=["~/.cache","~/.uv"]}',
"-c",
"mcp_servers.serena.startup_timeout_sec=30",
"--cd",
options.cwd,
];
if (options.sessionUuid) {
args.push("resume", options.sessionUuid, options.message);
} else {
args.push(options.message);
}
実行例:
codex exec \
--json \
--sandbox workspace-write \
-c 'sandbox_workspace_write={network_access=true,writable_roots=["~/.cache","~/.uv"]}' \
-c 'mcp_servers.serena.startup_timeout_sec=30' \
--cd /path/to/project \
"タスクを実行"
特徴:
- デフォルトは
--json(環境変数で--experimental-jsonに切り替え可能) -
ビューア専用の設定を含む
- ネットワークアクセス有効化
-
~/.cache,~/.uvへの書き込み許可 - MCPサーバーのタイムアウト設定
- メッセージを引数として直接渡す
3. 入力の送信方法
ユーザーのメッセージをCodexバイナリにどう渡すかの違いです。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:82-87
if (!child.stdin) {
child.kill();
throw new Error("Child process has no stdin");
}
// ⭐ stdin にメッセージを書き込む
child.stdin.write(args.input);
child.stdin.end();
特徴:
- stdin経由でメッセージを送信
- 1メッセージのみ
- 送信後すぐに stdin をクローズ
フロー:
Codex TypeScript SDK → stdin → Codex バイナリ
codex-viewer
// src/server/service/codex/CodexTaskController.ts:216-218
if (options.sessionUuid) {
args.push("resume", options.sessionUuid, options.message);
} else {
args.push(options.message);
}
// src/server/service/codex/CodexTaskController.ts:231
stdio: ["ignore", "pipe", "pipe"],
特徴:
- コマンドライン引数としてメッセージを渡す
- stdin は無視(
"ignore") - Codexバイナリが引数からメッセージを読み取る
フロー:
codex-viewer → コマンド引数 → Codex バイナリ
4. 出力の処理方法
Codexバイナリからの出力をどう受け取り、処理するかの違いです。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:101-110
const rl = readline.createInterface({
input: child.stdout,
crlfDelay: Infinity,
});
try {
for await (const line of rl) {
// ⭐ 各行を直接 yield
yield line as string;
}
// プロセス終了を待つ
await exitCode;
} finally {
rl.close();
child.removeAllListeners();
child.kill();
}
特徴:
- AsyncGenerator で1行ずつストリーミング
- 呼び出し元が直接イベントを受け取る
- プロセス終了まで待機
使用例:
const { events } = await thread.runStreamed("タスク");
for await (const event of events) {
const parsed = JSON.parse(event);
console.log(parsed);
}
codex-viewer
// src/server/service/codex/CodexTaskController.ts:236
const rl = readline.createInterface({ input: child.stdout });
// セッションIDを抽出
rl.on("line", (line) => {
const trimmed = line.trim();
if (trimmed.length === 0) return;
try {
const parsed = JSON.parse(trimmed) as {
type?: string;
payload?: unknown;
};
// session_meta イベントからセッションIDを取得
if (parsed.type === "session_meta" && parsed.payload) {
const sessionIdValue = (parsed.payload as { id?: unknown }).id;
if (typeof sessionIdValue === "string") {
task.sessionUuid = sessionIdValue;
this.emitTaskChange(); // ⭐ EventBus に通知
ensureSessionPath();
}
}
// turn_aborted イベントを検知
if (parsed.type === "event_msg" && parsed.payload) {
const payload = parsed.payload as { type?: string };
if (payload.type === "turn_aborted") {
updateStatus("waiting");
}
}
} catch (error) {
console.warn("Failed to parse Codex exec output", { error, line });
}
});
特徴:
- イベントリスナーでstdoutを監視
- 特定のイベント(
session_meta,turn_aborted)のみ処理 - EventBus経由でフロントエンドに通知
- 実際の会話内容はファイルから読み取る
フロー:
Codex stdout → readline → JSONパース → EventBus → SSE → フロントエンド
↓
ファイル監視 → 会話データ読み込み
5. セッション管理
複数のターン(会話)をまたいだセッション管理の方法の違いです。
Codex TypeScript SDK
セッション管理: なし(1ターン完結型)
// 新規スレッド
const thread = codex.startThread();
await thread.run("タスク1");
await thread.run("タスク2"); // ❌ 別プロセス(前のコンテキストは失われる)
// スレッド再開
const resumedThread = codex.resumeThread(threadId);
await resumedThread.run("続き"); // ✅ 前のコンテキストを引き継ぐ
特徴:
- 各
run()呼び出しで新しいプロセスを起動 - 同じスレッド内で複数回
run()を呼んでも、プロセスは再起動される - セッション継続は
resumeThread()でのみ可能
codex-viewer
セッション管理: あり(継続的なタスク管理)
// src/server/service/codex/CodexTaskController.ts:85-133
public async startOrContinueTask(
currentSession: StartSessionOptions,
message: string,
): Promise<SerializableAliveTask> {
const existing = this.findTask(
currentSession.sessionUuid,
currentSession.sessionPathId,
);
// ⭐ 既存のタスクがあればキューに追加
if (existing?.status === "running") {
return await new Promise<SerializableAliveTask>((resolve, reject) => {
existing.queue.push({ message, requestId, resolve, reject });
this.emitTaskChange();
});
}
// プロセスを起動
return await this.launchProcess(task, { ... });
}
タスクキューの仕組み:
// src/server/service/codex/CodexTaskController.ts:347-366
child.on("exit", (code) => {
// ...
// ⭐ キューに次のメッセージがあれば続行
if (task.queue.length > 0) {
const next = task.queue.shift();
if (!next) return;
this.launchProcess(task, {
message: next.message,
requestId: next.requestId,
cwd: task.cwd,
projectId: task.projectId,
sessionUuid: task.sessionUuid,
sessionPathId: task.sessionPathId,
})
.then(next.resolve)
.catch(next.reject);
return;
}
// ...
});
特徴:
- タスクキューで複数のメッセージを管理
- プロセスが実行中の場合、新しいメッセージはキューに入る
- プロセス終了後、キューに次のメッセージがあれば自動的に起動
- セッションIDで複数のワークスペースを管理
使用例:
// ユーザーが連続してメッセージを送信
await taskController.startOrContinueTask(session, "タスク1");
await taskController.startOrContinueTask(session, "タスク2"); // キューに追加
await taskController.startOrContinueTask(session, "タスク3"); // キューに追加
// 実行順序: タスク1 → タスク2 → タスク3(順次実行)
6. エラーハンドリング
実行時のエラーをどう検知し、処理するかの違いです。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:112-126
const exitCode = new Promise((resolve, reject) => {
child.once("exit", (code) => {
if (code === 0) {
resolve(code);
} else {
const stderrBuffer = Buffer.concat(stderrChunks);
reject(
new Error(`Codex Exec exited with code ${code}: ${stderrBuffer.toString("utf8")}`),
);
}
});
});
if (spawnError) throw spawnError;
await exitCode;
特徴:
- 終了コードが0以外なら例外をスロー
- stderrの内容をエラーメッセージに含める
- 呼び出し元で
try-catchで捕捉
codex-viewer
// src/server/service/codex/CodexTaskController.ts:335-386
child.on("exit", (code) => {
rl.close();
task.process = null;
// ステータスを更新
if (task.status === "running") {
updateStatus(code === 0 ? "completed" : "failed");
}
// キュー内のメッセージを拒否
if (task.status === "failed") {
this.rejectQueuedMessages(task, new Error("Codex task failed"));
}
// 次のキューを処理
if (task.queue.length > 0) {
const next = task.queue.shift();
// ...
}
// EventBus に通知
this.emitTaskChange();
this.pruneTaskIfInactive(task);
});
child.on("error", (error) => {
rl.close();
task.process = null;
updateStatus("failed");
this.emitTaskChange();
this.pruneTaskIfInactive(task);
rejectOnce(error);
});
特徴:
- タスクのステータスを更新(
"running"→"completed"or"failed") - EventBus経由でフロントエンドに通知
- キュー内のメッセージを適切に処理
- 例外をスローせずPromiseをreject
7. データの読み取り方法
実行結果やログをどこから取得するかの根本的な違いです。
Codex TypeScript SDK
stdout から直接読み取る
// イベントをリアルタイムで受信
for await (const line of events) {
const event = JSON.parse(line);
if (event.type === "item.completed") {
console.log(event.item);
}
}
特徴:
- stdout のストリームから直接データを取得
- リアルタイム性が高い
- ネットワーク越しの使用には向かない(Codex TypeScript SDKの制限)
codex-viewer
ファイルシステムから読み取る
// src/server/service/codex/parseCodexSession.ts:98-557
export const parseCodexSession = (
content: string, // ⭐ ファイルの内容
): {
entries: CodexConversationEntry[];
turns: CodexSessionTurn[];
metaEvents: CodexMetaEvent[];
sessionMeta: CodexSessionMeta;
} => {
const lines = content
.split("\n")
.map((line) => line.trim())
.filter((line) => line.length > 0);
for (const line of lines) {
const parsed: CodexLogLine = JSON.parse(line);
// session_meta
if (parsed.type === "session_meta") {
// ...
}
// response_item
if (parsed.type === "response_item") {
// ...
}
// event_msg
if (parsed.type === "event_msg") {
// ...
}
}
return { entries, turns, metaEvents, sessionMeta };
};
ファイルの場所:
~/.codex/sessions/2025/10/19/rollout-550e8400-e29b-41d4-a716-446655440000.jsonl
フロー:
1. Codex バイナリがファイルに書き込み
↓
2. ファイルシステムウォッチャーが変更を検知
↓
3. ファイルを読み込んで parseCodexSession() でパース
↓
4. EventBus 経由でフロントエンドに通知
↓
5. SSE で Web クライアントに送信
特徴:
- 永続化されたデータを読み取る
- ファイル監視でリアルタイム更新
- 複数クライアントで同じセッションを参照可能
- ネットワーク越しで使用可能(Webアプリ)
詳細な差分
ここでは、コードレベルでの具体的な実装の違いをまとめます。
コード比較表
| 項目 | Codex TypeScript SDK | codex-viewer |
|---|---|---|
| ファイル | sdk/typescript/src/exec.ts |
src/server/service/codex/CodexTaskController.ts |
| クラス | CodexExec |
CodexTaskController |
| spawn 実行 | spawn(this.executablePath, commandArgs, { env }) |
spawn("codex", args, { cwd, env, stdio }) |
| バイナリパス | 動的に解決(findCodexPath()) |
固定("codex") |
| stdin | メッセージを書き込む | 無視("ignore") |
| stdout | AsyncGenerator で yield | イベントリスナーで部分的に処理 |
| stderr | バッファに蓄積 | console.error に出力 |
| 終了処理 | Promise で await | イベントリスナーで処理 |
| データ取得 | stdout から直接 | ファイルシステムから |
| セッション管理 | なし(1ターン完結) | タスクキューで管理 |
設定ファイルの共有
両者とも同じ ~/.codex ディレクトリを使用する理由と、その仕組みを解説します。
すべてが ~/.codex を使用
重要な仕様: Codex TypeScript SDK、codex CLI、codex-viewer のすべてが同じ設定ディレクトリを使用します。
~/.codex/
├── config.toml # 設定ファイル(全ツールで共有)
├── auth.json # 認証情報(全ツールで共有)
├── history.jsonl # グローバル履歴(全ツールで共有)
├── sessions/ # セッションファイル(全ツールで共有)
│ └── 2025/10/19/...
└── log/ # ログファイル
設定ファイルのパス決定ロジック:
// codex-rs/core/src/config.rs:1301-1318
pub fn find_codex_home() -> std::io::Result<PathBuf> {
// 1. 環境変数 CODEX_HOME が設定されていればそれを使用
if let Ok(val) = std::env::var("CODEX_HOME")
&& !val.is_empty()
{
return PathBuf::from(val).canonicalize();
}
// 2. デフォルトは ~/.codex
let mut p = home_dir()?;
p.push(".codex");
Ok(p)
}
設定ファイルの読み込み順序:
// codex-rs/core/src/config_loader/mod.rs:87-88
let user_config_path = codex_home.join(CONFIG_TOML_FILE); // ~/.codex/config.toml
let user_config = read_config_from_path(&user_config_path, true).await?;
つまり、Codex TypeScript SDKの内包バイナリも通常のCLIと全く同じ設定を読み込みます。
環境変数の違い
Codexバイナリに渡される環境変数の設定内容の違いです。
Codex TypeScript SDK
// sdk/typescript/src/exec.ts:62-73
const env = {
...process.env,
};
if (!env[INTERNAL_ORIGINATOR_ENV]) {
env[INTERNAL_ORIGINATOR_ENV] = TYPESCRIPT_SDK_ORIGINATOR; // "codex_sdk_ts"
}
if (args.baseUrl) {
env.OPENAI_BASE_URL = args.baseUrl;
}
if (args.apiKey) {
env.CODEX_API_KEY = args.apiKey;
}
設定される環境変数:
CODEX_INTERNAL_ORIGINATOR_OVERRIDE="codex_sdk_ts"-
OPENAI_BASE_URL=...(オプション) -
CODEX_API_KEY=...(オプション)
codex-viewer
// src/server/service/codex/CodexTaskController.ts:221-226
const childEnv = {
...process.env,
} as NodeJS.ProcessEnv & { RUST_LOG?: string };
if (!childEnv.RUST_LOG) {
childEnv.RUST_LOG = "warn,codex_core::mcp_connection_manager=off";
}
設定される環境変数:
-
RUST_LOG="warn,codex_core::mcp_connection_manager=off"(デフォルト) - 親プロセスの環境変数をすべて継承
セッションファイルの扱い
~/.codex/sessions/ 配下のJSONLファイルとの関わり方の違いです。
Codex TypeScript SDK
セッションファイルに関与しない
- Codexバイナリがファイルを作成・更新
- Codex TypeScript SDKはファイルを読まない
- スレッドIDのみを管理
codex-viewer
セッションファイルを積極的に活用
// src/server/service/codex/sessionFiles.ts:179-196
export const findSessionRecordByUuid = async (
sessionUuid: string,
): Promise<CodexSessionRecord | null> => {
// キャッシュから検索
for (const record of sessionCache.values()) {
if (record.sessionUuid === sessionUuid) {
return record;
}
}
// ファイルシステムから検索
const records = await listCodexSessionRecords();
for (const record of records) {
if (record.sessionUuid === sessionUuid) {
return record;
}
}
return null;
};
機能:
- セッションファイルの一覧取得
- セッションヘッダーの読み取り
- セッションIDからファイルパスへのマッピング
- ファイルの変更時刻の追跡
- キャッシュ機構
ファイル構造:
~/.codex/sessions/
├── 2025/
│ └── 10/
│ └── 19/
│ ├── rollout-550e8400-....jsonl
│ ├── rollout-660e8400-....jsonl
│ └── ...
└── history.jsonl
ユースケースの違い
それぞれのアプローチがどんな場面で力を発揮するかを解説します。
Codex TypeScript SDK の適用シーン
✅ 適している:
-
Node.jsアプリケーションからの呼び出し
- Express/Fastify API
- CLI ツール
- バッチ処理
-
1ターン完結型のタスク
- 単発の質問・回答
- コード生成
- 構造化データ抽出
-
プログラム的な制御
- 複雑なロジックとの組み合わせ
- エラーハンドリング
- 条件分岐
-
バイナリバージョンの固定が必要な場合
- 本番環境での安定性
- CI/CD パイプライン
使用例:
import { Codex } from "@openai/codex-sdk";
const codex = new Codex();
const thread = codex.startThread();
// APIエンドポイントで使用
app.post("/api/chat", async (req, res) => {
const turn = await thread.run(req.body.message);
res.json({ response: turn.finalResponse });
});
❌ 適していない:
-
長時間実行のタスク
- 複数ターンに渡る会話
- バックグラウンドタスク
-
複数クライアントでの共有
- Webアプリケーション
- リアルタイムコラボレーション
-
履歴の可視化・管理
- セッションブラウザ
- 会話履歴の検索
codex-viewer の適用シーン
✅ 適している:
-
Webベースのインターフェース
- ブラウザからの操作
- リアルタイム更新
- SSEストリーミング
-
複数セッションの管理
- プロジェクトごとのセッション
- 履歴の閲覧・検索
- セッション間の切り替え
-
長時間実行のタスク
- 継続的な会話
- バックグラウンド実行
- タスクキュー
-
ファイルベースの永続化
- セッションの永続化
- 複数クライアントでの参照
- オフライン閲覧
使用例:
// フロントエンド
const response = await fetch("/api/codex/task", {
method: "POST",
body: JSON.stringify({
projectId: "abc123",
message: "テストを書いて",
}),
});
// バックエンド(CodexTaskController)
const task = await taskController.startOrContinueTask(session, message);
// SSE経由でリアルタイム更新を受信
const eventSource = new EventSource("/api/events");
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
// UIを更新
};
❌ 適していない:
-
単純なCLIツール
- 余計な依存関係が多い
- オーバーキル
-
npm パッケージとしての配布
- Next.jsアプリ全体が必要
- 再利用性が低い
-
オフライン環境
- システムに
codexコマンドが必要 - インターネット接続が前提
- システムに
まとめ
ここまでの比較を一覧表にまとめ、選択のための指針を示します。
主要な違い
| 側面 | Codex TypeScript SDK | codex-viewer |
|---|---|---|
| 目的 | 汎用ライブラリ | Webビューアアプリ |
| バイナリ | 内包(197MB) | システムコマンド |
| 設定ファイル |
~/.codex/config.toml(共有) |
~/.codex/config.toml(共有) |
| 入力方式 | stdin | コマンド引数 |
| 出力方式 | stdout ストリーム | ファイル + EventBus |
| セッション管理 | なし(1ターン完結) | タスクキュー |
| データ取得 | リアルタイム(stdout) | ファイルベース |
| ユースケース | Node.jsアプリ | Webアプリ |
| 配布形態 | npm パッケージ | Next.jsアプリ |
重要: Codex TypeScript SDKもcodex-viewerも同じ
~/.codexディレクトリを使用します。設定、認証情報、セッション履歴はすべて共有されます。
選択の指針
あなたのプロジェクトにどちらが適しているかを判断するためのチェックリストです。
Codex TypeScript SDK を使うべき場合:
- ✅ Node.jsアプリケーションに組み込みたい
- ✅ プログラム的な制御が必要
- ✅ バイナリバージョンを固定したい
- ✅ 1ターン完結型のタスク
codex-viewer を参考にすべき場合:
- ✅ Webベースのインターフェースを作りたい
- ✅ 複数セッションを管理したい
- ✅ 長時間実行のタスクを扱いたい
- ✅ ファイルベースの永続化が必要
アーキテクチャの選択
視覚的に選択肢を理解するためのフローチャートです。
参考資料
さらに詳しく学ぶための関連ドキュメントとリソースです。
Codex TypeScript SDK 関連
codex-viewer 関連
- codex-viewer GitHub リポジトリ
- codex-viewer 日本語 README
- Qiita: Codexプロジェクト管理を加速するCodex Viewerガイド
- Zenn: Codex ViewerでCodexセッションを俯瞰する
Coding Agent Viewer SDK 関連
- Coding Agent Viewer GitHub リポジトリ
- npm: @nogataka/coding-agent-viewer-sdk
- npm: @nogataka/coding-agent-viewer
- Qiita: たった3行でAIエージェントを管理できる!Coding Agent Viewer SDKで開発が10倍速になった話
その他
この比較ドキュメントは、両方のアプローチの特徴を理解し、適切な実装方法を選択するための詳細なガイドです。
おわりに
Codex TypeScript SDKとcodex-viewerは、どちらも「Codexバイナリをどう呼び出すか」という同じ課題に対する、異なる設計判断の結果です。
- Codex TypeScript SDKは、「npm install だけで動く」という開発者体験を最優先し、バイナリを内包することで信頼性と再現性を実現しました。
- codex-viewerは、「複数セッションのリアルタイム管理」を重視し、システムコマンドとファイルベースの同期で柔軟性と拡張性を確保しました。
あなたのユースケースに最適な方を選ぶことが重要です。
このドキュメントが、Codexを使った開発の理解を深め、プロジェクトに最適なアプローチを選択する一助となれば幸いです。