@mariozechner/pi-coding-agent 0.46.0 → 0.47.0
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/CHANGELOG.md +29 -0
- package/dist/core/agent-session.d.ts +11 -3
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +81 -14
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
- package/dist/core/export-html/ansi-to-html.js +249 -0
- package/dist/core/export-html/ansi-to-html.js.map +1 -0
- package/dist/core/export-html/index.d.ts +17 -0
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +52 -23
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +0 -33
- package/dist/core/export-html/template.js +171 -18
- package/dist/core/export-html/tool-renderer.d.ts +35 -0
- package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
- package/dist/core/export-html/tool-renderer.js +57 -0
- package/dist/core/export-html/tool-renderer.js.map +1 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +41 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +24 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +4 -27
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +6 -37
- package/dist/core/skills.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +19 -14
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +1 -0
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +7 -0
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +13 -2
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +32 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +2 -2
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +2 -2
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +1 -1
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/tree-selector.d.ts +7 -0
- package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/tree-selector.js +140 -4
- package/dist/modes/interactive/components/tree-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +0 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -1
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +8 -0
- package/dist/utils/frontmatter.d.ts.map +1 -0
- package/dist/utils/frontmatter.js +26 -0
- package/dist/utils/frontmatter.js.map +1 -0
- package/docs/extensions.md +51 -0
- package/docs/rpc.md +15 -15
- package/docs/tui.md +26 -0
- package/examples/extensions/input-transform.ts +43 -0
- package/examples/extensions/modal-editor.ts +1 -1
- package/examples/extensions/overlay-test.ts +8 -3
- package/examples/extensions/question.ts +1 -1
- package/examples/extensions/questionnaire.ts +1 -1
- package/examples/extensions/rainbow-editor.ts +1 -8
- package/examples/extensions/subagent/agents.ts +3 -32
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +6 -5
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape code to HTML converter.
|
|
3
|
+
*
|
|
4
|
+
* Converts terminal ANSI color/style codes to HTML with inline styles.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Standard foreground colors (30-37) and bright variants (90-97)
|
|
7
|
+
* - Standard background colors (40-47) and bright variants (100-107)
|
|
8
|
+
* - 256-color palette (38;5;N and 48;5;N)
|
|
9
|
+
* - RGB true color (38;2;R;G;B and 48;2;R;G;B)
|
|
10
|
+
* - Text styles: bold (1), dim (2), italic (3), underline (4)
|
|
11
|
+
* - Reset (0)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Convert ANSI-escaped text to HTML with inline styles.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ansiToHtml(text: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Convert array of ANSI-escaped lines to HTML.
|
|
19
|
+
* Each line is wrapped in a div element.
|
|
20
|
+
*/
|
|
21
|
+
export declare function ansiLinesToHtml(lines: string[]): string;
|
|
22
|
+
//# sourceMappingURL=ansi-to-html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi-to-html.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/ansi-to-html.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuLH;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoD/C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAEvD","sourcesContent":["/**\n * ANSI escape code to HTML converter.\n *\n * Converts terminal ANSI color/style codes to HTML with inline styles.\n * Supports:\n * - Standard foreground colors (30-37) and bright variants (90-97)\n * - Standard background colors (40-47) and bright variants (100-107)\n * - 256-color palette (38;5;N and 48;5;N)\n * - RGB true color (38;2;R;G;B and 48;2;R;G;B)\n * - Text styles: bold (1), dim (2), italic (3), underline (4)\n * - Reset (0)\n */\n\n// Standard ANSI color palette (0-15)\nconst ANSI_COLORS = [\n\t\"#000000\", // 0: black\n\t\"#800000\", // 1: red\n\t\"#008000\", // 2: green\n\t\"#808000\", // 3: yellow\n\t\"#000080\", // 4: blue\n\t\"#800080\", // 5: magenta\n\t\"#008080\", // 6: cyan\n\t\"#c0c0c0\", // 7: white\n\t\"#808080\", // 8: bright black\n\t\"#ff0000\", // 9: bright red\n\t\"#00ff00\", // 10: bright green\n\t\"#ffff00\", // 11: bright yellow\n\t\"#0000ff\", // 12: bright blue\n\t\"#ff00ff\", // 13: bright magenta\n\t\"#00ffff\", // 14: bright cyan\n\t\"#ffffff\", // 15: bright white\n];\n\n/**\n * Convert 256-color index to hex.\n */\nfunction color256ToHex(index: number): string {\n\t// Standard colors (0-15)\n\tif (index < 16) {\n\t\treturn ANSI_COLORS[index];\n\t}\n\n\t// Color cube (16-231): 6x6x6 = 216 colors\n\tif (index < 232) {\n\t\tconst cubeIndex = index - 16;\n\t\tconst r = Math.floor(cubeIndex / 36);\n\t\tconst g = Math.floor((cubeIndex % 36) / 6);\n\t\tconst b = cubeIndex % 6;\n\t\tconst toComponent = (n: number) => (n === 0 ? 0 : 55 + n * 40);\n\t\tconst toHex = (n: number) => toComponent(n).toString(16).padStart(2, \"0\");\n\t\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\t}\n\n\t// Grayscale (232-255): 24 shades\n\tconst gray = 8 + (index - 232) * 10;\n\tconst grayHex = gray.toString(16).padStart(2, \"0\");\n\treturn `#${grayHex}${grayHex}${grayHex}`;\n}\n\n/**\n * Escape HTML special characters.\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\ninterface TextStyle {\n\tfg: string | null;\n\tbg: string | null;\n\tbold: boolean;\n\tdim: boolean;\n\titalic: boolean;\n\tunderline: boolean;\n}\n\nfunction createEmptyStyle(): TextStyle {\n\treturn {\n\t\tfg: null,\n\t\tbg: null,\n\t\tbold: false,\n\t\tdim: false,\n\t\titalic: false,\n\t\tunderline: false,\n\t};\n}\n\nfunction styleToInlineCSS(style: TextStyle): string {\n\tconst parts: string[] = [];\n\tif (style.fg) parts.push(`color:${style.fg}`);\n\tif (style.bg) parts.push(`background-color:${style.bg}`);\n\tif (style.bold) parts.push(\"font-weight:bold\");\n\tif (style.dim) parts.push(\"opacity:0.6\");\n\tif (style.italic) parts.push(\"font-style:italic\");\n\tif (style.underline) parts.push(\"text-decoration:underline\");\n\treturn parts.join(\";\");\n}\n\nfunction hasStyle(style: TextStyle): boolean {\n\treturn style.fg !== null || style.bg !== null || style.bold || style.dim || style.italic || style.underline;\n}\n\n/**\n * Parse ANSI SGR (Select Graphic Rendition) codes and update style.\n */\nfunction applySgrCode(params: number[], style: TextStyle): void {\n\tlet i = 0;\n\twhile (i < params.length) {\n\t\tconst code = params[i];\n\n\t\tif (code === 0) {\n\t\t\t// Reset all\n\t\t\tstyle.fg = null;\n\t\t\tstyle.bg = null;\n\t\t\tstyle.bold = false;\n\t\t\tstyle.dim = false;\n\t\t\tstyle.italic = false;\n\t\t\tstyle.underline = false;\n\t\t} else if (code === 1) {\n\t\t\tstyle.bold = true;\n\t\t} else if (code === 2) {\n\t\t\tstyle.dim = true;\n\t\t} else if (code === 3) {\n\t\t\tstyle.italic = true;\n\t\t} else if (code === 4) {\n\t\t\tstyle.underline = true;\n\t\t} else if (code === 22) {\n\t\t\t// Reset bold/dim\n\t\t\tstyle.bold = false;\n\t\t\tstyle.dim = false;\n\t\t} else if (code === 23) {\n\t\t\tstyle.italic = false;\n\t\t} else if (code === 24) {\n\t\t\tstyle.underline = false;\n\t\t} else if (code >= 30 && code <= 37) {\n\t\t\t// Standard foreground colors\n\t\t\tstyle.fg = ANSI_COLORS[code - 30];\n\t\t} else if (code === 38) {\n\t\t\t// Extended foreground color\n\t\t\tif (params[i + 1] === 5 && params.length > i + 2) {\n\t\t\t\t// 256-color: 38;5;N\n\t\t\t\tstyle.fg = color256ToHex(params[i + 2]);\n\t\t\t\ti += 2;\n\t\t\t} else if (params[i + 1] === 2 && params.length > i + 4) {\n\t\t\t\t// RGB: 38;2;R;G;B\n\t\t\t\tconst r = params[i + 2];\n\t\t\t\tconst g = params[i + 3];\n\t\t\t\tconst b = params[i + 4];\n\t\t\t\tstyle.fg = `rgb(${r},${g},${b})`;\n\t\t\t\ti += 4;\n\t\t\t}\n\t\t} else if (code === 39) {\n\t\t\t// Default foreground\n\t\t\tstyle.fg = null;\n\t\t} else if (code >= 40 && code <= 47) {\n\t\t\t// Standard background colors\n\t\t\tstyle.bg = ANSI_COLORS[code - 40];\n\t\t} else if (code === 48) {\n\t\t\t// Extended background color\n\t\t\tif (params[i + 1] === 5 && params.length > i + 2) {\n\t\t\t\t// 256-color: 48;5;N\n\t\t\t\tstyle.bg = color256ToHex(params[i + 2]);\n\t\t\t\ti += 2;\n\t\t\t} else if (params[i + 1] === 2 && params.length > i + 4) {\n\t\t\t\t// RGB: 48;2;R;G;B\n\t\t\t\tconst r = params[i + 2];\n\t\t\t\tconst g = params[i + 3];\n\t\t\t\tconst b = params[i + 4];\n\t\t\t\tstyle.bg = `rgb(${r},${g},${b})`;\n\t\t\t\ti += 4;\n\t\t\t}\n\t\t} else if (code === 49) {\n\t\t\t// Default background\n\t\t\tstyle.bg = null;\n\t\t} else if (code >= 90 && code <= 97) {\n\t\t\t// Bright foreground colors\n\t\t\tstyle.fg = ANSI_COLORS[code - 90 + 8];\n\t\t} else if (code >= 100 && code <= 107) {\n\t\t\t// Bright background colors\n\t\t\tstyle.bg = ANSI_COLORS[code - 100 + 8];\n\t\t}\n\t\t// Ignore unrecognized codes\n\n\t\ti++;\n\t}\n}\n\n// Match ANSI escape sequences: ESC[ followed by params and ending with 'm'\nconst ANSI_REGEX = /\\x1b\\[([\\d;]*)m/g;\n\n/**\n * Convert ANSI-escaped text to HTML with inline styles.\n */\nexport function ansiToHtml(text: string): string {\n\tconst style = createEmptyStyle();\n\tlet result = \"\";\n\tlet lastIndex = 0;\n\tlet inSpan = false;\n\n\t// Reset regex state\n\tANSI_REGEX.lastIndex = 0;\n\n\tlet match = ANSI_REGEX.exec(text);\n\twhile (match !== null) {\n\t\t// Add text before this escape sequence\n\t\tconst beforeText = text.slice(lastIndex, match.index);\n\t\tif (beforeText) {\n\t\t\tresult += escapeHtml(beforeText);\n\t\t}\n\n\t\t// Parse SGR parameters\n\t\tconst paramStr = match[1];\n\t\tconst params = paramStr ? paramStr.split(\";\").map((p) => parseInt(p, 10) || 0) : [0];\n\n\t\t// Close existing span if we have one\n\t\tif (inSpan) {\n\t\t\tresult += \"</span>\";\n\t\t\tinSpan = false;\n\t\t}\n\n\t\t// Apply the codes\n\t\tapplySgrCode(params, style);\n\n\t\t// Open new span if we have any styling\n\t\tif (hasStyle(style)) {\n\t\t\tresult += `<span style=\"${styleToInlineCSS(style)}\">`;\n\t\t\tinSpan = true;\n\t\t}\n\n\t\tlastIndex = match.index + match[0].length;\n\t\tmatch = ANSI_REGEX.exec(text);\n\t}\n\n\t// Add remaining text\n\tconst remainingText = text.slice(lastIndex);\n\tif (remainingText) {\n\t\tresult += escapeHtml(remainingText);\n\t}\n\n\t// Close any open span\n\tif (inSpan) {\n\t\tresult += \"</span>\";\n\t}\n\n\treturn result;\n}\n\n/**\n * Convert array of ANSI-escaped lines to HTML.\n * Each line is wrapped in a div element.\n */\nexport function ansiLinesToHtml(lines: string[]): string {\n\treturn lines.map((line) => `<div class=\"ansi-line\">${ansiToHtml(line) || \" \"}</div>`).join(\"\\n\");\n}\n"]}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape code to HTML converter.
|
|
3
|
+
*
|
|
4
|
+
* Converts terminal ANSI color/style codes to HTML with inline styles.
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Standard foreground colors (30-37) and bright variants (90-97)
|
|
7
|
+
* - Standard background colors (40-47) and bright variants (100-107)
|
|
8
|
+
* - 256-color palette (38;5;N and 48;5;N)
|
|
9
|
+
* - RGB true color (38;2;R;G;B and 48;2;R;G;B)
|
|
10
|
+
* - Text styles: bold (1), dim (2), italic (3), underline (4)
|
|
11
|
+
* - Reset (0)
|
|
12
|
+
*/
|
|
13
|
+
// Standard ANSI color palette (0-15)
|
|
14
|
+
const ANSI_COLORS = [
|
|
15
|
+
"#000000", // 0: black
|
|
16
|
+
"#800000", // 1: red
|
|
17
|
+
"#008000", // 2: green
|
|
18
|
+
"#808000", // 3: yellow
|
|
19
|
+
"#000080", // 4: blue
|
|
20
|
+
"#800080", // 5: magenta
|
|
21
|
+
"#008080", // 6: cyan
|
|
22
|
+
"#c0c0c0", // 7: white
|
|
23
|
+
"#808080", // 8: bright black
|
|
24
|
+
"#ff0000", // 9: bright red
|
|
25
|
+
"#00ff00", // 10: bright green
|
|
26
|
+
"#ffff00", // 11: bright yellow
|
|
27
|
+
"#0000ff", // 12: bright blue
|
|
28
|
+
"#ff00ff", // 13: bright magenta
|
|
29
|
+
"#00ffff", // 14: bright cyan
|
|
30
|
+
"#ffffff", // 15: bright white
|
|
31
|
+
];
|
|
32
|
+
/**
|
|
33
|
+
* Convert 256-color index to hex.
|
|
34
|
+
*/
|
|
35
|
+
function color256ToHex(index) {
|
|
36
|
+
// Standard colors (0-15)
|
|
37
|
+
if (index < 16) {
|
|
38
|
+
return ANSI_COLORS[index];
|
|
39
|
+
}
|
|
40
|
+
// Color cube (16-231): 6x6x6 = 216 colors
|
|
41
|
+
if (index < 232) {
|
|
42
|
+
const cubeIndex = index - 16;
|
|
43
|
+
const r = Math.floor(cubeIndex / 36);
|
|
44
|
+
const g = Math.floor((cubeIndex % 36) / 6);
|
|
45
|
+
const b = cubeIndex % 6;
|
|
46
|
+
const toComponent = (n) => (n === 0 ? 0 : 55 + n * 40);
|
|
47
|
+
const toHex = (n) => toComponent(n).toString(16).padStart(2, "0");
|
|
48
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
49
|
+
}
|
|
50
|
+
// Grayscale (232-255): 24 shades
|
|
51
|
+
const gray = 8 + (index - 232) * 10;
|
|
52
|
+
const grayHex = gray.toString(16).padStart(2, "0");
|
|
53
|
+
return `#${grayHex}${grayHex}${grayHex}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Escape HTML special characters.
|
|
57
|
+
*/
|
|
58
|
+
function escapeHtml(text) {
|
|
59
|
+
return text
|
|
60
|
+
.replace(/&/g, "&")
|
|
61
|
+
.replace(/</g, "<")
|
|
62
|
+
.replace(/>/g, ">")
|
|
63
|
+
.replace(/"/g, """)
|
|
64
|
+
.replace(/'/g, "'");
|
|
65
|
+
}
|
|
66
|
+
function createEmptyStyle() {
|
|
67
|
+
return {
|
|
68
|
+
fg: null,
|
|
69
|
+
bg: null,
|
|
70
|
+
bold: false,
|
|
71
|
+
dim: false,
|
|
72
|
+
italic: false,
|
|
73
|
+
underline: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function styleToInlineCSS(style) {
|
|
77
|
+
const parts = [];
|
|
78
|
+
if (style.fg)
|
|
79
|
+
parts.push(`color:${style.fg}`);
|
|
80
|
+
if (style.bg)
|
|
81
|
+
parts.push(`background-color:${style.bg}`);
|
|
82
|
+
if (style.bold)
|
|
83
|
+
parts.push("font-weight:bold");
|
|
84
|
+
if (style.dim)
|
|
85
|
+
parts.push("opacity:0.6");
|
|
86
|
+
if (style.italic)
|
|
87
|
+
parts.push("font-style:italic");
|
|
88
|
+
if (style.underline)
|
|
89
|
+
parts.push("text-decoration:underline");
|
|
90
|
+
return parts.join(";");
|
|
91
|
+
}
|
|
92
|
+
function hasStyle(style) {
|
|
93
|
+
return style.fg !== null || style.bg !== null || style.bold || style.dim || style.italic || style.underline;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse ANSI SGR (Select Graphic Rendition) codes and update style.
|
|
97
|
+
*/
|
|
98
|
+
function applySgrCode(params, style) {
|
|
99
|
+
let i = 0;
|
|
100
|
+
while (i < params.length) {
|
|
101
|
+
const code = params[i];
|
|
102
|
+
if (code === 0) {
|
|
103
|
+
// Reset all
|
|
104
|
+
style.fg = null;
|
|
105
|
+
style.bg = null;
|
|
106
|
+
style.bold = false;
|
|
107
|
+
style.dim = false;
|
|
108
|
+
style.italic = false;
|
|
109
|
+
style.underline = false;
|
|
110
|
+
}
|
|
111
|
+
else if (code === 1) {
|
|
112
|
+
style.bold = true;
|
|
113
|
+
}
|
|
114
|
+
else if (code === 2) {
|
|
115
|
+
style.dim = true;
|
|
116
|
+
}
|
|
117
|
+
else if (code === 3) {
|
|
118
|
+
style.italic = true;
|
|
119
|
+
}
|
|
120
|
+
else if (code === 4) {
|
|
121
|
+
style.underline = true;
|
|
122
|
+
}
|
|
123
|
+
else if (code === 22) {
|
|
124
|
+
// Reset bold/dim
|
|
125
|
+
style.bold = false;
|
|
126
|
+
style.dim = false;
|
|
127
|
+
}
|
|
128
|
+
else if (code === 23) {
|
|
129
|
+
style.italic = false;
|
|
130
|
+
}
|
|
131
|
+
else if (code === 24) {
|
|
132
|
+
style.underline = false;
|
|
133
|
+
}
|
|
134
|
+
else if (code >= 30 && code <= 37) {
|
|
135
|
+
// Standard foreground colors
|
|
136
|
+
style.fg = ANSI_COLORS[code - 30];
|
|
137
|
+
}
|
|
138
|
+
else if (code === 38) {
|
|
139
|
+
// Extended foreground color
|
|
140
|
+
if (params[i + 1] === 5 && params.length > i + 2) {
|
|
141
|
+
// 256-color: 38;5;N
|
|
142
|
+
style.fg = color256ToHex(params[i + 2]);
|
|
143
|
+
i += 2;
|
|
144
|
+
}
|
|
145
|
+
else if (params[i + 1] === 2 && params.length > i + 4) {
|
|
146
|
+
// RGB: 38;2;R;G;B
|
|
147
|
+
const r = params[i + 2];
|
|
148
|
+
const g = params[i + 3];
|
|
149
|
+
const b = params[i + 4];
|
|
150
|
+
style.fg = `rgb(${r},${g},${b})`;
|
|
151
|
+
i += 4;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else if (code === 39) {
|
|
155
|
+
// Default foreground
|
|
156
|
+
style.fg = null;
|
|
157
|
+
}
|
|
158
|
+
else if (code >= 40 && code <= 47) {
|
|
159
|
+
// Standard background colors
|
|
160
|
+
style.bg = ANSI_COLORS[code - 40];
|
|
161
|
+
}
|
|
162
|
+
else if (code === 48) {
|
|
163
|
+
// Extended background color
|
|
164
|
+
if (params[i + 1] === 5 && params.length > i + 2) {
|
|
165
|
+
// 256-color: 48;5;N
|
|
166
|
+
style.bg = color256ToHex(params[i + 2]);
|
|
167
|
+
i += 2;
|
|
168
|
+
}
|
|
169
|
+
else if (params[i + 1] === 2 && params.length > i + 4) {
|
|
170
|
+
// RGB: 48;2;R;G;B
|
|
171
|
+
const r = params[i + 2];
|
|
172
|
+
const g = params[i + 3];
|
|
173
|
+
const b = params[i + 4];
|
|
174
|
+
style.bg = `rgb(${r},${g},${b})`;
|
|
175
|
+
i += 4;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else if (code === 49) {
|
|
179
|
+
// Default background
|
|
180
|
+
style.bg = null;
|
|
181
|
+
}
|
|
182
|
+
else if (code >= 90 && code <= 97) {
|
|
183
|
+
// Bright foreground colors
|
|
184
|
+
style.fg = ANSI_COLORS[code - 90 + 8];
|
|
185
|
+
}
|
|
186
|
+
else if (code >= 100 && code <= 107) {
|
|
187
|
+
// Bright background colors
|
|
188
|
+
style.bg = ANSI_COLORS[code - 100 + 8];
|
|
189
|
+
}
|
|
190
|
+
// Ignore unrecognized codes
|
|
191
|
+
i++;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Match ANSI escape sequences: ESC[ followed by params and ending with 'm'
|
|
195
|
+
const ANSI_REGEX = /\x1b\[([\d;]*)m/g;
|
|
196
|
+
/**
|
|
197
|
+
* Convert ANSI-escaped text to HTML with inline styles.
|
|
198
|
+
*/
|
|
199
|
+
export function ansiToHtml(text) {
|
|
200
|
+
const style = createEmptyStyle();
|
|
201
|
+
let result = "";
|
|
202
|
+
let lastIndex = 0;
|
|
203
|
+
let inSpan = false;
|
|
204
|
+
// Reset regex state
|
|
205
|
+
ANSI_REGEX.lastIndex = 0;
|
|
206
|
+
let match = ANSI_REGEX.exec(text);
|
|
207
|
+
while (match !== null) {
|
|
208
|
+
// Add text before this escape sequence
|
|
209
|
+
const beforeText = text.slice(lastIndex, match.index);
|
|
210
|
+
if (beforeText) {
|
|
211
|
+
result += escapeHtml(beforeText);
|
|
212
|
+
}
|
|
213
|
+
// Parse SGR parameters
|
|
214
|
+
const paramStr = match[1];
|
|
215
|
+
const params = paramStr ? paramStr.split(";").map((p) => parseInt(p, 10) || 0) : [0];
|
|
216
|
+
// Close existing span if we have one
|
|
217
|
+
if (inSpan) {
|
|
218
|
+
result += "</span>";
|
|
219
|
+
inSpan = false;
|
|
220
|
+
}
|
|
221
|
+
// Apply the codes
|
|
222
|
+
applySgrCode(params, style);
|
|
223
|
+
// Open new span if we have any styling
|
|
224
|
+
if (hasStyle(style)) {
|
|
225
|
+
result += `<span style="${styleToInlineCSS(style)}">`;
|
|
226
|
+
inSpan = true;
|
|
227
|
+
}
|
|
228
|
+
lastIndex = match.index + match[0].length;
|
|
229
|
+
match = ANSI_REGEX.exec(text);
|
|
230
|
+
}
|
|
231
|
+
// Add remaining text
|
|
232
|
+
const remainingText = text.slice(lastIndex);
|
|
233
|
+
if (remainingText) {
|
|
234
|
+
result += escapeHtml(remainingText);
|
|
235
|
+
}
|
|
236
|
+
// Close any open span
|
|
237
|
+
if (inSpan) {
|
|
238
|
+
result += "</span>";
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Convert array of ANSI-escaped lines to HTML.
|
|
244
|
+
* Each line is wrapped in a div element.
|
|
245
|
+
*/
|
|
246
|
+
export function ansiLinesToHtml(lines) {
|
|
247
|
+
return lines.map((line) => `<div class="ansi-line">${ansiToHtml(line) || " "}</div>`).join("\n");
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=ansi-to-html.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi-to-html.js","sourceRoot":"","sources":["../../../src/core/export-html/ansi-to-html.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ANSI escape code to HTML converter.\n *\n * Converts terminal ANSI color/style codes to HTML with inline styles.\n * Supports:\n * - Standard foreground colors (30-37) and bright variants (90-97)\n * - Standard background colors (40-47) and bright variants (100-107)\n * - 256-color palette (38;5;N and 48;5;N)\n * - RGB true color (38;2;R;G;B and 48;2;R;G;B)\n * - Text styles: bold (1), dim (2), italic (3), underline (4)\n * - Reset (0)\n */\n\n// Standard ANSI color palette (0-15)\nconst ANSI_COLORS = [\n\t\"#000000\", // 0: black\n\t\"#800000\", // 1: red\n\t\"#008000\", // 2: green\n\t\"#808000\", // 3: yellow\n\t\"#000080\", // 4: blue\n\t\"#800080\", // 5: magenta\n\t\"#008080\", // 6: cyan\n\t\"#c0c0c0\", // 7: white\n\t\"#808080\", // 8: bright black\n\t\"#ff0000\", // 9: bright red\n\t\"#00ff00\", // 10: bright green\n\t\"#ffff00\", // 11: bright yellow\n\t\"#0000ff\", // 12: bright blue\n\t\"#ff00ff\", // 13: bright magenta\n\t\"#00ffff\", // 14: bright cyan\n\t\"#ffffff\", // 15: bright white\n];\n\n/**\n * Convert 256-color index to hex.\n */\nfunction color256ToHex(index: number): string {\n\t// Standard colors (0-15)\n\tif (index < 16) {\n\t\treturn ANSI_COLORS[index];\n\t}\n\n\t// Color cube (16-231): 6x6x6 = 216 colors\n\tif (index < 232) {\n\t\tconst cubeIndex = index - 16;\n\t\tconst r = Math.floor(cubeIndex / 36);\n\t\tconst g = Math.floor((cubeIndex % 36) / 6);\n\t\tconst b = cubeIndex % 6;\n\t\tconst toComponent = (n: number) => (n === 0 ? 0 : 55 + n * 40);\n\t\tconst toHex = (n: number) => toComponent(n).toString(16).padStart(2, \"0\");\n\t\treturn `#${toHex(r)}${toHex(g)}${toHex(b)}`;\n\t}\n\n\t// Grayscale (232-255): 24 shades\n\tconst gray = 8 + (index - 232) * 10;\n\tconst grayHex = gray.toString(16).padStart(2, \"0\");\n\treturn `#${grayHex}${grayHex}${grayHex}`;\n}\n\n/**\n * Escape HTML special characters.\n */\nfunction escapeHtml(text: string): string {\n\treturn text\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\ninterface TextStyle {\n\tfg: string | null;\n\tbg: string | null;\n\tbold: boolean;\n\tdim: boolean;\n\titalic: boolean;\n\tunderline: boolean;\n}\n\nfunction createEmptyStyle(): TextStyle {\n\treturn {\n\t\tfg: null,\n\t\tbg: null,\n\t\tbold: false,\n\t\tdim: false,\n\t\titalic: false,\n\t\tunderline: false,\n\t};\n}\n\nfunction styleToInlineCSS(style: TextStyle): string {\n\tconst parts: string[] = [];\n\tif (style.fg) parts.push(`color:${style.fg}`);\n\tif (style.bg) parts.push(`background-color:${style.bg}`);\n\tif (style.bold) parts.push(\"font-weight:bold\");\n\tif (style.dim) parts.push(\"opacity:0.6\");\n\tif (style.italic) parts.push(\"font-style:italic\");\n\tif (style.underline) parts.push(\"text-decoration:underline\");\n\treturn parts.join(\";\");\n}\n\nfunction hasStyle(style: TextStyle): boolean {\n\treturn style.fg !== null || style.bg !== null || style.bold || style.dim || style.italic || style.underline;\n}\n\n/**\n * Parse ANSI SGR (Select Graphic Rendition) codes and update style.\n */\nfunction applySgrCode(params: number[], style: TextStyle): void {\n\tlet i = 0;\n\twhile (i < params.length) {\n\t\tconst code = params[i];\n\n\t\tif (code === 0) {\n\t\t\t// Reset all\n\t\t\tstyle.fg = null;\n\t\t\tstyle.bg = null;\n\t\t\tstyle.bold = false;\n\t\t\tstyle.dim = false;\n\t\t\tstyle.italic = false;\n\t\t\tstyle.underline = false;\n\t\t} else if (code === 1) {\n\t\t\tstyle.bold = true;\n\t\t} else if (code === 2) {\n\t\t\tstyle.dim = true;\n\t\t} else if (code === 3) {\n\t\t\tstyle.italic = true;\n\t\t} else if (code === 4) {\n\t\t\tstyle.underline = true;\n\t\t} else if (code === 22) {\n\t\t\t// Reset bold/dim\n\t\t\tstyle.bold = false;\n\t\t\tstyle.dim = false;\n\t\t} else if (code === 23) {\n\t\t\tstyle.italic = false;\n\t\t} else if (code === 24) {\n\t\t\tstyle.underline = false;\n\t\t} else if (code >= 30 && code <= 37) {\n\t\t\t// Standard foreground colors\n\t\t\tstyle.fg = ANSI_COLORS[code - 30];\n\t\t} else if (code === 38) {\n\t\t\t// Extended foreground color\n\t\t\tif (params[i + 1] === 5 && params.length > i + 2) {\n\t\t\t\t// 256-color: 38;5;N\n\t\t\t\tstyle.fg = color256ToHex(params[i + 2]);\n\t\t\t\ti += 2;\n\t\t\t} else if (params[i + 1] === 2 && params.length > i + 4) {\n\t\t\t\t// RGB: 38;2;R;G;B\n\t\t\t\tconst r = params[i + 2];\n\t\t\t\tconst g = params[i + 3];\n\t\t\t\tconst b = params[i + 4];\n\t\t\t\tstyle.fg = `rgb(${r},${g},${b})`;\n\t\t\t\ti += 4;\n\t\t\t}\n\t\t} else if (code === 39) {\n\t\t\t// Default foreground\n\t\t\tstyle.fg = null;\n\t\t} else if (code >= 40 && code <= 47) {\n\t\t\t// Standard background colors\n\t\t\tstyle.bg = ANSI_COLORS[code - 40];\n\t\t} else if (code === 48) {\n\t\t\t// Extended background color\n\t\t\tif (params[i + 1] === 5 && params.length > i + 2) {\n\t\t\t\t// 256-color: 48;5;N\n\t\t\t\tstyle.bg = color256ToHex(params[i + 2]);\n\t\t\t\ti += 2;\n\t\t\t} else if (params[i + 1] === 2 && params.length > i + 4) {\n\t\t\t\t// RGB: 48;2;R;G;B\n\t\t\t\tconst r = params[i + 2];\n\t\t\t\tconst g = params[i + 3];\n\t\t\t\tconst b = params[i + 4];\n\t\t\t\tstyle.bg = `rgb(${r},${g},${b})`;\n\t\t\t\ti += 4;\n\t\t\t}\n\t\t} else if (code === 49) {\n\t\t\t// Default background\n\t\t\tstyle.bg = null;\n\t\t} else if (code >= 90 && code <= 97) {\n\t\t\t// Bright foreground colors\n\t\t\tstyle.fg = ANSI_COLORS[code - 90 + 8];\n\t\t} else if (code >= 100 && code <= 107) {\n\t\t\t// Bright background colors\n\t\t\tstyle.bg = ANSI_COLORS[code - 100 + 8];\n\t\t}\n\t\t// Ignore unrecognized codes\n\n\t\ti++;\n\t}\n}\n\n// Match ANSI escape sequences: ESC[ followed by params and ending with 'm'\nconst ANSI_REGEX = /\\x1b\\[([\\d;]*)m/g;\n\n/**\n * Convert ANSI-escaped text to HTML with inline styles.\n */\nexport function ansiToHtml(text: string): string {\n\tconst style = createEmptyStyle();\n\tlet result = \"\";\n\tlet lastIndex = 0;\n\tlet inSpan = false;\n\n\t// Reset regex state\n\tANSI_REGEX.lastIndex = 0;\n\n\tlet match = ANSI_REGEX.exec(text);\n\twhile (match !== null) {\n\t\t// Add text before this escape sequence\n\t\tconst beforeText = text.slice(lastIndex, match.index);\n\t\tif (beforeText) {\n\t\t\tresult += escapeHtml(beforeText);\n\t\t}\n\n\t\t// Parse SGR parameters\n\t\tconst paramStr = match[1];\n\t\tconst params = paramStr ? paramStr.split(\";\").map((p) => parseInt(p, 10) || 0) : [0];\n\n\t\t// Close existing span if we have one\n\t\tif (inSpan) {\n\t\t\tresult += \"</span>\";\n\t\t\tinSpan = false;\n\t\t}\n\n\t\t// Apply the codes\n\t\tapplySgrCode(params, style);\n\n\t\t// Open new span if we have any styling\n\t\tif (hasStyle(style)) {\n\t\t\tresult += `<span style=\"${styleToInlineCSS(style)}\">`;\n\t\t\tinSpan = true;\n\t\t}\n\n\t\tlastIndex = match.index + match[0].length;\n\t\tmatch = ANSI_REGEX.exec(text);\n\t}\n\n\t// Add remaining text\n\tconst remainingText = text.slice(lastIndex);\n\tif (remainingText) {\n\t\tresult += escapeHtml(remainingText);\n\t}\n\n\t// Close any open span\n\tif (inSpan) {\n\t\tresult += \"</span>\";\n\t}\n\n\treturn result;\n}\n\n/**\n * Convert array of ANSI-escaped lines to HTML.\n * Each line is wrapped in a div element.\n */\nexport function ansiLinesToHtml(lines: string[]): string {\n\treturn lines.map((line) => `<div class=\"ansi-line\">${ansiToHtml(line) || \" \"}</div>`).join(\"\\n\");\n}\n"]}
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import type { AgentState } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import { SessionManager } from "../session-manager.js";
|
|
3
|
+
/**
|
|
4
|
+
* Interface for rendering custom tools to HTML.
|
|
5
|
+
* Used by agent-session to pre-render extension tool output.
|
|
6
|
+
*/
|
|
7
|
+
export interface ToolHtmlRenderer {
|
|
8
|
+
/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */
|
|
9
|
+
renderCall(toolName: string, args: unknown): string | undefined;
|
|
10
|
+
/** Render a tool result to HTML. Returns undefined if tool has no custom renderer. */
|
|
11
|
+
renderResult(toolName: string, result: Array<{
|
|
12
|
+
type: string;
|
|
13
|
+
text?: string;
|
|
14
|
+
data?: string;
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
}>, details: unknown, isError: boolean): string | undefined;
|
|
17
|
+
}
|
|
3
18
|
export interface ExportOptions {
|
|
4
19
|
outputPath?: string;
|
|
5
20
|
themeName?: string;
|
|
21
|
+
/** Optional tool renderer for custom tools */
|
|
22
|
+
toolRenderer?: ToolHtmlRenderer;
|
|
6
23
|
}
|
|
7
24
|
/**
|
|
8
25
|
* Export session to HTML using SessionManager and AgentState.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,6BAA6B,CAAC;AAMzE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAuKD;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,EAAE,EAAE,cAAc,EAClB,KAAK,CAAC,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,GAC9B,OAAO,CAAC,MAAM,CAAC,CA8BjB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4BzG","sourcesContent":["import type { AgentState, AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { buildCodexPiBridge, getCodexInstructions } from \"@mariozechner/pi-ai\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.js\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.js\";\nimport { SessionManager } from \"../session-manager.js\";\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n}\n\n/** Info about Codex injection to show inline with model_change entries */\ninterface CodexInjectionInfo {\n\t/** Codex instructions text */\n\tinstructions: string;\n\t/** Bridge text (tool list) */\n\tbridge: string;\n}\n\n/**\n * Build Codex injection info for display inline with model_change entries.\n */\nasync function buildCodexInjectionInfo(tools?: AgentTool[]): Promise<CodexInjectionInfo | undefined> {\n\t// Try to get cached instructions for default model family\n\tlet instructions: string | null = null;\n\ttry {\n\t\tinstructions = getCodexInstructions();\n\t} catch {\n\t\t// Cache miss - that's fine\n\t}\n\n\tconst bridgeText = buildCodexPiBridge(tools);\n\n\tconst instructionsText =\n\t\tinstructions || \"(Codex instructions not cached. Run a Codex request to populate the local cache.)\";\n\n\treturn {\n\t\tinstructions: instructionsText,\n\t\tbridge: bridgeText,\n\t};\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\t/** Info for rendering Codex injection inline with model_change entries */\n\tcodexInjectionInfo?: CodexInjectionInfo;\n\ttools?: { name: string; description: string }[];\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst exportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = exportColors.pageBg;\n\tconst containerBg = exportColors.cardBg;\n\tconst infoBg = exportColors.infoBg;\n\n\t// Base64 encode session data to avoid escaping issues\n\tconst sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString(\"base64\");\n\n\t// Build the CSS with theme variables injected\n\tconst css = templateCss\n\t\t.replace(\"{{THEME_VARS}}\", themeVars)\n\t\t.replace(\"{{BODY_BG}}\", bodyBg)\n\t\t.replace(\"{{CONTAINER_BG}}\", containerBg)\n\t\t.replace(\"{{INFO_BG}}\", infoBg);\n\n\treturn template\n\t\t.replace(\"{{CSS}}\", css)\n\t\t.replace(\"{{JS}}\", templateJs)\n\t\t.replace(\"{{SESSION_DATA}}\", sessionDataBase64)\n\t\t.replace(\"{{MARKED_JS}}\", markedJs)\n\t\t.replace(\"{{HIGHLIGHT_JS}}\", hljsJs);\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\tcodexInjectionInfo: await buildCodexInjectionInfo(state?.tools),\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${sessionBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n\n/**\n * Export session file to HTML (standalone, without AgentState).\n * Used by CLI for exporting arbitrary session files.\n */\nexport async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tif (!existsSync(inputPath)) {\n\t\tthrow new Error(`File not found: ${inputPath}`);\n\t}\n\n\tconst sm = SessionManager.open(inputPath);\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: undefined,\n\t\tcodexInjectionInfo: await buildCodexInjectionInfo(undefined),\n\t\ttools: undefined,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(inputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAM9D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAChC,oFAAoF;IACpF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IAChE,sFAAsF;IACtF,YAAY,CACX,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,EAChF,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,OAAO,GACd,MAAM,GAAG,SAAS,CAAC;CACtB;AAQD,MAAM,WAAW,aAAa;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAwLD;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,EAAE,EAAE,cAAc,EAClB,KAAK,CAAC,EAAE,UAAU,EAClB,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,GAC9B,OAAO,CAAC,MAAM,CAAC,CA0CjB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BzG","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent-core\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.js\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.js\";\nimport type { SessionEntry } from \"../session-manager.js\";\nimport { SessionManager } from \"../session-manager.js\";\n\n/**\n * Interface for rendering custom tools to HTML.\n * Used by agent-session to pre-render extension tool output.\n */\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to HTML. Returns undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): string | undefined;\n}\n\n/** Pre-rendered HTML for a custom tool call and result */\ninterface RenderedToolHtml {\n\tcallHtml?: string;\n\tresultHtml?: string;\n}\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n\t/** Optional tool renderer for custom tools */\n\ttoolRenderer?: ToolHtmlRenderer;\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\ttools?: { name: string; description: string }[];\n\t/** Pre-rendered HTML for custom tool calls/results, keyed by tool call ID */\n\trenderedTools?: Record<string, RenderedToolHtml>;\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst exportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = exportColors.pageBg;\n\tconst containerBg = exportColors.cardBg;\n\tconst infoBg = exportColors.infoBg;\n\n\t// Base64 encode session data to avoid escaping issues\n\tconst sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString(\"base64\");\n\n\t// Build the CSS with theme variables injected\n\tconst css = templateCss\n\t\t.replace(\"{{THEME_VARS}}\", themeVars)\n\t\t.replace(\"{{BODY_BG}}\", bodyBg)\n\t\t.replace(\"{{CONTAINER_BG}}\", containerBg)\n\t\t.replace(\"{{INFO_BG}}\", infoBg);\n\n\treturn template\n\t\t.replace(\"{{CSS}}\", css)\n\t\t.replace(\"{{JS}}\", templateJs)\n\t\t.replace(\"{{SESSION_DATA}}\", sessionDataBase64)\n\t\t.replace(\"{{MARKED_JS}}\", markedJs)\n\t\t.replace(\"{{HIGHLIGHT_JS}}\", hljsJs);\n}\n\n/** Built-in tool names that have custom rendering in template.js */\nconst BUILTIN_TOOLS = new Set([\"bash\", \"read\", \"write\", \"edit\", \"ls\", \"find\", \"grep\"]);\n\n/**\n * Pre-render custom tools to HTML using their TUI renderers.\n */\nfunction preRenderCustomTools(\n\tentries: SessionEntry[],\n\ttoolRenderer: ToolHtmlRenderer,\n): Record<string, RenderedToolHtml> {\n\tconst renderedTools: Record<string, RenderedToolHtml> = {};\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = entry.message;\n\n\t\t// Find tool calls in assistant messages\n\t\tif (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"toolCall\" && !BUILTIN_TOOLS.has(block.name)) {\n\t\t\t\t\tconst callHtml = toolRenderer.renderCall(block.name, block.arguments);\n\t\t\t\t\tif (callHtml) {\n\t\t\t\t\t\trenderedTools[block.id] = { callHtml };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find tool results\n\t\tif (msg.role === \"toolResult\" && msg.toolCallId) {\n\t\t\tconst toolName = msg.toolName || \"\";\n\t\t\t// Only render if we have a pre-rendered call OR it's not a built-in tool\n\t\t\tconst existing = renderedTools[msg.toolCallId];\n\t\t\tif (existing || !BUILTIN_TOOLS.has(toolName)) {\n\t\t\t\tconst resultHtml = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false);\n\t\t\t\tif (resultHtml) {\n\t\t\t\t\trenderedTools[msg.toolCallId] = {\n\t\t\t\t\t\t...existing,\n\t\t\t\t\t\tresultHtml,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn renderedTools;\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst entries = sm.getEntries();\n\n\t// Pre-render custom tools if a tool renderer is provided\n\tlet renderedTools: Record<string, RenderedToolHtml> | undefined;\n\tif (opts.toolRenderer) {\n\t\trenderedTools = preRenderCustomTools(entries, opts.toolRenderer);\n\t\t// Only include if we actually rendered something\n\t\tif (Object.keys(renderedTools).length === 0) {\n\t\t\trenderedTools = undefined;\n\t\t}\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries,\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),\n\t\trenderedTools,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${sessionBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n\n/**\n * Export session file to HTML (standalone, without AgentState).\n * Used by CLI for exporting arbitrary session files.\n */\nexport async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tif (!existsSync(inputPath)) {\n\t\tthrow new Error(`File not found: ${inputPath}`);\n\t}\n\n\tconst sm = SessionManager.open(inputPath);\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: undefined,\n\t\ttools: undefined,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(inputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
@@ -1,28 +1,8 @@
|
|
|
1
|
-
import { buildCodexPiBridge, getCodexInstructions } from "@mariozechner/pi-ai";
|
|
2
1
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
2
|
import { basename, join } from "path";
|
|
4
3
|
import { APP_NAME, getExportTemplateDir } from "../../config.js";
|
|
5
4
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
|
|
6
5
|
import { SessionManager } from "../session-manager.js";
|
|
7
|
-
/**
|
|
8
|
-
* Build Codex injection info for display inline with model_change entries.
|
|
9
|
-
*/
|
|
10
|
-
async function buildCodexInjectionInfo(tools) {
|
|
11
|
-
// Try to get cached instructions for default model family
|
|
12
|
-
let instructions = null;
|
|
13
|
-
try {
|
|
14
|
-
instructions = getCodexInstructions();
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
// Cache miss - that's fine
|
|
18
|
-
}
|
|
19
|
-
const bridgeText = buildCodexPiBridge(tools);
|
|
20
|
-
const instructionsText = instructions || "(Codex instructions not cached. Run a Codex request to populate the local cache.)";
|
|
21
|
-
return {
|
|
22
|
-
instructions: instructionsText,
|
|
23
|
-
bridge: bridgeText,
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
6
|
/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */
|
|
27
7
|
function parseColor(color) {
|
|
28
8
|
const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
|
|
@@ -133,6 +113,46 @@ function generateHtml(sessionData, themeName) {
|
|
|
133
113
|
.replace("{{MARKED_JS}}", markedJs)
|
|
134
114
|
.replace("{{HIGHLIGHT_JS}}", hljsJs);
|
|
135
115
|
}
|
|
116
|
+
/** Built-in tool names that have custom rendering in template.js */
|
|
117
|
+
const BUILTIN_TOOLS = new Set(["bash", "read", "write", "edit", "ls", "find", "grep"]);
|
|
118
|
+
/**
|
|
119
|
+
* Pre-render custom tools to HTML using their TUI renderers.
|
|
120
|
+
*/
|
|
121
|
+
function preRenderCustomTools(entries, toolRenderer) {
|
|
122
|
+
const renderedTools = {};
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
if (entry.type !== "message")
|
|
125
|
+
continue;
|
|
126
|
+
const msg = entry.message;
|
|
127
|
+
// Find tool calls in assistant messages
|
|
128
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
129
|
+
for (const block of msg.content) {
|
|
130
|
+
if (block.type === "toolCall" && !BUILTIN_TOOLS.has(block.name)) {
|
|
131
|
+
const callHtml = toolRenderer.renderCall(block.name, block.arguments);
|
|
132
|
+
if (callHtml) {
|
|
133
|
+
renderedTools[block.id] = { callHtml };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Find tool results
|
|
139
|
+
if (msg.role === "toolResult" && msg.toolCallId) {
|
|
140
|
+
const toolName = msg.toolName || "";
|
|
141
|
+
// Only render if we have a pre-rendered call OR it's not a built-in tool
|
|
142
|
+
const existing = renderedTools[msg.toolCallId];
|
|
143
|
+
if (existing || !BUILTIN_TOOLS.has(toolName)) {
|
|
144
|
+
const resultHtml = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false);
|
|
145
|
+
if (resultHtml) {
|
|
146
|
+
renderedTools[msg.toolCallId] = {
|
|
147
|
+
...existing,
|
|
148
|
+
resultHtml,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return renderedTools;
|
|
155
|
+
}
|
|
136
156
|
/**
|
|
137
157
|
* Export session to HTML using SessionManager and AgentState.
|
|
138
158
|
* Used by TUI's /export command.
|
|
@@ -146,13 +166,23 @@ export async function exportSessionToHtml(sm, state, options) {
|
|
|
146
166
|
if (!existsSync(sessionFile)) {
|
|
147
167
|
throw new Error("Nothing to export yet - start a conversation first");
|
|
148
168
|
}
|
|
169
|
+
const entries = sm.getEntries();
|
|
170
|
+
// Pre-render custom tools if a tool renderer is provided
|
|
171
|
+
let renderedTools;
|
|
172
|
+
if (opts.toolRenderer) {
|
|
173
|
+
renderedTools = preRenderCustomTools(entries, opts.toolRenderer);
|
|
174
|
+
// Only include if we actually rendered something
|
|
175
|
+
if (Object.keys(renderedTools).length === 0) {
|
|
176
|
+
renderedTools = undefined;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
149
179
|
const sessionData = {
|
|
150
180
|
header: sm.getHeader(),
|
|
151
|
-
entries
|
|
181
|
+
entries,
|
|
152
182
|
leafId: sm.getLeafId(),
|
|
153
183
|
systemPrompt: state?.systemPrompt,
|
|
154
|
-
codexInjectionInfo: await buildCodexInjectionInfo(state?.tools),
|
|
155
184
|
tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),
|
|
185
|
+
renderedTools,
|
|
156
186
|
};
|
|
157
187
|
const html = generateHtml(sessionData, opts.themeName);
|
|
158
188
|
let outputPath = opts.outputPath;
|
|
@@ -178,7 +208,6 @@ export async function exportFromFile(inputPath, options) {
|
|
|
178
208
|
entries: sm.getEntries(),
|
|
179
209
|
leafId: sm.getLeafId(),
|
|
180
210
|
systemPrompt: undefined,
|
|
181
|
-
codexInjectionInfo: await buildCodexInjectionInfo(undefined),
|
|
182
211
|
tools: undefined,
|
|
183
212
|
};
|
|
184
213
|
const html = generateHtml(sessionData, opts.themeName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState, AgentTool } from \"@mariozechner/pi-agent-core\";\nimport { buildCodexPiBridge, getCodexInstructions } from \"@mariozechner/pi-ai\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.js\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.js\";\nimport { SessionManager } from \"../session-manager.js\";\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n}\n\n/** Info about Codex injection to show inline with model_change entries */\ninterface CodexInjectionInfo {\n\t/** Codex instructions text */\n\tinstructions: string;\n\t/** Bridge text (tool list) */\n\tbridge: string;\n}\n\n/**\n * Build Codex injection info for display inline with model_change entries.\n */\nasync function buildCodexInjectionInfo(tools?: AgentTool[]): Promise<CodexInjectionInfo | undefined> {\n\t// Try to get cached instructions for default model family\n\tlet instructions: string | null = null;\n\ttry {\n\t\tinstructions = getCodexInstructions();\n\t} catch {\n\t\t// Cache miss - that's fine\n\t}\n\n\tconst bridgeText = buildCodexPiBridge(tools);\n\n\tconst instructionsText =\n\t\tinstructions || \"(Codex instructions not cached. Run a Codex request to populate the local cache.)\";\n\n\treturn {\n\t\tinstructions: instructionsText,\n\t\tbridge: bridgeText,\n\t};\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\t/** Info for rendering Codex injection inline with model_change entries */\n\tcodexInjectionInfo?: CodexInjectionInfo;\n\ttools?: { name: string; description: string }[];\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst exportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = exportColors.pageBg;\n\tconst containerBg = exportColors.cardBg;\n\tconst infoBg = exportColors.infoBg;\n\n\t// Base64 encode session data to avoid escaping issues\n\tconst sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString(\"base64\");\n\n\t// Build the CSS with theme variables injected\n\tconst css = templateCss\n\t\t.replace(\"{{THEME_VARS}}\", themeVars)\n\t\t.replace(\"{{BODY_BG}}\", bodyBg)\n\t\t.replace(\"{{CONTAINER_BG}}\", containerBg)\n\t\t.replace(\"{{INFO_BG}}\", infoBg);\n\n\treturn template\n\t\t.replace(\"{{CSS}}\", css)\n\t\t.replace(\"{{JS}}\", templateJs)\n\t\t.replace(\"{{SESSION_DATA}}\", sessionDataBase64)\n\t\t.replace(\"{{MARKED_JS}}\", markedJs)\n\t\t.replace(\"{{HIGHLIGHT_JS}}\", hljsJs);\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\tcodexInjectionInfo: await buildCodexInjectionInfo(state?.tools),\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${sessionBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n\n/**\n * Export session file to HTML (standalone, without AgentState).\n * Used by CLI for exporting arbitrary session files.\n */\nexport async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tif (!existsSync(inputPath)) {\n\t\tthrow new Error(`File not found: ${inputPath}`);\n\t}\n\n\tconst sm = SessionManager.open(inputPath);\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: undefined,\n\t\tcodexInjectionInfo: await buildCodexInjectionInfo(undefined),\n\t\ttools: undefined,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(inputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@mariozechner/pi-agent-core\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.js\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.js\";\nimport type { SessionEntry } from \"../session-manager.js\";\nimport { SessionManager } from \"../session-manager.js\";\n\n/**\n * Interface for rendering custom tools to HTML.\n * Used by agent-session to pre-render extension tool output.\n */\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to HTML. Returns undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): string | undefined;\n}\n\n/** Pre-rendered HTML for a custom tool call and result */\ninterface RenderedToolHtml {\n\tcallHtml?: string;\n\tresultHtml?: string;\n}\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n\t/** Optional tool renderer for custom tools */\n\ttoolRenderer?: ToolHtmlRenderer;\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\ttools?: { name: string; description: string }[];\n\t/** Pre-rendered HTML for custom tool calls/results, keyed by tool call ID */\n\trenderedTools?: Record<string, RenderedToolHtml>;\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst exportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = exportColors.pageBg;\n\tconst containerBg = exportColors.cardBg;\n\tconst infoBg = exportColors.infoBg;\n\n\t// Base64 encode session data to avoid escaping issues\n\tconst sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString(\"base64\");\n\n\t// Build the CSS with theme variables injected\n\tconst css = templateCss\n\t\t.replace(\"{{THEME_VARS}}\", themeVars)\n\t\t.replace(\"{{BODY_BG}}\", bodyBg)\n\t\t.replace(\"{{CONTAINER_BG}}\", containerBg)\n\t\t.replace(\"{{INFO_BG}}\", infoBg);\n\n\treturn template\n\t\t.replace(\"{{CSS}}\", css)\n\t\t.replace(\"{{JS}}\", templateJs)\n\t\t.replace(\"{{SESSION_DATA}}\", sessionDataBase64)\n\t\t.replace(\"{{MARKED_JS}}\", markedJs)\n\t\t.replace(\"{{HIGHLIGHT_JS}}\", hljsJs);\n}\n\n/** Built-in tool names that have custom rendering in template.js */\nconst BUILTIN_TOOLS = new Set([\"bash\", \"read\", \"write\", \"edit\", \"ls\", \"find\", \"grep\"]);\n\n/**\n * Pre-render custom tools to HTML using their TUI renderers.\n */\nfunction preRenderCustomTools(\n\tentries: SessionEntry[],\n\ttoolRenderer: ToolHtmlRenderer,\n): Record<string, RenderedToolHtml> {\n\tconst renderedTools: Record<string, RenderedToolHtml> = {};\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = entry.message;\n\n\t\t// Find tool calls in assistant messages\n\t\tif (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"toolCall\" && !BUILTIN_TOOLS.has(block.name)) {\n\t\t\t\t\tconst callHtml = toolRenderer.renderCall(block.name, block.arguments);\n\t\t\t\t\tif (callHtml) {\n\t\t\t\t\t\trenderedTools[block.id] = { callHtml };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find tool results\n\t\tif (msg.role === \"toolResult\" && msg.toolCallId) {\n\t\t\tconst toolName = msg.toolName || \"\";\n\t\t\t// Only render if we have a pre-rendered call OR it's not a built-in tool\n\t\t\tconst existing = renderedTools[msg.toolCallId];\n\t\t\tif (existing || !BUILTIN_TOOLS.has(toolName)) {\n\t\t\t\tconst resultHtml = toolRenderer.renderResult(toolName, msg.content, msg.details, msg.isError || false);\n\t\t\t\tif (resultHtml) {\n\t\t\t\t\trenderedTools[msg.toolCallId] = {\n\t\t\t\t\t\t...existing,\n\t\t\t\t\t\tresultHtml,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn renderedTools;\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst entries = sm.getEntries();\n\n\t// Pre-render custom tools if a tool renderer is provided\n\tlet renderedTools: Record<string, RenderedToolHtml> | undefined;\n\tif (opts.toolRenderer) {\n\t\trenderedTools = preRenderCustomTools(entries, opts.toolRenderer);\n\t\t// Only include if we actually rendered something\n\t\tif (Object.keys(renderedTools).length === 0) {\n\t\t\trenderedTools = undefined;\n\t\t}\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries,\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),\n\t\trenderedTools,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst sessionBasename = basename(sessionFile, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${sessionBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n\n/**\n * Export session file to HTML (standalone, without AgentState).\n * Used by CLI for exporting arbitrary session files.\n */\nexport async function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tif (!existsSync(inputPath)) {\n\t\tthrow new Error(`File not found: ${inputPath}`);\n\t}\n\n\tconst sm = SessionManager.open(inputPath);\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries: sm.getEntries(),\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: undefined,\n\t\ttools: undefined,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(inputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
@@ -512,39 +512,6 @@
|
|
|
512
512
|
font-weight: bold;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
.codex-bridge-toggle {
|
|
516
|
-
color: var(--muted);
|
|
517
|
-
cursor: pointer;
|
|
518
|
-
text-decoration: underline;
|
|
519
|
-
font-size: 10px;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
.codex-bridge-toggle:hover {
|
|
523
|
-
color: var(--accent);
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
.codex-bridge-content {
|
|
527
|
-
display: none;
|
|
528
|
-
margin-top: 8px;
|
|
529
|
-
padding: 8px;
|
|
530
|
-
background: var(--exportCardBg);
|
|
531
|
-
border-radius: 4px;
|
|
532
|
-
font-size: 11px;
|
|
533
|
-
max-height: 300px;
|
|
534
|
-
overflow: auto;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
.codex-bridge-content pre {
|
|
538
|
-
margin: 0;
|
|
539
|
-
white-space: pre-wrap;
|
|
540
|
-
word-break: break-word;
|
|
541
|
-
color: var(--muted);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
.model-change.show-bridge .codex-bridge-content {
|
|
545
|
-
display: block;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
515
|
/* Compaction / Branch Summary - matches customMessage colors from TUI */
|
|
549
516
|
.compaction {
|
|
550
517
|
background: var(--customMessageBg);
|