For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale

Shell local

Permettez aux agents d’exécuter des commandes dans un shell local.

L’outil de shell local est obsolète. Pour les nouveaux cas d’utilisation, utilisez plutôt l’outil shell avec GPT-5.1. En savoir plus.

Le shell local est un outil qui permet aux agents d’exécuter des commandes shell localement sur une machine que vous ou l’utilisateur mettez à disposition. Il est conçu pour fonctionner avec Codex CLI et codex-mini-latest. Les commandes sont exécutées dans votre propre environnement d’exécution : vous gardez le contrôle total sur les commandes réellement exécutées. L’API renvoie uniquement des instructions ; elle ne les exécute pas sur l’infrastructure d’OpenAI.

Le shell local est disponible via l’API Responses pour une utilisation avec codex-mini-latest. Il n’est pas disponible avec d’autres modèles ni via l’API Chat Completions.

L’exécution de commandes shell arbitraires peut être dangereuse. Isolez toujours l’exécution dans un bac à sable ou ajoutez des listes strictes de commandes autorisées ou interdites avant de transmettre une commande au shell du système.


Consultez Codex CLI pour une implémentation de référence.

Fonctionnement

L’outil de shell local permet aux agents de fonctionner en boucle continue avec un accès à un terminal.

Le modèle envoie des commandes shell que votre code exécute sur une machine locale avant d’en renvoyer la sortie au modèle. Cette boucle permet au modèle de mener à bien le cycle de compilation, de test et d’exécution sans intervention supplémentaire de l’utilisateur.

Votre code doit implémenter une boucle qui attend les éléments de sortie local_shell_call et exécute les commandes qu’ils contiennent. Nous vous recommandons vivement d’isoler l’exécution dans un bac à sable pour empêcher l’exécution de commandes inattendues.

Intégration de l’outil de shell local

Voici les principales étapes à suivre pour intégrer l’outil de shell local dans votre application :

  1. Envoyez une requête au modèle : Incluez l’outil local_shell parmi les outils disponibles.

  2. Recevez une réponse du modèle : Vérifiez si la réponse contient des éléments local_shell_call. Cet appel d’outil contient une action comme exec, accompagnée d’une commande à exécuter.

  3. Exécutez l’action demandée : Exécutez la commande dans l’environnement local que vous contrôlez.

  4. Renvoyez la sortie de l’action : Après avoir exécuté l’action, renvoyez la sortie de la commande au modèle.

  5. Répétez l’opération : Envoyez une nouvelle requête contenant l’état mis à jour sous la forme d’un élément local_shell_call_output, puis répétez cette boucle jusqu’à ce que le modèle cesse de demander des actions ou que vous décidiez de l’arrêter.

Exemple de workflow

Voici un exemple minimal illustrant la boucle de requête et de réponse. Choisissez un langage pour voir le workflow équivalent avec son SDK. Par souci de concision, les mesures d’isolation dans un bac à sable et les contrôles de sécurité nécessaires en production sont omis : n’exécutez pas de commandes non fiables en production sans protections supplémentaires.

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);

Bonnes pratiques

  • Isolez l’exécution dans un bac à sable ou un conteneur . Envisagez d’utiliser Docker ou un compte utilisateur confiné.
  • Imposez des limites de ressources (temps, mémoire, réseau). La valeur timeout_ms fournie par le modèle n’est qu’une indication : vous devez appliquer vos propres limites.
  • Filtrez ou examinez attentivement les commandes à haut risque (par exemple, rm, curl et les utilitaires réseau).
  • Consignez chaque commande et sa sortie pour faciliter les audits et le débogage.

Gestion des erreurs

Si la commande échoue de votre côté, par exemple avec un code de sortie non nul ou un dépassement du délai d’attente, vous pouvez tout de même envoyer un élément local_shell_call_output ; incluez le message d’erreur dans le champ output.

Le modèle peut choisir de corriger l’erreur ou d’essayer d’exécuter une autre commande. Si vous envoyez des données mal formées (par exemple, sans id), l’API renvoie une erreur de validation standard 400.