UNPKG

@langchain/openai

Version:
1 lines 10.3 kB
{"version":3,"file":"shell.cjs","names":["z","options: ShellOptions","action: ShellAction"],"sources":["../../src/tools/shell.ts"],"sourcesContent":["import { z } from \"zod/v4\";\nimport { OpenAI as OpenAIClient } from \"openai\";\nimport { tool, type DynamicStructuredTool } from \"@langchain/core/tools\";\n\n/**\n * Re-export action type from OpenAI SDK for convenience.\n * The action contains command details like commands array, timeout, and max output length.\n */\nexport type ShellAction =\n OpenAIClient.Responses.ResponseFunctionShellToolCall.Action;\n\n// Zod schema for shell action\nexport const ShellActionSchema = z.object({\n commands: z.array(z.string()).describe(\"Array of shell commands to execute\"),\n timeout_ms: z\n .number()\n .optional()\n .describe(\"Optional timeout in milliseconds for the commands\"),\n max_output_length: z\n .number()\n .optional()\n .describe(\n \"Optional maximum number of characters to return from each command\"\n ),\n});\n\n/**\n * Result of a single shell command execution.\n * Contains stdout, stderr, and the outcome (exit code or timeout).\n */\nexport type ShellCommandOutput =\n OpenAIClient.Responses.ResponseFunctionShellCallOutputContent;\n\n/**\n * Outcome type for shell command execution - either exit with code or timeout.\n */\nexport type ShellCallOutcome = ShellCommandOutput[\"outcome\"];\n\n/**\n * Result of executing shell commands.\n * Contains an array of outputs (one per command) and the max_output_length parameter.\n */\nexport interface ShellResult {\n /**\n * Array of command outputs. Each entry corresponds to a command from the action.\n * The order should match the order of commands in the action.\n */\n output: ShellCommandOutput[];\n /**\n * The max_output_length from the action, which must be passed back to the API.\n * If not provided in the action, can be omitted.\n */\n maxOutputLength?: number | null;\n}\n\n/**\n * Options for the Shell tool.\n */\nexport interface ShellOptions {\n /**\n * Execute function that handles shell command execution.\n * This function receives the action input containing the commands and limits,\n * and should return a ShellResult with stdout, stderr, and outcome for each command.\n *\n * @example\n * ```typescript\n * execute: async (action) => {\n * const outputs = await Promise.all(\n * action.commands.map(async (cmd) => {\n * try {\n * const { stdout, stderr } = await exec(cmd, {\n * timeout: action.timeout_ms ?? undefined,\n * });\n * return {\n * stdout,\n * stderr,\n * outcome: { type: \"exit\" as const, exit_code: 0 },\n * };\n * } catch (error) {\n * const timedOut = error.killed && error.signal === \"SIGTERM\";\n * return {\n * stdout: error.stdout ?? \"\",\n * stderr: error.stderr ?? String(error),\n * outcome: timedOut\n * ? { type: \"timeout\" as const }\n * : { type: \"exit\" as const, exit_code: error.code ?? 1 },\n * };\n * }\n * })\n * );\n * return {\n * output: outputs,\n * maxOutputLength: action.max_output_length,\n * };\n * }\n * ```\n */\n execute: (action: ShellAction) => ShellResult | Promise<ShellResult>;\n}\n\n/**\n * OpenAI Shell tool type for the Responses API.\n */\nexport type ShellTool = OpenAIClient.Responses.FunctionShellTool;\n\nconst TOOL_NAME = \"shell\";\n\n/**\n * Creates a Shell tool that allows models to run shell commands through your integration.\n *\n * The shell tool allows the model to interact with your local computer through a controlled\n * command-line interface. The model proposes shell commands; your integration executes them\n * and returns the outputs. This creates a simple plan-execute loop that lets models inspect\n * the system, run utilities, and gather data until they can finish the task.\n *\n * **Important**: The shell tool is available through the Responses API for use with `GPT-5.1`.\n * It is not available on other models, or via the Chat Completions API.\n *\n * **When to use**:\n * - **Automating filesystem or process diagnostics** – For example, \"find the largest PDF\n * under ~/Documents\" or \"show running gunicorn processes.\"\n * - **Extending the model's capabilities** – Using built-in UNIX utilities, python runtime\n * and other CLIs in your environment.\n * - **Running multi-step build and test flows** – Chaining commands like `pip install` and `pytest`.\n * - **Complex agentic coding workflows** – Using other tools like `apply_patch` to complete\n * workflows that involve complex file operations.\n *\n * **How it works**:\n * The tool operates in a continuous loop:\n * 1. Model sends shell commands (`shell_call` with `commands` array)\n * 2. Your code executes the commands (can be concurrent)\n * 3. You return stdout, stderr, and outcome for each command\n * 4. Repeat until the task is complete\n *\n * **Security Warning**: Running arbitrary shell commands can be dangerous.\n * Always sandbox execution or add strict allow/deny-lists before forwarding\n * a command to the system shell.\n *\n * @see {@link https://platform.openai.com/docs/guides/tools-shell | OpenAI Shell Documentation}\n * @see {@link https://github.com/openai/codex | Codex CLI} for reference implementation.\n *\n * @param options - Configuration for the Shell tool\n * @returns A Shell tool that can be passed to `bindTools`\n *\n * @example\n * ```typescript\n * import { ChatOpenAI, tools } from \"@langchain/openai\";\n * import { exec } from \"child_process/promises\";\n *\n * const model = new ChatOpenAI({ model: \"gpt-5.1\" });\n *\n * // With execute callback for automatic command handling\n * const shellTool = tools.shell({\n * execute: async (action) => {\n * const outputs = await Promise.all(\n * action.commands.map(async (cmd) => {\n * try {\n * const { stdout, stderr } = await exec(cmd, {\n * timeout: action.timeout_ms ?? undefined,\n * });\n * return {\n * stdout,\n * stderr,\n * outcome: { type: \"exit\" as const, exit_code: 0 },\n * };\n * } catch (error) {\n * const timedOut = error.killed && error.signal === \"SIGTERM\";\n * return {\n * stdout: error.stdout ?? \"\",\n * stderr: error.stderr ?? String(error),\n * outcome: timedOut\n * ? { type: \"timeout\" as const }\n * : { type: \"exit\" as const, exit_code: error.code ?? 1 },\n * };\n * }\n * })\n * );\n * return {\n * output: outputs,\n * maxOutputLength: action.max_output_length,\n * };\n * },\n * });\n *\n * const llmWithShell = model.bindTools([shellTool]);\n * const response = await llmWithShell.invoke(\n * \"Find the largest PDF file in ~/Documents\"\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Full shell loop example\n * async function shellLoop(model, task) {\n * let response = await model.invoke(task, {\n * tools: [tools.shell({ execute: myExecutor })],\n * });\n *\n * while (true) {\n * const shellCall = response.additional_kwargs.tool_outputs?.find(\n * (output) => output.type === \"shell_call\"\n * );\n *\n * if (!shellCall) break;\n *\n * // Execute commands (with proper sandboxing!)\n * const result = await executeCommands(shellCall.action);\n *\n * // Send output back to model\n * response = await model.invoke([\n * response,\n * {\n * type: \"shell_call_output\",\n * call_id: shellCall.call_id,\n * output: result.output,\n * max_output_length: result.maxOutputLength,\n * },\n * ], {\n * tools: [tools.shell({ execute: myExecutor })],\n * });\n * }\n *\n * return response;\n * }\n * ```\n *\n * @remarks\n * - Only available through the Responses API (not Chat Completions)\n * - Designed for use with `gpt-5.1` model\n * - Commands are provided as an array of strings that can be executed concurrently\n * - Action includes: `commands`, `timeout_ms`, `max_output_length`\n * - Always sandbox or validate commands before execution\n * - The `timeout_ms` from the model is only a hint—enforce your own limits\n * - If `max_output_length` exists in the action, always pass it back in the output\n * - Many CLI tools return non-zero exit codes for warnings; still capture stdout/stderr\n */\nexport function shell(options: ShellOptions) {\n // Wrapper that converts ShellResult to string for LangChain tool compatibility\n const executeWrapper = async (action: ShellAction): Promise<string> => {\n const result = await options.execute(action);\n // Return a JSON string representation for the tool result\n return JSON.stringify({\n output: result.output,\n max_output_length: result.maxOutputLength,\n });\n };\n\n const shellTool = tool(executeWrapper, {\n name: TOOL_NAME,\n description:\n \"Execute shell commands in a managed environment. Commands can be run concurrently.\",\n schema: ShellActionSchema,\n });\n\n shellTool.extras = {\n ...(shellTool.extras ?? {}),\n providerToolDefinition: {\n type: \"shell\",\n } satisfies ShellTool,\n };\n\n return shellTool as DynamicStructuredTool<\n typeof ShellActionSchema,\n ShellAction,\n unknown,\n string\n >;\n}\n"],"mappings":";;;;;AAYA,MAAa,oBAAoBA,SAAE,OAAO;CACxC,UAAUA,SAAE,MAAMA,SAAE,QAAQ,CAAC,CAAC,SAAS,qCAAqC;CAC5E,YAAYA,SACT,QAAQ,CACR,UAAU,CACV,SAAS,oDAAoD;CAChE,mBAAmBA,SAChB,QAAQ,CACR,UAAU,CACV,SACC,oEACD;AACJ,EAAC;AAiFF,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmIlB,SAAgB,MAAMC,SAAuB;CAE3C,MAAM,iBAAiB,OAAOC,WAAyC;EACrE,MAAM,SAAS,MAAM,QAAQ,QAAQ,OAAO;AAE5C,SAAO,KAAK,UAAU;GACpB,QAAQ,OAAO;GACf,mBAAmB,OAAO;EAC3B,EAAC;CACH;CAED,MAAM,6CAAiB,gBAAgB;EACrC,MAAM;EACN,aACE;EACF,QAAQ;CACT,EAAC;CAEF,UAAU,SAAS;EACjB,GAAI,UAAU,UAAU,CAAE;EAC1B,wBAAwB,EACtB,MAAM,QACP;CACF;AAED,QAAO;AAMR"}