@mariozechner/pi-coding-agent 0.37.4 → 0.37.6
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 +22 -0
- package/dist/core/agent-session.d.ts +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/export-html/index.d.ts +2 -2
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +24 -2
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/export-html/template.css +44 -0
- package/dist/core/export-html/template.js +13 -2
- 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/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +33 -3
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +4 -1
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +3 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +22 -4
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +1 -1
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +28 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -0
- package/dist/modes/interactive/components/index.js +29 -0
- package/dist/modes/interactive/components/index.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +42 -11
- 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 +9 -0
- 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 +40 -4
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/docs/extensions.md +181 -9
- package/docs/tui.md +220 -2
- package/examples/extensions/README.md +2 -0
- package/examples/extensions/preset.ts +398 -0
- package/examples/extensions/timed-confirm.ts +63 -0
- package/examples/extensions/truncated-tool.ts +192 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -8,10 +8,10 @@ export interface ExportOptions {
|
|
|
8
8
|
* Export session to HTML using SessionManager and AgentState.
|
|
9
9
|
* Used by TUI's /export command.
|
|
10
10
|
*/
|
|
11
|
-
export declare function exportSessionToHtml(sm: SessionManager, state?: AgentState, options?: ExportOptions | string): string
|
|
11
|
+
export declare function exportSessionToHtml(sm: SessionManager, state?: AgentState, options?: ExportOptions | string): Promise<string>;
|
|
12
12
|
/**
|
|
13
13
|
* Export session file to HTML (standalone, without AgentState).
|
|
14
14
|
* Used by CLI for exporting arbitrary session files.
|
|
15
15
|
*/
|
|
16
|
-
export declare function exportFromFile(inputPath: string, options?: ExportOptions | string): string
|
|
16
|
+
export declare function exportFromFile(inputPath: string, options?: ExportOptions | string): Promise<string>;
|
|
17
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,
|
|
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 = await getCodexInstructions(\"gpt-5.1-codex\");\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,8 +1,28 @@
|
|
|
1
|
+
import { buildCodexPiBridge, getCodexInstructions } from "@mariozechner/pi-ai";
|
|
1
2
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
3
|
import { basename, join } from "path";
|
|
3
4
|
import { APP_NAME, getExportTemplateDir } from "../../config.js";
|
|
4
5
|
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
|
|
5
6
|
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 = await getCodexInstructions("gpt-5.1-codex");
|
|
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
|
+
}
|
|
6
26
|
/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */
|
|
7
27
|
function parseColor(color) {
|
|
8
28
|
const hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
|
|
@@ -117,7 +137,7 @@ function generateHtml(sessionData, themeName) {
|
|
|
117
137
|
* Export session to HTML using SessionManager and AgentState.
|
|
118
138
|
* Used by TUI's /export command.
|
|
119
139
|
*/
|
|
120
|
-
export function exportSessionToHtml(sm, state, options) {
|
|
140
|
+
export async function exportSessionToHtml(sm, state, options) {
|
|
121
141
|
const opts = typeof options === "string" ? { outputPath: options } : options || {};
|
|
122
142
|
const sessionFile = sm.getSessionFile();
|
|
123
143
|
if (!sessionFile) {
|
|
@@ -131,6 +151,7 @@ export function exportSessionToHtml(sm, state, options) {
|
|
|
131
151
|
entries: sm.getEntries(),
|
|
132
152
|
leafId: sm.getLeafId(),
|
|
133
153
|
systemPrompt: state?.systemPrompt,
|
|
154
|
+
codexInjectionInfo: await buildCodexInjectionInfo(state?.tools),
|
|
134
155
|
tools: state?.tools?.map((t) => ({ name: t.name, description: t.description })),
|
|
135
156
|
};
|
|
136
157
|
const html = generateHtml(sessionData, opts.themeName);
|
|
@@ -146,7 +167,7 @@ export function exportSessionToHtml(sm, state, options) {
|
|
|
146
167
|
* Export session file to HTML (standalone, without AgentState).
|
|
147
168
|
* Used by CLI for exporting arbitrary session files.
|
|
148
169
|
*/
|
|
149
|
-
export function exportFromFile(inputPath, options) {
|
|
170
|
+
export async function exportFromFile(inputPath, options) {
|
|
150
171
|
const opts = typeof options === "string" ? { outputPath: options } : options || {};
|
|
151
172
|
if (!existsSync(inputPath)) {
|
|
152
173
|
throw new Error(`File not found: ${inputPath}`);
|
|
@@ -157,6 +178,7 @@ export function exportFromFile(inputPath, options) {
|
|
|
157
178
|
entries: sm.getEntries(),
|
|
158
179
|
leafId: sm.getLeafId(),
|
|
159
180
|
systemPrompt: undefined,
|
|
181
|
+
codexInjectionInfo: await buildCodexInjectionInfo(undefined),
|
|
160
182
|
tools: undefined,
|
|
161
183
|
};
|
|
162
184
|
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 } 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 { SessionManager } from \"../session-manager.js\";\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\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}\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 function exportSessionToHtml(sm: SessionManager, state?: AgentState, options?: ExportOptions | string): 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\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 function exportFromFile(inputPath: string, options?: ExportOptions | string): 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
|
+
{"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 = await getCodexInstructions(\"gpt-5.1-codex\");\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"]}
|
|
@@ -512,6 +512,39 @@
|
|
|
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
|
+
|
|
515
548
|
/* Compaction / Branch Summary - matches customMessage colors from TUI */
|
|
516
549
|
.compaction {
|
|
517
550
|
background: var(--customMessageBg);
|
|
@@ -593,6 +626,17 @@
|
|
|
593
626
|
display: block;
|
|
594
627
|
}
|
|
595
628
|
|
|
629
|
+
.system-prompt.provider-prompt {
|
|
630
|
+
border-left: 3px solid var(--warning);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.system-prompt-note {
|
|
634
|
+
font-size: 10px;
|
|
635
|
+
font-style: italic;
|
|
636
|
+
color: var(--muted);
|
|
637
|
+
margin-top: 4px;
|
|
638
|
+
}
|
|
639
|
+
|
|
596
640
|
/* Tools list */
|
|
597
641
|
.tools-list {
|
|
598
642
|
background: var(--customMessageBg);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
bytes[i] = binary.charCodeAt(i);
|
|
13
13
|
}
|
|
14
14
|
const data = JSON.parse(new TextDecoder('utf-8').decode(bytes));
|
|
15
|
-
const { header, entries, leafId: defaultLeafId, systemPrompt, tools } = data;
|
|
15
|
+
const { header, entries, leafId: defaultLeafId, systemPrompt, codexInjectionInfo, tools } = data;
|
|
16
16
|
|
|
17
17
|
// ============================================================
|
|
18
18
|
// URL PARAMETER HANDLING
|
|
@@ -954,7 +954,17 @@
|
|
|
954
954
|
}
|
|
955
955
|
|
|
956
956
|
if (entry.type === 'model_change') {
|
|
957
|
-
|
|
957
|
+
let html = `<div class="model-change" id="${entryId}">${tsHtml}Switched to model: <span class="model-name">${escapeHtml(entry.provider)}/${escapeHtml(entry.modelId)}</span>`;
|
|
958
|
+
|
|
959
|
+
// Show expandable bridge prompt info when switching to openai-codex
|
|
960
|
+
if (entry.provider === 'openai-codex' && codexInjectionInfo) {
|
|
961
|
+
const fullContent = `# Codex Instructions\n${codexInjectionInfo.instructions}\n\n# Codex-Pi Bridge\n${codexInjectionInfo.bridge}`;
|
|
962
|
+
html += ` <span class="codex-bridge-toggle" onclick="event.stopPropagation(); this.parentElement.classList.toggle('show-bridge')">[bridge prompt]</span>`;
|
|
963
|
+
html += `<div class="codex-bridge-content"><pre>${escapeHtml(fullContent)}</pre></div>`;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
html += '</div>';
|
|
967
|
+
return html;
|
|
958
968
|
}
|
|
959
969
|
|
|
960
970
|
if (entry.type === 'compaction') {
|
|
@@ -1060,6 +1070,7 @@
|
|
|
1060
1070
|
</div>
|
|
1061
1071
|
</div>`;
|
|
1062
1072
|
|
|
1073
|
+
// Render system prompt (user's base prompt, applies to all providers)
|
|
1063
1074
|
if (systemPrompt) {
|
|
1064
1075
|
const lines = systemPrompt.split('\n');
|
|
1065
1076
|
const previewLines = 10;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
export { discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from "./loader.js";
|
|
5
5
|
export type { BranchHandler, ExtensionErrorListener, NavigateTreeHandler, NewSessionHandler } from "./runner.js";
|
|
6
6
|
export { ExtensionRunner } from "./runner.js";
|
|
7
|
-
export type { AgentEndEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppendEntryHandler, BashToolResultEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, ContextEvent, ContextEventResult, CustomToolResultEvent, EditToolResultEvent, ExecOptions, ExecResult, ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionShortcut, ExtensionUIContext, FindToolResultEvent, GetActiveToolsHandler, GetAllToolsHandler, GrepToolResultEvent, LoadExtensionsResult, LoadedExtension, LsToolResultEvent, MessageRenderer, MessageRenderOptions, ReadToolResultEvent, RegisteredCommand, RegisteredTool, SendMessageHandler, SendUserMessageHandler, SessionBeforeBranchEvent, SessionBeforeBranchResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionBranchEvent, SessionCompactEvent, SessionEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, SetActiveToolsHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TreePreparation, TurnEndEvent, TurnStartEvent, WriteToolResultEvent, } from "./types.js";
|
|
7
|
+
export type { AgentEndEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppendEntryHandler, BashToolResultEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, ContextEvent, ContextEventResult, CustomToolResultEvent, EditToolResultEvent, ExecOptions, ExecResult, ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionShortcut, ExtensionUIContext, FindToolResultEvent, GetActiveToolsHandler, GetAllToolsHandler, GetThinkingLevelHandler, GrepToolResultEvent, LoadExtensionsResult, LoadedExtension, LsToolResultEvent, MessageRenderer, MessageRenderOptions, ReadToolResultEvent, RegisteredCommand, RegisteredTool, SendMessageHandler, SendUserMessageHandler, SessionBeforeBranchEvent, SessionBeforeBranchResult, SessionBeforeCompactEvent, SessionBeforeCompactResult, SessionBeforeSwitchEvent, SessionBeforeSwitchResult, SessionBeforeTreeEvent, SessionBeforeTreeResult, SessionBranchEvent, SessionCompactEvent, SessionEvent, SessionShutdownEvent, SessionStartEvent, SessionSwitchEvent, SessionTreeEvent, SetActiveToolsHandler, SetModelHandler, SetThinkingLevelHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolRenderResultOptions, ToolResultEvent, ToolResultEventResult, TreePreparation, TurnEndEvent, TurnStartEvent, WriteToolResultEvent, } from "./types.js";
|
|
8
8
|
export { isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isWriteToolResult, } from "./types.js";
|
|
9
9
|
export { wrapRegisteredTool, wrapRegisteredTools, wrapToolsWithExtensions, wrapToolWithExtensions, } from "./wrapper.js";
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClG,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACX,aAAa,EACb,eAAe,EAEf,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAE3B,YAAY,EAEZ,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,YAAY,EACZ,uBAAuB,EAEvB,gBAAgB,EAEhB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EAEnB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClG,YAAY,EAAE,aAAa,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACjH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EACX,aAAa,EACb,eAAe,EAEf,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,2BAA2B,EAE3B,YAAY,EAEZ,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,UAAU,EAEV,YAAY,EACZ,uBAAuB,EAEvB,gBAAgB,EAEhB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EAEpB,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EAEnB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,wBAAwB,EACxB,yBAAyB,EACzB,yBAAyB,EACzB,0BAA0B,EAC1B,wBAAwB,EACxB,yBAAyB,EACzB,sBAAsB,EACtB,uBAAuB,EACvB,kBAAkB,EAClB,mBAAmB,EACnB,YAAY,EACZ,oBAAoB,EAEpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,uBAAuB,EAEvB,aAAa,EACb,mBAAmB,EAEnB,cAAc,EACd,uBAAuB,EACvB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,oBAAoB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACN,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,GACtB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * Extension system for lifecycle events and custom tools.\n */\n\nexport { discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from \"./loader.js\";\nexport type { BranchHandler, ExtensionErrorListener, NavigateTreeHandler, NewSessionHandler } from \"./runner.js\";\nexport { ExtensionRunner } from \"./runner.js\";\nexport type {\n\tAgentEndEvent,\n\tAgentStartEvent,\n\t// Re-exports\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tAppendEntryHandler,\n\tBashToolResultEvent,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\t// Events - Agent\n\tContextEvent,\n\t// Event Results\n\tContextEventResult,\n\tCustomToolResultEvent,\n\tEditToolResultEvent,\n\tExecOptions,\n\tExecResult,\n\t// API\n\tExtensionAPI,\n\tExtensionCommandContext,\n\t// Context\n\tExtensionContext,\n\t// Errors\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionHandler,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tFindToolResultEvent,\n\tGetActiveToolsHandler,\n\tGetAllToolsHandler,\n\tGetThinkingLevelHandler,\n\tGrepToolResultEvent,\n\tLoadExtensionsResult,\n\t// Loaded Extension\n\tLoadedExtension,\n\tLsToolResultEvent,\n\t// Message Rendering\n\tMessageRenderer,\n\tMessageRenderOptions,\n\tReadToolResultEvent,\n\t// Commands\n\tRegisteredCommand,\n\tRegisteredTool,\n\tSendMessageHandler,\n\tSendUserMessageHandler,\n\tSessionBeforeBranchEvent,\n\tSessionBeforeBranchResult,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeCompactResult,\n\tSessionBeforeSwitchEvent,\n\tSessionBeforeSwitchResult,\n\tSessionBeforeTreeEvent,\n\tSessionBeforeTreeResult,\n\tSessionBranchEvent,\n\tSessionCompactEvent,\n\tSessionEvent,\n\tSessionShutdownEvent,\n\t// Events - Session\n\tSessionStartEvent,\n\tSessionSwitchEvent,\n\tSessionTreeEvent,\n\tSetActiveToolsHandler,\n\tSetModelHandler,\n\tSetThinkingLevelHandler,\n\t// Events - Tool\n\tToolCallEvent,\n\tToolCallEventResult,\n\t// Tools\n\tToolDefinition,\n\tToolRenderResultOptions,\n\tToolResultEvent,\n\tToolResultEventResult,\n\tTreePreparation,\n\tTurnEndEvent,\n\tTurnStartEvent,\n\tWriteToolResultEvent,\n} from \"./types.js\";\n// Type guards\nexport {\n\tisBashToolResult,\n\tisEditToolResult,\n\tisFindToolResult,\n\tisGrepToolResult,\n\tisLsToolResult,\n\tisReadToolResult,\n\tisWriteToolResult,\n} from \"./types.js\";\nexport {\n\twrapRegisteredTool,\n\twrapRegisteredTools,\n\twrapToolsWithExtensions,\n\twrapToolWithExtensions,\n} from \"./wrapper.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/extensions/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/extensions/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAkF9C,cAAc;AACd,OAAO,EACN,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,GACtB,MAAM,cAAc,CAAC","sourcesContent":["/**\n * Extension system for lifecycle events and custom tools.\n */\n\nexport { discoverAndLoadExtensions, loadExtensionFromFactory, loadExtensions } from \"./loader.js\";\nexport type { BranchHandler, ExtensionErrorListener, NavigateTreeHandler, NewSessionHandler } from \"./runner.js\";\nexport { ExtensionRunner } from \"./runner.js\";\nexport type {\n\tAgentEndEvent,\n\tAgentStartEvent,\n\t// Re-exports\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tAppendEntryHandler,\n\tBashToolResultEvent,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\t// Events - Agent\n\tContextEvent,\n\t// Event Results\n\tContextEventResult,\n\tCustomToolResultEvent,\n\tEditToolResultEvent,\n\tExecOptions,\n\tExecResult,\n\t// API\n\tExtensionAPI,\n\tExtensionCommandContext,\n\t// Context\n\tExtensionContext,\n\t// Errors\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionHandler,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tFindToolResultEvent,\n\tGetActiveToolsHandler,\n\tGetAllToolsHandler,\n\tGetThinkingLevelHandler,\n\tGrepToolResultEvent,\n\tLoadExtensionsResult,\n\t// Loaded Extension\n\tLoadedExtension,\n\tLsToolResultEvent,\n\t// Message Rendering\n\tMessageRenderer,\n\tMessageRenderOptions,\n\tReadToolResultEvent,\n\t// Commands\n\tRegisteredCommand,\n\tRegisteredTool,\n\tSendMessageHandler,\n\tSendUserMessageHandler,\n\tSessionBeforeBranchEvent,\n\tSessionBeforeBranchResult,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeCompactResult,\n\tSessionBeforeSwitchEvent,\n\tSessionBeforeSwitchResult,\n\tSessionBeforeTreeEvent,\n\tSessionBeforeTreeResult,\n\tSessionBranchEvent,\n\tSessionCompactEvent,\n\tSessionEvent,\n\tSessionShutdownEvent,\n\t// Events - Session\n\tSessionStartEvent,\n\tSessionSwitchEvent,\n\tSessionTreeEvent,\n\tSetActiveToolsHandler,\n\tSetModelHandler,\n\tSetThinkingLevelHandler,\n\t// Events - Tool\n\tToolCallEvent,\n\tToolCallEventResult,\n\t// Tools\n\tToolDefinition,\n\tToolRenderResultOptions,\n\tToolResultEvent,\n\tToolResultEventResult,\n\tTreePreparation,\n\tTurnEndEvent,\n\tTurnStartEvent,\n\tWriteToolResultEvent,\n} from \"./types.js\";\n// Type guards\nexport {\n\tisBashToolResult,\n\tisEditToolResult,\n\tisFindToolResult,\n\tisGrepToolResult,\n\tisLsToolResult,\n\tisReadToolResult,\n\tisWriteToolResult,\n} from \"./types.js\";\nexport {\n\twrapRegisteredTool,\n\twrapRegisteredTools,\n\twrapToolsWithExtensions,\n\twrapToolWithExtensions,\n} from \"./wrapper.js\";\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,KAAK,EAGX,gBAAgB,EAGhB,kBAAkB,EAGlB,oBAAoB,EACpB,eAAe,EAQf,MAAM,YAAY,CAAC;AAuWpB;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE;IAAE,EAAE,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EACpD,IAAI,SAAa,GACf,eAAe,CAuCjB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2BrH;AA8GD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,eAAe,EAAE,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,EAChC,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAqC/B","sourcesContent":["/**\n * Extension loader - loads TypeScript extension modules using jiti.\n */\n\nimport * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { KeyId } from \"@mariozechner/pi-tui\";\nimport { createJiti } from \"jiti\";\nimport { getAgentDir, isBunBinary } from \"../../config.js\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport { createEventBus, type EventBus } from \"../event-bus.js\";\nimport type { ExecOptions } from \"../exec.js\";\nimport { execCommand } from \"../exec.js\";\nimport type {\n\tAppendEntryHandler,\n\tExtensionAPI,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tGetActiveToolsHandler,\n\tGetAllToolsHandler,\n\tLoadExtensionsResult,\n\tLoadedExtension,\n\tMessageRenderer,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tSendMessageHandler,\n\tSendUserMessageHandler,\n\tSetActiveToolsHandler,\n\tToolDefinition,\n} from \"./types.js\";\n\nconst require = createRequire(import.meta.url);\n\nlet _aliases: Record<string, string> | null = null;\nfunction getAliases(): Record<string, string> {\n\tif (_aliases) return _aliases;\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\tconst packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n\tconst typeboxEntry = require.resolve(\"@sinclair/typebox\");\n\tconst typeboxRoot = typeboxEntry.replace(/\\/build\\/cjs\\/index\\.js$/, \"\");\n\n\t_aliases = {\n\t\t\"@mariozechner/pi-coding-agent\": packageIndex,\n\t\t\"@mariozechner/pi-coding-agent/extensions\": path.resolve(__dirname, \"index.js\"),\n\t\t\"@mariozechner/pi-tui\": require.resolve(\"@mariozechner/pi-tui\"),\n\t\t\"@mariozechner/pi-ai\": require.resolve(\"@mariozechner/pi-ai\"),\n\t\t\"@sinclair/typebox\": typeboxRoot,\n\t};\n\treturn _aliases;\n}\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nfunction normalizeUnicodeSpaces(str: string): string {\n\treturn str.replace(UNICODE_SPACES, \" \");\n}\n\nfunction expandPath(p: string): string {\n\tconst normalized = normalizeUnicodeSpaces(p);\n\tif (normalized.startsWith(\"~/\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(2));\n\t}\n\tif (normalized.startsWith(\"~\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(1));\n\t}\n\treturn normalized;\n}\n\nfunction resolvePath(extPath: string, cwd: string): string {\n\tconst expanded = expandPath(extPath);\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\treturn path.resolve(cwd, expanded);\n}\n\nfunction createNoOpUIContext(): ExtensionUIContext {\n\treturn {\n\t\tselect: async () => undefined,\n\t\tconfirm: async () => false,\n\t\tinput: async () => undefined,\n\t\tnotify: () => {},\n\t\tsetStatus: () => {},\n\t\tsetWidget: () => {},\n\t\tsetFooter: () => {},\n\t\tsetHeader: () => {},\n\t\tsetTitle: () => {},\n\t\tcustom: async () => undefined as never,\n\t\tsetEditorText: () => {},\n\t\tgetEditorText: () => \"\",\n\t\teditor: async () => undefined,\n\t\tget theme() {\n\t\t\treturn theme;\n\t\t},\n\t};\n}\n\ntype HandlerFn = (...args: unknown[]) => Promise<unknown>;\n\nfunction createExtensionAPI(\n\thandlers: Map<string, HandlerFn[]>,\n\ttools: Map<string, RegisteredTool>,\n\tcwd: string,\n\textensionPath: string,\n\teventBus: EventBus,\n\t_sharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): {\n\tapi: ExtensionAPI;\n\tmessageRenderers: Map<string, MessageRenderer>;\n\tcommands: Map<string, RegisteredCommand>;\n\tflags: Map<string, ExtensionFlag>;\n\tflagValues: Map<string, boolean | string>;\n\tshortcuts: Map<KeyId, ExtensionShortcut>;\n\tsetSendMessageHandler: (handler: SendMessageHandler) => void;\n\tsetSendUserMessageHandler: (handler: SendUserMessageHandler) => void;\n\tsetAppendEntryHandler: (handler: AppendEntryHandler) => void;\n\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;\n\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => void;\n\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;\n\tsetFlagValue: (name: string, value: boolean | string) => void;\n} {\n\tlet sendMessageHandler: SendMessageHandler = () => {};\n\tlet sendUserMessageHandler: SendUserMessageHandler = () => {};\n\tlet appendEntryHandler: AppendEntryHandler = () => {};\n\tlet getActiveToolsHandler: GetActiveToolsHandler = () => [];\n\tlet getAllToolsHandler: GetAllToolsHandler = () => [];\n\tlet setActiveToolsHandler: SetActiveToolsHandler = () => {};\n\n\tconst messageRenderers = new Map<string, MessageRenderer>();\n\tconst commands = new Map<string, RegisteredCommand>();\n\tconst flags = new Map<string, ExtensionFlag>();\n\tconst flagValues = new Map<string, boolean | string>();\n\tconst shortcuts = new Map<KeyId, ExtensionShortcut>();\n\n\tconst api = {\n\t\ton(event: string, handler: HandlerFn): void {\n\t\t\tconst list = handlers.get(event) ?? [];\n\t\t\tlist.push(handler);\n\t\t\thandlers.set(event, list);\n\t\t},\n\n\t\tregisterTool(tool: ToolDefinition): void {\n\t\t\ttools.set(tool.name, {\n\t\t\t\tdefinition: tool,\n\t\t\t\textensionPath,\n\t\t\t});\n\t\t},\n\n\t\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void {\n\t\t\tcommands.set(name, { name, ...options });\n\t\t},\n\n\t\tregisterShortcut(\n\t\t\tshortcut: KeyId,\n\t\t\toptions: {\n\t\t\t\tdescription?: string;\n\t\t\t\thandler: (ctx: import(\"./types.js\").ExtensionContext) => Promise<void> | void;\n\t\t\t},\n\t\t): void {\n\t\t\tshortcuts.set(shortcut, { shortcut, extensionPath, ...options });\n\t\t},\n\n\t\tregisterFlag(\n\t\t\tname: string,\n\t\t\toptions: { description?: string; type: \"boolean\" | \"string\"; default?: boolean | string },\n\t\t): void {\n\t\t\tflags.set(name, { name, extensionPath, ...options });\n\t\t\tif (options.default !== undefined) {\n\t\t\t\tflagValues.set(name, options.default);\n\t\t\t}\n\t\t},\n\n\t\tgetFlag(name: string): boolean | string | undefined {\n\t\t\treturn flagValues.get(name);\n\t\t},\n\n\t\tregisterMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {\n\t\t\tmessageRenderers.set(customType, renderer as MessageRenderer);\n\t\t},\n\n\t\tsendMessage(message, options): void {\n\t\t\tsendMessageHandler(message, options);\n\t\t},\n\n\t\tsendUserMessage(content, options): void {\n\t\t\tsendUserMessageHandler(content, options);\n\t\t},\n\n\t\tappendEntry(customType: string, data?: unknown): void {\n\t\t\tappendEntryHandler(customType, data);\n\t\t},\n\n\t\texec(command: string, args: string[], options?: ExecOptions) {\n\t\t\treturn execCommand(command, args, options?.cwd ?? cwd, options);\n\t\t},\n\n\t\tgetActiveTools(): string[] {\n\t\t\treturn getActiveToolsHandler();\n\t\t},\n\n\t\tgetAllTools(): string[] {\n\t\t\treturn getAllToolsHandler();\n\t\t},\n\n\t\tsetActiveTools(toolNames: string[]): void {\n\t\t\tsetActiveToolsHandler(toolNames);\n\t\t},\n\n\t\tevents: eventBus,\n\t} as ExtensionAPI;\n\n\treturn {\n\t\tapi,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler: (handler: SendMessageHandler) => {\n\t\t\tsendMessageHandler = handler;\n\t\t},\n\t\tsetSendUserMessageHandler: (handler: SendUserMessageHandler) => {\n\t\t\tsendUserMessageHandler = handler;\n\t\t},\n\t\tsetAppendEntryHandler: (handler: AppendEntryHandler) => {\n\t\t\tappendEntryHandler = handler;\n\t\t},\n\t\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => {\n\t\t\tgetActiveToolsHandler = handler;\n\t\t},\n\t\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => {\n\t\t\tgetAllToolsHandler = handler;\n\t\t},\n\t\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => {\n\t\t\tsetActiveToolsHandler = handler;\n\t\t},\n\t\tsetFlagValue: (name: string, value: boolean | string) => {\n\t\t\tflagValues.set(name, value);\n\t\t},\n\t};\n}\n\nasync function loadExtensionWithBun(\n\tresolvedPath: string,\n\tcwd: string,\n\textensionPath: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): Promise<{ extension: LoadedExtension | null; error: string | null }> {\n\ttry {\n\t\tconst module = await import(resolvedPath);\n\t\tconst factory = (module.default ?? module) as ExtensionFactory;\n\n\t\tif (typeof factory !== \"function\") {\n\t\t\treturn { extension: null, error: \"Extension must export a default function\" };\n\t\t}\n\n\t\tconst handlers = new Map<string, HandlerFn[]>();\n\t\tconst tools = new Map<string, RegisteredTool>();\n\t\tconst {\n\t\t\tapi,\n\t\t\tmessageRenderers,\n\t\t\tcommands,\n\t\t\tflags,\n\t\t\tflagValues,\n\t\t\tshortcuts,\n\t\t\tsetSendMessageHandler,\n\t\t\tsetSendUserMessageHandler,\n\t\t\tsetAppendEntryHandler,\n\t\t\tsetGetActiveToolsHandler,\n\t\t\tsetGetAllToolsHandler,\n\t\t\tsetSetActiveToolsHandler,\n\t\t\tsetFlagValue,\n\t\t} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);\n\n\t\tfactory(api);\n\n\t\treturn {\n\t\t\textension: {\n\t\t\t\tpath: extensionPath,\n\t\t\t\tresolvedPath,\n\t\t\t\thandlers,\n\t\t\t\ttools,\n\t\t\t\tmessageRenderers,\n\t\t\t\tcommands,\n\t\t\t\tflags,\n\t\t\t\tflagValues,\n\t\t\t\tshortcuts,\n\t\t\t\tsetSendMessageHandler,\n\t\t\t\tsetSendUserMessageHandler,\n\t\t\t\tsetAppendEntryHandler,\n\t\t\t\tsetGetActiveToolsHandler,\n\t\t\t\tsetGetAllToolsHandler,\n\t\t\t\tsetSetActiveToolsHandler,\n\t\t\t\tsetFlagValue,\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\n\t\tif (message.includes(\"Cannot find module\") && message.includes(\"@mariozechner/\")) {\n\t\t\treturn {\n\t\t\t\textension: null,\n\t\t\t\terror:\n\t\t\t\t\t`${message}\\n` +\n\t\t\t\t\t\"Note: Extensions importing from @mariozechner/* packages are not supported in the standalone binary.\\n\" +\n\t\t\t\t\t\"Please install pi via npm: npm install -g @mariozechner/pi-coding-agent\",\n\t\t\t};\n\t\t}\n\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\nasync function loadExtension(\n\textensionPath: string,\n\tcwd: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): Promise<{ extension: LoadedExtension | null; error: string | null }> {\n\tconst resolvedPath = resolvePath(extensionPath, cwd);\n\n\tif (isBunBinary) {\n\t\treturn loadExtensionWithBun(resolvedPath, cwd, extensionPath, eventBus, sharedUI);\n\t}\n\n\ttry {\n\t\tconst jiti = createJiti(import.meta.url, {\n\t\t\talias: getAliases(),\n\t\t});\n\n\t\tconst module = await jiti.import(resolvedPath, { default: true });\n\t\tconst factory = module as ExtensionFactory;\n\n\t\tif (typeof factory !== \"function\") {\n\t\t\treturn { extension: null, error: \"Extension must export a default function\" };\n\t\t}\n\n\t\tconst handlers = new Map<string, HandlerFn[]>();\n\t\tconst tools = new Map<string, RegisteredTool>();\n\t\tconst {\n\t\t\tapi,\n\t\t\tmessageRenderers,\n\t\t\tcommands,\n\t\t\tflags,\n\t\t\tflagValues,\n\t\t\tshortcuts,\n\t\t\tsetSendMessageHandler,\n\t\t\tsetSendUserMessageHandler,\n\t\t\tsetAppendEntryHandler,\n\t\t\tsetGetActiveToolsHandler,\n\t\t\tsetGetAllToolsHandler,\n\t\t\tsetSetActiveToolsHandler,\n\t\t\tsetFlagValue,\n\t\t} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);\n\n\t\tfactory(api);\n\n\t\treturn {\n\t\t\textension: {\n\t\t\t\tpath: extensionPath,\n\t\t\t\tresolvedPath,\n\t\t\t\thandlers,\n\t\t\t\ttools,\n\t\t\t\tmessageRenderers,\n\t\t\t\tcommands,\n\t\t\t\tflags,\n\t\t\t\tflagValues,\n\t\t\t\tshortcuts,\n\t\t\t\tsetSendMessageHandler,\n\t\t\t\tsetSendUserMessageHandler,\n\t\t\t\tsetAppendEntryHandler,\n\t\t\t\tsetGetActiveToolsHandler,\n\t\t\t\tsetGetAllToolsHandler,\n\t\t\t\tsetSetActiveToolsHandler,\n\t\t\t\tsetFlagValue,\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\n/**\n * Create a LoadedExtension from an inline factory function.\n */\nexport function loadExtensionFromFactory(\n\tfactory: ExtensionFactory,\n\tcwd: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n\tname = \"<inline>\",\n): LoadedExtension {\n\tconst handlers = new Map<string, HandlerFn[]>();\n\tconst tools = new Map<string, RegisteredTool>();\n\tconst {\n\t\tapi,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler,\n\t\tsetSendUserMessageHandler,\n\t\tsetAppendEntryHandler,\n\t\tsetGetActiveToolsHandler,\n\t\tsetGetAllToolsHandler,\n\t\tsetSetActiveToolsHandler,\n\t\tsetFlagValue,\n\t} = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);\n\n\tfactory(api);\n\n\treturn {\n\t\tpath: name,\n\t\tresolvedPath: name,\n\t\thandlers,\n\t\ttools,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler,\n\t\tsetSendUserMessageHandler,\n\t\tsetAppendEntryHandler,\n\t\tsetGetActiveToolsHandler,\n\t\tsetGetAllToolsHandler,\n\t\tsetSetActiveToolsHandler,\n\t\tsetFlagValue,\n\t};\n}\n\n/**\n * Load extensions from paths.\n */\nexport async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {\n\tconst extensions: LoadedExtension[] = [];\n\tconst errors: Array<{ path: string; error: string }> = [];\n\tconst resolvedEventBus = eventBus ?? createEventBus();\n\tconst sharedUI = { ui: createNoOpUIContext(), hasUI: false };\n\n\tfor (const extPath of paths) {\n\t\tconst { extension, error } = await loadExtension(extPath, cwd, resolvedEventBus, sharedUI);\n\n\t\tif (error) {\n\t\t\terrors.push({ path: extPath, error });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (extension) {\n\t\t\textensions.push(extension);\n\t\t}\n\t}\n\n\treturn {\n\t\textensions,\n\t\terrors,\n\t\tsetUIContext(uiContext, hasUI) {\n\t\t\tsharedUI.ui = uiContext;\n\t\t\tsharedUI.hasUI = hasUI;\n\t\t},\n\t};\n}\n\ninterface PiManifest {\n\textensions?: string[];\n\tthemes?: string[];\n\tskills?: string[];\n}\n\nfunction readPiManifest(packageJsonPath: string): PiManifest | null {\n\ttry {\n\t\tconst content = fs.readFileSync(packageJsonPath, \"utf-8\");\n\t\tconst pkg = JSON.parse(content);\n\t\tif (pkg.pi && typeof pkg.pi === \"object\") {\n\t\t\treturn pkg.pi as PiManifest;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isExtensionFile(name: string): boolean {\n\treturn name.endsWith(\".ts\") || name.endsWith(\".js\");\n}\n\n/**\n * Resolve extension entry points from a directory.\n *\n * Checks for:\n * 1. package.json with \"pi.extensions\" field -> returns declared paths\n * 2. index.ts or index.js -> returns the index file\n *\n * Returns resolved paths or null if no entry points found.\n */\nfunction resolveExtensionEntries(dir: string): string[] | null {\n\t// Check for package.json with \"pi\" field first\n\tconst packageJsonPath = path.join(dir, \"package.json\");\n\tif (fs.existsSync(packageJsonPath)) {\n\t\tconst manifest = readPiManifest(packageJsonPath);\n\t\tif (manifest?.extensions?.length) {\n\t\t\tconst entries: string[] = [];\n\t\t\tfor (const extPath of manifest.extensions) {\n\t\t\t\tconst resolvedExtPath = path.resolve(dir, extPath);\n\t\t\t\tif (fs.existsSync(resolvedExtPath)) {\n\t\t\t\t\tentries.push(resolvedExtPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entries.length > 0) {\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for index.ts or index.js\n\tconst indexTs = path.join(dir, \"index.ts\");\n\tconst indexJs = path.join(dir, \"index.js\");\n\tif (fs.existsSync(indexTs)) {\n\t\treturn [indexTs];\n\t}\n\tif (fs.existsSync(indexJs)) {\n\t\treturn [indexJs];\n\t}\n\n\treturn null;\n}\n\n/**\n * Discover extensions in a directory.\n *\n * Discovery rules:\n * 1. Direct files: `extensions/*.ts` or `*.js` → load\n * 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load\n * 3. Subdirectory with package.json: `extensions/* /package.json` with \"pi\" field → load what it declares\n *\n * No recursion beyond one level. Complex packages must use package.json manifest.\n */\nfunction discoverExtensionsInDir(dir: string): string[] {\n\tif (!fs.existsSync(dir)) {\n\t\treturn [];\n\t}\n\n\tconst discovered: string[] = [];\n\n\ttry {\n\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst entryPath = path.join(dir, entry.name);\n\n\t\t\t// 1. Direct files: *.ts or *.js\n\t\t\tif ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {\n\t\t\t\tdiscovered.push(entryPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// 2 & 3. Subdirectories\n\t\t\tif (entry.isDirectory() || entry.isSymbolicLink()) {\n\t\t\t\tconst entries = resolveExtensionEntries(entryPath);\n\t\t\t\tif (entries) {\n\t\t\t\t\tdiscovered.push(...entries);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn discovered;\n}\n\n/**\n * Discover and load extensions from standard locations.\n */\nexport async function discoverAndLoadExtensions(\n\tconfiguredPaths: string[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n\teventBus?: EventBus,\n): Promise<LoadExtensionsResult> {\n\tconst allPaths: string[] = [];\n\tconst seen = new Set<string>();\n\n\tconst addPaths = (paths: string[]) => {\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = path.resolve(p);\n\t\t\tif (!seen.has(resolved)) {\n\t\t\t\tseen.add(resolved);\n\t\t\t\tallPaths.push(p);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Global extensions: agentDir/extensions/\n\tconst globalExtDir = path.join(agentDir, \"extensions\");\n\taddPaths(discoverExtensionsInDir(globalExtDir));\n\n\t// 2. Project-local extensions: cwd/.pi/extensions/\n\tconst localExtDir = path.join(cwd, \".pi\", \"extensions\");\n\taddPaths(discoverExtensionsInDir(localExtDir));\n\n\t// 3. Explicitly configured paths\n\tfor (const p of configuredPaths) {\n\t\tconst resolved = resolvePath(p, cwd);\n\t\tif (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n\t\t\tconst entries = resolveExtensionEntries(resolved);\n\t\t\tif (entries) {\n\t\t\t\taddPaths(entries);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\taddPaths([resolved]);\n\t}\n\n\treturn loadExtensions(allPaths, cwd, eventBus);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAGhE,OAAO,KAAK,EAGX,gBAAgB,EAGhB,kBAAkB,EAIlB,oBAAoB,EACpB,eAAe,EAUf,MAAM,YAAY,CAAC;AA8YpB;;GAEG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,gBAAgB,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE;IAAE,EAAE,EAAE,kBAAkB,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EACpD,IAAI,SAAa,GACf,eAAe,CA6CjB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA2BrH;AA8GD;;GAEG;AACH,wBAAsB,yBAAyB,CAC9C,eAAe,EAAE,MAAM,EAAE,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,EAChC,QAAQ,CAAC,EAAE,QAAQ,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAqC/B","sourcesContent":["/**\n * Extension loader - loads TypeScript extension modules using jiti.\n */\n\nimport * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { KeyId } from \"@mariozechner/pi-tui\";\nimport { createJiti } from \"jiti\";\nimport { getAgentDir, isBunBinary } from \"../../config.js\";\nimport { theme } from \"../../modes/interactive/theme/theme.js\";\nimport { createEventBus, type EventBus } from \"../event-bus.js\";\nimport type { ExecOptions } from \"../exec.js\";\nimport { execCommand } from \"../exec.js\";\nimport type {\n\tAppendEntryHandler,\n\tExtensionAPI,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tGetActiveToolsHandler,\n\tGetAllToolsHandler,\n\tGetThinkingLevelHandler,\n\tLoadExtensionsResult,\n\tLoadedExtension,\n\tMessageRenderer,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tSendMessageHandler,\n\tSendUserMessageHandler,\n\tSetActiveToolsHandler,\n\tSetModelHandler,\n\tSetThinkingLevelHandler,\n\tToolDefinition,\n} from \"./types.js\";\n\nconst require = createRequire(import.meta.url);\n\nlet _aliases: Record<string, string> | null = null;\nfunction getAliases(): Record<string, string> {\n\tif (_aliases) return _aliases;\n\n\tconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\tconst packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n\tconst typeboxEntry = require.resolve(\"@sinclair/typebox\");\n\tconst typeboxRoot = typeboxEntry.replace(/\\/build\\/cjs\\/index\\.js$/, \"\");\n\n\t_aliases = {\n\t\t\"@mariozechner/pi-coding-agent\": packageIndex,\n\t\t\"@mariozechner/pi-coding-agent/extensions\": path.resolve(__dirname, \"index.js\"),\n\t\t\"@mariozechner/pi-tui\": require.resolve(\"@mariozechner/pi-tui\"),\n\t\t\"@mariozechner/pi-ai\": require.resolve(\"@mariozechner/pi-ai\"),\n\t\t\"@sinclair/typebox\": typeboxRoot,\n\t};\n\treturn _aliases;\n}\n\nconst UNICODE_SPACES = /[\\u00A0\\u2000-\\u200A\\u202F\\u205F\\u3000]/g;\n\nfunction normalizeUnicodeSpaces(str: string): string {\n\treturn str.replace(UNICODE_SPACES, \" \");\n}\n\nfunction expandPath(p: string): string {\n\tconst normalized = normalizeUnicodeSpaces(p);\n\tif (normalized.startsWith(\"~/\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(2));\n\t}\n\tif (normalized.startsWith(\"~\")) {\n\t\treturn path.join(os.homedir(), normalized.slice(1));\n\t}\n\treturn normalized;\n}\n\nfunction resolvePath(extPath: string, cwd: string): string {\n\tconst expanded = expandPath(extPath);\n\tif (path.isAbsolute(expanded)) {\n\t\treturn expanded;\n\t}\n\treturn path.resolve(cwd, expanded);\n}\n\nfunction createNoOpUIContext(): ExtensionUIContext {\n\treturn {\n\t\tselect: async () => undefined,\n\t\tconfirm: async () => false,\n\t\tinput: async () => undefined,\n\t\tnotify: () => {},\n\t\tsetStatus: () => {},\n\t\tsetWidget: () => {},\n\t\tsetFooter: () => {},\n\t\tsetHeader: () => {},\n\t\tsetTitle: () => {},\n\t\tcustom: async () => undefined as never,\n\t\tsetEditorText: () => {},\n\t\tgetEditorText: () => \"\",\n\t\teditor: async () => undefined,\n\t\tget theme() {\n\t\t\treturn theme;\n\t\t},\n\t};\n}\n\ntype HandlerFn = (...args: unknown[]) => Promise<unknown>;\n\nfunction createExtensionAPI(\n\thandlers: Map<string, HandlerFn[]>,\n\ttools: Map<string, RegisteredTool>,\n\tcwd: string,\n\textensionPath: string,\n\teventBus: EventBus,\n\t_sharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): {\n\tapi: ExtensionAPI;\n\tmessageRenderers: Map<string, MessageRenderer>;\n\tcommands: Map<string, RegisteredCommand>;\n\tflags: Map<string, ExtensionFlag>;\n\tflagValues: Map<string, boolean | string>;\n\tshortcuts: Map<KeyId, ExtensionShortcut>;\n\tsetSendMessageHandler: (handler: SendMessageHandler) => void;\n\tsetSendUserMessageHandler: (handler: SendUserMessageHandler) => void;\n\tsetAppendEntryHandler: (handler: AppendEntryHandler) => void;\n\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => void;\n\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => void;\n\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => void;\n\tsetSetModelHandler: (handler: SetModelHandler) => void;\n\tsetGetThinkingLevelHandler: (handler: GetThinkingLevelHandler) => void;\n\tsetSetThinkingLevelHandler: (handler: SetThinkingLevelHandler) => void;\n\tsetFlagValue: (name: string, value: boolean | string) => void;\n} {\n\tlet sendMessageHandler: SendMessageHandler = () => {};\n\tlet sendUserMessageHandler: SendUserMessageHandler = () => {};\n\tlet appendEntryHandler: AppendEntryHandler = () => {};\n\tlet getActiveToolsHandler: GetActiveToolsHandler = () => [];\n\tlet getAllToolsHandler: GetAllToolsHandler = () => [];\n\tlet setActiveToolsHandler: SetActiveToolsHandler = () => {};\n\tlet setModelHandler: SetModelHandler = async () => false;\n\tlet getThinkingLevelHandler: GetThinkingLevelHandler = () => \"off\";\n\tlet setThinkingLevelHandler: SetThinkingLevelHandler = () => {};\n\n\tconst messageRenderers = new Map<string, MessageRenderer>();\n\tconst commands = new Map<string, RegisteredCommand>();\n\tconst flags = new Map<string, ExtensionFlag>();\n\tconst flagValues = new Map<string, boolean | string>();\n\tconst shortcuts = new Map<KeyId, ExtensionShortcut>();\n\n\tconst api = {\n\t\ton(event: string, handler: HandlerFn): void {\n\t\t\tconst list = handlers.get(event) ?? [];\n\t\t\tlist.push(handler);\n\t\t\thandlers.set(event, list);\n\t\t},\n\n\t\tregisterTool(tool: ToolDefinition): void {\n\t\t\ttools.set(tool.name, {\n\t\t\t\tdefinition: tool,\n\t\t\t\textensionPath,\n\t\t\t});\n\t\t},\n\n\t\tregisterCommand(name: string, options: { description?: string; handler: RegisteredCommand[\"handler\"] }): void {\n\t\t\tcommands.set(name, { name, ...options });\n\t\t},\n\n\t\tregisterShortcut(\n\t\t\tshortcut: KeyId,\n\t\t\toptions: {\n\t\t\t\tdescription?: string;\n\t\t\t\thandler: (ctx: import(\"./types.js\").ExtensionContext) => Promise<void> | void;\n\t\t\t},\n\t\t): void {\n\t\t\tshortcuts.set(shortcut, { shortcut, extensionPath, ...options });\n\t\t},\n\n\t\tregisterFlag(\n\t\t\tname: string,\n\t\t\toptions: { description?: string; type: \"boolean\" | \"string\"; default?: boolean | string },\n\t\t): void {\n\t\t\tflags.set(name, { name, extensionPath, ...options });\n\t\t\tif (options.default !== undefined) {\n\t\t\t\tflagValues.set(name, options.default);\n\t\t\t}\n\t\t},\n\n\t\tgetFlag(name: string): boolean | string | undefined {\n\t\t\treturn flagValues.get(name);\n\t\t},\n\n\t\tregisterMessageRenderer<T>(customType: string, renderer: MessageRenderer<T>): void {\n\t\t\tmessageRenderers.set(customType, renderer as MessageRenderer);\n\t\t},\n\n\t\tsendMessage(message, options): void {\n\t\t\tsendMessageHandler(message, options);\n\t\t},\n\n\t\tsendUserMessage(content, options): void {\n\t\t\tsendUserMessageHandler(content, options);\n\t\t},\n\n\t\tappendEntry(customType: string, data?: unknown): void {\n\t\t\tappendEntryHandler(customType, data);\n\t\t},\n\n\t\texec(command: string, args: string[], options?: ExecOptions) {\n\t\t\treturn execCommand(command, args, options?.cwd ?? cwd, options);\n\t\t},\n\n\t\tgetActiveTools(): string[] {\n\t\t\treturn getActiveToolsHandler();\n\t\t},\n\n\t\tgetAllTools(): string[] {\n\t\t\treturn getAllToolsHandler();\n\t\t},\n\n\t\tsetActiveTools(toolNames: string[]): void {\n\t\t\tsetActiveToolsHandler(toolNames);\n\t\t},\n\n\t\tsetModel(model) {\n\t\t\treturn setModelHandler(model);\n\t\t},\n\n\t\tgetThinkingLevel() {\n\t\t\treturn getThinkingLevelHandler();\n\t\t},\n\n\t\tsetThinkingLevel(level) {\n\t\t\tsetThinkingLevelHandler(level);\n\t\t},\n\n\t\tevents: eventBus,\n\t} as ExtensionAPI;\n\n\treturn {\n\t\tapi,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler: (handler: SendMessageHandler) => {\n\t\t\tsendMessageHandler = handler;\n\t\t},\n\t\tsetSendUserMessageHandler: (handler: SendUserMessageHandler) => {\n\t\t\tsendUserMessageHandler = handler;\n\t\t},\n\t\tsetAppendEntryHandler: (handler: AppendEntryHandler) => {\n\t\t\tappendEntryHandler = handler;\n\t\t},\n\t\tsetGetActiveToolsHandler: (handler: GetActiveToolsHandler) => {\n\t\t\tgetActiveToolsHandler = handler;\n\t\t},\n\t\tsetGetAllToolsHandler: (handler: GetAllToolsHandler) => {\n\t\t\tgetAllToolsHandler = handler;\n\t\t},\n\t\tsetSetActiveToolsHandler: (handler: SetActiveToolsHandler) => {\n\t\t\tsetActiveToolsHandler = handler;\n\t\t},\n\t\tsetSetModelHandler: (handler: SetModelHandler) => {\n\t\t\tsetModelHandler = handler;\n\t\t},\n\t\tsetGetThinkingLevelHandler: (handler: GetThinkingLevelHandler) => {\n\t\t\tgetThinkingLevelHandler = handler;\n\t\t},\n\t\tsetSetThinkingLevelHandler: (handler: SetThinkingLevelHandler) => {\n\t\t\tsetThinkingLevelHandler = handler;\n\t\t},\n\t\tsetFlagValue: (name: string, value: boolean | string) => {\n\t\t\tflagValues.set(name, value);\n\t\t},\n\t};\n}\n\nasync function loadExtensionWithBun(\n\tresolvedPath: string,\n\tcwd: string,\n\textensionPath: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): Promise<{ extension: LoadedExtension | null; error: string | null }> {\n\ttry {\n\t\tconst module = await import(resolvedPath);\n\t\tconst factory = (module.default ?? module) as ExtensionFactory;\n\n\t\tif (typeof factory !== \"function\") {\n\t\t\treturn { extension: null, error: \"Extension must export a default function\" };\n\t\t}\n\n\t\tconst handlers = new Map<string, HandlerFn[]>();\n\t\tconst tools = new Map<string, RegisteredTool>();\n\t\tconst {\n\t\t\tapi,\n\t\t\tmessageRenderers,\n\t\t\tcommands,\n\t\t\tflags,\n\t\t\tflagValues,\n\t\t\tshortcuts,\n\t\t\tsetSendMessageHandler,\n\t\t\tsetSendUserMessageHandler,\n\t\t\tsetAppendEntryHandler,\n\t\t\tsetGetActiveToolsHandler,\n\t\t\tsetGetAllToolsHandler,\n\t\t\tsetSetActiveToolsHandler,\n\t\t\tsetSetModelHandler,\n\t\t\tsetGetThinkingLevelHandler,\n\t\t\tsetSetThinkingLevelHandler,\n\t\t\tsetFlagValue,\n\t\t} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);\n\n\t\tfactory(api);\n\n\t\treturn {\n\t\t\textension: {\n\t\t\t\tpath: extensionPath,\n\t\t\t\tresolvedPath,\n\t\t\t\thandlers,\n\t\t\t\ttools,\n\t\t\t\tmessageRenderers,\n\t\t\t\tcommands,\n\t\t\t\tflags,\n\t\t\t\tflagValues,\n\t\t\t\tshortcuts,\n\t\t\t\tsetSendMessageHandler,\n\t\t\t\tsetSendUserMessageHandler,\n\t\t\t\tsetAppendEntryHandler,\n\t\t\t\tsetGetActiveToolsHandler,\n\t\t\t\tsetGetAllToolsHandler,\n\t\t\t\tsetSetActiveToolsHandler,\n\t\t\t\tsetSetModelHandler,\n\t\t\t\tsetGetThinkingLevelHandler,\n\t\t\t\tsetSetThinkingLevelHandler,\n\t\t\t\tsetFlagValue,\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\n\t\tif (message.includes(\"Cannot find module\") && message.includes(\"@mariozechner/\")) {\n\t\t\treturn {\n\t\t\t\textension: null,\n\t\t\t\terror:\n\t\t\t\t\t`${message}\\n` +\n\t\t\t\t\t\"Note: Extensions importing from @mariozechner/* packages are not supported in the standalone binary.\\n\" +\n\t\t\t\t\t\"Please install pi via npm: npm install -g @mariozechner/pi-coding-agent\",\n\t\t\t};\n\t\t}\n\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\nasync function loadExtension(\n\textensionPath: string,\n\tcwd: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n): Promise<{ extension: LoadedExtension | null; error: string | null }> {\n\tconst resolvedPath = resolvePath(extensionPath, cwd);\n\n\tif (isBunBinary) {\n\t\treturn loadExtensionWithBun(resolvedPath, cwd, extensionPath, eventBus, sharedUI);\n\t}\n\n\ttry {\n\t\tconst jiti = createJiti(import.meta.url, {\n\t\t\talias: getAliases(),\n\t\t});\n\n\t\tconst module = await jiti.import(resolvedPath, { default: true });\n\t\tconst factory = module as ExtensionFactory;\n\n\t\tif (typeof factory !== \"function\") {\n\t\t\treturn { extension: null, error: \"Extension must export a default function\" };\n\t\t}\n\n\t\tconst handlers = new Map<string, HandlerFn[]>();\n\t\tconst tools = new Map<string, RegisteredTool>();\n\t\tconst {\n\t\t\tapi,\n\t\t\tmessageRenderers,\n\t\t\tcommands,\n\t\t\tflags,\n\t\t\tflagValues,\n\t\t\tshortcuts,\n\t\t\tsetSendMessageHandler,\n\t\t\tsetSendUserMessageHandler,\n\t\t\tsetAppendEntryHandler,\n\t\t\tsetGetActiveToolsHandler,\n\t\t\tsetGetAllToolsHandler,\n\t\t\tsetSetActiveToolsHandler,\n\t\t\tsetSetModelHandler,\n\t\t\tsetGetThinkingLevelHandler,\n\t\t\tsetSetThinkingLevelHandler,\n\t\t\tsetFlagValue,\n\t\t} = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);\n\n\t\tfactory(api);\n\n\t\treturn {\n\t\t\textension: {\n\t\t\t\tpath: extensionPath,\n\t\t\t\tresolvedPath,\n\t\t\t\thandlers,\n\t\t\t\ttools,\n\t\t\t\tmessageRenderers,\n\t\t\t\tcommands,\n\t\t\t\tflags,\n\t\t\t\tflagValues,\n\t\t\t\tshortcuts,\n\t\t\t\tsetSendMessageHandler,\n\t\t\t\tsetSendUserMessageHandler,\n\t\t\t\tsetAppendEntryHandler,\n\t\t\t\tsetGetActiveToolsHandler,\n\t\t\t\tsetGetAllToolsHandler,\n\t\t\t\tsetSetActiveToolsHandler,\n\t\t\t\tsetSetModelHandler,\n\t\t\t\tsetGetThinkingLevelHandler,\n\t\t\t\tsetSetThinkingLevelHandler,\n\t\t\t\tsetFlagValue,\n\t\t\t},\n\t\t\terror: null,\n\t\t};\n\t} catch (err) {\n\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\treturn { extension: null, error: `Failed to load extension: ${message}` };\n\t}\n}\n\n/**\n * Create a LoadedExtension from an inline factory function.\n */\nexport function loadExtensionFromFactory(\n\tfactory: ExtensionFactory,\n\tcwd: string,\n\teventBus: EventBus,\n\tsharedUI: { ui: ExtensionUIContext; hasUI: boolean },\n\tname = \"<inline>\",\n): LoadedExtension {\n\tconst handlers = new Map<string, HandlerFn[]>();\n\tconst tools = new Map<string, RegisteredTool>();\n\tconst {\n\t\tapi,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler,\n\t\tsetSendUserMessageHandler,\n\t\tsetAppendEntryHandler,\n\t\tsetGetActiveToolsHandler,\n\t\tsetGetAllToolsHandler,\n\t\tsetSetActiveToolsHandler,\n\t\tsetSetModelHandler,\n\t\tsetGetThinkingLevelHandler,\n\t\tsetSetThinkingLevelHandler,\n\t\tsetFlagValue,\n\t} = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);\n\n\tfactory(api);\n\n\treturn {\n\t\tpath: name,\n\t\tresolvedPath: name,\n\t\thandlers,\n\t\ttools,\n\t\tmessageRenderers,\n\t\tcommands,\n\t\tflags,\n\t\tflagValues,\n\t\tshortcuts,\n\t\tsetSendMessageHandler,\n\t\tsetSendUserMessageHandler,\n\t\tsetAppendEntryHandler,\n\t\tsetGetActiveToolsHandler,\n\t\tsetGetAllToolsHandler,\n\t\tsetSetActiveToolsHandler,\n\t\tsetSetModelHandler,\n\t\tsetGetThinkingLevelHandler,\n\t\tsetSetThinkingLevelHandler,\n\t\tsetFlagValue,\n\t};\n}\n\n/**\n * Load extensions from paths.\n */\nexport async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {\n\tconst extensions: LoadedExtension[] = [];\n\tconst errors: Array<{ path: string; error: string }> = [];\n\tconst resolvedEventBus = eventBus ?? createEventBus();\n\tconst sharedUI = { ui: createNoOpUIContext(), hasUI: false };\n\n\tfor (const extPath of paths) {\n\t\tconst { extension, error } = await loadExtension(extPath, cwd, resolvedEventBus, sharedUI);\n\n\t\tif (error) {\n\t\t\terrors.push({ path: extPath, error });\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (extension) {\n\t\t\textensions.push(extension);\n\t\t}\n\t}\n\n\treturn {\n\t\textensions,\n\t\terrors,\n\t\tsetUIContext(uiContext, hasUI) {\n\t\t\tsharedUI.ui = uiContext;\n\t\t\tsharedUI.hasUI = hasUI;\n\t\t},\n\t};\n}\n\ninterface PiManifest {\n\textensions?: string[];\n\tthemes?: string[];\n\tskills?: string[];\n}\n\nfunction readPiManifest(packageJsonPath: string): PiManifest | null {\n\ttry {\n\t\tconst content = fs.readFileSync(packageJsonPath, \"utf-8\");\n\t\tconst pkg = JSON.parse(content);\n\t\tif (pkg.pi && typeof pkg.pi === \"object\") {\n\t\t\treturn pkg.pi as PiManifest;\n\t\t}\n\t\treturn null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\nfunction isExtensionFile(name: string): boolean {\n\treturn name.endsWith(\".ts\") || name.endsWith(\".js\");\n}\n\n/**\n * Resolve extension entry points from a directory.\n *\n * Checks for:\n * 1. package.json with \"pi.extensions\" field -> returns declared paths\n * 2. index.ts or index.js -> returns the index file\n *\n * Returns resolved paths or null if no entry points found.\n */\nfunction resolveExtensionEntries(dir: string): string[] | null {\n\t// Check for package.json with \"pi\" field first\n\tconst packageJsonPath = path.join(dir, \"package.json\");\n\tif (fs.existsSync(packageJsonPath)) {\n\t\tconst manifest = readPiManifest(packageJsonPath);\n\t\tif (manifest?.extensions?.length) {\n\t\t\tconst entries: string[] = [];\n\t\t\tfor (const extPath of manifest.extensions) {\n\t\t\t\tconst resolvedExtPath = path.resolve(dir, extPath);\n\t\t\t\tif (fs.existsSync(resolvedExtPath)) {\n\t\t\t\t\tentries.push(resolvedExtPath);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (entries.length > 0) {\n\t\t\t\treturn entries;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check for index.ts or index.js\n\tconst indexTs = path.join(dir, \"index.ts\");\n\tconst indexJs = path.join(dir, \"index.js\");\n\tif (fs.existsSync(indexTs)) {\n\t\treturn [indexTs];\n\t}\n\tif (fs.existsSync(indexJs)) {\n\t\treturn [indexJs];\n\t}\n\n\treturn null;\n}\n\n/**\n * Discover extensions in a directory.\n *\n * Discovery rules:\n * 1. Direct files: `extensions/*.ts` or `*.js` → load\n * 2. Subdirectory with index: `extensions/* /index.ts` or `index.js` → load\n * 3. Subdirectory with package.json: `extensions/* /package.json` with \"pi\" field → load what it declares\n *\n * No recursion beyond one level. Complex packages must use package.json manifest.\n */\nfunction discoverExtensionsInDir(dir: string): string[] {\n\tif (!fs.existsSync(dir)) {\n\t\treturn [];\n\t}\n\n\tconst discovered: string[] = [];\n\n\ttry {\n\t\tconst entries = fs.readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst entryPath = path.join(dir, entry.name);\n\n\t\t\t// 1. Direct files: *.ts or *.js\n\t\t\tif ((entry.isFile() || entry.isSymbolicLink()) && isExtensionFile(entry.name)) {\n\t\t\t\tdiscovered.push(entryPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// 2 & 3. Subdirectories\n\t\t\tif (entry.isDirectory() || entry.isSymbolicLink()) {\n\t\t\t\tconst entries = resolveExtensionEntries(entryPath);\n\t\t\t\tif (entries) {\n\t\t\t\t\tdiscovered.push(...entries);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn [];\n\t}\n\n\treturn discovered;\n}\n\n/**\n * Discover and load extensions from standard locations.\n */\nexport async function discoverAndLoadExtensions(\n\tconfiguredPaths: string[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n\teventBus?: EventBus,\n): Promise<LoadExtensionsResult> {\n\tconst allPaths: string[] = [];\n\tconst seen = new Set<string>();\n\n\tconst addPaths = (paths: string[]) => {\n\t\tfor (const p of paths) {\n\t\t\tconst resolved = path.resolve(p);\n\t\t\tif (!seen.has(resolved)) {\n\t\t\t\tseen.add(resolved);\n\t\t\t\tallPaths.push(p);\n\t\t\t}\n\t\t}\n\t};\n\n\t// 1. Global extensions: agentDir/extensions/\n\tconst globalExtDir = path.join(agentDir, \"extensions\");\n\taddPaths(discoverExtensionsInDir(globalExtDir));\n\n\t// 2. Project-local extensions: cwd/.pi/extensions/\n\tconst localExtDir = path.join(cwd, \".pi\", \"extensions\");\n\taddPaths(discoverExtensionsInDir(localExtDir));\n\n\t// 3. Explicitly configured paths\n\tfor (const p of configuredPaths) {\n\t\tconst resolved = resolvePath(p, cwd);\n\t\tif (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n\t\t\tconst entries = resolveExtensionEntries(resolved);\n\t\t\tif (entries) {\n\t\t\t\taddPaths(entries);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\taddPaths([resolved]);\n\t}\n\n\treturn loadExtensions(allPaths, cwd, eventBus);\n}\n"]}
|
|
@@ -77,6 +77,9 @@ function createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, _shar
|
|
|
77
77
|
let getActiveToolsHandler = () => [];
|
|
78
78
|
let getAllToolsHandler = () => [];
|
|
79
79
|
let setActiveToolsHandler = () => { };
|
|
80
|
+
let setModelHandler = async () => false;
|
|
81
|
+
let getThinkingLevelHandler = () => "off";
|
|
82
|
+
let setThinkingLevelHandler = () => { };
|
|
80
83
|
const messageRenderers = new Map();
|
|
81
84
|
const commands = new Map();
|
|
82
85
|
const flags = new Map();
|
|
@@ -133,6 +136,15 @@ function createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, _shar
|
|
|
133
136
|
setActiveTools(toolNames) {
|
|
134
137
|
setActiveToolsHandler(toolNames);
|
|
135
138
|
},
|
|
139
|
+
setModel(model) {
|
|
140
|
+
return setModelHandler(model);
|
|
141
|
+
},
|
|
142
|
+
getThinkingLevel() {
|
|
143
|
+
return getThinkingLevelHandler();
|
|
144
|
+
},
|
|
145
|
+
setThinkingLevel(level) {
|
|
146
|
+
setThinkingLevelHandler(level);
|
|
147
|
+
},
|
|
136
148
|
events: eventBus,
|
|
137
149
|
};
|
|
138
150
|
return {
|
|
@@ -160,6 +172,15 @@ function createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, _shar
|
|
|
160
172
|
setSetActiveToolsHandler: (handler) => {
|
|
161
173
|
setActiveToolsHandler = handler;
|
|
162
174
|
},
|
|
175
|
+
setSetModelHandler: (handler) => {
|
|
176
|
+
setModelHandler = handler;
|
|
177
|
+
},
|
|
178
|
+
setGetThinkingLevelHandler: (handler) => {
|
|
179
|
+
getThinkingLevelHandler = handler;
|
|
180
|
+
},
|
|
181
|
+
setSetThinkingLevelHandler: (handler) => {
|
|
182
|
+
setThinkingLevelHandler = handler;
|
|
183
|
+
},
|
|
163
184
|
setFlagValue: (name, value) => {
|
|
164
185
|
flagValues.set(name, value);
|
|
165
186
|
},
|
|
@@ -174,7 +195,7 @@ async function loadExtensionWithBun(resolvedPath, cwd, extensionPath, eventBus,
|
|
|
174
195
|
}
|
|
175
196
|
const handlers = new Map();
|
|
176
197
|
const tools = new Map();
|
|
177
|
-
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
|
198
|
+
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setSetModelHandler, setGetThinkingLevelHandler, setSetThinkingLevelHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
|
178
199
|
factory(api);
|
|
179
200
|
return {
|
|
180
201
|
extension: {
|
|
@@ -193,6 +214,9 @@ async function loadExtensionWithBun(resolvedPath, cwd, extensionPath, eventBus,
|
|
|
193
214
|
setGetActiveToolsHandler,
|
|
194
215
|
setGetAllToolsHandler,
|
|
195
216
|
setSetActiveToolsHandler,
|
|
217
|
+
setSetModelHandler,
|
|
218
|
+
setGetThinkingLevelHandler,
|
|
219
|
+
setSetThinkingLevelHandler,
|
|
196
220
|
setFlagValue,
|
|
197
221
|
},
|
|
198
222
|
error: null,
|
|
@@ -227,7 +251,7 @@ async function loadExtension(extensionPath, cwd, eventBus, sharedUI) {
|
|
|
227
251
|
}
|
|
228
252
|
const handlers = new Map();
|
|
229
253
|
const tools = new Map();
|
|
230
|
-
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
|
254
|
+
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setSetModelHandler, setGetThinkingLevelHandler, setSetThinkingLevelHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, extensionPath, eventBus, sharedUI);
|
|
231
255
|
factory(api);
|
|
232
256
|
return {
|
|
233
257
|
extension: {
|
|
@@ -246,6 +270,9 @@ async function loadExtension(extensionPath, cwd, eventBus, sharedUI) {
|
|
|
246
270
|
setGetActiveToolsHandler,
|
|
247
271
|
setGetAllToolsHandler,
|
|
248
272
|
setSetActiveToolsHandler,
|
|
273
|
+
setSetModelHandler,
|
|
274
|
+
setGetThinkingLevelHandler,
|
|
275
|
+
setSetThinkingLevelHandler,
|
|
249
276
|
setFlagValue,
|
|
250
277
|
},
|
|
251
278
|
error: null,
|
|
@@ -262,7 +289,7 @@ async function loadExtension(extensionPath, cwd, eventBus, sharedUI) {
|
|
|
262
289
|
export function loadExtensionFromFactory(factory, cwd, eventBus, sharedUI, name = "<inline>") {
|
|
263
290
|
const handlers = new Map();
|
|
264
291
|
const tools = new Map();
|
|
265
|
-
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);
|
|
292
|
+
const { api, messageRenderers, commands, flags, flagValues, shortcuts, setSendMessageHandler, setSendUserMessageHandler, setAppendEntryHandler, setGetActiveToolsHandler, setGetAllToolsHandler, setSetActiveToolsHandler, setSetModelHandler, setGetThinkingLevelHandler, setSetThinkingLevelHandler, setFlagValue, } = createExtensionAPI(handlers, tools, cwd, name, eventBus, sharedUI);
|
|
266
293
|
factory(api);
|
|
267
294
|
return {
|
|
268
295
|
path: name,
|
|
@@ -280,6 +307,9 @@ export function loadExtensionFromFactory(factory, cwd, eventBus, sharedUI, name
|
|
|
280
307
|
setGetActiveToolsHandler,
|
|
281
308
|
setGetAllToolsHandler,
|
|
282
309
|
setSetActiveToolsHandler,
|
|
310
|
+
setSetModelHandler,
|
|
311
|
+
setGetThinkingLevelHandler,
|
|
312
|
+
setSetThinkingLevelHandler,
|
|
283
313
|
setFlagValue,
|
|
284
314
|
};
|
|
285
315
|
}
|