@librechat/agents 3.2.41 → 3.2.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +4 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +5 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +9 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +6 -0
- package/dist/cjs/messages/cache.cjs +43 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +2 -2
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +118 -22
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/CompileCheckTool.cjs +11 -3
- package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
- package/dist/cjs/tools/local/FileCheckpointer.cjs +8 -3
- package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +26 -7
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
- package/dist/cjs/tools/local/syntaxCheck.cjs +6 -2
- package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
- package/dist/cjs/tools/local/workspaceFS.cjs +20 -0
- package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +5 -4
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +6 -3
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
- package/dist/esm/llm/bedrock/index.mjs +10 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/cache.mjs +42 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/session/JsonlSessionStore.mjs +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +3 -3
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +115 -23
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/CompileCheckTool.mjs +11 -3
- package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
- package/dist/esm/tools/local/FileCheckpointer.mjs +9 -4
- package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +26 -7
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
- package/dist/esm/tools/local/syntaxCheck.mjs +6 -2
- package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
- package/dist/esm/tools/local/workspaceFS.mjs +19 -1
- package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +3 -2
- package/dist/types/llm/bedrock/index.d.ts +7 -0
- package/dist/types/messages/cache.d.ts +27 -0
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +53 -0
- package/dist/types/tools/local/workspaceFS.d.ts +13 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +10 -3
- package/src/graphs/Graph.ts +24 -6
- package/src/llm/bedrock/index.ts +19 -3
- package/src/llm/bedrock/llm.spec.ts +97 -0
- package/src/messages/cache.test.ts +63 -0
- package/src/messages/cache.ts +51 -0
- package/src/specs/vllm-reasoning-toolcalls.test.ts +340 -0
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +323 -0
- package/src/tools/__tests__/LocalExecutionTools.test.ts +54 -0
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +7 -2
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +269 -37
- package/src/tools/local/CompileCheckTool.ts +19 -3
- package/src/tools/local/FileCheckpointer.ts +20 -4
- package/src/tools/local/LocalCodingTools.ts +61 -8
- package/src/tools/local/__tests__/FileCheckpointer.test.ts +42 -0
- package/src/tools/local/syntaxCheck.ts +14 -2
- package/src/tools/local/workspaceFS.ts +27 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CloudflareProgrammaticToolCalling.mjs","names":[],"sources":["../../../../src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts"],"sourcesContent":["import { tool } from '@langchain/core/tools';\nimport type { DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\n\n/* eslint-disable no-useless-escape -- generated sandbox helper source needs escapes for emitted JS/Python string literals. */\nimport {\n formatCompletedResponse,\n normalizeToPythonIdentifier,\n ProgrammaticToolCallingDescription,\n ProgrammaticToolCallingName,\n ProgrammaticToolCallingSchema,\n filterToolsByUsage,\n} from '@/tools/ProgrammaticToolCalling';\nimport {\n BashProgrammaticToolCallingDescription,\n BashProgrammaticToolCallingSchema,\n filterBashToolsByUsage,\n normalizeToBashIdentifier,\n} from '@/tools/BashProgrammaticToolCalling';\nimport { Constants } from '@/common';\nimport {\n executeCloudflareCode,\n getCloudflareWorkspaceRoot,\n resolveCloudflareSandbox,\n validateCloudflareBashCommand,\n} from './CloudflareSandboxExecutionEngine';\n\ntype ProgrammaticParams = {\n code: string;\n timeout?: number;\n lang?: string;\n runtime?: string;\n language?: string;\n};\n\nconst DEFAULT_TIMEOUT = 60000;\nconst MIN_TIMEOUT = 1000;\nconst MAX_TIMEOUT = 300000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 200000;\n\ntype TimeoutSchema = {\n type: 'integer';\n minimum: number;\n maximum: number;\n default: number;\n description: string;\n};\n\ntype CloudflareProgrammaticToolCallingJsonSchema = {\n type: 'object';\n properties: typeof ProgrammaticToolCallingSchema.properties & {\n timeout: TimeoutSchema;\n lang: {\n type: 'string';\n enum: readonly ['py', 'python', 'bash', 'sh'];\n default: 'bash';\n description: string;\n };\n };\n required: readonly ['code'];\n};\n\ntype CloudflareBashProgrammaticToolCallingJsonSchema = {\n type: 'object';\n properties: typeof BashProgrammaticToolCallingSchema.properties & {\n timeout: TimeoutSchema;\n };\n required: readonly ['code'];\n};\n\nconst NATIVE_TOOL_NAMES = new Set<string>([\n Constants.READ_FILE,\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n Constants.BASH_TOOL,\n Constants.EXECUTE_CODE,\n]);\n\nfunction normalizeTimeout(timeoutMs: number | undefined): number {\n if (timeoutMs == null || !Number.isFinite(timeoutMs)) {\n return DEFAULT_TIMEOUT;\n }\n return Math.max(MIN_TIMEOUT, Math.floor(timeoutMs));\n}\n\nfunction formatTimeout(timeoutMs: number): string {\n return timeoutMs % 1000 === 0\n ? `${timeoutMs / 1000} seconds`\n : `${timeoutMs} milliseconds`;\n}\n\nfunction createTimeoutSchema(timeoutMs?: number): TimeoutSchema {\n const defaultTimeout = normalizeTimeout(timeoutMs);\n const maxTimeout = Math.max(MAX_TIMEOUT, defaultTimeout);\n return {\n type: 'integer',\n minimum: MIN_TIMEOUT,\n maximum: maxTimeout,\n default: defaultTimeout,\n description:\n 'Maximum Cloudflare Sandbox execution time in milliseconds. ' +\n `Default: ${formatTimeout(defaultTimeout)}. Max: ${formatTimeout(maxTimeout)}.`,\n };\n}\n\nfunction clampExecutionTimeout(\n requestedTimeoutMs: number | undefined,\n configuredTimeoutMs: number | undefined\n): number {\n const defaultTimeout = normalizeTimeout(configuredTimeoutMs);\n const maxTimeout = Math.max(MAX_TIMEOUT, defaultTimeout);\n if (requestedTimeoutMs == null || !Number.isFinite(requestedTimeoutMs)) {\n return defaultTimeout;\n }\n return Math.min(\n Math.max(MIN_TIMEOUT, Math.floor(requestedTimeoutMs)),\n maxTimeout\n );\n}\n\nfunction quoteShell(value: string): string {\n if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) {\n return value;\n }\n const escapedQuote = String.raw`'\\''`;\n return `'${value.replace(/'/g, escapedQuote)}'`;\n}\n\nfunction truncateOutput(\n value: string,\n maxChars = DEFAULT_MAX_OUTPUT_CHARS\n): string {\n if (value.length <= maxChars) {\n return value;\n }\n const head = Math.floor(maxChars * 0.6);\n const tail = maxChars - head;\n const omitted = value.length - maxChars;\n return `${value.slice(0, head)}\\n\\n[... ${omitted} characters truncated ...]\\n\\n${value.slice(\n value.length - tail\n )}`;\n}\n\nfunction withInSandboxTimeout(command: string, timeoutMs: number): string {\n const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));\n return `timeout -k 2s ${timeoutSeconds}s ${command}`;\n}\n\nfunction outerTimeoutMs(timeoutMs: number): number {\n return timeoutMs + 5000;\n}\n\nfunction isInSandboxTimeoutExit(exitCode: number | null): boolean {\n return exitCode === 124 || exitCode === 137;\n}\n\nasync function executeGeneratedCloudflareBash(\n command: string,\n config: t.CloudflareSandboxExecutionConfig\n): ReturnType<typeof executeCloudflareCode> {\n const sandbox = await resolveCloudflareSandbox(config);\n const workspaceRoot = getCloudflareWorkspaceRoot(config);\n const shell = config.shell ?? 'bash';\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT;\n const result = await sandbox.exec(\n withInSandboxTimeout(`${shell} -lc ${quoteShell(command)}`, timeoutMs),\n {\n cwd: workspaceRoot,\n env: config.env,\n timeout: outerTimeoutMs(timeoutMs),\n }\n );\n const maxOutputChars = config.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;\n return {\n stdout: truncateOutput(result.stdout, maxOutputChars),\n stderr: truncateOutput(result.stderr, maxOutputChars),\n exitCode: result.exitCode,\n timedOut: isInSandboxTimeoutExit(result.exitCode),\n };\n}\n\nfunction createCloudflareProgrammaticToolCallingSchema(\n config: t.CloudflareSandboxExecutionConfig\n): CloudflareProgrammaticToolCallingJsonSchema {\n return {\n ...ProgrammaticToolCallingSchema,\n properties: {\n ...ProgrammaticToolCallingSchema.properties,\n timeout: createTimeoutSchema(config.timeoutMs),\n lang: {\n type: 'string',\n enum: ['py', 'python', 'bash', 'sh'],\n default: 'bash',\n description:\n 'Cloudflare Sandbox runtime for orchestration code. Defaults to bash; use py/python for Python orchestration.',\n },\n },\n } as const;\n}\n\nfunction createCloudflareBashProgrammaticToolCallingSchema(\n config: t.CloudflareSandboxExecutionConfig\n): CloudflareBashProgrammaticToolCallingJsonSchema {\n return {\n ...BashProgrammaticToolCallingSchema,\n properties: {\n ...BashProgrammaticToolCallingSchema.properties,\n timeout: createTimeoutSchema(config.timeoutMs),\n },\n } as const;\n}\n\nfunction resolveRuntime(params: ProgrammaticParams): 'python' | 'bash' {\n const raw = params.lang ?? params.runtime ?? params.language ?? 'bash';\n return raw === 'py' || raw === 'python' ? 'python' : 'bash';\n}\n\nfunction filterNativeTools(\n toolDefs: t.LCTool[],\n code: string,\n runtime: 'python' | 'bash'\n): t.LCTool[] {\n const nativeDefs = toolDefs.filter((def) => NATIVE_TOOL_NAMES.has(def.name));\n const filter =\n runtime === 'bash' ? filterBashToolsByUsage : filterToolsByUsage;\n return filter(nativeDefs, code);\n}\n\nfunction indent(code: string, spaces = 4): string {\n const prefix = ' '.repeat(spaces);\n return code\n .split('\\n')\n .map((line) => (line === '' ? line : prefix + line))\n .join('\\n');\n}\n\nfunction pythonBoolean(value: boolean | undefined): 'True' | 'False' {\n return value === true ? 'True' : 'False';\n}\n\nfunction createPythonNativeToolSource(\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n return `\nimport asyncio, fnmatch, glob, json, os, pathlib, re, shlex, shutil, subprocess, sys, tempfile\n\nWORKSPACE = ${JSON.stringify(workspaceRoot)}\nSHELL = ${JSON.stringify(config.shell ?? 'bash')}\nREAD_ONLY = ${pythonBoolean(config.readOnly)}\nALLOW_DANGEROUS_COMMANDS = ${pythonBoolean(config.allowDangerousCommands)}\nDESTRUCTIVE_TARGET = r\"(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:/?\\\\.?\\\\*|/)?\"\nDANGEROUS_COMMAND_PATTERNS = [\n re.compile(r\"\\\\brm\\\\s+(?:-[^\\\\s]*[rf][^\\\\s]*\\\\s+|-[^\\\\s]*[r][^\\\\s]*\\\\s+-[^\\\\s]*[f][^\\\\s]*\\\\s+)(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"\\\\s*(?:$|[;&|])\"),\n re.compile(r\"\\\\b(?:mkfs|mkswap|fdisk|parted|diskutil)\\\\b\"),\n re.compile(r\"\\\\bdd\\\\s+[^;&|]*\\\\bof=/dev/\"),\n re.compile(r\"\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"(?:$|\\\\s|[;&|])\"),\n re.compile(r\"\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"(?:$|\\\\s|[;&|])\"),\n re.compile(r\":\\\\s*\\\\(\\\\s*\\\\)\\\\s*\\\\{\\\\s*:\\\\s*\\\\|\\\\s*:\\\\s*&\\\\s*\\\\}\\\\s*;\\\\s*:\"),\n]\nQUOTED_DESTRUCTIVE_PATTERNS = [\n re.compile(r\"\\\\brm\\\\s+(?:-[^\\\\s]*[rf][^\\\\s]*\\\\s+){1,3}(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n re.compile(r\"\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n re.compile(r\"\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n]\nNESTED_SHELL_PREFIX = r\"(?:(?:ba|z|da|k)?sh|eval)\\\\s+(?:-l?c\\\\s+)?\"\nNESTED_SHELL_DESTRUCTIVE_PATTERNS = [\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\brm\\\\s+-[^\\\\s\\\\\"']*[rf][^\\\\s\\\\\"']*\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n]\nMUTATING_COMMAND_PATTERN = re.compile(r\"\\\\b(?:rm|mv|cp|touch|mkdir|rmdir|ln|truncate|tee|sed\\\\s+-i|perl\\\\s+-pi|python(?:3)?\\\\s+-c|node\\\\s+-e|npm\\\\s+(?:install|ci|update|publish)|pnpm\\\\s+(?:install|update|publish)|yarn\\\\s+(?:install|add|publish)|git\\\\s+(?:add|commit|checkout|switch|reset|clean|rebase|merge|push|pull|stash|tag|branch)|chmod|chown)\\\\b|(?:^|[^<])>\\\\s*[^&]|\\\\bcat\\\\s+[^|;&]*>\\\\s*\")\nPROTECTED_TARGET_ARG_RE = re.compile(r\"^(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:/?\\\\.?\\\\*|/)?$\")\nDESTRUCTIVE_OP_IN_COMMAND_RE = re.compile(r\"\\\\b(?:rm\\\\s+-[^\\\\s]*[rf]|chmod\\\\s+-R|chown\\\\s+-R)\\\\b\")\n\ndef _is_within_workspace(file_path):\n resolved = os.path.abspath(file_path)\n root = os.path.abspath(WORKSPACE)\n return os.path.commonpath([root, resolved]) == root\n\ndef _resolve(file_path=\".\"):\n raw = file_path or \".\"\n candidate = raw if os.path.isabs(raw) else os.path.join(WORKSPACE, raw)\n resolved = os.path.abspath(candidate)\n if not _is_within_workspace(resolved):\n raise ValueError(f\"Path is outside the Cloudflare sandbox workspace: {file_path}\")\n return resolved\n\ndef _assert_writable(tool_name):\n if READ_ONLY:\n raise PermissionError(f\"{tool_name} is blocked in read-only Cloudflare sandbox mode.\")\n\ndef _strip_quoted_content(command):\n output = []\n quote = None\n escaped = False\n index = 0\n while index < len(command):\n char = command[index]\n if escaped:\n escaped = False\n output.append(\" \")\n index += 1\n continue\n if char == \"\\\\\\\\\":\n escaped = True\n output.append(\" \")\n index += 1\n continue\n if quote is not None:\n if char == quote:\n quote = None\n output.append(\" \")\n index += 1\n continue\n if char in (\"'\", '\"', \"\\`\"):\n quote = char\n output.append(\" \")\n index += 1\n continue\n if char == \"#\":\n while index < len(command) and command[index] != \"\\\\n\":\n output.append(\" \")\n index += 1\n output.append(\"\\\\n\")\n index += 1\n continue\n output.append(char)\n index += 1\n return \"\".join(output)\n\ndef _validate_bash_command(command, args=None):\n errors = []\n normalized = _strip_quoted_content(command)\n if command.strip() == \"\":\n errors.append(\"Command is empty.\")\n if \"\\\\0\" in command:\n errors.append(\"Command contains a NUL byte.\")\n if not ALLOW_DANGEROUS_COMMANDS:\n if any(pattern.search(normalized) for pattern in DANGEROUS_COMMAND_PATTERNS):\n errors.append(\"Command matches a destructive command pattern.\")\n elif any(pattern.search(command) for pattern in QUOTED_DESTRUCTIVE_PATTERNS):\n errors.append(\"Command matches a destructive command pattern (quoted target).\")\n elif any(pattern.search(command) for pattern in NESTED_SHELL_DESTRUCTIVE_PATTERNS):\n errors.append(\"Command matches a destructive command pattern (nested shell payload).\")\n elif args and DESTRUCTIVE_OP_IN_COMMAND_RE.search(command):\n offending = next((str(arg) for arg in args if PROTECTED_TARGET_ARG_RE.search(str(arg))), None)\n if offending is not None:\n errors.append(f\"Command matches a destructive command pattern (protected target \\\\\"{offending}\\\\\" passed via positional arg).\")\n if READ_ONLY and MUTATING_COMMAND_PATTERN.search(normalized):\n errors.append(\"Command appears to mutate files or repository state in read-only Cloudflare sandbox mode.\")\n if errors:\n raise ValueError(\"\\\\n\".join(errors))\n\ndef _line_window(content, offset=None, limit=None):\n start = max((offset or 1) - 1, 0)\n lines = content.split(\"\\\\n\")\n selected = lines[start:] if not limit or limit <= 0 else lines[start:start + limit]\n return \"\\\\n\".join(f\"{start + idx + 1:6d}\\\\t{line}\" for idx, line in enumerate(selected))\n\ndef _run(command, timeout=None, args=None):\n _validate_bash_command(command, args=args)\n completed = subprocess.run(\n [SHELL, \"-lc\", command, \"--\"] + [str(arg) for arg in (args or [])],\n cwd=WORKSPACE,\n capture_output=True,\n text=True,\n timeout=(timeout / 1000 if timeout else None),\n )\n return {\n \"stdout\": completed.stdout,\n \"stderr\": completed.stderr,\n \"exit_code\": completed.returncode,\n }\n\ndef _format_run(result):\n text = \"\"\n if result.get(\"stdout\"):\n text += f\"stdout:\\\\n{result['stdout']}\\\\n\"\n else:\n text += \"stdout: Empty. Ensure you're writing output explicitly.\\\\n\"\n if result.get(\"stderr\"):\n text += f\"stderr:\\\\n{result['stderr']}\\\\n\"\n if result.get(\"exit_code\") not in (None, 0):\n text += f\"exit_code: {result['exit_code']}\\\\n\"\n text += f\"working_directory: {WORKSPACE}\"\n return text.strip()\n\ndef _detect_compile_command():\n if os.path.exists(os.path.join(WORKSPACE, \"tsconfig.json\")):\n return \"typescript\", \"npx --no-install tsc --noEmit\", \"tsconfig.json present\"\n package_json = os.path.join(WORKSPACE, \"package.json\")\n if os.path.exists(package_json):\n try:\n if '\"typescript\"' in open(package_json, encoding=\"utf-8\").read():\n return \"typescript\", \"npx --no-install tsc --noEmit\", \"package.json declares typescript\"\n except Exception:\n pass\n if os.path.exists(os.path.join(WORKSPACE, \"Cargo.toml\")):\n return \"rust\", \"cargo check --message-format=short\", \"Cargo.toml present\"\n if os.path.exists(os.path.join(WORKSPACE, \"go.mod\")):\n return \"go\", \"go vet ./...\", \"go.mod present\"\n if any(os.path.exists(os.path.join(WORKSPACE, name)) for name in [\"pyproject.toml\", \"setup.py\", \"setup.cfg\"]):\n return \"python-compile\", \"python3 -m py_compile $(find . -name '*.py' -not -path './.venv/*' -not -path './node_modules/*')\", \"Python project\"\n return \"unknown\", \"\", \"no recognised project marker\"\n\nasync def bash_tool(command, args=None):\n return _format_run(_run(command, args=args))\n\nasync def execute_code(lang, code, args=None):\n args = args or []\n temp_dir = tempfile.mkdtemp(prefix=\"lc-ptc-\", dir=WORKSPACE)\n try:\n def q(value):\n import shlex\n return shlex.quote(str(value))\n arg_text = \" \".join(q(arg) for arg in args)\n if lang in (\"py\", \"python\"):\n file_path = os.path.join(temp_dir, \"main.py\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"python3 {q(file_path)} {arg_text}\"))\n if lang in (\"js\", \"javascript\"):\n file_path = os.path.join(temp_dir, \"main.js\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"node {q(file_path)} {arg_text}\"))\n if lang in (\"ts\", \"typescript\"):\n file_path = os.path.join(temp_dir, \"main.ts\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"npx --no-install tsx {q(file_path)} {arg_text}\"))\n if lang == \"php\":\n file_path = os.path.join(temp_dir, \"main.php\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"php {q(file_path)} {arg_text}\"))\n if lang == \"go\":\n file_path = os.path.join(temp_dir, \"main.go\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"go run {q(file_path)} {arg_text}\"))\n if lang == \"rs\":\n file_path = os.path.join(temp_dir, \"main.rs\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-rs\")\n return _format_run(_run(f\"rustc {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"c\":\n file_path = os.path.join(temp_dir, \"main.c\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-c\")\n return _format_run(_run(f\"cc {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"cpp\":\n file_path = os.path.join(temp_dir, \"main.cpp\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-cpp\")\n return _format_run(_run(f\"c++ {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"java\":\n file_path = os.path.join(temp_dir, \"Main.java\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"javac {q(file_path)} && java -cp {q(temp_dir)} Main {arg_text}\"))\n if lang == \"r\":\n file_path = os.path.join(temp_dir, \"main.R\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"Rscript {q(file_path)} {arg_text}\"))\n if lang == \"d\":\n file_path = os.path.join(temp_dir, \"main.d\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-d\")\n return _format_run(_run(f\"dmd {q(file_path)} -of={q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"f90\":\n file_path = os.path.join(temp_dir, \"main.f90\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-f90\")\n return _format_run(_run(f\"gfortran {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang in (\"bash\", \"sh\"):\n return _format_run(_run(code, args=args))\n raise ValueError(f\"Unsupported Cloudflare sandbox runtime: {lang}\")\n finally:\n shutil.rmtree(temp_dir, ignore_errors=True)\n\nasync def read_file(path, offset=None, limit=None):\n resolved = _resolve(path)\n with open(resolved, encoding=\"utf-8\") as handle:\n return _line_window(handle.read(), offset, limit)\n\nasync def write_file(path, content):\n _assert_writable(\"write_file\")\n resolved = _resolve(path)\n os.makedirs(os.path.dirname(resolved), exist_ok=True)\n existed = os.path.exists(resolved)\n with open(resolved, \"w\", encoding=\"utf-8\") as handle:\n handle.write(content)\n return f\"{'Overwrote' if existed else 'Created'} {resolved} ({len(content)} chars).\"\n\nasync def edit_file(path, old_text=None, new_text=None, edits=None):\n _assert_writable(\"edit_file\")\n resolved = _resolve(path)\n edits = edits or [{\"old_text\": old_text, \"new_text\": new_text}]\n content = open(resolved, encoding=\"utf-8\").read()\n for edit in edits:\n old = edit.get(\"old_text\") or \"\"\n new = edit.get(\"new_text\") or \"\"\n if content.count(old) != 1:\n raise ValueError(f\"Could not locate old_text exactly once in {path}\")\n content = content.replace(old, new, 1)\n open(resolved, \"w\", encoding=\"utf-8\").write(content)\n return f\"Applied {len(edits)} edit(s) to {resolved}.\"\n\nasync def list_directory(path=\".\"):\n resolved = _resolve(path)\n entries = []\n for name in sorted(os.listdir(resolved)):\n full = os.path.join(resolved, name)\n entries.append((\"dir \" if os.path.isdir(full) else \"file\") + \"\\\\t\" + name)\n return \"\\\\n\".join(entries) or \"Directory is empty.\"\n\nasync def grep_search(pattern, path=\".\", glob=None, max_results=200):\n root = _resolve(path)\n regex = re.compile(pattern)\n out = []\n for current, dirs, files in os.walk(root):\n dirs[:] = [d for d in dirs if d not in {\".git\", \"node_modules\", \".venv\", \"dist\", \"build\"}]\n for name in files:\n rel = os.path.relpath(os.path.join(current, name), root)\n if glob and not fnmatch.fnmatch(rel, glob):\n continue\n try:\n for line_no, line in enumerate(open(os.path.join(current, name), encoding=\"utf-8\", errors=\"ignore\"), 1):\n if regex.search(line):\n out.append(f\"{os.path.join(current, name)}:{line_no}:{line.rstrip()}\")\n if len(out) >= max_results:\n return \"\\\\n\".join(out)\n except Exception:\n pass\n return \"\\\\n\".join(out) if out else \"No matches found.\"\n\nasync def glob_search(pattern, path=\".\", max_results=200):\n root = _resolve(path)\n target = pattern if os.path.isabs(pattern) else os.path.join(root, pattern)\n matches = []\n for match in glob_module.glob(target, recursive=True):\n resolved = os.path.abspath(match)\n if _is_within_workspace(resolved):\n matches.append(resolved)\n if len(matches) >= max_results:\n break\n return \"\\\\n\".join(matches) if matches else \"No files found.\"\n\nasync def compile_check(command=None, timeout_ms=None):\n kind, detected, reason = _detect_compile_command()\n command = command or detected\n if not command:\n return f\"compile_check: {reason}. Pass an explicit command to override.\"\n result = _run(command, timeout_ms)\n status = \"PASSED\" if result[\"exit_code\"] == 0 else \"FAILED\"\n return f\"compile_check ({kind}) {status} via {command}\\\\n\\\\nstdout:\\\\n{result['stdout']}\\\\nstderr:\\\\n{result['stderr']}\\\\nworking_directory: {WORKSPACE}\\\\nreason: {reason}\"\n\n# Avoid shadowing the glob_search function argument named \"glob\".\nglob_module = glob\n`.trim();\n}\n\nfunction createNodeNativeToolSource(\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n return `\nconst fs = require(\"fs\");\nconst fsp = fs.promises;\nconst path = require(\"path\");\nconst cp = require(\"child_process\");\n\nconst WORKSPACE = ${JSON.stringify(workspaceRoot)};\nconst SHELL = ${JSON.stringify(config.shell ?? 'bash')};\nconst READ_ONLY = ${JSON.stringify(config.readOnly === true)};\nconst ALLOW_DANGEROUS_COMMANDS = ${JSON.stringify(config.allowDangerousCommands === true)};\nconst DESTRUCTIVE_TARGET = \"(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)(?:\\\\\\\\/?\\\\\\\\.?\\\\\\\\*|\\\\\\\\/)?\";\nconst DANGEROUS_COMMAND_PATTERNS = [\n new RegExp(\"\\\\\\\\brm\\\\\\\\s+(?:-[^\\\\\\\\s]*[rf][^\\\\\\\\s]*\\\\\\\\s+|-[^\\\\\\\\s]*[r][^\\\\\\\\s]*\\\\\\\\s+-[^\\\\\\\\s]*[f][^\\\\\\\\s]*\\\\\\\\s+)(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"\\\\\\\\s*(?:$|[;&|])\"),\n /\\\\b(?:mkfs|mkswap|fdisk|parted|diskutil)\\\\b/,\n /\\\\bdd\\\\s+[^;&|]*\\\\bof=\\\\/dev\\\\//,\n new RegExp(\"\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"(?:$|\\\\\\\\s|[;&|])\"),\n new RegExp(\"\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"(?:$|\\\\\\\\s|[;&|])\"),\n /:\\\\s*\\\\(\\\\s*\\\\)\\\\s*\\\\{\\\\s*:\\\\s*\\\\|\\\\s*:\\\\s*&\\\\s*\\\\}\\\\s*;\\\\s*:/,\n];\nconst QUOTED_DESTRUCTIVE_PATTERNS = [\n new RegExp(\"\\\\\\\\brm\\\\\\\\s+(?:-[^\\\\\\\\s]*[rf][^\\\\\\\\s]*\\\\\\\\s+){1,3}(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n new RegExp(\"\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n new RegExp(\"\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n];\nconst NESTED_SHELL_PREFIX = \"(?:(?:ba|z|da|k)?sh|eval)\\\\\\\\s+(?:-l?c\\\\\\\\s+)?\";\nconst NESTED_SHELL_DESTRUCTIVE_PATTERNS = [\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\brm\\\\\\\\s+-[^\\\\\\\\s\\\\\\\"']*[rf][^\\\\\\\\s\\\\\\\"']*\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n];\nconst MUTATING_COMMAND_PATTERN = /\\\\b(?:rm|mv|cp|touch|mkdir|rmdir|ln|truncate|tee|sed\\\\s+-i|perl\\\\s+-pi|python(?:3)?\\\\s+-c|node\\\\s+-e|npm\\\\s+(?:install|ci|update|publish)|pnpm\\\\s+(?:install|update|publish)|yarn\\\\s+(?:install|add|publish)|git\\\\s+(?:add|commit|checkout|switch|reset|clean|rebase|merge|push|pull|stash|tag|branch)|chmod|chown)\\\\b|(?:^|[^<])>\\\\s*[^&]|\\\\bcat\\\\s+[^|;&]*>\\\\s*/;\nconst PROTECTED_TARGET_ARG_RE = /^(?:\\\\/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:\\\\/?\\\\.?\\\\*|\\\\/)?$/;\nconst DESTRUCTIVE_OP_IN_COMMAND_RE = /\\\\b(?:rm\\\\s+-[^\\\\s]*[rf]|chmod\\\\s+-R|chown\\\\s+-R)\\\\b/;\n\nfunction resolvePath(filePath) {\n const raw = filePath || \".\";\n const candidate = path.isAbsolute(raw) ? raw : path.join(WORKSPACE, raw);\n const resolved = path.resolve(candidate);\n const root = path.resolve(WORKSPACE);\n const relative = path.relative(root, resolved);\n if (relative && (relative.startsWith(\"..\") || path.isAbsolute(relative))) {\n throw new Error(\"Path is outside the Cloudflare sandbox workspace: \" + filePath);\n }\n return resolved;\n}\n\nfunction assertWritable(toolName) {\n if (READ_ONLY) {\n throw new Error(toolName + \" is blocked in read-only Cloudflare sandbox mode.\");\n }\n}\n\nfunction stripQuotedContent(command) {\n let output = \"\";\n let quote;\n let escaped = false;\n for (let index = 0; index < command.length; index++) {\n const char = command[index];\n if (escaped) {\n escaped = false;\n output += \" \";\n continue;\n }\n if (char === \"\\\\\\\\\") {\n escaped = true;\n output += \" \";\n continue;\n }\n if (quote != null) {\n if (char === quote) quote = undefined;\n output += \" \";\n continue;\n }\n if (char === \"\\\\\\\"\" || char === \"'\" || char === \"\\`\") {\n quote = char;\n output += \" \";\n continue;\n }\n if (char === \"#\") {\n while (index < command.length && command[index] !== \"\\\\n\") {\n output += \" \";\n index += 1;\n }\n output += \"\\\\n\";\n continue;\n }\n output += char;\n }\n return output;\n}\n\nfunction validateBashCommand(command, args) {\n const errors = [];\n const normalized = stripQuotedContent(command);\n if (command.trim() === \"\") {\n errors.push(\"Command is empty.\");\n }\n if (command.includes(\"\\\\0\")) {\n errors.push(\"Command contains a NUL byte.\");\n }\n if (!ALLOW_DANGEROUS_COMMANDS) {\n if (DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(normalized))) {\n errors.push(\"Command matches a destructive command pattern.\");\n } else if (QUOTED_DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command))) {\n errors.push(\"Command matches a destructive command pattern (quoted target).\");\n } else if (NESTED_SHELL_DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command))) {\n errors.push(\"Command matches a destructive command pattern (nested shell payload).\");\n } else if ((args || []).length > 0 && DESTRUCTIVE_OP_IN_COMMAND_RE.test(command)) {\n const offending = (args || []).map(String).find((arg) => PROTECTED_TARGET_ARG_RE.test(arg));\n if (offending !== undefined) {\n errors.push(\"Command matches a destructive command pattern (protected target \\\\\"\" + offending + \"\\\\\" passed via positional arg).\");\n }\n }\n }\n if (READ_ONLY && MUTATING_COMMAND_PATTERN.test(normalized)) {\n errors.push(\"Command appears to mutate files or repository state in read-only Cloudflare sandbox mode.\");\n }\n if (errors.length > 0) {\n throw new Error(errors.join(\"\\\\n\"));\n }\n}\n\nfunction lineWindow(content, offset, limit) {\n const start = Math.max((offset || 1) - 1, 0);\n const lines = content.split(\"\\\\n\");\n const selected = !limit || limit <= 0 ? lines.slice(start) : lines.slice(start, start + limit);\n return selected.map((line, index) => String(start + index + 1).padStart(6, \" \") + \"\\\\t\" + line).join(\"\\\\n\");\n}\n\nfunction quote(value) {\n const text = String(value);\n if (text === \"\") return \"''\";\n if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(text)) return text;\n return \"'\" + text.replace(/'/g, \"'\\\\\\\\''\") + \"'\";\n}\n\nfunction run(command, timeoutMs, args) {\n validateBashCommand(command, args);\n return new Promise((resolve) => {\n const child = cp.spawn(SHELL, [\"-lc\", command, \"--\", ...((args || []).map(String))], {\n cwd: WORKSPACE,\n env: process.env,\n });\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n const timer = timeoutMs\n ? setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n }, timeoutMs)\n : null;\n\n child.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n child.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", (error) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr: stderr + error.message, exit_code: 1, timed_out: timedOut });\n });\n child.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr, exit_code: timedOut ? null : code, timed_out: timedOut });\n });\n });\n}\n\nfunction formatRun(result) {\n let text = \"\";\n if (result.stdout) {\n text += \"stdout:\\\\n\" + result.stdout + \"\\\\n\";\n } else {\n text += \"stdout: Empty. Ensure you're writing output explicitly.\\\\n\";\n }\n if (result.stderr) {\n text += \"stderr:\\\\n\" + result.stderr + \"\\\\n\";\n }\n if (result.timed_out) {\n text += \"timed_out: true\\\\n\";\n }\n if (result.exit_code !== null && result.exit_code !== undefined && result.exit_code !== 0) {\n text += \"exit_code: \" + result.exit_code + \"\\\\n\";\n }\n text += \"working_directory: \" + WORKSPACE;\n return text.trim();\n}\n\nasync function detectCompileCommand() {\n async function exists(name) {\n try {\n await fsp.access(path.join(WORKSPACE, name));\n return true;\n } catch {\n return false;\n }\n }\n if (await exists(\"tsconfig.json\")) {\n return [\"typescript\", \"npx --no-install tsc --noEmit\", \"tsconfig.json present\"];\n }\n if (await exists(\"package.json\")) {\n try {\n if ((await fsp.readFile(path.join(WORKSPACE, \"package.json\"), \"utf8\")).includes('\"typescript\"')) {\n return [\"typescript\", \"npx --no-install tsc --noEmit\", \"package.json declares typescript\"];\n }\n } catch {}\n }\n if (await exists(\"Cargo.toml\")) return [\"rust\", \"cargo check --message-format=short\", \"Cargo.toml present\"];\n if (await exists(\"go.mod\")) return [\"go\", \"go vet ./...\", \"go.mod present\"];\n if (await exists(\"pyproject.toml\") || await exists(\"setup.py\") || await exists(\"setup.cfg\")) {\n return [\"python-compile\", \"python3 -m py_compile $(find . -name '*.py' -not -path './.venv/*' -not -path './node_modules/*')\", \"Python project\"];\n }\n return [\"unknown\", \"\", \"no recognised project marker\"];\n}\n\nfunction globToRegExp(pattern) {\n const escaped = pattern.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, \"\\\\\\\\$&\");\n return new RegExp(\"^\" + escaped.replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, \".*\").replace(/\\\\\\\\\\\\*/g, \"[^/]*\") + \"$\");\n}\n\nfunction globMatch(relativePath, pattern) {\n const matcher = globToRegExp(pattern);\n return matcher.test(relativePath) || matcher.test(path.basename(relativePath));\n}\n\nasync function walkFiles(root, visit) {\n const entries = await fsp.readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(root, entry.name);\n if (entry.isDirectory()) {\n if ([\".git\", \"node_modules\", \".venv\", \"dist\", \"build\"].includes(entry.name)) continue;\n await walkFiles(full, visit);\n } else if (entry.isFile()) {\n await visit(full);\n }\n }\n}\n\nasync function bash_tool(payload) {\n return formatRun(await run(payload.command, undefined, payload.args));\n}\n\nasync function execute_code(payload) {\n const lang = payload.lang;\n const code = payload.code;\n const args = payload.args || [];\n const tempDir = await fsp.mkdtemp(path.join(WORKSPACE, \"lc-ptc-\"));\n try {\n const argText = args.map(quote).join(\" \");\n async function writeAndRun(fileName, command) {\n const filePath = path.join(tempDir, fileName);\n await fsp.writeFile(filePath, code, \"utf8\");\n return formatRun(await run(command(filePath, argText), undefined, []));\n }\n if (lang === \"py\" || lang === \"python\") {\n return writeAndRun(\"main.py\", (filePath, argText) => \"python3 \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"js\" || lang === \"javascript\") {\n return writeAndRun(\"main.js\", (filePath, argText) => \"node \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"ts\" || lang === \"typescript\") {\n return writeAndRun(\"main.ts\", (filePath, argText) => \"npx --no-install tsx \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"php\") {\n return writeAndRun(\"main.php\", (filePath, argText) => \"php \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"go\") {\n return writeAndRun(\"main.go\", (filePath, argText) => \"go run \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"rs\") {\n return writeAndRun(\"main.rs\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-rs\");\n return \"rustc \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"c\") {\n return writeAndRun(\"main.c\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-c\");\n return \"cc \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"cpp\") {\n return writeAndRun(\"main.cpp\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-cpp\");\n return \"c++ \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"java\") {\n return writeAndRun(\"Main.java\", (filePath, argText) => \"javac \" + quote(filePath) + \" && java -cp \" + quote(tempDir) + \" Main \" + argText);\n }\n if (lang === \"r\") {\n return writeAndRun(\"main.R\", (filePath, argText) => \"Rscript \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"d\") {\n return writeAndRun(\"main.d\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-d\");\n return \"dmd \" + quote(filePath) + \" -of=\" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"f90\") {\n return writeAndRun(\"main.f90\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-f90\");\n return \"gfortran \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"bash\" || lang === \"sh\") {\n return formatRun(await run(code, undefined, args));\n }\n throw new Error(\"Unsupported Cloudflare sandbox runtime: \" + lang);\n } finally {\n await fsp.rm(tempDir, { recursive: true, force: true });\n }\n}\n\nasync function read_file(payload) {\n const resolved = resolvePath(payload.path);\n return lineWindow(await fsp.readFile(resolved, \"utf8\"), payload.offset, payload.limit);\n}\n\nasync function write_file(payload) {\n assertWritable(\"write_file\");\n const resolved = resolvePath(payload.path);\n await fsp.mkdir(path.dirname(resolved), { recursive: true });\n const existed = fs.existsSync(resolved);\n await fsp.writeFile(resolved, payload.content, \"utf8\");\n return (existed ? \"Overwrote \" : \"Created \") + resolved + \" (\" + payload.content.length + \" chars).\";\n}\n\nasync function edit_file(payload) {\n assertWritable(\"edit_file\");\n const resolved = resolvePath(payload.path);\n const edits = payload.edits || [{ old_text: payload.old_text, new_text: payload.new_text }];\n let content = await fsp.readFile(resolved, \"utf8\");\n for (const edit of edits) {\n const oldText = edit.old_text || \"\";\n const newText = edit.new_text || \"\";\n if (oldText === \"\" || content.split(oldText).length - 1 !== 1) {\n throw new Error(\"Could not locate old_text exactly once in \" + payload.path);\n }\n content = content.replace(oldText, newText);\n }\n await fsp.writeFile(resolved, content, \"utf8\");\n return \"Applied \" + edits.length + \" edit(s) to \" + resolved + \".\";\n}\n\nasync function list_directory(payload) {\n const resolved = resolvePath(payload.path || \".\");\n const entries = await fsp.readdir(resolved, { withFileTypes: true });\n const lines = entries\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => (entry.isDirectory() ? \"dir\" : \"file\") + \"\\\\t\" + entry.name);\n return lines.join(\"\\\\n\") || \"Directory is empty.\";\n}\n\nasync function grep_search(payload) {\n const root = resolvePath(payload.path || \".\");\n const regex = new RegExp(payload.pattern);\n const maxResults = payload.max_results || 200;\n const out = [];\n await walkFiles(root, async (filePath) => {\n if (out.length >= maxResults) return;\n const relative = path.relative(root, filePath);\n if (payload.glob && !globMatch(relative, payload.glob)) return;\n let text = \"\";\n try {\n text = await fsp.readFile(filePath, \"utf8\");\n } catch {\n return;\n }\n text.split(\"\\\\n\").forEach((line, index) => {\n if (out.length < maxResults && regex.test(line)) {\n out.push(filePath + \":\" + (index + 1) + \":\" + line);\n }\n });\n });\n return out.join(\"\\\\n\") || \"No matches found.\";\n}\n\nasync function glob_search(payload) {\n const root = resolvePath(payload.path || \".\");\n const maxResults = payload.max_results || 200;\n const out = [];\n await walkFiles(root, async (filePath) => {\n if (out.length >= maxResults) return;\n const relative = path.relative(root, filePath);\n if (globMatch(relative, payload.pattern)) out.push(filePath);\n });\n return out.join(\"\\\\n\") || \"No files found.\";\n}\n\nasync function compile_check(payload) {\n const [kind, detected, reason] = await detectCompileCommand();\n const command = payload.command || detected;\n if (!command) {\n return \"compile_check: \" + reason + \". Pass an explicit command to override.\";\n }\n const result = await run(command, payload.timeout_ms);\n const status = result.exit_code === 0 ? \"PASSED\" : \"FAILED\";\n return \"compile_check (\" + kind + \") \" + status + \" via \" + command + \"\\\\n\\\\nstdout:\\\\n\" + result.stdout + \"\\\\nstderr:\\\\n\" + result.stderr + \"\\\\nworking_directory: \" + WORKSPACE + \"\\\\nreason: \" + reason;\n}\n\nconst TOOLS = {\n bash_tool,\n execute_code,\n read_file,\n write_file,\n edit_file,\n list_directory,\n grep_search,\n glob_search,\n compile_check,\n};\n\nasync function main() {\n const name = process.argv[2];\n const payload = JSON.parse(process.argv[3] || \"{}\");\n if (!TOOLS[name]) throw new Error(\"Unknown tool: \" + name);\n const result = await TOOLS[name](payload);\n process.stdout.write(typeof result === \"string\" ? result : JSON.stringify(result));\n}\n\nmain().catch((error) => {\n console.error(error && error.stack ? error.stack : String(error));\n process.exit(1);\n});\n`.trim();\n}\n\nfunction createPythonProgram(\n userCode: string,\n toolDefs: t.LCTool[],\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n const aliases = toolDefs\n .map((def) => {\n const pythonName = normalizeToPythonIdentifier(def.name);\n return NATIVE_TOOL_NAMES.has(def.name) && pythonName !== def.name\n ? `${pythonName} = globals()[${JSON.stringify(def.name)}]`\n : '';\n })\n .filter(Boolean)\n .join('\\n');\n return `${createPythonNativeToolSource(config, workspaceRoot)}\n${aliases}\n\nasync def __lc_user_main__():\n${indent(userCode)}\n\nasyncio.run(__lc_user_main__())\n`;\n}\n\nfunction createBashProgram(\n userCode: string,\n toolDefs: t.LCTool[],\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n const helper = createNodeNativeToolSource(config, workspaceRoot);\n const functions = toolDefs\n .map((def) => {\n const bashName = normalizeToBashIdentifier(def.name);\n if (!NATIVE_TOOL_NAMES.has(def.name)) {\n return '';\n }\n return `${bashName}() { node \"$__LC_TOOL_HELPER\" ${JSON.stringify(def.name)} \"$1\"; }`;\n })\n .filter(Boolean)\n .join('\\n');\n return `\nset -euo pipefail\ncommand -v node >/dev/null 2>&1 || { echo \"Cloudflare programmatic tool calling requires node in the sandbox image.\" >&2; exit 127; }\n__LC_TOOL_HELPER=\"$(mktemp /tmp/lc-tools.XXXXXX.js)\"\ncat > \"$__LC_TOOL_HELPER\" <<'JS'\n${helper}\nJS\ntrap 'rm -f \"$__LC_TOOL_HELPER\"' EXIT\n${functions}\n${userCode}\n`.trim();\n}\n\nasync function runProgrammatic(args: {\n params: ProgrammaticParams;\n config?: { toolCall?: unknown };\n cloudflareConfig: t.CloudflareSandboxExecutionConfig;\n runtime: 'python' | 'bash';\n}): Promise<[string, t.ProgrammaticExecutionArtifact]> {\n const toolCall = (args.config?.toolCall ??\n {}) as Partial<t.ProgrammaticCache>;\n const toolDefs = toolCall.toolDefs ?? [];\n const effectiveTools = filterNativeTools(\n toolDefs,\n args.params.code,\n args.runtime\n );\n const timeoutMs = clampExecutionTimeout(\n args.params.timeout,\n args.cloudflareConfig.timeoutMs\n );\n const workspaceRoot = getCloudflareWorkspaceRoot(args.cloudflareConfig);\n let result: Awaited<ReturnType<typeof executeCloudflareCode>>;\n\n if (args.runtime === 'bash') {\n await validateCloudflareBashCommand(args.params.code, [], {\n ...args.cloudflareConfig,\n timeoutMs,\n });\n result = await executeGeneratedCloudflareBash(\n createBashProgram(\n args.params.code,\n effectiveTools,\n args.cloudflareConfig,\n workspaceRoot\n ),\n { ...args.cloudflareConfig, timeoutMs }\n );\n } else {\n result = await executeCloudflareCode(\n {\n lang: 'py',\n code: createPythonProgram(\n args.params.code,\n effectiveTools,\n args.cloudflareConfig,\n workspaceRoot\n ),\n },\n { ...args.cloudflareConfig, timeoutMs }\n );\n }\n\n if (result.exitCode !== 0 || result.timedOut) {\n throw new Error(\n result.stderr !== ''\n ? result.stderr\n : `Cloudflare ${args.runtime} programmatic execution exited with code ${result.exitCode ?? 'unknown'}`\n );\n }\n\n return formatCompletedResponse({\n status: 'completed',\n session_id: 'cloudflare-sandbox',\n stdout: result.stdout,\n stderr: result.stderr,\n files: [],\n });\n}\n\nexport function createCloudflareProgrammaticToolCallingTool(\n cloudflareConfig: t.CloudflareSandboxExecutionConfig\n): DynamicStructuredTool {\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ProgrammaticParams;\n return runProgrammatic({\n params,\n config,\n cloudflareConfig,\n runtime: resolveRuntime(params),\n });\n },\n {\n name: ProgrammaticToolCallingName,\n description: `${ProgrammaticToolCallingDescription}\\n\\nCloudflare Sandbox engine: exposes the built-in coding tools inside the sandbox process. Non-coding host tools are not callable from this in-sandbox programmatic runner.`,\n schema: createCloudflareProgrammaticToolCallingSchema(cloudflareConfig),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport function createCloudflareBashProgrammaticToolCallingTool(\n cloudflareConfig: t.CloudflareSandboxExecutionConfig\n): DynamicStructuredTool {\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ProgrammaticParams;\n return runProgrammatic({\n params,\n config,\n cloudflareConfig,\n runtime: 'bash',\n });\n },\n {\n name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n description: `${BashProgrammaticToolCallingDescription}\\n\\nCloudflare Sandbox engine: exposes the built-in coding tools as bash functions inside the sandbox process. Non-coding host tools are not callable from this in-sandbox programmatic runner.`,\n schema:\n createCloudflareBashProgrammaticToolCallingSchema(cloudflareConfig),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CloudflareProgrammaticToolCalling.mjs","names":[],"sources":["../../../../src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts"],"sourcesContent":["import { tool } from '@langchain/core/tools';\nimport type { DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\n\n/* eslint-disable no-useless-escape -- generated sandbox helper source needs escapes for emitted JS/Python string literals. */\nimport {\n formatCompletedResponse,\n normalizeToPythonIdentifier,\n ProgrammaticToolCallingDescription,\n ProgrammaticToolCallingName,\n ProgrammaticToolCallingSchema,\n filterToolsByUsage,\n} from '@/tools/ProgrammaticToolCalling';\nimport {\n BashProgrammaticToolCallingDescription,\n BashProgrammaticToolCallingSchema,\n filterBashToolsByUsage,\n normalizeToBashIdentifier,\n} from '@/tools/BashProgrammaticToolCalling';\nimport { Constants } from '@/common';\nimport {\n clientExecTimeoutMs,\n execWithClientTimeout,\n executeCloudflareCode,\n getCloudflareWorkspaceRoot,\n resolveCloudflareSandbox,\n validateCloudflareBashCommand,\n} from './CloudflareSandboxExecutionEngine';\n\ntype ProgrammaticParams = {\n code: string;\n timeout?: number;\n lang?: string;\n runtime?: string;\n language?: string;\n};\n\nconst DEFAULT_TIMEOUT = 60000;\nconst MIN_TIMEOUT = 1000;\nconst MAX_TIMEOUT = 300000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 200000;\n\ntype TimeoutSchema = {\n type: 'integer';\n minimum: number;\n maximum: number;\n default: number;\n description: string;\n};\n\ntype CloudflareProgrammaticToolCallingJsonSchema = {\n type: 'object';\n properties: typeof ProgrammaticToolCallingSchema.properties & {\n timeout: TimeoutSchema;\n lang: {\n type: 'string';\n enum: readonly ['py', 'python', 'bash', 'sh'];\n default: 'bash';\n description: string;\n };\n };\n required: readonly ['code'];\n};\n\ntype CloudflareBashProgrammaticToolCallingJsonSchema = {\n type: 'object';\n properties: typeof BashProgrammaticToolCallingSchema.properties & {\n timeout: TimeoutSchema;\n };\n required: readonly ['code'];\n};\n\nconst NATIVE_TOOL_NAMES = new Set<string>([\n Constants.READ_FILE,\n Constants.WRITE_FILE,\n Constants.EDIT_FILE,\n Constants.GREP_SEARCH,\n Constants.GLOB_SEARCH,\n Constants.LIST_DIRECTORY,\n Constants.COMPILE_CHECK,\n Constants.BASH_TOOL,\n Constants.EXECUTE_CODE,\n]);\n\nfunction normalizeTimeout(timeoutMs: number | undefined): number {\n if (timeoutMs == null || !Number.isFinite(timeoutMs)) {\n return DEFAULT_TIMEOUT;\n }\n return Math.max(MIN_TIMEOUT, Math.floor(timeoutMs));\n}\n\nfunction formatTimeout(timeoutMs: number): string {\n return timeoutMs % 1000 === 0\n ? `${timeoutMs / 1000} seconds`\n : `${timeoutMs} milliseconds`;\n}\n\nfunction createTimeoutSchema(timeoutMs?: number): TimeoutSchema {\n const defaultTimeout = normalizeTimeout(timeoutMs);\n const maxTimeout = Math.max(MAX_TIMEOUT, defaultTimeout);\n return {\n type: 'integer',\n minimum: MIN_TIMEOUT,\n maximum: maxTimeout,\n default: defaultTimeout,\n description:\n 'Maximum Cloudflare Sandbox execution time in milliseconds. ' +\n `Default: ${formatTimeout(defaultTimeout)}. Max: ${formatTimeout(maxTimeout)}.`,\n };\n}\n\nfunction clampExecutionTimeout(\n requestedTimeoutMs: number | undefined,\n configuredTimeoutMs: number | undefined\n): number {\n const defaultTimeout = normalizeTimeout(configuredTimeoutMs);\n const maxTimeout = Math.max(MAX_TIMEOUT, defaultTimeout);\n if (requestedTimeoutMs == null || !Number.isFinite(requestedTimeoutMs)) {\n return defaultTimeout;\n }\n return Math.min(\n Math.max(MIN_TIMEOUT, Math.floor(requestedTimeoutMs)),\n maxTimeout\n );\n}\n\nfunction quoteShell(value: string): string {\n if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(value)) {\n return value;\n }\n const escapedQuote = String.raw`'\\''`;\n return `'${value.replace(/'/g, escapedQuote)}'`;\n}\n\nfunction truncateOutput(\n value: string,\n maxChars = DEFAULT_MAX_OUTPUT_CHARS\n): string {\n if (value.length <= maxChars) {\n return value;\n }\n const head = Math.floor(maxChars * 0.6);\n const tail = maxChars - head;\n const omitted = value.length - maxChars;\n return `${value.slice(0, head)}\\n\\n[... ${omitted} characters truncated ...]\\n\\n${value.slice(\n value.length - tail\n )}`;\n}\n\nfunction withInSandboxTimeout(command: string, timeoutMs: number): string {\n const timeoutSeconds = Math.max(1, Math.ceil(timeoutMs / 1000));\n return `timeout -k 2s ${timeoutSeconds}s ${command}`;\n}\n\nfunction outerTimeoutMs(timeoutMs: number): number {\n return timeoutMs + 5000;\n}\n\nfunction isInSandboxTimeoutExit(exitCode: number | null): boolean {\n return exitCode === 124 || exitCode === 137;\n}\n\nasync function executeGeneratedCloudflareBash(\n command: string,\n config: t.CloudflareSandboxExecutionConfig\n): ReturnType<typeof executeCloudflareCode> {\n const sandbox = await resolveCloudflareSandbox(config);\n const workspaceRoot = getCloudflareWorkspaceRoot(config);\n const shell = config.shell ?? 'bash';\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT;\n const result = await execWithClientTimeout(\n sandbox,\n withInSandboxTimeout(`${shell} -lc ${quoteShell(command)}`, timeoutMs),\n {\n cwd: workspaceRoot,\n env: config.env,\n timeout: outerTimeoutMs(timeoutMs),\n },\n clientExecTimeoutMs(timeoutMs),\n 'cloudflare bash programmatic exec'\n );\n const maxOutputChars = config.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;\n return {\n stdout: truncateOutput(result.stdout, maxOutputChars),\n stderr: truncateOutput(result.stderr, maxOutputChars),\n exitCode: result.exitCode,\n timedOut: isInSandboxTimeoutExit(result.exitCode),\n };\n}\n\nfunction createCloudflareProgrammaticToolCallingSchema(\n config: t.CloudflareSandboxExecutionConfig\n): CloudflareProgrammaticToolCallingJsonSchema {\n return {\n ...ProgrammaticToolCallingSchema,\n properties: {\n ...ProgrammaticToolCallingSchema.properties,\n timeout: createTimeoutSchema(config.timeoutMs),\n lang: {\n type: 'string',\n enum: ['py', 'python', 'bash', 'sh'],\n default: 'bash',\n description:\n 'Cloudflare Sandbox runtime for orchestration code. Defaults to bash; use py/python for Python orchestration.',\n },\n },\n } as const;\n}\n\nfunction createCloudflareBashProgrammaticToolCallingSchema(\n config: t.CloudflareSandboxExecutionConfig\n): CloudflareBashProgrammaticToolCallingJsonSchema {\n return {\n ...BashProgrammaticToolCallingSchema,\n properties: {\n ...BashProgrammaticToolCallingSchema.properties,\n timeout: createTimeoutSchema(config.timeoutMs),\n },\n } as const;\n}\n\nfunction resolveRuntime(params: ProgrammaticParams): 'python' | 'bash' {\n const raw = params.lang ?? params.runtime ?? params.language ?? 'bash';\n return raw === 'py' || raw === 'python' ? 'python' : 'bash';\n}\n\nfunction filterNativeTools(\n toolDefs: t.LCTool[],\n code: string,\n runtime: 'python' | 'bash'\n): t.LCTool[] {\n const nativeDefs = toolDefs.filter((def) => NATIVE_TOOL_NAMES.has(def.name));\n const filter =\n runtime === 'bash' ? filterBashToolsByUsage : filterToolsByUsage;\n return filter(nativeDefs, code);\n}\n\nfunction indent(code: string, spaces = 4): string {\n const prefix = ' '.repeat(spaces);\n return code\n .split('\\n')\n .map((line) => (line === '' ? line : prefix + line))\n .join('\\n');\n}\n\nfunction pythonBoolean(value: boolean | undefined): 'True' | 'False' {\n return value === true ? 'True' : 'False';\n}\n\nfunction createPythonNativeToolSource(\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n return `\nimport asyncio, fnmatch, glob, json, os, pathlib, re, shlex, shutil, subprocess, sys, tempfile\n\nWORKSPACE = ${JSON.stringify(workspaceRoot)}\nSHELL = ${JSON.stringify(config.shell ?? 'bash')}\nREAD_ONLY = ${pythonBoolean(config.readOnly)}\nALLOW_DANGEROUS_COMMANDS = ${pythonBoolean(config.allowDangerousCommands)}\nDESTRUCTIVE_TARGET = r\"(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:/?\\\\.?\\\\*|/)?\"\nDANGEROUS_COMMAND_PATTERNS = [\n re.compile(r\"\\\\brm\\\\s+(?:-[^\\\\s]*[rf][^\\\\s]*\\\\s+|-[^\\\\s]*[r][^\\\\s]*\\\\s+-[^\\\\s]*[f][^\\\\s]*\\\\s+)(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"\\\\s*(?:$|[;&|])\"),\n re.compile(r\"\\\\b(?:mkfs|mkswap|fdisk|parted|diskutil)\\\\b\"),\n re.compile(r\"\\\\bdd\\\\s+[^;&|]*\\\\bof=/dev/\"),\n re.compile(r\"\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"(?:$|\\\\s|[;&|])\"),\n re.compile(r\"\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?\" + DESTRUCTIVE_TARGET + r\"(?:$|\\\\s|[;&|])\"),\n re.compile(r\":\\\\s*\\\\(\\\\s*\\\\)\\\\s*\\\\{\\\\s*:\\\\s*\\\\|\\\\s*:\\\\s*&\\\\s*\\\\}\\\\s*;\\\\s*:\"),\n]\nQUOTED_DESTRUCTIVE_PATTERNS = [\n re.compile(r\"\\\\brm\\\\s+(?:-[^\\\\s]*[rf][^\\\\s]*\\\\s+){1,3}(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n re.compile(r\"\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n re.compile(r\"\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?[\\\\\"']\" + DESTRUCTIVE_TARGET + r\"[\\\\\"']\"),\n]\nNESTED_SHELL_PREFIX = r\"(?:(?:ba|z|da|k)?sh|eval)\\\\s+(?:-l?c\\\\s+)?\"\nNESTED_SHELL_DESTRUCTIVE_PATTERNS = [\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\brm\\\\s+-[^\\\\s\\\\\"']*[rf][^\\\\s\\\\\"']*\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\bchmod\\\\s+-R\\\\s+(?:777|a\\\\+w)\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n re.compile(NESTED_SHELL_PREFIX + r\"[\\\\\"'][^\\\\\"']*\\\\bchown\\\\s+-R\\\\s+[^;&|]+\\\\s+(?:--\\\\s+)?(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)\"),\n]\nMUTATING_COMMAND_PATTERN = re.compile(r\"\\\\b(?:rm|mv|cp|touch|mkdir|rmdir|ln|truncate|tee|sed\\\\s+-i|perl\\\\s+-pi|python(?:3)?\\\\s+-c|node\\\\s+-e|npm\\\\s+(?:install|ci|update|publish)|pnpm\\\\s+(?:install|update|publish)|yarn\\\\s+(?:install|add|publish)|git\\\\s+(?:add|commit|checkout|switch|reset|clean|rebase|merge|push|pull|stash|tag|branch)|chmod|chown)\\\\b|(?:^|[^<])>\\\\s*[^&]|\\\\bcat\\\\s+[^|;&]*>\\\\s*\")\nPROTECTED_TARGET_ARG_RE = re.compile(r\"^(?:/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:/?\\\\.?\\\\*|/)?$\")\nDESTRUCTIVE_OP_IN_COMMAND_RE = re.compile(r\"\\\\b(?:rm\\\\s+-[^\\\\s]*[rf]|chmod\\\\s+-R|chown\\\\s+-R)\\\\b\")\n\ndef _is_within_workspace(file_path):\n resolved = os.path.abspath(file_path)\n root = os.path.abspath(WORKSPACE)\n return os.path.commonpath([root, resolved]) == root\n\ndef _resolve(file_path=\".\"):\n raw = file_path or \".\"\n candidate = raw if os.path.isabs(raw) else os.path.join(WORKSPACE, raw)\n resolved = os.path.abspath(candidate)\n if not _is_within_workspace(resolved):\n raise ValueError(f\"Path is outside the Cloudflare sandbox workspace: {file_path}\")\n return resolved\n\ndef _assert_writable(tool_name):\n if READ_ONLY:\n raise PermissionError(f\"{tool_name} is blocked in read-only Cloudflare sandbox mode.\")\n\ndef _strip_quoted_content(command):\n output = []\n quote = None\n escaped = False\n index = 0\n while index < len(command):\n char = command[index]\n if escaped:\n escaped = False\n output.append(\" \")\n index += 1\n continue\n if char == \"\\\\\\\\\":\n escaped = True\n output.append(\" \")\n index += 1\n continue\n if quote is not None:\n if char == quote:\n quote = None\n output.append(\" \")\n index += 1\n continue\n if char in (\"'\", '\"', \"\\`\"):\n quote = char\n output.append(\" \")\n index += 1\n continue\n if char == \"#\":\n while index < len(command) and command[index] != \"\\\\n\":\n output.append(\" \")\n index += 1\n output.append(\"\\\\n\")\n index += 1\n continue\n output.append(char)\n index += 1\n return \"\".join(output)\n\ndef _validate_bash_command(command, args=None):\n errors = []\n normalized = _strip_quoted_content(command)\n if command.strip() == \"\":\n errors.append(\"Command is empty.\")\n if \"\\\\0\" in command:\n errors.append(\"Command contains a NUL byte.\")\n if not ALLOW_DANGEROUS_COMMANDS:\n if any(pattern.search(normalized) for pattern in DANGEROUS_COMMAND_PATTERNS):\n errors.append(\"Command matches a destructive command pattern.\")\n elif any(pattern.search(command) for pattern in QUOTED_DESTRUCTIVE_PATTERNS):\n errors.append(\"Command matches a destructive command pattern (quoted target).\")\n elif any(pattern.search(command) for pattern in NESTED_SHELL_DESTRUCTIVE_PATTERNS):\n errors.append(\"Command matches a destructive command pattern (nested shell payload).\")\n elif args and DESTRUCTIVE_OP_IN_COMMAND_RE.search(command):\n offending = next((str(arg) for arg in args if PROTECTED_TARGET_ARG_RE.search(str(arg))), None)\n if offending is not None:\n errors.append(f\"Command matches a destructive command pattern (protected target \\\\\"{offending}\\\\\" passed via positional arg).\")\n if READ_ONLY and MUTATING_COMMAND_PATTERN.search(normalized):\n errors.append(\"Command appears to mutate files or repository state in read-only Cloudflare sandbox mode.\")\n if errors:\n raise ValueError(\"\\\\n\".join(errors))\n\ndef _line_window(content, offset=None, limit=None):\n start = max((offset or 1) - 1, 0)\n lines = content.split(\"\\\\n\")\n selected = lines[start:] if not limit or limit <= 0 else lines[start:start + limit]\n return \"\\\\n\".join(f\"{start + idx + 1:6d}\\\\t{line}\" for idx, line in enumerate(selected))\n\ndef _run(command, timeout=None, args=None):\n _validate_bash_command(command, args=args)\n completed = subprocess.run(\n [SHELL, \"-lc\", command, \"--\"] + [str(arg) for arg in (args or [])],\n cwd=WORKSPACE,\n capture_output=True,\n text=True,\n timeout=(timeout / 1000 if timeout else None),\n )\n return {\n \"stdout\": completed.stdout,\n \"stderr\": completed.stderr,\n \"exit_code\": completed.returncode,\n }\n\ndef _format_run(result):\n text = \"\"\n if result.get(\"stdout\"):\n text += f\"stdout:\\\\n{result['stdout']}\\\\n\"\n else:\n text += \"stdout: Empty. Ensure you're writing output explicitly.\\\\n\"\n if result.get(\"stderr\"):\n text += f\"stderr:\\\\n{result['stderr']}\\\\n\"\n if result.get(\"exit_code\") not in (None, 0):\n text += f\"exit_code: {result['exit_code']}\\\\n\"\n text += f\"working_directory: {WORKSPACE}\"\n return text.strip()\n\ndef _detect_compile_command():\n if os.path.exists(os.path.join(WORKSPACE, \"tsconfig.json\")):\n return \"typescript\", \"npx --no-install tsc --noEmit\", \"tsconfig.json present\"\n package_json = os.path.join(WORKSPACE, \"package.json\")\n if os.path.exists(package_json):\n try:\n if '\"typescript\"' in open(package_json, encoding=\"utf-8\").read():\n return \"typescript\", \"npx --no-install tsc --noEmit\", \"package.json declares typescript\"\n except Exception:\n pass\n if os.path.exists(os.path.join(WORKSPACE, \"Cargo.toml\")):\n return \"rust\", \"cargo check --message-format=short\", \"Cargo.toml present\"\n if os.path.exists(os.path.join(WORKSPACE, \"go.mod\")):\n return \"go\", \"go vet ./...\", \"go.mod present\"\n if any(os.path.exists(os.path.join(WORKSPACE, name)) for name in [\"pyproject.toml\", \"setup.py\", \"setup.cfg\"]):\n return \"python-compile\", \"python3 -m py_compile $(find . -name '*.py' -not -path './.venv/*' -not -path './node_modules/*')\", \"Python project\"\n return \"unknown\", \"\", \"no recognised project marker\"\n\nasync def bash_tool(command, args=None):\n return _format_run(_run(command, args=args))\n\nasync def execute_code(lang, code, args=None):\n args = args or []\n temp_dir = tempfile.mkdtemp(prefix=\"lc-ptc-\", dir=WORKSPACE)\n try:\n def q(value):\n import shlex\n return shlex.quote(str(value))\n arg_text = \" \".join(q(arg) for arg in args)\n if lang in (\"py\", \"python\"):\n file_path = os.path.join(temp_dir, \"main.py\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"python3 {q(file_path)} {arg_text}\"))\n if lang in (\"js\", \"javascript\"):\n file_path = os.path.join(temp_dir, \"main.js\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"node {q(file_path)} {arg_text}\"))\n if lang in (\"ts\", \"typescript\"):\n file_path = os.path.join(temp_dir, \"main.ts\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"npx --no-install tsx {q(file_path)} {arg_text}\"))\n if lang == \"php\":\n file_path = os.path.join(temp_dir, \"main.php\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"php {q(file_path)} {arg_text}\"))\n if lang == \"go\":\n file_path = os.path.join(temp_dir, \"main.go\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"go run {q(file_path)} {arg_text}\"))\n if lang == \"rs\":\n file_path = os.path.join(temp_dir, \"main.rs\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-rs\")\n return _format_run(_run(f\"rustc {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"c\":\n file_path = os.path.join(temp_dir, \"main.c\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-c\")\n return _format_run(_run(f\"cc {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"cpp\":\n file_path = os.path.join(temp_dir, \"main.cpp\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-cpp\")\n return _format_run(_run(f\"c++ {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"java\":\n file_path = os.path.join(temp_dir, \"Main.java\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"javac {q(file_path)} && java -cp {q(temp_dir)} Main {arg_text}\"))\n if lang == \"r\":\n file_path = os.path.join(temp_dir, \"main.R\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n return _format_run(_run(f\"Rscript {q(file_path)} {arg_text}\"))\n if lang == \"d\":\n file_path = os.path.join(temp_dir, \"main.d\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-d\")\n return _format_run(_run(f\"dmd {q(file_path)} -of={q(binary)} && {q(binary)} {arg_text}\"))\n if lang == \"f90\":\n file_path = os.path.join(temp_dir, \"main.f90\")\n open(file_path, \"w\", encoding=\"utf-8\").write(code)\n binary = os.path.join(temp_dir, \"main-f90\")\n return _format_run(_run(f\"gfortran {q(file_path)} -o {q(binary)} && {q(binary)} {arg_text}\"))\n if lang in (\"bash\", \"sh\"):\n return _format_run(_run(code, args=args))\n raise ValueError(f\"Unsupported Cloudflare sandbox runtime: {lang}\")\n finally:\n shutil.rmtree(temp_dir, ignore_errors=True)\n\nasync def read_file(path, offset=None, limit=None):\n resolved = _resolve(path)\n with open(resolved, encoding=\"utf-8\") as handle:\n return _line_window(handle.read(), offset, limit)\n\nasync def write_file(path, content):\n _assert_writable(\"write_file\")\n resolved = _resolve(path)\n os.makedirs(os.path.dirname(resolved), exist_ok=True)\n existed = os.path.exists(resolved)\n with open(resolved, \"w\", encoding=\"utf-8\") as handle:\n handle.write(content)\n return f\"{'Overwrote' if existed else 'Created'} {resolved} ({len(content)} chars).\"\n\nasync def edit_file(path, old_text=None, new_text=None, edits=None):\n _assert_writable(\"edit_file\")\n resolved = _resolve(path)\n edits = edits or [{\"old_text\": old_text, \"new_text\": new_text}]\n content = open(resolved, encoding=\"utf-8\").read()\n for edit in edits:\n old = edit.get(\"old_text\") or \"\"\n new = edit.get(\"new_text\") or \"\"\n if content.count(old) != 1:\n raise ValueError(f\"Could not locate old_text exactly once in {path}\")\n content = content.replace(old, new, 1)\n open(resolved, \"w\", encoding=\"utf-8\").write(content)\n return f\"Applied {len(edits)} edit(s) to {resolved}.\"\n\nasync def list_directory(path=\".\"):\n resolved = _resolve(path)\n entries = []\n for name in sorted(os.listdir(resolved)):\n full = os.path.join(resolved, name)\n entries.append((\"dir \" if os.path.isdir(full) else \"file\") + \"\\\\t\" + name)\n return \"\\\\n\".join(entries) or \"Directory is empty.\"\n\nasync def grep_search(pattern, path=\".\", glob=None, max_results=200):\n root = _resolve(path)\n regex = re.compile(pattern)\n out = []\n for current, dirs, files in os.walk(root):\n dirs[:] = [d for d in dirs if d not in {\".git\", \"node_modules\", \".venv\", \"dist\", \"build\"}]\n for name in files:\n rel = os.path.relpath(os.path.join(current, name), root)\n if glob and not fnmatch.fnmatch(rel, glob):\n continue\n try:\n for line_no, line in enumerate(open(os.path.join(current, name), encoding=\"utf-8\", errors=\"ignore\"), 1):\n if regex.search(line):\n out.append(f\"{os.path.join(current, name)}:{line_no}:{line.rstrip()}\")\n if len(out) >= max_results:\n return \"\\\\n\".join(out)\n except Exception:\n pass\n return \"\\\\n\".join(out) if out else \"No matches found.\"\n\nasync def glob_search(pattern, path=\".\", max_results=200):\n root = _resolve(path)\n target = pattern if os.path.isabs(pattern) else os.path.join(root, pattern)\n matches = []\n for match in glob_module.glob(target, recursive=True):\n resolved = os.path.abspath(match)\n if _is_within_workspace(resolved):\n matches.append(resolved)\n if len(matches) >= max_results:\n break\n return \"\\\\n\".join(matches) if matches else \"No files found.\"\n\nasync def compile_check(command=None, timeout_ms=None):\n kind, detected, reason = _detect_compile_command()\n command = command or detected\n if not command:\n return f\"compile_check: {reason}. Pass an explicit command to override.\"\n result = _run(command, timeout_ms)\n status = \"PASSED\" if result[\"exit_code\"] == 0 else \"FAILED\"\n return f\"compile_check ({kind}) {status} via {command}\\\\n\\\\nstdout:\\\\n{result['stdout']}\\\\nstderr:\\\\n{result['stderr']}\\\\nworking_directory: {WORKSPACE}\\\\nreason: {reason}\"\n\n# Avoid shadowing the glob_search function argument named \"glob\".\nglob_module = glob\n`.trim();\n}\n\nfunction createNodeNativeToolSource(\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n return `\nconst fs = require(\"fs\");\nconst fsp = fs.promises;\nconst path = require(\"path\");\nconst cp = require(\"child_process\");\n\nconst WORKSPACE = ${JSON.stringify(workspaceRoot)};\nconst SHELL = ${JSON.stringify(config.shell ?? 'bash')};\nconst READ_ONLY = ${JSON.stringify(config.readOnly === true)};\nconst ALLOW_DANGEROUS_COMMANDS = ${JSON.stringify(config.allowDangerousCommands === true)};\nconst DESTRUCTIVE_TARGET = \"(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)(?:\\\\\\\\/?\\\\\\\\.?\\\\\\\\*|\\\\\\\\/)?\";\nconst DANGEROUS_COMMAND_PATTERNS = [\n new RegExp(\"\\\\\\\\brm\\\\\\\\s+(?:-[^\\\\\\\\s]*[rf][^\\\\\\\\s]*\\\\\\\\s+|-[^\\\\\\\\s]*[r][^\\\\\\\\s]*\\\\\\\\s+-[^\\\\\\\\s]*[f][^\\\\\\\\s]*\\\\\\\\s+)(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"\\\\\\\\s*(?:$|[;&|])\"),\n /\\\\b(?:mkfs|mkswap|fdisk|parted|diskutil)\\\\b/,\n /\\\\bdd\\\\s+[^;&|]*\\\\bof=\\\\/dev\\\\//,\n new RegExp(\"\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"(?:$|\\\\\\\\s|[;&|])\"),\n new RegExp(\"\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?\" + DESTRUCTIVE_TARGET + \"(?:$|\\\\\\\\s|[;&|])\"),\n /:\\\\s*\\\\(\\\\s*\\\\)\\\\s*\\\\{\\\\s*:\\\\s*\\\\|\\\\s*:\\\\s*&\\\\s*\\\\}\\\\s*;\\\\s*:/,\n];\nconst QUOTED_DESTRUCTIVE_PATTERNS = [\n new RegExp(\"\\\\\\\\brm\\\\\\\\s+(?:-[^\\\\\\\\s]*[rf][^\\\\\\\\s]*\\\\\\\\s+){1,3}(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n new RegExp(\"\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n new RegExp(\"\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?[\\\\\\\"']\" + DESTRUCTIVE_TARGET + \"[\\\\\\\"']\"),\n];\nconst NESTED_SHELL_PREFIX = \"(?:(?:ba|z|da|k)?sh|eval)\\\\\\\\s+(?:-l?c\\\\\\\\s+)?\";\nconst NESTED_SHELL_DESTRUCTIVE_PATTERNS = [\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\brm\\\\\\\\s+-[^\\\\\\\\s\\\\\\\"']*[rf][^\\\\\\\\s\\\\\\\"']*\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\bchmod\\\\\\\\s+-R\\\\\\\\s+(?:777|a\\\\\\\\+w)\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n new RegExp(NESTED_SHELL_PREFIX + \"[\\\\\\\"'][^\\\\\\\"']*\\\\\\\\bchown\\\\\\\\s+-R\\\\\\\\s+[^;&|]+\\\\\\\\s+(?:--\\\\\\\\s+)?(?:\\\\\\\\/|~|\\\\\\\\$\\\\\\\\{?HOME\\\\\\\\}?|\\\\\\\\.)\"),\n];\nconst MUTATING_COMMAND_PATTERN = /\\\\b(?:rm|mv|cp|touch|mkdir|rmdir|ln|truncate|tee|sed\\\\s+-i|perl\\\\s+-pi|python(?:3)?\\\\s+-c|node\\\\s+-e|npm\\\\s+(?:install|ci|update|publish)|pnpm\\\\s+(?:install|update|publish)|yarn\\\\s+(?:install|add|publish)|git\\\\s+(?:add|commit|checkout|switch|reset|clean|rebase|merge|push|pull|stash|tag|branch)|chmod|chown)\\\\b|(?:^|[^<])>\\\\s*[^&]|\\\\bcat\\\\s+[^|;&]*>\\\\s*/;\nconst PROTECTED_TARGET_ARG_RE = /^(?:\\\\/|~|\\\\$\\\\{?HOME\\\\}?|\\\\.)(?:\\\\/?\\\\.?\\\\*|\\\\/)?$/;\nconst DESTRUCTIVE_OP_IN_COMMAND_RE = /\\\\b(?:rm\\\\s+-[^\\\\s]*[rf]|chmod\\\\s+-R|chown\\\\s+-R)\\\\b/;\n\nfunction resolvePath(filePath) {\n const raw = filePath || \".\";\n const candidate = path.isAbsolute(raw) ? raw : path.join(WORKSPACE, raw);\n const resolved = path.resolve(candidate);\n const root = path.resolve(WORKSPACE);\n const relative = path.relative(root, resolved);\n if (relative && (relative.startsWith(\"..\") || path.isAbsolute(relative))) {\n throw new Error(\"Path is outside the Cloudflare sandbox workspace: \" + filePath);\n }\n return resolved;\n}\n\nfunction assertWritable(toolName) {\n if (READ_ONLY) {\n throw new Error(toolName + \" is blocked in read-only Cloudflare sandbox mode.\");\n }\n}\n\nfunction stripQuotedContent(command) {\n let output = \"\";\n let quote;\n let escaped = false;\n for (let index = 0; index < command.length; index++) {\n const char = command[index];\n if (escaped) {\n escaped = false;\n output += \" \";\n continue;\n }\n if (char === \"\\\\\\\\\") {\n escaped = true;\n output += \" \";\n continue;\n }\n if (quote != null) {\n if (char === quote) quote = undefined;\n output += \" \";\n continue;\n }\n if (char === \"\\\\\\\"\" || char === \"'\" || char === \"\\`\") {\n quote = char;\n output += \" \";\n continue;\n }\n if (char === \"#\") {\n while (index < command.length && command[index] !== \"\\\\n\") {\n output += \" \";\n index += 1;\n }\n output += \"\\\\n\";\n continue;\n }\n output += char;\n }\n return output;\n}\n\nfunction validateBashCommand(command, args) {\n const errors = [];\n const normalized = stripQuotedContent(command);\n if (command.trim() === \"\") {\n errors.push(\"Command is empty.\");\n }\n if (command.includes(\"\\\\0\")) {\n errors.push(\"Command contains a NUL byte.\");\n }\n if (!ALLOW_DANGEROUS_COMMANDS) {\n if (DANGEROUS_COMMAND_PATTERNS.some((pattern) => pattern.test(normalized))) {\n errors.push(\"Command matches a destructive command pattern.\");\n } else if (QUOTED_DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command))) {\n errors.push(\"Command matches a destructive command pattern (quoted target).\");\n } else if (NESTED_SHELL_DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(command))) {\n errors.push(\"Command matches a destructive command pattern (nested shell payload).\");\n } else if ((args || []).length > 0 && DESTRUCTIVE_OP_IN_COMMAND_RE.test(command)) {\n const offending = (args || []).map(String).find((arg) => PROTECTED_TARGET_ARG_RE.test(arg));\n if (offending !== undefined) {\n errors.push(\"Command matches a destructive command pattern (protected target \\\\\"\" + offending + \"\\\\\" passed via positional arg).\");\n }\n }\n }\n if (READ_ONLY && MUTATING_COMMAND_PATTERN.test(normalized)) {\n errors.push(\"Command appears to mutate files or repository state in read-only Cloudflare sandbox mode.\");\n }\n if (errors.length > 0) {\n throw new Error(errors.join(\"\\\\n\"));\n }\n}\n\nfunction lineWindow(content, offset, limit) {\n const start = Math.max((offset || 1) - 1, 0);\n const lines = content.split(\"\\\\n\");\n const selected = !limit || limit <= 0 ? lines.slice(start) : lines.slice(start, start + limit);\n return selected.map((line, index) => String(start + index + 1).padStart(6, \" \") + \"\\\\t\" + line).join(\"\\\\n\");\n}\n\nfunction quote(value) {\n const text = String(value);\n if (text === \"\") return \"''\";\n if (/^[A-Za-z0-9_/:=.,@%+-]+$/.test(text)) return text;\n return \"'\" + text.replace(/'/g, \"'\\\\\\\\''\") + \"'\";\n}\n\nfunction run(command, timeoutMs, args) {\n validateBashCommand(command, args);\n return new Promise((resolve) => {\n const child = cp.spawn(SHELL, [\"-lc\", command, \"--\", ...((args || []).map(String))], {\n cwd: WORKSPACE,\n env: process.env,\n });\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n const timer = timeoutMs\n ? setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n }, timeoutMs)\n : null;\n\n child.stdout.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n child.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", (error) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr: stderr + error.message, exit_code: 1, timed_out: timedOut });\n });\n child.on(\"close\", (code) => {\n if (timer) clearTimeout(timer);\n resolve({ stdout, stderr, exit_code: timedOut ? null : code, timed_out: timedOut });\n });\n });\n}\n\nfunction formatRun(result) {\n let text = \"\";\n if (result.stdout) {\n text += \"stdout:\\\\n\" + result.stdout + \"\\\\n\";\n } else {\n text += \"stdout: Empty. Ensure you're writing output explicitly.\\\\n\";\n }\n if (result.stderr) {\n text += \"stderr:\\\\n\" + result.stderr + \"\\\\n\";\n }\n if (result.timed_out) {\n text += \"timed_out: true\\\\n\";\n }\n if (result.exit_code !== null && result.exit_code !== undefined && result.exit_code !== 0) {\n text += \"exit_code: \" + result.exit_code + \"\\\\n\";\n }\n text += \"working_directory: \" + WORKSPACE;\n return text.trim();\n}\n\nasync function detectCompileCommand() {\n async function exists(name) {\n try {\n await fsp.access(path.join(WORKSPACE, name));\n return true;\n } catch {\n return false;\n }\n }\n if (await exists(\"tsconfig.json\")) {\n return [\"typescript\", \"npx --no-install tsc --noEmit\", \"tsconfig.json present\"];\n }\n if (await exists(\"package.json\")) {\n try {\n if ((await fsp.readFile(path.join(WORKSPACE, \"package.json\"), \"utf8\")).includes('\"typescript\"')) {\n return [\"typescript\", \"npx --no-install tsc --noEmit\", \"package.json declares typescript\"];\n }\n } catch {}\n }\n if (await exists(\"Cargo.toml\")) return [\"rust\", \"cargo check --message-format=short\", \"Cargo.toml present\"];\n if (await exists(\"go.mod\")) return [\"go\", \"go vet ./...\", \"go.mod present\"];\n if (await exists(\"pyproject.toml\") || await exists(\"setup.py\") || await exists(\"setup.cfg\")) {\n return [\"python-compile\", \"python3 -m py_compile $(find . -name '*.py' -not -path './.venv/*' -not -path './node_modules/*')\", \"Python project\"];\n }\n return [\"unknown\", \"\", \"no recognised project marker\"];\n}\n\nfunction globToRegExp(pattern) {\n const escaped = pattern.replace(/[|\\\\\\\\{}()[\\\\]^$+*?.]/g, \"\\\\\\\\$&\");\n return new RegExp(\"^\" + escaped.replace(/\\\\\\\\\\\\*\\\\\\\\\\\\*/g, \".*\").replace(/\\\\\\\\\\\\*/g, \"[^/]*\") + \"$\");\n}\n\nfunction globMatch(relativePath, pattern) {\n const matcher = globToRegExp(pattern);\n return matcher.test(relativePath) || matcher.test(path.basename(relativePath));\n}\n\nasync function walkFiles(root, visit) {\n const entries = await fsp.readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n const full = path.join(root, entry.name);\n if (entry.isDirectory()) {\n if ([\".git\", \"node_modules\", \".venv\", \"dist\", \"build\"].includes(entry.name)) continue;\n await walkFiles(full, visit);\n } else if (entry.isFile()) {\n await visit(full);\n }\n }\n}\n\nasync function bash_tool(payload) {\n return formatRun(await run(payload.command, undefined, payload.args));\n}\n\nasync function execute_code(payload) {\n const lang = payload.lang;\n const code = payload.code;\n const args = payload.args || [];\n const tempDir = await fsp.mkdtemp(path.join(WORKSPACE, \"lc-ptc-\"));\n try {\n const argText = args.map(quote).join(\" \");\n async function writeAndRun(fileName, command) {\n const filePath = path.join(tempDir, fileName);\n await fsp.writeFile(filePath, code, \"utf8\");\n return formatRun(await run(command(filePath, argText), undefined, []));\n }\n if (lang === \"py\" || lang === \"python\") {\n return writeAndRun(\"main.py\", (filePath, argText) => \"python3 \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"js\" || lang === \"javascript\") {\n return writeAndRun(\"main.js\", (filePath, argText) => \"node \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"ts\" || lang === \"typescript\") {\n return writeAndRun(\"main.ts\", (filePath, argText) => \"npx --no-install tsx \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"php\") {\n return writeAndRun(\"main.php\", (filePath, argText) => \"php \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"go\") {\n return writeAndRun(\"main.go\", (filePath, argText) => \"go run \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"rs\") {\n return writeAndRun(\"main.rs\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-rs\");\n return \"rustc \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"c\") {\n return writeAndRun(\"main.c\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-c\");\n return \"cc \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"cpp\") {\n return writeAndRun(\"main.cpp\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-cpp\");\n return \"c++ \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"java\") {\n return writeAndRun(\"Main.java\", (filePath, argText) => \"javac \" + quote(filePath) + \" && java -cp \" + quote(tempDir) + \" Main \" + argText);\n }\n if (lang === \"r\") {\n return writeAndRun(\"main.R\", (filePath, argText) => \"Rscript \" + quote(filePath) + \" \" + argText);\n }\n if (lang === \"d\") {\n return writeAndRun(\"main.d\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-d\");\n return \"dmd \" + quote(filePath) + \" -of=\" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"f90\") {\n return writeAndRun(\"main.f90\", (filePath, argText) => {\n const binary = path.join(tempDir, \"main-f90\");\n return \"gfortran \" + quote(filePath) + \" -o \" + quote(binary) + \" && \" + quote(binary) + \" \" + argText;\n });\n }\n if (lang === \"bash\" || lang === \"sh\") {\n return formatRun(await run(code, undefined, args));\n }\n throw new Error(\"Unsupported Cloudflare sandbox runtime: \" + lang);\n } finally {\n await fsp.rm(tempDir, { recursive: true, force: true });\n }\n}\n\nasync function read_file(payload) {\n const resolved = resolvePath(payload.path);\n return lineWindow(await fsp.readFile(resolved, \"utf8\"), payload.offset, payload.limit);\n}\n\nasync function write_file(payload) {\n assertWritable(\"write_file\");\n const resolved = resolvePath(payload.path);\n await fsp.mkdir(path.dirname(resolved), { recursive: true });\n const existed = fs.existsSync(resolved);\n await fsp.writeFile(resolved, payload.content, \"utf8\");\n return (existed ? \"Overwrote \" : \"Created \") + resolved + \" (\" + payload.content.length + \" chars).\";\n}\n\nasync function edit_file(payload) {\n assertWritable(\"edit_file\");\n const resolved = resolvePath(payload.path);\n const edits = payload.edits || [{ old_text: payload.old_text, new_text: payload.new_text }];\n let content = await fsp.readFile(resolved, \"utf8\");\n for (const edit of edits) {\n const oldText = edit.old_text || \"\";\n const newText = edit.new_text || \"\";\n if (oldText === \"\" || content.split(oldText).length - 1 !== 1) {\n throw new Error(\"Could not locate old_text exactly once in \" + payload.path);\n }\n content = content.replace(oldText, newText);\n }\n await fsp.writeFile(resolved, content, \"utf8\");\n return \"Applied \" + edits.length + \" edit(s) to \" + resolved + \".\";\n}\n\nasync function list_directory(payload) {\n const resolved = resolvePath(payload.path || \".\");\n const entries = await fsp.readdir(resolved, { withFileTypes: true });\n const lines = entries\n .sort((a, b) => a.name.localeCompare(b.name))\n .map((entry) => (entry.isDirectory() ? \"dir\" : \"file\") + \"\\\\t\" + entry.name);\n return lines.join(\"\\\\n\") || \"Directory is empty.\";\n}\n\nasync function grep_search(payload) {\n const root = resolvePath(payload.path || \".\");\n const regex = new RegExp(payload.pattern);\n const maxResults = payload.max_results || 200;\n const out = [];\n await walkFiles(root, async (filePath) => {\n if (out.length >= maxResults) return;\n const relative = path.relative(root, filePath);\n if (payload.glob && !globMatch(relative, payload.glob)) return;\n let text = \"\";\n try {\n text = await fsp.readFile(filePath, \"utf8\");\n } catch {\n return;\n }\n text.split(\"\\\\n\").forEach((line, index) => {\n if (out.length < maxResults && regex.test(line)) {\n out.push(filePath + \":\" + (index + 1) + \":\" + line);\n }\n });\n });\n return out.join(\"\\\\n\") || \"No matches found.\";\n}\n\nasync function glob_search(payload) {\n const root = resolvePath(payload.path || \".\");\n const maxResults = payload.max_results || 200;\n const out = [];\n await walkFiles(root, async (filePath) => {\n if (out.length >= maxResults) return;\n const relative = path.relative(root, filePath);\n if (globMatch(relative, payload.pattern)) out.push(filePath);\n });\n return out.join(\"\\\\n\") || \"No files found.\";\n}\n\nasync function compile_check(payload) {\n const [kind, detected, reason] = await detectCompileCommand();\n const command = payload.command || detected;\n if (!command) {\n return \"compile_check: \" + reason + \". Pass an explicit command to override.\";\n }\n const result = await run(command, payload.timeout_ms);\n const status = result.exit_code === 0 ? \"PASSED\" : \"FAILED\";\n return \"compile_check (\" + kind + \") \" + status + \" via \" + command + \"\\\\n\\\\nstdout:\\\\n\" + result.stdout + \"\\\\nstderr:\\\\n\" + result.stderr + \"\\\\nworking_directory: \" + WORKSPACE + \"\\\\nreason: \" + reason;\n}\n\nconst TOOLS = {\n bash_tool,\n execute_code,\n read_file,\n write_file,\n edit_file,\n list_directory,\n grep_search,\n glob_search,\n compile_check,\n};\n\nasync function main() {\n const name = process.argv[2];\n const payload = JSON.parse(process.argv[3] || \"{}\");\n if (!TOOLS[name]) throw new Error(\"Unknown tool: \" + name);\n const result = await TOOLS[name](payload);\n process.stdout.write(typeof result === \"string\" ? result : JSON.stringify(result));\n}\n\nmain().catch((error) => {\n console.error(error && error.stack ? error.stack : String(error));\n process.exit(1);\n});\n`.trim();\n}\n\nfunction createPythonProgram(\n userCode: string,\n toolDefs: t.LCTool[],\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n const aliases = toolDefs\n .map((def) => {\n const pythonName = normalizeToPythonIdentifier(def.name);\n return NATIVE_TOOL_NAMES.has(def.name) && pythonName !== def.name\n ? `${pythonName} = globals()[${JSON.stringify(def.name)}]`\n : '';\n })\n .filter(Boolean)\n .join('\\n');\n return `${createPythonNativeToolSource(config, workspaceRoot)}\n${aliases}\n\nasync def __lc_user_main__():\n${indent(userCode)}\n\nasyncio.run(__lc_user_main__())\n`;\n}\n\nfunction createBashProgram(\n userCode: string,\n toolDefs: t.LCTool[],\n config: t.CloudflareSandboxExecutionConfig,\n workspaceRoot: string\n): string {\n const helper = createNodeNativeToolSource(config, workspaceRoot);\n const functions = toolDefs\n .map((def) => {\n const bashName = normalizeToBashIdentifier(def.name);\n if (!NATIVE_TOOL_NAMES.has(def.name)) {\n return '';\n }\n return `${bashName}() { node \"$__LC_TOOL_HELPER\" ${JSON.stringify(def.name)} \"$1\"; }`;\n })\n .filter(Boolean)\n .join('\\n');\n return `\nset -euo pipefail\ncommand -v node >/dev/null 2>&1 || { echo \"Cloudflare programmatic tool calling requires node in the sandbox image.\" >&2; exit 127; }\n__LC_TOOL_HELPER=\"$(mktemp /tmp/lc-tools.XXXXXX.js)\"\ncat > \"$__LC_TOOL_HELPER\" <<'JS'\n${helper}\nJS\ntrap 'rm -f \"$__LC_TOOL_HELPER\"' EXIT\n${functions}\n${userCode}\n`.trim();\n}\n\nasync function runProgrammatic(args: {\n params: ProgrammaticParams;\n config?: { toolCall?: unknown };\n cloudflareConfig: t.CloudflareSandboxExecutionConfig;\n runtime: 'python' | 'bash';\n}): Promise<[string, t.ProgrammaticExecutionArtifact]> {\n const toolCall = (args.config?.toolCall ??\n {}) as Partial<t.ProgrammaticCache>;\n const toolDefs = toolCall.toolDefs ?? [];\n const effectiveTools = filterNativeTools(\n toolDefs,\n args.params.code,\n args.runtime\n );\n const timeoutMs = clampExecutionTimeout(\n args.params.timeout,\n args.cloudflareConfig.timeoutMs\n );\n const workspaceRoot = getCloudflareWorkspaceRoot(args.cloudflareConfig);\n let result: Awaited<ReturnType<typeof executeCloudflareCode>>;\n\n if (args.runtime === 'bash') {\n await validateCloudflareBashCommand(args.params.code, [], {\n ...args.cloudflareConfig,\n timeoutMs,\n });\n result = await executeGeneratedCloudflareBash(\n createBashProgram(\n args.params.code,\n effectiveTools,\n args.cloudflareConfig,\n workspaceRoot\n ),\n { ...args.cloudflareConfig, timeoutMs }\n );\n } else {\n result = await executeCloudflareCode(\n {\n lang: 'py',\n code: createPythonProgram(\n args.params.code,\n effectiveTools,\n args.cloudflareConfig,\n workspaceRoot\n ),\n },\n { ...args.cloudflareConfig, timeoutMs }\n );\n }\n\n if (result.exitCode !== 0 || result.timedOut) {\n throw new Error(\n result.stderr !== ''\n ? result.stderr\n : `Cloudflare ${args.runtime} programmatic execution exited with code ${result.exitCode ?? 'unknown'}`\n );\n }\n\n return formatCompletedResponse({\n status: 'completed',\n session_id: 'cloudflare-sandbox',\n stdout: result.stdout,\n stderr: result.stderr,\n files: [],\n });\n}\n\nexport function createCloudflareProgrammaticToolCallingTool(\n cloudflareConfig: t.CloudflareSandboxExecutionConfig\n): DynamicStructuredTool {\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ProgrammaticParams;\n return runProgrammatic({\n params,\n config,\n cloudflareConfig,\n runtime: resolveRuntime(params),\n });\n },\n {\n name: ProgrammaticToolCallingName,\n description: `${ProgrammaticToolCallingDescription}\\n\\nCloudflare Sandbox engine: exposes the built-in coding tools inside the sandbox process. Non-coding host tools are not callable from this in-sandbox programmatic runner.`,\n schema: createCloudflareProgrammaticToolCallingSchema(cloudflareConfig),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport function createCloudflareBashProgrammaticToolCallingTool(\n cloudflareConfig: t.CloudflareSandboxExecutionConfig\n): DynamicStructuredTool {\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ProgrammaticParams;\n return runProgrammatic({\n params,\n config,\n cloudflareConfig,\n runtime: 'bash',\n });\n },\n {\n name: Constants.BASH_PROGRAMMATIC_TOOL_CALLING,\n description: `${BashProgrammaticToolCallingDescription}\\n\\nCloudflare Sandbox engine: exposes the built-in coding tools as bash functions inside the sandbox process. Non-coding host tools are not callable from this in-sandbox programmatic runner.`,\n schema:\n createCloudflareBashProgrammaticToolCallingSchema(cloudflareConfig),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WorkspaceClientTimeoutError, isWorkspaceClientTimeoutError } from "../local/workspaceFS.mjs";
|
|
1
2
|
import { LOCAL_SPAWN_TIMEOUT_MS, validateBashCommand } from "../local/LocalExecutionEngine.mjs";
|
|
2
3
|
import { posix } from "path";
|
|
3
4
|
import { PassThrough } from "stream";
|
|
@@ -61,6 +62,87 @@ function outerTimeoutMs(timeoutMs) {
|
|
|
61
62
|
function isInSandboxTimeoutExit(exitCode) {
|
|
62
63
|
return exitCode === 124 || exitCode === 137;
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Client-side backstop timeout for a `sandbox.exec()` await: a few seconds beyond
|
|
67
|
+
* the exec's own `timeout` option, so a stalled exec that never honors `timeout`
|
|
68
|
+
* still can't outlast this.
|
|
69
|
+
*/
|
|
70
|
+
function clientExecTimeoutMs(timeoutMs) {
|
|
71
|
+
return outerTimeoutMs(timeoutMs) + 5e3;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Client-side backstop timeout for a native-DO sandbox FILE-IO RPC
|
|
75
|
+
* (`readFile`/`writeFile`/`listFiles`/`mkdir`/`deleteFile`). Unlike `exec()`
|
|
76
|
+
* there is no in-sandbox `timeout(1)` layer for these to honor, so this is just a
|
|
77
|
+
* few seconds of headroom over the configured tool timeout — enough that a normal
|
|
78
|
+
* (even large, byte-capped) read completes, while a stalled/cold container can't
|
|
79
|
+
* outlast it. See `withClientTimeout` for why the native DO RPC needs this.
|
|
80
|
+
*/
|
|
81
|
+
function clientFsTimeoutMs(timeoutMs) {
|
|
82
|
+
return timeoutMs + 5e3;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Bound a `sandbox.exec()` await with a CLIENT-SIDE timeout.
|
|
86
|
+
*
|
|
87
|
+
* The native Cloudflare Sandbox Durable Object `exec()` is effectively
|
|
88
|
+
* uncancellable from the host: `ExecOptions` has no `signal` (so
|
|
89
|
+
* `supportsExecSignal` is false for the native transport), and its `timeout`
|
|
90
|
+
* option is not reliably enforced when the container/RPC itself stalls — while
|
|
91
|
+
* the in-sandbox `timeout(1)` wrapper only bounds a command that is actually
|
|
92
|
+
* running. So a stalled exec (an unresponsive/cold container) otherwise hangs
|
|
93
|
+
* until the host's run-level abort, burning the whole run budget on one tool
|
|
94
|
+
* call. This race guarantees the host await settles within `timeoutMs`
|
|
95
|
+
* regardless of the transport.
|
|
96
|
+
*
|
|
97
|
+
* On timeout the underlying `exec` promise may keep running in the DO (a
|
|
98
|
+
* native-DO exec cannot be truly cancelled), so its late settlement is swallowed
|
|
99
|
+
* to avoid an unhandled rejection.
|
|
100
|
+
*/
|
|
101
|
+
async function withClientTimeout(exec, timeoutMs, label, options = {}) {
|
|
102
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return exec;
|
|
103
|
+
exec.catch(() => void 0);
|
|
104
|
+
let timer;
|
|
105
|
+
try {
|
|
106
|
+
return await Promise.race([exec, new Promise((_resolve, reject) => {
|
|
107
|
+
timer = setTimeout(() => {
|
|
108
|
+
reject(new WorkspaceClientTimeoutError(`${label} exceeded ${timeoutMs}ms client-side timeout (sandbox RPC did not return)`));
|
|
109
|
+
options.onTimeout?.();
|
|
110
|
+
}, timeoutMs);
|
|
111
|
+
if (options.unref === true) timer?.unref?.();
|
|
112
|
+
})]);
|
|
113
|
+
} finally {
|
|
114
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Run `sandbox.exec()` bounded by a client-side timeout, and — for signal-aware
|
|
119
|
+
* transports (e.g. the HTTP bridge, `supportsExecSignal === true`) — abort the
|
|
120
|
+
* underlying exec when the timeout fires instead of merely abandoning it. The
|
|
121
|
+
* native DO transport ignores `signal`, so it only gets the timeout. Leaves the
|
|
122
|
+
* backstop timer ref'd (this is an awaited direct-exec path).
|
|
123
|
+
*/
|
|
124
|
+
async function execWithClientTimeout(sandbox, command, options, timeoutMs, label, runOptions = {}) {
|
|
125
|
+
const controller = new AbortController();
|
|
126
|
+
const execOptions = { ...options };
|
|
127
|
+
const callerSignal = options.signal;
|
|
128
|
+
let onCallerAbort;
|
|
129
|
+
if (sandbox.supportsExecSignal === true) {
|
|
130
|
+
if (callerSignal != null) if (callerSignal.aborted) controller.abort();
|
|
131
|
+
else {
|
|
132
|
+
onCallerAbort = () => controller.abort();
|
|
133
|
+
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
134
|
+
}
|
|
135
|
+
execOptions.signal = controller.signal;
|
|
136
|
+
} else if ("signal" in execOptions) delete execOptions.signal;
|
|
137
|
+
try {
|
|
138
|
+
return await withClientTimeout(sandbox.exec(command, execOptions), timeoutMs, label, {
|
|
139
|
+
unref: runOptions.unref,
|
|
140
|
+
onTimeout: () => controller.abort()
|
|
141
|
+
});
|
|
142
|
+
} finally {
|
|
143
|
+
if (onCallerAbort != null && callerSignal != null) callerSignal.removeEventListener("abort", onCallerAbort);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
64
146
|
function truncateOutput(value, maxChars) {
|
|
65
147
|
if (maxChars <= 0 || value.length <= maxChars) return value;
|
|
66
148
|
const head = Math.max(Math.floor(maxChars / 2), 0);
|
|
@@ -160,48 +242,51 @@ function createDirent(info) {
|
|
|
160
242
|
isSymbolicLink: () => info.type === "symlink"
|
|
161
243
|
};
|
|
162
244
|
}
|
|
163
|
-
async function findChildInfo(sandbox, filePath) {
|
|
245
|
+
async function findChildInfo(sandbox, filePath, timeoutMs) {
|
|
164
246
|
const parent = posix.dirname(filePath);
|
|
165
247
|
const basename = posix.basename(filePath);
|
|
166
|
-
return normalizeFileList(await sandbox.listFiles(parent, { includeHidden: true })).find((entry) => {
|
|
248
|
+
return normalizeFileList(await withClientTimeout(sandbox.listFiles(parent, { includeHidden: true }), timeoutMs, "cloudflare sandbox listFiles")).find((entry) => {
|
|
167
249
|
return entryAbsolutePath(entry, parent) === filePath || entryNameFor(entry, parent) === basename;
|
|
168
250
|
});
|
|
169
251
|
}
|
|
170
252
|
function createCloudflareWorkspaceFS(config) {
|
|
171
253
|
const workspaceRoot = getCloudflareWorkspaceRoot(config);
|
|
254
|
+
const fsTimeoutMs = clientFsTimeoutMs(config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
255
|
+
const bound = (op, label) => withClientTimeout(op, fsTimeoutMs, `cloudflare sandbox ${label}`);
|
|
172
256
|
return {
|
|
173
257
|
readFile: (async (filePath, encoding) => {
|
|
174
258
|
const sandbox = await resolveCloudflareSandbox(config);
|
|
175
259
|
const resolved = toSandboxPath(filePath, workspaceRoot);
|
|
176
|
-
const buffer = await normalizeReadFileContent(await sandbox.readFile(resolved, encoding ? { encoding } : void 0));
|
|
260
|
+
const buffer = await bound((async () => normalizeReadFileContent(await sandbox.readFile(resolved, encoding ? { encoding } : void 0)))(), "readFile");
|
|
177
261
|
return encoding != null ? buffer.toString(encoding) : buffer;
|
|
178
262
|
}),
|
|
179
263
|
writeFile: async (filePath, content, _options) => {
|
|
180
264
|
const sandbox = await resolveCloudflareSandbox(config);
|
|
181
265
|
const resolved = toSandboxPath(filePath, workspaceRoot);
|
|
182
266
|
const normalized = normalizeWriteFileContent(content);
|
|
183
|
-
await sandbox.writeFile(resolved, normalized.content, normalized.options);
|
|
267
|
+
await bound(sandbox.writeFile(resolved, normalized.content, normalized.options), "writeFile");
|
|
184
268
|
},
|
|
185
269
|
stat: async (filePath) => {
|
|
186
270
|
const sandbox = await resolveCloudflareSandbox(config);
|
|
187
271
|
const resolved = toSandboxPath(filePath, workspaceRoot);
|
|
188
272
|
if (resolved === workspaceRoot) return createStats({
|
|
189
|
-
size: normalizeFileList(await sandbox.listFiles(resolved, { includeHidden: true })).length,
|
|
273
|
+
size: normalizeFileList(await bound(sandbox.listFiles(resolved, { includeHidden: true }), "listFiles")).length,
|
|
190
274
|
type: "directory"
|
|
191
275
|
});
|
|
192
|
-
const info = await findChildInfo(sandbox, resolved);
|
|
276
|
+
const info = await findChildInfo(sandbox, resolved, fsTimeoutMs);
|
|
193
277
|
if (info != null) return createStats({
|
|
194
278
|
size: info.size,
|
|
195
279
|
type: info.type
|
|
196
280
|
});
|
|
197
281
|
try {
|
|
198
282
|
return createStats({
|
|
199
|
-
size: normalizeFileList(await sandbox.listFiles(resolved, { includeHidden: true })).length,
|
|
283
|
+
size: normalizeFileList(await bound(sandbox.listFiles(resolved, { includeHidden: true }), "listFiles")).length,
|
|
200
284
|
type: "directory"
|
|
201
285
|
});
|
|
202
|
-
} catch {
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (isWorkspaceClientTimeoutError(error)) throw error;
|
|
203
288
|
return createStats({
|
|
204
|
-
size: (await normalizeReadFileContent(await sandbox.readFile(resolved))).length,
|
|
289
|
+
size: (await bound((async () => normalizeReadFileContent(await sandbox.readFile(resolved)))(), "readFile")).length,
|
|
205
290
|
type: "file"
|
|
206
291
|
});
|
|
207
292
|
}
|
|
@@ -209,21 +294,21 @@ function createCloudflareWorkspaceFS(config) {
|
|
|
209
294
|
readdir: (async (filePath, options) => {
|
|
210
295
|
const sandbox = await resolveCloudflareSandbox(config);
|
|
211
296
|
const resolved = toSandboxPath(filePath, workspaceRoot);
|
|
212
|
-
const entries = normalizeFileList(await sandbox.listFiles(resolved, { includeHidden: true }));
|
|
297
|
+
const entries = normalizeFileList(await bound(sandbox.listFiles(resolved, { includeHidden: true }), "listFiles"));
|
|
213
298
|
if (options?.withFileTypes === true) return entries.map(createDirent);
|
|
214
299
|
return entries.map((entry) => entryNameFor(entry, resolved));
|
|
215
300
|
}),
|
|
216
301
|
mkdir: async (filePath, options) => {
|
|
217
|
-
await (await resolveCloudflareSandbox(config)).mkdir(toSandboxPath(filePath, workspaceRoot), { recursive: options?.recursive });
|
|
302
|
+
await bound((await resolveCloudflareSandbox(config)).mkdir(toSandboxPath(filePath, workspaceRoot), { recursive: options?.recursive }), "mkdir");
|
|
218
303
|
},
|
|
219
304
|
realpath: async (filePath) => toSandboxPath(filePath, workspaceRoot),
|
|
220
305
|
unlink: async (filePath) => {
|
|
221
|
-
await (await resolveCloudflareSandbox(config)).deleteFile(toSandboxPath(filePath, workspaceRoot));
|
|
306
|
+
await bound((await resolveCloudflareSandbox(config)).deleteFile(toSandboxPath(filePath, workspaceRoot)), "deleteFile");
|
|
222
307
|
},
|
|
223
308
|
open: async (filePath, _flags) => {
|
|
224
309
|
const sandbox = await resolveCloudflareSandbox(config);
|
|
225
310
|
const resolved = toSandboxPath(filePath, workspaceRoot);
|
|
226
|
-
const buffer = await normalizeReadFileContent(await sandbox.readFile(resolved));
|
|
311
|
+
const buffer = await bound((async () => normalizeReadFileContent(await sandbox.readFile(resolved)))(), "readFile");
|
|
227
312
|
return {
|
|
228
313
|
read: async (target, offset, length, position) => {
|
|
229
314
|
const start = Math.max(position, 0);
|
|
@@ -298,7 +383,10 @@ function createCloudflareSpawn(config) {
|
|
|
298
383
|
};
|
|
299
384
|
if (ctx.sandbox.supportsExecSignal === true) execOptions.signal = abortController.signal;
|
|
300
385
|
try {
|
|
301
|
-
const result = await ctx.sandbox.exec(timedCommand, execOptions)
|
|
386
|
+
const result = await withClientTimeout(ctx.sandbox.exec(timedCommand, execOptions), clientExecTimeoutMs(timeoutMs), "cloudflare sandbox exec", {
|
|
387
|
+
unref: true,
|
|
388
|
+
onTimeout: () => abortController.abort()
|
|
389
|
+
});
|
|
302
390
|
if (isClosed()) return;
|
|
303
391
|
if (result.stdout) stdout.write(result.stdout);
|
|
304
392
|
if (result.stderr) stderr.write(result.stderr);
|
|
@@ -350,11 +438,11 @@ async function executeCloudflareBash(command, config, args = []) {
|
|
|
350
438
|
await validateCloudflareBashCommand(command, args, config);
|
|
351
439
|
const ctx = await getRuntimeContext(config);
|
|
352
440
|
const shellCommand = args.length > 0 ? `${ctx.shell} -lc ${quote(command)} -- ${args.map(quote).join(" ")}` : `${ctx.shell} -lc ${quote(command)}`;
|
|
353
|
-
const result = await ctx.sandbox
|
|
441
|
+
const result = await execWithClientTimeout(ctx.sandbox, withInSandboxTimeout(shellCommand, ctx.timeoutMs), {
|
|
354
442
|
cwd: ctx.workspaceRoot,
|
|
355
443
|
env: ctx.env,
|
|
356
444
|
timeout: outerTimeoutMs(ctx.timeoutMs)
|
|
357
|
-
});
|
|
445
|
+
}, clientExecTimeoutMs(ctx.timeoutMs), "cloudflare sandbox bash exec");
|
|
358
446
|
return {
|
|
359
447
|
stdout: truncateOutput(result.stdout, ctx.maxOutputChars),
|
|
360
448
|
stderr: truncateOutput(result.stderr, ctx.maxOutputChars),
|
|
@@ -444,14 +532,16 @@ async function executeCloudflareCode(input, config) {
|
|
|
444
532
|
const id = globalThis.crypto.randomUUID();
|
|
445
533
|
const tempDir = posix.join(ctx.workspaceRoot, ".lc-exec", id);
|
|
446
534
|
const runtime = runtimeForCode(input.lang, tempDir, input.code, input.args, ctx.shell);
|
|
447
|
-
|
|
448
|
-
if (runtime.source != null) await ctx.sandbox.writeFile(posix.join(tempDir, runtime.fileName), runtime.source, { encoding: "utf8" });
|
|
535
|
+
let execSucceeded = false;
|
|
449
536
|
try {
|
|
450
|
-
|
|
537
|
+
await withClientTimeout(ctx.sandbox.mkdir(tempDir, { recursive: true }), clientFsTimeoutMs(ctx.timeoutMs), "cloudflare sandbox mkdir");
|
|
538
|
+
if (runtime.source != null) await withClientTimeout(ctx.sandbox.writeFile(posix.join(tempDir, runtime.fileName), runtime.source, { encoding: "utf8" }), clientFsTimeoutMs(ctx.timeoutMs), "cloudflare sandbox writeFile");
|
|
539
|
+
const result = await execWithClientTimeout(ctx.sandbox, withInSandboxTimeout(runtime.command, ctx.timeoutMs), {
|
|
451
540
|
cwd: ctx.workspaceRoot,
|
|
452
541
|
env: ctx.env,
|
|
453
542
|
timeout: outerTimeoutMs(ctx.timeoutMs)
|
|
454
|
-
});
|
|
543
|
+
}, clientExecTimeoutMs(ctx.timeoutMs), "cloudflare sandbox code-exec");
|
|
544
|
+
execSucceeded = true;
|
|
455
545
|
return {
|
|
456
546
|
stdout: truncateOutput(result.stdout, ctx.maxOutputChars),
|
|
457
547
|
stderr: truncateOutput(result.stderr, ctx.maxOutputChars),
|
|
@@ -459,11 +549,13 @@ async function executeCloudflareCode(input, config) {
|
|
|
459
549
|
timedOut: isInSandboxTimeoutExit(result.exitCode)
|
|
460
550
|
};
|
|
461
551
|
} finally {
|
|
462
|
-
|
|
552
|
+
const detach = !execSucceeded;
|
|
553
|
+
const cleanup = execWithClientTimeout(ctx.sandbox, `rm -rf ${quote(tempDir)}`, {
|
|
463
554
|
cwd: ctx.workspaceRoot,
|
|
464
555
|
env: ctx.env,
|
|
465
556
|
timeout: 1e4
|
|
466
|
-
}).catch(() => void 0);
|
|
557
|
+
}, clientExecTimeoutMs(1e4), "cloudflare sandbox cleanup", { unref: detach }).catch(() => void 0);
|
|
558
|
+
if (!detach) await cleanup;
|
|
467
559
|
}
|
|
468
560
|
}
|
|
469
561
|
function formatCloudflareOutput(result, cwd) {
|
|
@@ -477,6 +569,6 @@ function formatCloudflareOutput(result, cwd) {
|
|
|
477
569
|
return formatted.trim();
|
|
478
570
|
}
|
|
479
571
|
//#endregion
|
|
480
|
-
export { createCloudflareLocalExecutionConfig, createCloudflareWorkspaceFS, executeCloudflareBash, executeCloudflareCode, formatCloudflareOutput, getCloudflareWorkspaceRoot, resolveCloudflareSandbox, validateCloudflareBashCommand };
|
|
572
|
+
export { clientExecTimeoutMs, clientFsTimeoutMs, createCloudflareLocalExecutionConfig, createCloudflareWorkspaceFS, execWithClientTimeout, executeCloudflareBash, executeCloudflareCode, formatCloudflareOutput, getCloudflareWorkspaceRoot, resolveCloudflareSandbox, validateCloudflareBashCommand, withClientTimeout };
|
|
481
573
|
|
|
482
574
|
//# sourceMappingURL=CloudflareSandboxExecutionEngine.mjs.map
|