Something went wrong. Try again.
fork of prime agent with changes i want but are not upstreamable probqbly (and some fixes)
Something went wrong. Try again.
24 kB · 681 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682import { parseIpythonBashCell } from "./ipython-cell-code.js";
const DESCRIPTOR_MAX_WIDTH = 64;
const MAGIC_LINE_PATTERN = /^\s*!/;const COMMENT_LINE_PATTERN = /^\s*#/;const CD_PREFIX_PATTERN = /^\s*cd\s+([^&;|]+)(?:&&|;)\s*/;const BASH_SET_PATTERN = /^\s*set\s+[-+][A-Za-z]*(?:\s+[-+]?\w+)*(?:\s+pipefail)?\s*$/;const BASH_SETUP_PATTERN = /^(?:export\s+\w+=|source\s+\S+|\.\s+\S+)/;const PYTHON_IMPORT_PATTERN = /^\s*(?:import\s+\S|from\s+\S+\s+import\s+)/;const PYTHON_DECORATOR_PATTERN = /^\s*@/;const PYTHON_DEFINITION_PATTERN = /^\s*(?:async\s+def|def|class)\s+/;const PYTHON_MAIN_PATTERN = /^\s*if\s+__name__\s*==\s*['"]__main__['"]\s*:/;const PYTHON_CONTROL_PATTERN = /^\s*(?:if|elif|else|for|while|with|try|except|finally)\b.*:\s*$/;const PYTHON_CALL_PATTERN = /^\s*(?:await\s+)?[A-Za-z_][A-Za-z0-9_.]*\s*\(/;const BASH_SKILL_CALL_PATTERN = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?bash\s*\(\s*[rR]?("""|'''|"|')/;const DYNAMIC_BASH_SKILL_CALL_PATTERN = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?bash\s*\(/;const PYTHON_LOW_SIGNAL_CALL_PATTERN = /^\s*(?:await\s+)?(?:print|len|str|repr|int|float|list|dict|set|tuple)\s*\(/;const PYTHON_SLEEP_CALL_PATTERN = /^\s*(?:await\s+)?(?:asyncio\.sleep|time\.sleep|sleep)\s*\(/;const PYTHON_CLEANUP_CALL_PATTERN = /^\s*(?:await\s+)?(?:[A-Za-z0-9_.]*\.)?(?:close|quit|shutdown)\s*\(\s*\)/;const PYTHON_LOW_SIGNAL_STATEMENT_PATTERN = /^\s*(?:break|continue|pass|return(?:\s+None)?)\s*$/;const PYTHON_ASSIGNMENT_CALL_PATTERN = /^\s*[A-Za-z_][A-Za-z0-9_]*(?:\s*:\s*[^=]+)?\s*=\s*(?:await\s+)?[A-Za-z_][A-Za-z0-9_.]*\s*\(/;const PYTHON_LOW_SIGNAL_ASSIGNMENT_CALL_PATTERN = /^\s*[A-Za-z_][A-Za-z0-9_]*(?:\s*:\s*[^=]+)?\s*=\s*(?:await\s+)?(?:Path|pathlib\.Path|json\.loads|json\.dumps|str|int|float|list|dict|set|tuple)\s*\(/;const PYTHON_EFFECT_CALL_PATTERN = /^\s*(?:await\s+)?[A-Za-z_][A-Za-z0-9_.]*\.(?:write_text|write_bytes|mkdir|unlink|rename|replace|touch|append|extend|update|add|remove|discard|commit|execute|run)\s*\(/;const EDIT_SKILL_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?edit\s*\(\s*(?:path\s*=\s*)?["']([^"']+)["']/;const SEARCH_SKILL_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?(?:search|search\.files)\s*\(\s*["']([^"']+)["'](?:\s*,\s*(?:path\s*=\s*)?["']([^"']+)["'])?/;const VCS_SKILL_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?(git|jj)\.([a-z_]+)\s*\(/;const RLM_SUBAGENT_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?rlm\(\s*["']([^"']+)["'](?:\s*,\s*name\s*=\s*["']([^"']+)["'])?/;const INLINE_PATH_FILE_OP_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?(?:Path|pathlib\.Path)\(\s*["']([^"']+)["']\s*\)\.(write_text|write_bytes|read_text|read_bytes|mkdir|unlink|rename|replace|touch)\s*\(/;const INLINE_OPEN_READ_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?open\(\s*["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?\s*\)\.(read|readlines|read_text)\s*\(/;const INLINE_OPEN_WRITE_PATTERN = /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?open\(\s*["']([^"']+)["']\s*,\s*["']([wa][xb+]*)["']\s*\)\.write\s*\(/;const WITH_OPEN_PATTERN = /^\s*with\s+open\(\s*["']([^"']+)["'](?:\s*,\s*["']([^"']+)["'])?\s*\)/;const HEREDOC_PATTERN = /<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/;const PATH_ASSIGN_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(?:Path|pathlib\.Path)\(["']([^"']+)["']\)/;const STRING_ASSIGN_PATTERN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*["']([^"']+)["']/;
export type CodePreviewLanguage = "bash" | "python";
export interface CodePreview { language: CodePreviewLanguage; text: string;}
interface PreviewCandidate { language: CodePreviewLanguage; text: string; score: number; index: number;}
function collapseWhitespace(text: string): string { return text.replace(/\s+/g, " ").trim();}
function truncateDescriptor(text: string): string { if (text.length <= DESCRIPTOR_MAX_WIDTH) { return text; } return `${text.slice(0, DESCRIPTOR_MAX_WIDTH - 1).trimEnd()}…`;}
function redactNoise(text: string): string { return text .replace(/[A-Za-z0-9+/]{80,}={0,2}/g, "<blob>") .replace(/\b((?=\w*(?:token|key|secret|password))[A-Za-z_]\w*)\s*=\s*(["'])[^"']*\2/gi, "$1=<redacted>") .replace( /\b((?=\w*(?:token|key|secret|password))[A-Za-z_]\w*)\s*=\s*(?!<redacted>)(?!["'])\S+/gi, "$1=<redacted>", ) .replace(/\b(authorization:\s*(?:bearer\s+)?)[^\s"']+/gi, "$1<redacted>") .replace(/(["'])sk-[^"']+\1/g, "$1<redacted>$1") .replace(/(["']).{160,}\1/g, "$1…$1");}
function descriptor(text: string): string { return truncateDescriptor(collapseWhitespace(redactNoise(text)));}
function stripBashPrefix(line: string): string { return line.replace(MAGIC_LINE_PATTERN, "").trim().replace(CD_PREFIX_PATTERN, "").trim();}
function isSkippableBashLine(line: string): boolean { const trimmed = line.trim(); return ( !trimmed || COMMENT_LINE_PATTERN.test(trimmed) || BASH_SET_PATTERN.test(trimmed) || BASH_SETUP_PATTERN.test(trimmed) );}
function shellWords(line: string): string[] { const words: string[] = []; const pattern = /"([^"]*)"|'([^']*)'|(\S+)/g; for (const match of line.matchAll(pattern)) { words.push(match[1] ?? match[2] ?? match[3] ?? ""); } return words;}
function pathTail(path: string): string { return path.replace(/^\.\//, "");}
function simplifyRunnerCommand(line: string): string | undefined { const words = shellWords(line); const joined = words.join(" "); const vitestIndex = words.findIndex((word) => /(?:^|\/)vitest\/dist\/cli\.js$/.test(word)); if (words[0] === "npx" && words[1] === "tsx" && vitestIndex >= 2) { return `vitest ${words.slice(vitestIndex + 1).join(" ")}`.trim(); } if (words[0] === "npm") { const prefixIndex = words.indexOf("--prefix"); const cwd = prefixIndex >= 0 ? words[prefixIndex + 1] : undefined; const runIndex = words.indexOf("run"); if (runIndex >= 0 && words[runIndex + 1]) { const command = `npm ${words[runIndex + 1]} ${words.slice(runIndex + 2).join(" ")}`.trim(); return cwd ? `${command} (${pathTail(cwd)})` : command; } } if (words[0] === "pnpm") { const cwdIndex = words.findIndex((word) => word === "-C" || word === "--dir"); const cwd = cwdIndex >= 0 ? words[cwdIndex + 1] : undefined; const rest = words.filter((_, index) => index !== cwdIndex && index !== cwdIndex + 1); return cwd ? `${rest.join(" ")} (${pathTail(cwd)})` : undefined; } const pytestIndex = words.findIndex( (word, index) => word === "pytest" || (word === "pytest" && words[index - 1] === "-m"), ); if (words[0] === "uv" && words[1] === "run" && pytestIndex >= 0) { return `pytest ${words.slice(pytestIndex + 1).join(" ")}`.trim(); } if ((words[0] === "python" || words[0] === "python3") && words[1] === "-m" && words[2] === "pytest") { return `pytest ${words.slice(3).join(" ")}`.trim(); } if (joined.includes("node_modules/.bin/")) { return joined.replace(/\S*node_modules\/\.bin\//g, ""); } return undefined;}
function simplifyMutationCommand(line: string): string | undefined { const words = shellWords(line); if (words.length === 0) return undefined; if (words[0] === "cat" && words[1] === ">" && words[2]) return `write ${pathTail(words[2])}`; if (words[0] === "tee" && words.at(-1)) return `${words.includes("-a") ? "append" : "write"} ${pathTail(words.at(-1) ?? "")}`; if (words[0] === "apply_patch") return "apply patch"; if (["rm", "mv", "cp", "git", "npm"].includes(words[0] ?? "")) return line; if ( (words[0] === "sed" && words.some((word) => word.startsWith("-i"))) || (words[0] === "perl" && words.includes("-pi")) ) { return line; } return undefined;}
function simplifyBashCommandLine(line: string): string { return simplifyRunnerCommand(line) ?? simplifyMutationCommand(line) ?? line;}
function splitCommandChain(line: string): string[] { return line .split(/\s*(?:&&|;)\s*/) .map((part) => part.trim()) .filter(Boolean);}
function heredocBody(lines: readonly string[], startIndex: number, delimiter: string): string | undefined { // While args stream, preview the partial heredoc body rather than the low-signal heredoc opener. const body: string[] = []; for (let i = startIndex + 1; i < lines.length; i++) { const line = lines[i] ?? ""; if (line.trim() === delimiter) { return body.join("\n"); } body.push(line); } return body.length > 0 ? body.join("\n") : undefined;}
function previewHeredoc(lines: readonly string[]): CodePreview | undefined { // A generic heredoc body is low-signal; keep it as a fallback and prefer a // later, more specific heredoc (python/bash/node/write) if one follows. let fallback: CodePreview | undefined; for (let i = 0; i < lines.length; i++) { const line = stripBashPrefix(lines[i] ?? ""); if (isSkippableBashLine(line)) { continue; } const heredocMatch = line.match(HEREDOC_PATTERN); const delimiter = heredocMatch?.[1]; if (!delimiter) { continue; } const body = heredocBody(lines, i, delimiter); if (!body) { continue; } if (/\b(?:uv\s+run\s+)?python3?\b/.test(line)) { const preview = previewPythonCode(body); if (preview.text) { return preview; } continue; } // Match bash/sh as an interpreter word (incl. /bin/sh), not a path suffix like script.sh. if (/(?<![\w.])(?:bash|sh)\b/.test(line)) { const preview = previewBashCommand(body); return preview.text ? preview : { language: "bash", text: descriptor(body) }; } if (/\bnode\b/.test(line)) { return { language: "bash", text: `node: ${descriptor(body)}` }; } const catWrite = line.match(/\b(?:cat|tee)\b.*(?:>|\s)(\S+)\s*<<-?/); if (catWrite?.[1]) { return { language: "bash", text: `${line.includes("tee -a") ? "append" : "write"} ${pathTail(catWrite[1])}` }; } if (/\bapply_patch\b/.test(line)) { return { language: "bash", text: "apply patch" }; } fallback ??= { language: "bash", text: descriptor(body) }; } return fallback;}
function bashLineScore(line: string, index: number): number { const simplified = simplifyBashCommandLine(line); const words = shellWords(line); let score = 30; if (simplified !== line) score += 40; if (["rm", "mv", "cp", "git", "npm", "pnpm", "pytest", "vitest"].includes(words[0] ?? "")) score += 20; if (/\b(?:rm|mv|cp|git\s+(?:add|commit)|npm\s+install|sed\s+-i|perl\s+-pi|tee|cat\s*>|apply_patch)\b/.test(line)) score += 40; return score + index;}
export function previewBashCommand(command: string): CodePreview { const lines = command.split("\n"); const heredoc = previewHeredoc(lines); if (heredoc?.text) { return { language: heredoc.language, text: descriptor(heredoc.text) }; }
let best: PreviewCandidate | undefined; let index = 0; for (const rawLine of lines) { for (const rawPart of splitCommandChain(rawLine)) { const commandLine = stripBashPrefix(rawPart.trim()); if (!commandLine || isSkippableBashLine(commandLine)) { continue; } const candidate = { language: "bash" as const, text: simplifyBashCommandLine(commandLine), score: bashLineScore(commandLine, index), index, }; if (!best || candidate.score > best.score) { best = candidate; } index += 1; } } return { language: "bash", text: best ? descriptor(best.text) : "" };}
function isSkippablePythonLine(line: string): boolean { const trimmed = line.trim(); return !trimmed || COMMENT_LINE_PATTERN.test(trimmed) || PYTHON_IMPORT_PATTERN.test(trimmed);}
function pythonIndent(line: string): number { const match = line.match(/^\s*/); return match?.[0].length ?? 0;}
function pythonPrintInnerCall(line: string): string | undefined { const printMatch = line.trim().match(/^print\((.*)\)$/); const inner = printMatch?.[1]?.trim(); if (!inner) return undefined; const callMatch = inner.match(/(?:await\s+)?[A-Za-z_][A-Za-z0-9_.]*\s*\(/); return callMatch && !PYTHON_LOW_SIGNAL_CALL_PATTERN.test(callMatch[0]) ? inner : undefined;}
function pythonPathVars(lines: readonly string[]): Map<string, string> { const vars = new Map<string, string>(); for (const line of lines) { const pathMatch = line.match(PATH_ASSIGN_PATTERN) ?? line.match(STRING_ASSIGN_PATTERN); if (pathMatch?.[1] && pathMatch[2]?.includes("/")) { vars.set(pathMatch[1], pathMatch[2]); } } return vars;}
function pythonFileOperation(line: string, paths: ReadonlyMap<string, string>): string | undefined { const trimmed = line.trim(); const varMatch = trimmed.match( /^(?:[A-Za-z_][A-Za-z0-9_]*\s*=\s*)?(?:await\s+)?([A-Za-z_][A-Za-z0-9_]*)\.(write_text|write_bytes|read_text|read_bytes|mkdir|unlink|rename|replace|touch)\s*\(/, ); if (varMatch?.[1] && varMatch[2]) { const path = paths.get(varMatch[1]); if (path) { const action: Record<string, string> = { write_text: "write", write_bytes: "write", read_text: "read", read_bytes: "read", mkdir: "mkdir", unlink: "delete", rename: "rename", replace: "replace", touch: "touch", }; return `${action[varMatch[2]] ?? varMatch[2]} ${pathTail(path)}`; } } const inlinePathMatch = trimmed.match(INLINE_PATH_FILE_OP_PATTERN); if (inlinePathMatch?.[1] && inlinePathMatch[2]) { const action: Record<string, string> = { write_text: "write", write_bytes: "write", read_text: "read", read_bytes: "read", mkdir: "mkdir", unlink: "delete", rename: "rename", replace: "replace", touch: "touch", }; return `${action[inlinePathMatch[2]] ?? inlinePathMatch[2]} ${pathTail(inlinePathMatch[1])}`; } const inlineOpenRead = trimmed.match(INLINE_OPEN_READ_PATTERN); if (inlineOpenRead?.[1]) { return `read ${pathTail(inlineOpenRead[1])}`; } const inlineOpenWrite = trimmed.match(INLINE_OPEN_WRITE_PATTERN); if (inlineOpenWrite?.[1]) { return `write ${pathTail(inlineOpenWrite[1])}`; } const withOpen = trimmed.match(WITH_OPEN_PATTERN); if (withOpen?.[1]) { const mode = withOpen[2] ?? ""; const verb = mode.includes("w") || mode.includes("a") ? "write" : "read"; return `${verb} ${pathTail(withOpen[1])}`; } return undefined;}
function pythonSkillOperation(line: string): string | undefined { const trimmed = line.trim(); const editMatch = trimmed.match(EDIT_SKILL_PATTERN); if (editMatch?.[1]) { return `edit ${pathTail(editMatch[1])}`; } const searchMatch = trimmed.match(SEARCH_SKILL_PATTERN); if (searchMatch?.[1]) { return searchMatch[2] ? `search "${searchMatch[1]}" (${pathTail(searchMatch[2])})` : `search "${searchMatch[1]}"`; } const vcsMatch = trimmed.match(VCS_SKILL_PATTERN); if (vcsMatch?.[1] && vcsMatch[2]) { return `${vcsMatch[1]} ${vcsMatch[2].replace(/_/g, " ")}`; } const rlmMatch = trimmed.match(RLM_SUBAGENT_PATTERN); if (rlmMatch?.[1]) { return rlmMatch[2] ? `subagent ${rlmMatch[2]}` : `subagent "${rlmMatch[1]}"`; } return undefined;}
function pythonSubprocessCommand(line: string): string | undefined { const trimmed = line.trim(); const shellMatch = trimmed.match(/subprocess\.(?:run|check_call|check_output|Popen)\(\s*["`]([^"`]+)["`]/); if (shellMatch?.[1]) return simplifyBashCommandLine(shellMatch[1]); const listMatch = trimmed.match(/subprocess\.(?:run|check_call|check_output|Popen)\(\s*\[([^\]]+)\]/); if (listMatch?.[1]) { const words = [...listMatch[1].matchAll(/["']([^"']+)["']/g)].map((match) => match[1] ?? ""); return simplifyBashCommandLine(words.join(" ")); } return undefined;}
function simplifyPythonPreviewLine(line: string, paths: ReadonlyMap<string, string>): string { return ( pythonFileOperation(line, paths) ?? pythonSkillOperation(line) ?? pythonSubprocessCommand(line) ?? pythonPrintInnerCall(line) ?? line.trim() );}
function pythonPreviewLine(lines: readonly string[], index: number, paths: ReadonlyMap<string, string>): string { const line = lines[index] ?? ""; const fileOp = pythonFileOperation(line, paths); if (fileOp) { return fileOp; } const skillOp = pythonSkillOperation(line); if (skillOp) { return skillOp; } if (index > 0 && PYTHON_DEFINITION_PATTERN.test(line)) { const previous = lines[index - 1] ?? ""; if (PYTHON_DECORATOR_PATTERN.test(previous.trim())) { return `${previous.trim()} ${line.trim()}`; } } if (PYTHON_CONTROL_PATTERN.test(line)) { const childIndex = firstPythonChildLine(lines, index); if (childIndex !== undefined) { return `${line.trim().replace(/:\s*$/, ":")} ${simplifyPythonPreviewLine(lines[childIndex] ?? "", paths)}`; } } return simplifyPythonPreviewLine(line, paths);}
function isSkippablePythonChildLine(line: string): boolean { const trimmed = line.trim(); return ( isSkippablePythonLine(trimmed) || PYTHON_DECORATOR_PATTERN.test(trimmed) || PYTHON_LOW_SIGNAL_STATEMENT_PATTERN.test(trimmed) || PYTHON_SLEEP_CALL_PATTERN.test(trimmed) || PYTHON_CLEANUP_CALL_PATTERN.test(trimmed) );}
function firstPythonChildLine(lines: readonly string[], parentIndex: number): number | undefined { const parentIndent = pythonIndent(lines[parentIndex] ?? ""); let fallbackChildIndex: number | undefined; for (let i = parentIndex + 1; i < lines.length; i++) { const line = lines[i] ?? ""; if (isSkippablePythonLine(line) || PYTHON_DECORATOR_PATTERN.test(line.trim())) { continue; } if (pythonIndent(line) <= parentIndent) { break; } if (isSkippablePythonChildLine(line)) { if (fallbackChildIndex === undefined) { fallbackChildIndex = i; } continue; } return i; } return fallbackChildIndex;}
function pythonLineScore(lines: readonly string[], index: number, paths: ReadonlyMap<string, string>): number { const line = lines[index] ?? ""; const trimmed = line.trim(); if (isSkippablePythonLine(line) || PYTHON_DECORATOR_PATTERN.test(trimmed) || /^[)\]},;\s]+(?:#.*)?$/.test(trimmed)) { return -1; } const fileOp = pythonFileOperation(line, paths); if (fileOp) { const isMutation = fileOp.startsWith("write ") || fileOp.startsWith("delete ") || fileOp.startsWith("replace ") || fileOp.startsWith("rename "); return isMutation ? 95 : 92; } if (EDIT_SKILL_PATTERN.test(trimmed)) { return 95; } if (pythonSubprocessCommand(line) || BASH_SKILL_CALL_PATTERN.test(trimmed)) { return 90; } if (DYNAMIC_BASH_SKILL_CALL_PATTERN.test(trimmed)) { return 88; } if (pythonSkillOperation(line)) { return 85; } if (PYTHON_EFFECT_CALL_PATTERN.test(line)) { return 80; } if (PYTHON_MAIN_PATTERN.test(line)) { return 70; } if (PYTHON_CONTROL_PATTERN.test(line)) { const childIndex = firstPythonChildLine(lines, index); return childIndex === undefined ? 20 : Math.max(20, pythonLineScore(lines, childIndex, paths) - 5); } if (PYTHON_DEFINITION_PATTERN.test(line)) { return 50; } if (PYTHON_LOW_SIGNAL_ASSIGNMENT_CALL_PATTERN.test(line)) { return 25; } const printInnerCall = pythonPrintInnerCall(line); if (printInnerCall && !PYTHON_LOW_SIGNAL_CALL_PATTERN.test(printInnerCall)) { return 55; } if (PYTHON_ASSIGNMENT_CALL_PATTERN.test(line)) { return 60; } if (PYTHON_CLEANUP_CALL_PATTERN.test(trimmed)) { return 8; } if (PYTHON_SLEEP_CALL_PATTERN.test(trimmed)) { return 10; } if (PYTHON_LOW_SIGNAL_STATEMENT_PATTERN.test(trimmed)) { return 5; } if (PYTHON_CALL_PATTERN.test(line) && !PYTHON_LOW_SIGNAL_CALL_PATTERN.test(line)) { return 65; } if (PYTHON_CALL_PATTERN.test(line)) { return 15; } return 30;}
function pythonPreviewIndex(lines: readonly string[], index: number, paths: ReadonlyMap<string, string>): number { const line = lines[index] ?? ""; if (!PYTHON_CONTROL_PATTERN.test(line) || pythonFileOperation(line, paths)) { return index; } const childIndex = firstPythonChildLine(lines, index); if (childIndex === undefined) { return index; } const childLine = lines[childIndex] ?? ""; if (PYTHON_LOW_SIGNAL_STATEMENT_PATTERN.test(childLine.trim())) { return index; } return pythonPreviewIndex(lines, childIndex, paths);}
const PYTHON_ESCAPES: Record<string, string> = { "\n": "", // backslash-newline is a line continuation '"': '"', "'": "'", "\\": "\\", n: "\n", r: "\r", t: " ",};
interface PythonStringScan { value: string; end: number; closed: boolean; /** Saw a cooked escape (\x, \u, octal, \a…) whose value is not computed here. */ unsupportedEscape: boolean;}
// Walks a python string-literal body from just after the opening delimiter,// following python's escape rules (in raw strings backslash-quote never closes).function scanPythonStringLiteral(code: string, start: number, quote: string, raw: boolean): PythonStringScan { let value = ""; let i = start; let unsupportedEscape = false; while (i < code.length) { const char = code[i] ?? ""; if (char === "\\" && i + 1 < code.length) { const next = code[i + 1] ?? ""; if (!raw && /[xuUN0-7abfv]/.test(next)) { unsupportedEscape = true; } value += raw ? char + next : (PYTHON_ESCAPES[next] ?? char + next); i += 2; continue; } if (code.startsWith(quote, i)) { return { value, end: i + quote.length, closed: true, unsupportedEscape }; } if (quote.length === 1 && char === "\n") { break; // single-quoted literals cannot span lines } value += char; i += 1; } return { value, end: i, closed: false, unsupportedEscape };}
/** Keep source-line positions while masking multiline-string continuations. */export function pythonStatementLines(code: string): string[] { const lines = code.split("\n"); let line = 0; let i = 0; while (i < code.length) { const char = code[i]!; if (char === "#") { const newline = code.indexOf("\n", i); if (newline < 0) break; i = newline; continue; } if (char === '"' || char === "'") { const quote = code.startsWith(char.repeat(3), i) ? char.repeat(3) : char; const scan = scanPythonStringLiteral(code, i + quote.length, quote, true); const startLine = line; for (let end = i; end < scan.end; end++) { if (code[end] === "\n") lines[++line] = ""; } if (scan.closed && line > startLine) { const column = scan.end - code.lastIndexOf("\n", scan.end - 1) - 1; const newline = code.indexOf("\n", scan.end); lines[line] = " ".repeat(column) + code.slice(scan.end, newline < 0 ? undefined : newline); } i = scan.end; continue; } if (char === "\n") line++; i++; } return lines;}
function extractBashSkillCommand(code: string): string | undefined { const match = code.match(BASH_SKILL_CALL_PATTERN); const quote = match?.[1]; if (!match || !quote) return undefined; const start = match[0].length; const prefixChar = match[0][start - quote.length - 1]; const scan = scanPythonStringLiteral(code, start, quote, prefixChar === "r" || prefixChar === "R"); if (!scan.closed || scan.unsupportedEscape) return undefined; const rest = code.slice(scan.end).trimStart(); // Require a plain literal first argument; concatenation or other expressions fall back. if (!rest.startsWith(",") && !rest.startsWith(")")) return undefined; return scan.value;}
export function previewPythonCode(code: string): CodePreview { const rawLines = code.split("\n"); const lines = pythonStatementLines(code).map((line) => line.replace(/^(\s*);\s*/, "$1")); const paths = pythonPathVars(lines); let bestIndex: number | undefined; let bestScore = -1;
for (let i = 0; i < lines.length; i++) { const score = pythonLineScore(lines, i, paths); if (score > bestScore) { bestIndex = i; bestScore = score; } }
if (bestIndex !== undefined && bestScore >= 0) { const previewIndex = pythonPreviewIndex(lines, bestIndex, paths); // Keep the original tail for multiline commands, excluding any preceding string continuation. const bashCommand = extractBashSkillCommand( [lines[previewIndex], ...rawLines.slice(previewIndex + 1)].join("\n"), ); if (bashCommand) { return previewBashCommand(bashCommand); } return { language: "python", text: descriptor(pythonPreviewLine(lines, previewIndex, paths)), }; } return { language: "python", text: "" };}
export function previewIpythonCode(code: string): CodePreview { const trimmedCode = code.trimEnd(); const bashCell = parseIpythonBashCell(trimmedCode); if (bashCell) { return previewBashCommand(bashCell.body); } return previewPythonCode(trimmedCode);}