For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
メインナビゲーション

ローカルシェル

エージェントがローカルシェルでコマンドを実行できるようにします。

ローカルシェルツールは旧式のツールです。新しいユースケースでは、 代わりに GPT-5.1 と shell ツールを使用してください。詳しく 見る

ローカルシェルは、開発者やユーザーが用意したマシン上で、エージェントがシェルコマンドをローカルに実行できるツールです。Codex CLIcodex-mini-latest での使用を想定して設計されています。コマンドは開発者自身のランタイム内で実行されるため、 実際にどのコマンドを実行するかは、開発者が完全に制御できます。API は指示を返すだけで、OpenAI のインフラストラクチャ上でコマンドを実行することはありません。

ローカルシェルは、Responses API を通じて codex-mini-latest で利用できます。他のモデルや Chat Completions API では利用できません。

任意のシェルコマンドの実行には危険が伴う場合があります。システムのシェルにコマンドを渡す前に、必ずサンドボックス内で実行するようにするか、厳格な許可リストまたは拒否リストを追加してください。


参考実装については、Codex CLI を参照してください。

仕組み

ローカルシェルツールを使用すると、エージェントはターミナルにアクセスしながら、継続的なループで動作できます。

モデルが送信したシェルコマンドを開発者のコードがローカルマシンで実行し、その出力をモデルに返します。このループにより、モデルはユーザーによる追加の操作なしで、ビルド、テスト、実行のサイクルを完了できます。

開発者のコードには、local_shell_call 出力項目を受け取り、その中に含まれるコマンドを実行するループを実装する必要があります。想定外のコマンドが実行されないよう、サンドボックス内での実行を強く推奨します。

ローカルシェルツールの組み込み

ローカルシェルツールをアプリケーションに組み込むための大まかな手順は、次のとおりです。

  1. モデルにリクエストを送信: 利用可能なツールに local_shell ツールを含めます。

  2. モデルからレスポンスを受信: レスポンスに local_shell_call 項目が含まれているか確認します。 このツール呼び出しには、実行するコマンドとともに、exec などのアクションが含まれます。

  3. 要求されたアクションを実行: 開発者が管理するローカル環境でコマンドを実行します。

  4. アクションの出力を返送: アクションを実行したら、コマンドの出力をモデルに返します。

  5. 繰り返し: 更新された状態を local_shell_call_output として含めた新しいリクエストを送信します。モデルがアクションを要求しなくなるか、開発者が停止すると決めるまで、このループを繰り返します。

ワークフローの例

以下は、リクエストとレスポンスのループを示す最小限の例です。 言語を選択すると、その言語の SDK を使った同等のワークフローを確認できます。簡潔にするため、 本番環境に必要なサンドボックスとセキュリティチェックは省略しています。本番環境では、追加の安全対策を講じずに 信頼できないコマンドを実行しないでください

import { spawn } from "node:child_process";
import process from "node:process";
import OpenAI from "openai";

const client = new OpenAI();
const MAX_TIMEOUT_MS = 10_000;

function runCommand(command, options) {
  return new Promise((resolve) => {
    let stdout = "";
    let stderr = "";
    let settled = false;
    let groupPoll;
    const child = spawn(command[0], command.slice(1), {
      ...options,
      detached: process.platform !== "win32",
      stdio: ["ignore", "pipe", "pipe"],
    });
    const finish = (suffix = "") => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      clearTimeout(groupPoll);
      resolve(stdout + stderr + suffix);
    };
    const processGroupIsRunning = () => {
      if (process.platform === "win32" || !child.pid) return false;
      try {
        process.kill(-child.pid, 0);
        return true;
      } catch {
        return false;
      }
    };
    const finishAfterProcessGroup = (suffix) => {
      if (settled) return;
      if (processGroupIsRunning()) {
        groupPoll = setTimeout(() => finishAfterProcessGroup(suffix), 10);
      } else {
        finish(suffix);
      }
    };
    const killProcessTree = () => {
      try {
        if (process.platform !== "win32" && child.pid) {
          process.kill(-child.pid, "SIGKILL");
        } else {
          child.kill("SIGKILL");
        }
      } catch {
        child.kill("SIGKILL");
      }
      child.stdout?.destroy();
      child.stderr?.destroy();
    };
    const timer = setTimeout(() => {
      killProcessTree();
      finish("Command timed out.\n");
    }, options.timeout);

    child.stdout?.on("data", (chunk) => {
      stdout += chunk;
    });
    child.stderr?.on("data", (chunk) => {
      stderr += chunk;
    });
    child.on("error", (error) => {
      finish(`Command failed: ${error.message}.\n`);
    });
    child.on("close", (code, signal) => {
      if (signal) {
        finishAfterProcessGroup(`Command failed with signal ${signal}.\n`);
      } else if (code !== 0) {
        finishAfterProcessGroup(`Command failed with exit code ${code}.\n`);
      } else {
        finishAfterProcessGroup("");
      }
    });
  });
}

let response = await client.responses.create({
  model: "codex-mini-latest",
  tools: [{ type: "local_shell" }],
  parallel_tool_calls: false,
  input: "List files in the current directory.",
});

while (true) {
  const shellCall = response.output.find(
    (item) => item.type === "local_shell_call"
  );
  if (!shellCall) break;

  const { command, env, timeout_ms, user, working_directory } =
    shellCall.action;
  let output;
  if (user) {
    output = `Unsupported execution user: ${user}.\n`;
  } else if (command.length === 0) {
    output = "Command is empty.\n";
  } else {
    const timeout =
      timeout_ms && timeout_ms > 0
        ? Math.min(timeout_ms, MAX_TIMEOUT_MS)
        : MAX_TIMEOUT_MS;
    try {
      output = await runCommand(command, {
        cwd: working_directory ?? process.cwd(),
        env: { PATH: process.env.PATH ?? "", ...env },
        timeout,
      });
    } catch (error) {
      output = `Command failed: ${error instanceof Error ? error.message : String(error)}.\n`;
    }
  }

  response = await client.responses.create({
    model: "codex-mini-latest",
    tools: [{ type: "local_shell" }],
    parallel_tool_calls: false,
    previous_response_id: response.id,
    input: [
      {
        type: "local_shell_call_output",
        id: shellCall.call_id,
        output,
      },
    ],
  });
}

console.log(response.output_text);

ベストプラクティス

  • 実行環境をサンドボックスまたはコンテナで隔離してください 。 Docker または隔離されたユーザーアカウントの使用を検討してください。
  • 時間、メモリ、ネットワークなどのリソースに制限を設けてください 。 モデルが指定する timeout_ms は目安にすぎないため、独自の制限を適用してください。
  • リスクの高いコマンド(rmcurl、ネットワークユーティリティなど)は、 フィルタリングするか、厳密に確認してください
  • 監査やデバッグのために、すべてのコマンドとその出力をログに記録してください

エラー処理

開発者側でコマンドの実行に失敗した場合(終了コードが 0 以外、タイムアウトなど)でも、local_shell_call_output を送信できます。その際は、output フィールドにエラーメッセージを含めてください。

モデルは、復旧を試みるか、別のコマンドの実行を試みるかを選択できます。形式が正しくないデータ(id がないなど)を送信すると、API は標準の 400 検証エラーを返します。