';
}
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText().trim();
if (output) html += formatExpandableOutput(output, 5);
}
return html;
}
function renderJsLike(name, args, result, ctx) {
let html = toolHead(name, '');
const cells = result && result.details && Array.isArray(result.details.cells) ? result.details.cells : null;
if (cells) {
for (const cell of cells) {
html += '
';
}
} else if (typeof args.input === 'string') {
html += codeBlock(args.input, null);
} else {
html += '
[missing input]
';
}
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderRead(name, args, result, ctx) {
const filePath = str(args.file_path == null ? args.path : args.file_path);
let pathHtml = pathDisplay(filePath, args.offset, args.limit);
if (args.sel) pathHtml += ':' + escapeHtml(String(args.sel)) + '';
let html = toolHead('read', pathHtml);
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
const lang = filePath ? getLanguageFromPath(filePath) : null;
if (output) html += formatExpandableOutput(output, 10, lang);
}
return html;
}
function renderWrite(name, args, result, ctx) {
const filePath = str(args.file_path == null ? args.path : args.file_path);
const content = str(args.content);
const pathHtml = filePath === null ? invalidArgHtml() : escapeHtml(shortenPath(filePath || ''));
const lineCount = (content != null && content !== '') ? content.split('\n').length : 0;
const badges = lineCount > 10 ? ['(' + lineCount + ' lines)'] : null;
let html = toolHead('write', pathHtml, badges);
if (content === null) {
html += '
[invalid content arg - expected string]
';
} else if (content) {
const lang = filePath ? getLanguageFromPath(filePath) : null;
html += formatExpandableOutput(content, 10, lang);
}
if (result) {
const output = ctx.getResultText().trim();
if (output) html += '
' + escapeHtml(output) + '
';
}
return html;
}
function renderEdit(name, args, result, ctx) {
const filePath = str(args.file_path == null ? args.path : args.file_path);
const pathHtml = filePath ? escapeHtml(shortenPath(filePath)) : '';
let html = toolHead('edit', pathHtml);
if (typeof args.input === 'string' && args.input.length) {
html += codeBlock(args.input, null);
} else if (Array.isArray(args.edits)) {
html += '
';
for (const e of args.edits) {
const op = e && typeof e.op === 'string' ? e.op : '?';
const sel = e && typeof e.sel === 'string' ? e.sel : '?';
html += '
' + escapeHtml(op) + '' + escapeHtml(sel) + '
';
}
html += '
';
}
if (result?.details?.diff) {
const diffLines = String(result.details.diff).split('\n');
html += '
';
for (const line of diffLines) {
const cls = line.match(/^\+/) ? 'diff-added' : line.match(/^-/) ? 'diff-removed' : 'diff-context';
// Blank gap rows mark non-contiguous regions; show a unicode ellipsis.
const display = line.trim().length === 0 ? '\u2026' : replaceTabs(line);
html += '
' + escapeHtml(display) + '
';
}
html += '
';
} else if (result) {
const output = ctx.getResultText().trim();
if (output) html += '
' + escapeHtml(output) + '
';
}
return html;
}
function renderAstEdit(name, args, result, ctx) {
const lang = args.lang || null;
const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (args.path ? shortenPath(String(args.path)) : '');
const pathHtml = paths ? escapeHtml(paths) : '';
const badges = [];
if (lang) badges.push(lang);
if (args.glob) badges.push('glob=' + args.glob);
if (args.sel) badges.push('sel=' + args.sel);
let html = toolHead('ast_edit', pathHtml, badges);
if (Array.isArray(args.ops)) {
for (const op of args.ops) {
html += '
';
html += '
pattern
';
html += codeBlock(String(op?.pat == null ? '' : op.pat), lang);
html += '
replacement
';
html += codeBlock(String(op?.out == null ? '' : op.out), lang);
html += '
';
}
}
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderAstGrep(name, args, result, ctx) {
const lang = args.lang || null;
const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : '';
const badges = [];
if (lang) badges.push(lang);
if (args.glob) badges.push('glob=' + args.glob);
if (args.sel) badges.push('sel=' + args.sel);
let html = toolHead('ast_grep', pathHtml, badges);
if (Array.isArray(args.pat)) {
for (const pat of args.pat) {
html += '
';
}
}
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderGrep(name, args, result, ctx) {
const pattern = str(args.pattern);
const pathHtml = args.path ? escapeHtml(shortenPath(String(args.path))) : escapeHtml('.');
const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
let head = 'grep/' + patHtml + '/';
head += ' in' + pathHtml + '';
const badges = [];
if (args.glob) badges.push('glob=' + args.glob);
if (args.type) badges.push('type=' + args.type);
if (args.i) badges.push('i');
if (args.multiline) badges.push('multiline');
for (const b of badges) head += ' ' + escapeHtml(b) + '';
let html = '
' + head + '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderFind(name, args, result, ctx) {
const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (str(args.pattern) || '.');
const patHtml = paths ? escapeHtml(paths) : invalidArgHtml();
const badges = [];
if (args.limit) badges.push('limit=' + args.limit);
if (args.hidden === false) badges.push('no-hidden');
let html = toolHead('find', '' + patHtml + '', badges.length ? badges : null);
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderLsp(name, args, result, ctx) {
const action = str(args.action) || '?';
let head = 'lsp' + escapeHtml(action) + '';
if (args.file && args.file !== '*') {
head += ' ' + escapeHtml(shortenPath(String(args.file))) + '';
} else if (args.file === '*') {
head += ' workspace';
}
if (args.line) head += ':' + args.line + '';
if (args.symbol) head += ' ' + escapeHtml(String(args.symbol)) + '';
if (args.query) head += ' query=' + escapeHtml(String(args.query)) + '';
if (args.new_name) head += ' →' + escapeHtml(String(args.new_name)) + '';
let html = '
' + head + '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12);
}
return html;
}
function todoRoman(n) {
if (n <= 0) return '';
var pairs = [[1000,'M'],[900,'CM'],[500,'D'],[400,'CD'],[100,'C'],[90,'XC'],[50,'L'],[40,'XL'],[10,'X'],[9,'IX'],[5,'V'],[4,'IV'],[1,'I']];
var out = '', rem = n;
for (var i = 0; i < pairs.length; i++) {
while (rem >= pairs[i][0]) { out += pairs[i][1]; rem -= pairs[i][0]; }
}
return out;
}
function renderTodo(name, args, result, ctx) {
let html = toolHead('todo');
const ops = Array.isArray(args.ops) ? args.ops : null;
if (ops) {
html += '
';
for (const op of ops) {
const t = op && op.op ? op.op : '?';
let line = '' + escapeHtml(t) + '';
if (op?.id) line += ' ' + escapeHtml(String(op.id)) + '';
if (op?.status) line += ' ' + escapeHtml(String(op.status)) + '';
if (op?.content) line += ' ' + escapeHtml(truncate(String(op.content), 80));
if (op?.task && typeof op.task === 'object' && op.task.content) line += ' ' + escapeHtml(truncate(String(op.task.content), 80));
html += '
' + line + '
';
}
html += '
';
}
const phases = result?.details?.phases;
if (Array.isArray(phases)) {
html += '
';
for (var __i = 0; __i < phases.length; __i++) {
var phase = phases[__i];
var phaseLabel = todoRoman(__i + 1) + '. ' + String(phase.name || '');
html += '
' + escapeHtml(phaseLabel) + '
';
if (Array.isArray(phase.tasks)) {
for (const task of phase.tasks) {
const status = task.status || 'pending';
const icon = status === 'completed' ? '✓' : status === 'in_progress' ? '→' : status === 'abandoned' ? '✕' : '○';
html += '
';
} else if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 8);
}
return html;
}
function renderTask(name, args, result, ctx) {
const agent = str(args.agent) || '?';
const tasks = Array.isArray(args.tasks) ? args.tasks : [];
const badges = ['agent=' + agent, tasks.length + ' subtask' + (tasks.length === 1 ? '' : 's')];
if (args.isolated) badges.push('isolated');
let html = toolHead('task', '', badges);
if (tasks.length) {
html += '
';
for (const t of tasks) {
const id = t?.id ? escapeHtml(String(t.id)) : '?';
const desc = t?.description ? escapeHtml(String(t.description)) : '';
html += '
' + id + ' ' + desc + '
';
}
html += '
';
}
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12);
}
return html;
}
function renderWebSearch(name, args, result, ctx) {
const query = str(args.query);
const queryHtml = query === null ? invalidArgHtml() : escapeHtml(query);
const badges = [];
if (args.recency) badges.push('recency=' + args.recency);
if (args.limit) badges.push('limit=' + args.limit);
let html = toolHead('web_search', '' + queryHtml + '', badges);
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12, 'markdown');
}
return html;
}
function renderFetch(name, args, result, ctx) {
const url = str(args.url) || '';
const badges = args.method ? [String(args.method)] : null;
let html = toolHead('fetch', '' + escapeHtml(url) + '', badges);
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderDebug(name, args, result, ctx) {
const action = str(args.action) || '?';
const badges = [];
if (args.adapter) badges.push(args.adapter);
if (args.program) badges.push('program=' + shortenPath(String(args.program)));
if (args.file) badges.push('file=' + shortenPath(String(args.file)));
if (args.line) badges.push('line=' + args.line);
let head = 'debug' + escapeHtml(action) + '';
for (const b of badges) head += ' ' + escapeHtml(String(b)) + '';
let html = '
' + head + '
';
if (args.expression) html += codeBlock(String(args.expression));
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderBrowser(name, args, result, ctx) {
const action = str(args.action) || '?';
const tabName = str(args.name);
const badges = [];
if (tabName) badges.push('name=' + tabName);
if (args.url) badges.push(String(args.url));
if (args.app && typeof args.app === 'object') {
if (args.app.path) badges.push('app=' + shortenPath(String(args.app.path)));
else if (args.app.cdp_url) badges.push('cdp=' + String(args.app.cdp_url));
}
if (args.all) badges.push('all');
if (args.kill) badges.push('kill');
let head = 'browser' + escapeHtml(action) + '';
for (const b of badges) head += ' ' + escapeHtml(String(b)) + '';
let html = '
' + head + '
';
if (action === 'run' && args.code) {
html += codeBlock(String(args.code), 'javascript');
}
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 10);
}
return html;
}
function renderInspectImage(name, args, result, ctx) {
const p = str(args.path == null ? args.url : args.path) || '';
let html = toolHead('inspect_image', escapeHtml(shortenPath(p)));
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 8);
}
return html;
}
function renderGenerateImage(name, args, result, ctx) {
const subject = str(args.subject) || '';
const badges = args.aspect_ratio ? [String(args.aspect_ratio)] : null;
let html = toolHead('generate_image', '', badges);
if (subject) html += '
' + escapeHtml(subject) + '
';
if (result) {
html += ctx.renderResultImages();
}
return html;
}
function renderAsk(name, args, result, ctx) {
let html = toolHead('ask');
const questions = Array.isArray(args.questions) ? args.questions : null;
if (questions) {
html += '
';
for (const q of questions) {
html += '
Q: ' + escapeHtml(String(q?.question || '')) + '
';
if (Array.isArray(q?.options)) {
for (const opt of q.options) {
html += '
- ' + escapeHtml(String(opt?.label || '')) + '
';
}
}
}
html += '
';
}
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 8);
}
return html;
}
function renderResolve(name, args, result, ctx) {
const action = str(args.action) || '?';
let html = toolHead('resolve', '', [action]);
if (args.reason) html += '
' + escapeHtml(String(args.reason)) + '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 6);
}
return html;
}
function renderGh(name, args, result, ctx) {
const op = str(args.op);
const badges = [];
if (op) badges.push(op);
if (args.repo) badges.push(String(args.repo));
if (args.issue) badges.push('#' + args.issue);
if (args.pr) badges.push(Array.isArray(args.pr) ? 'PRs ' + args.pr.join(',') : 'PR ' + args.pr);
if (args.branch) badges.push('branch=' + args.branch);
if (args.query) badges.push('query=' + truncate(String(args.query), 60));
if (args.run) badges.push('run=' + args.run);
if (args.title) badges.push('title=' + truncate(String(args.title), 40));
let html = toolHead(name, '', badges);
if (args.body) html += '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12, 'markdown');
}
return html;
}
function renderMermaid(name, args, result, ctx) {
let html = toolHead('render_mermaid');
const code = args.code || args.source;
if (code) html += codeBlock(String(code), 'mermaid');
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 6);
}
return html;
}
function renderYield(name, args, result, ctx) {
let html = toolHead('yield');
if (args.data !== undefined) {
html += '
';
}
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 6);
}
return html;
}
function renderReportFinding(name, args, result, ctx) {
const badges = [];
if (args.priority) badges.push('priority=' + args.priority);
if (args.confidence != null) badges.push('confidence=' + args.confidence);
if (args.file_path) badges.push(shortenPath(String(args.file_path)));
let html = toolHead('report_finding', args.title ? escapeHtml(String(args.title)) : '', badges);
if (args.body) html += '
' + escapeHtml(String(args.body)) + '
';
return html;
}
function renderReportToolIssue(name, args, result, ctx) {
const pathHtml = args.tool ? '' + escapeHtml(String(args.tool)) + '' : '';
let html = toolHead('report_tool_issue', pathHtml);
if (args.report) html += '
' + escapeHtml(String(args.report)) + '
';
return html;
}
function renderJob(name, args, result, ctx) {
const badges = [];
const pollIds = Array.isArray(args.poll) ? args.poll : Array.isArray(args.jobs) ? args.jobs : Array.isArray(args.jobIds) ? args.jobIds : [];
const cancelIds = Array.isArray(args.cancel) ? args.cancel : args.jobId ? [String(args.jobId)] : [];
if (cancelIds.length > 0) badges.push('cancel ' + cancelIds.length);
if (pollIds.length > 0) badges.push('poll ' + pollIds.length);
let html = toolHead('job', '', badges);
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 8);
}
return html;
}
// Parse `*** Cell ` headers (canonical), plus legacy
// `*** Begin ` headers and `===== =====` bars used in
// older transcripts. Cells emitted before each format cutover still
// need to render in HTML exports.
function parseEvalCells(input) {
const text = String(input);
if (/^[*]{2,}\s*Cell\b/im.test(text)) return parseEvalCellsCell(text);
if (/^[*]{2,}\s*Begin\b/im.test(text)) return parseEvalCellsBegin(text);
return parseEvalCellsLegacy(text);
}
function evalLangAlias(token) {
const t = String(token || '').toUpperCase();
if (t === 'PY' || t === 'PYTHON' || t === 'IPY' || t === 'IPYTHON') return 'py';
if (t === 'JS' || t === 'JAVASCRIPT') return 'js';
if (t === 'TS' || t === 'TYPESCRIPT') return 'ts';
return null;
}
// Tokenize a `*** Cell` header attribute list, preserving quoted
// segments. Mirrors `tokenizeCellAttrs` in src/eval/parse.ts.
function tokenizeCellAttrsHtml(input) {
const tokens = [];
let i = 0;
while (i < input.length) {
while (i < input.length && /\s/.test(input[i])) i++;
if (i >= input.length) break;
let tok = '';
while (i < input.length && !/\s/.test(input[i])) {
const ch = input[i];
if (ch === '"' || ch === "'") {
tok += ch; i++;
while (i < input.length && input[i] !== ch) { tok += input[i]; i++; }
if (i < input.length) { tok += input[i]; i++; }
} else { tok += ch; i++; }
}
tokens.push(tok);
}
return tokens;
}
function parseEvalCellsCell(text) {
const STARS = '\\*{2,}';
const CELL = new RegExp('^' + STARS + '\\s*Cell\\b\\s*(.*)$', 'i');
const END = new RegExp('^' + STARS + '\\s*End\\b.*$', 'i');
const ATTR = /^([a-zA-Z][\w-]*)(?::(?:"([^"]*)"|'([^']*)'|(.*)))?$/;
const DUR = /^\d+(?:ms|s|m)?$/;
const ID_KEYS = ['id', 'title', 'name', 'cell', 'file', 'label'];
const T_KEYS = ['t', 'timeout', 'duration', 'time'];
const RST_KEYS = ['rst', 'reset'];
const lines = text.split('\n');
if (lines.length && lines[lines.length - 1] === '') lines.pop();
const cells = [];
let i = 0;
while (i < lines.length && lines[i].trim() === '') i++;
while (i < lines.length) {
const m = CELL.exec(lines[i]);
if (!m) { i++; continue; }
const tokens = tokenizeCellAttrsHtml(m[1] || '');
let lang = null;
let title = '';
const attrs = [];
let bareReset = false;
const titleParts = [];
for (const tok of tokens) {
const lower = tok.toLowerCase();
if (RST_KEYS.indexOf(lower) >= 0) { bareReset = true; continue; }
const am = ATTR.exec(tok);
if (am && tok.indexOf(':') >= 0) {
const key = am[1].toLowerCase();
const value = am[2] !== undefined ? am[2] : am[3] !== undefined ? am[3] : (am[4] || '');
const lc = evalLangAlias(key);
if (lc) {
if (!lang) lang = lc;
if (!title && value) title = value;
continue;
}
if (ID_KEYS.indexOf(key) >= 0) { if (!title) title = value; continue; }
if (T_KEYS.indexOf(key) >= 0) { attrs.push('t=' + value); continue; }
if (RST_KEYS.indexOf(key) >= 0) { attrs.push('rst'); continue; }
continue;
}
const lc = evalLangAlias(tok);
if (lc && !lang) { lang = lc; continue; }
if (DUR.test(tok)) { attrs.push('t=' + tok); continue; }
titleParts.push(tok);
}
if (!title && titleParts.length) title = titleParts.join(' ');
if (bareReset) attrs.push('rst');
lang = lang || 'py';
i++;
const codeLines = [];
while (i < lines.length) {
if (END.test(lines[i])) { i++; break; }
if (CELL.test(lines[i])) break;
codeLines.push(lines[i]);
i++;
}
while (codeLines.length && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
cells.push({ lang, title, attrs, code: codeLines.join('\n') });
while (i < lines.length && lines[i].trim() === '') i++;
}
return cells;
}
function parseEvalCellsBegin(text) {
const STARS = '\\*{2,}';
const BEGIN = new RegExp('^' + STARS + '\\s*Begin\\b\\s*(\\S+)?\\s*$', 'i');
const END = new RegExp('^' + STARS + '\\s*End\\b.*$', 'i');
const TITLE = new RegExp('^' + STARS + '\\s*Title\\s*:\\s*(.+?)\\s*$', 'i');
const TIMEOUT = new RegExp('^' + STARS + '\\s*Timeout\\s*:\\s*(\\S+)\\s*$', 'i');
const RESET = new RegExp('^' + STARS + '\\s*Reset\\s*$', 'i');
const lines = text.split('\n');
if (lines.length && lines[lines.length - 1] === '') lines.pop();
const cells = [];
let i = 0;
while (i < lines.length && lines[i].trim() === '') i++;
while (i < lines.length) {
const beginMatch = BEGIN.exec(lines[i]);
if (!beginMatch) { i++; continue; }
const lang = evalLangAlias(beginMatch[1]) || 'py';
i++;
let title = '';
const attrs = [];
while (i < lines.length) {
const tm = TITLE.exec(lines[i]);
if (tm) { if (!title) title = tm[1]; i++; continue; }
const to = TIMEOUT.exec(lines[i]);
if (to) { attrs.push('t=' + to[1]); i++; continue; }
if (RESET.test(lines[i])) { attrs.push('rst'); i++; continue; }
break;
}
const codeLines = [];
while (i < lines.length) {
if (END.test(lines[i])) { i++; break; }
if (BEGIN.test(lines[i])) break;
codeLines.push(lines[i]);
i++;
}
while (codeLines.length && codeLines[codeLines.length - 1].trim() === '') codeLines.pop();
cells.push({ lang, title, attrs, code: codeLines.join('\n') });
while (i < lines.length && lines[i].trim() === '') i++;
}
return cells;
}
function parseEvalCellsLegacy(input) {
const HEADER = /^={5,}\s*(.*?)\s*={5,}\s*$/;
const lines = String(input).split('\n');
const cells = [];
let inheritedLang = 'py';
let current = null;
for (const line of lines) {
const m = line.match(HEADER);
if (m) {
if (current) cells.push(current);
const info = m[1] || '';
let lang = inheritedLang;
let title = '';
const langMatch = info.match(/^(py|js|ts)(?::"([^"]*)")?/);
if (langMatch) {
lang = langMatch[1];
if (langMatch[2]) title = langMatch[2];
}
if (!title) {
const idMatch = info.match(/id:"([^"]*)"/);
if (idMatch) title = idMatch[1];
}
inheritedLang = lang;
const attrs = [];
const tMatch = info.match(/(?:^|\s)t:(\S+)/);
if (tMatch) attrs.push('t=' + tMatch[1]);
if (/(?:^|\s)rst(?:\s|$)/.test(info)) attrs.push('rst');
current = { lang, title, attrs, code: '' };
} else {
if (!current) current = { lang: inheritedLang, title: '', attrs: [], code: '' };
current.code += (current.code ? '\n' : '') + line;
}
}
if (current) cells.push(current);
return cells.map(c => ({ ...c, code: c.code.replace(/\s+$/, '') }));
}
function evalLangToHljs(lang) {
return lang === 'py' ? 'python' : lang === 'js' ? 'javascript' : lang === 'ts' ? 'typescript' : null;
}
function renderEval(name, args, result, ctx) {
let html = toolHead('eval');
if (typeof args.input !== 'string') {
html += '
[missing input]
';
} else {
const cells = parseEvalCells(args.input);
if (cells.length === 0) {
html += codeBlock(args.input, 'python');
} else {
for (const cell of cells) {
html += '
';
const titleParts = [];
if (cell.title) titleParts.push(cell.title);
titleParts.push(cell.lang);
if (cell.attrs && cell.attrs.length) titleParts.push(...cell.attrs);
html += '
' + escapeHtml(titleParts.join(' · ')) + '
';
html += codeBlock(cell.code, evalLangToHljs(cell.lang));
html += '
';
}
}
}
if (result) {
html += ctx.renderResultImages();
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12);
}
return html;
}
function renderSearch(name, args, result, ctx) {
const pattern = str(args.pattern);
const paths = Array.isArray(args.paths) ? args.paths.map(p => shortenPath(String(p))).join(', ') : (args.path ? shortenPath(String(args.path)) : '.');
const patHtml = pattern === null ? invalidArgHtml() : escapeHtml(pattern);
let head = 'search/' + patHtml + '/';
head += ' in' + escapeHtml(paths) + '';
const badges = [];
if (args.i) badges.push('i');
if (args.skip) badges.push('skip=' + args.skip);
if (args.gitignore === false) badges.push('no-gitignore');
for (const b of badges) head += ' ' + escapeHtml(b) + '';
let html = '
' + head + '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 12);
}
return html;
}
function renderIrc(name, args, result, ctx) {
const op = str(args.op) || '?';
const badges = [op];
if (args.to) badges.push('to=' + args.to);
if (args.awaitReply === false) badges.push('no-reply');
let html = toolHead('irc', '', badges);
if (args.message) html += '
' + escapeHtml(String(args.message)) + '
';
if (result) {
const output = ctx.getResultText();
if (output) html += formatExpandableOutput(output, 8);
}
return html;
}
function renderGenericTool(name, args, result, ctx) {
let html = toolHead(name);
const argText = JSON.stringify(args, null, 2);
if (argText && argText !== '{}') {
html += '
';
},
};
const renderer = TOOL_RENDERERS[name] || renderGenericTool;
let html = '
';
if (intent) html += '
' + escapeHtml(intent) + '
';
try {
html += renderer(name, args, result, ctx);
} catch (err) {
html += renderGenericTool(name, args, result, ctx);
}
html += '
';
return html;
}
/**
* Build a shareable URL for a specific message.
* URL format: base?gistId&leafId=&targetId=
*/
function buildShareUrl(entryId) {
// Check for injected base URL (used when loaded in iframe via srcdoc)
const baseUrlMeta = document.querySelector('meta[name="pi-share-base-url"]');
const baseUrl = baseUrlMeta ? baseUrlMeta.content : window.location.href.split('?')[0];
const url = new URL(window.location.href);
// Find the gist ID (first query param without value, e.g., ?abc123)
const gistId = Array.from(url.searchParams.keys()).find(k => !url.searchParams.get(k));
// Build the share URL
const params = new URLSearchParams();
params.set('leafId', currentLeafId);
params.set('targetId', entryId);
// If we have an injected base URL (iframe context), use it directly
if (baseUrlMeta) {
return `${baseUrl}&${params.toString()}`;
}
// Otherwise build from current location (direct file access)
url.search = gistId ? `?${gistId}&${params.toString()}` : `?${params.toString()}`;
return url.toString();
}
/**
* Copy text to clipboard with visual feedback.
* Uses navigator.clipboard with fallback to execCommand for HTTP contexts.
*/
async function copyToClipboard(text, button) {
let success = false;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(text);
success = true;
}
} catch {
// Clipboard API failed, try fallback
}
// Fallback for HTTP or when Clipboard API is unavailable
if (!success) {
try {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
success = document.execCommand('copy');
document.body.removeChild(textarea);
} catch {
}
}
if (success && button) {
const originalHtml = button.innerHTML;
button.innerHTML = '✓';
button.classList.add('copied');
setTimeout(() => {
button.innerHTML = originalHtml;
button.classList.remove('copied');
}, 1500);
}
}
/**
* Render the copy-link button HTML for a message.
*/
function renderCopyLinkButton(entryId) {
return ``;
}
function renderEntry(entry) {
const ts = formatTimestamp(entry.timestamp);
const tsHtml = ts ? `
${ts}
` : '';
const entryId = `entry-${entry.id}`;
const copyBtnHtml = renderCopyLinkButton(entry.id);
if (entry.type === 'message') {
const msg = entry.message;
if (msg.role === 'user') {
let html = `
${copyBtnHtml}${tsHtml}`;
const content = msg.content;
if (Array.isArray(content)) {
const images = content.filter(c => c.type === 'image');
if (images.length > 0) {
html += '
';
for (const img of images) {
html += ``;
}
html += '
';
}
}
const text = typeof content === 'string' ? content :
content.filter(c => c.type === 'text').map(c => c.text).join('\n');
if (text.trim()) {
html += `
${safeMarkedParse(text)}
`;
}
html += '
';
return html;
}
if (msg.role === 'developer') {
let html = `
${copyBtnHtml}${tsHtml}`;
const content = msg.content;
const text = typeof content === 'string' ? content :
content.filter(c => c.type === 'text').map(c => c.text).join('\n');
if (text.trim()) {
html += `
${safeMarkedParse(text)}
`;
}
html += '
';
return html;
}
if (msg.role === 'assistant') {
let html = `
${copyBtnHtml}${tsHtml}`;
for (const block of msg.content) {
if (block.type === 'text' && block.text.trim()) {
html += `
${safeMarkedParse(block.text)}
`;
} else if (block.type === 'thinking' && block.thinking.trim()) {
html += `
${escapeHtml(block.thinking)}
Thinking ...
`;
}
}
for (const block of msg.content) {
if (block.type === 'toolCall') {
html += renderToolCall(block);
}
}
if (msg.stopReason === 'aborted') {
html += '
Aborted
';
} else if (msg.stopReason === 'error') {
html += `