@caupulican/pi-adaptative 0.81.2 → 0.81.4
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 +32 -0
- package/dist/core/agent-session.d.ts +2 -23
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +139 -147
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +2 -0
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +3 -0
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/billing-failover-controller.d.ts +38 -0
- package/dist/core/billing-failover-controller.d.ts.map +1 -0
- package/dist/core/billing-failover-controller.js +78 -0
- package/dist/core/billing-failover-controller.js.map +1 -0
- package/dist/core/billing-failover.d.ts +25 -0
- package/dist/core/billing-failover.d.ts.map +1 -0
- package/dist/core/billing-failover.js +21 -0
- package/dist/core/billing-failover.js.map +1 -0
- package/dist/core/compaction-support.d.ts +10 -1
- package/dist/core/compaction-support.d.ts.map +1 -1
- package/dist/core/compaction-support.js +68 -9
- package/dist/core/compaction-support.js.map +1 -1
- package/dist/core/context-pipeline.d.ts +1 -0
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +51 -14
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/export-html/index.d.ts.map +1 -1
- package/dist/core/export-html/index.js +9 -9
- package/dist/core/export-html/index.js.map +1 -1
- package/dist/core/failure-corpus.d.ts +49 -0
- package/dist/core/failure-corpus.d.ts.map +1 -0
- package/dist/core/failure-corpus.js +61 -0
- package/dist/core/failure-corpus.js.map +1 -0
- package/dist/core/model-router/config-diagnostics.d.ts +1 -1
- package/dist/core/model-router/config-diagnostics.d.ts.map +1 -1
- package/dist/core/model-router/config-diagnostics.js +29 -1
- package/dist/core/model-router/config-diagnostics.js.map +1 -1
- package/dist/core/model-router/fitness-gate.d.ts +25 -0
- package/dist/core/model-router/fitness-gate.d.ts.map +1 -0
- package/dist/core/model-router/fitness-gate.js +34 -0
- package/dist/core/model-router/fitness-gate.js.map +1 -0
- package/dist/core/model-router/status.d.ts +29 -1
- package/dist/core/model-router/status.d.ts.map +1 -1
- package/dist/core/model-router/status.js +31 -4
- package/dist/core/model-router/status.js.map +1 -1
- package/dist/core/model-router-controller.d.ts +10 -1
- package/dist/core/model-router-controller.d.ts.map +1 -1
- package/dist/core/model-router-controller.js +110 -27
- package/dist/core/model-router-controller.js.map +1 -1
- package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
- package/dist/core/models/default-model-suggestions.js +18 -0
- package/dist/core/models/default-model-suggestions.js.map +1 -1
- package/dist/core/runtime-builder.d.ts +16 -0
- package/dist/core/runtime-builder.d.ts.map +1 -1
- package/dist/core/runtime-builder.js +88 -1
- package/dist/core/runtime-builder.js.map +1 -1
- package/dist/core/scout-controller.d.ts +44 -0
- package/dist/core/scout-controller.d.ts.map +1 -0
- package/dist/core/scout-controller.js +165 -0
- package/dist/core/scout-controller.js.map +1 -0
- package/dist/core/settings-manager.d.ts +17 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +47 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +6 -8
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/context-scout.d.ts +19 -0
- package/dist/core/tools/context-scout.d.ts.map +1 -0
- package/dist/core/tools/context-scout.js +47 -0
- package/dist/core/tools/context-scout.js.map +1 -0
- package/dist/core/tools/index.d.ts +1 -0
- 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/main.d.ts.map +1 -1
- package/dist/main.js +1 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/fitness-role-selector.d.ts +1 -1
- package/dist/modes/interactive/components/fitness-role-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/fitness-role-selector.js +5 -0
- package/dist/modes/interactive/components/fitness-role-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +77 -7
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +13 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/local-model-commands.d.ts +5 -2
- package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
- package/dist/modes/interactive/local-model-commands.js +129 -1
- package/dist/modes/interactive/local-model-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts +16 -0
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +32 -0
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/docs/bug-ledger.md +113 -0
- package/docs/index.md +1 -0
- package/docs/models.md +9 -0
- package/docs/providers.md +14 -0
- package/docs/scout.md +73 -0
- package/docs/settings.md +16 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/12-full-control.ts +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -104,16 +104,16 @@ function generateHtml(sessionData, themeName) {
|
|
|
104
104
|
const sessionDataBase64 = Buffer.from(JSON.stringify(sessionData)).toString("base64");
|
|
105
105
|
// Build the CSS with theme variables injected
|
|
106
106
|
const css = templateCss
|
|
107
|
-
.replace("{{THEME_VARS}}", themeVars)
|
|
108
|
-
.replace("{{BODY_BG}}", bodyBg)
|
|
109
|
-
.replace("{{CONTAINER_BG}}", containerBg)
|
|
110
|
-
.replace("{{INFO_BG}}", infoBg);
|
|
107
|
+
.replace("{{THEME_VARS}}", () => themeVars)
|
|
108
|
+
.replace("{{BODY_BG}}", () => bodyBg)
|
|
109
|
+
.replace("{{CONTAINER_BG}}", () => containerBg)
|
|
110
|
+
.replace("{{INFO_BG}}", () => infoBg);
|
|
111
111
|
return template
|
|
112
|
-
.replace("{{CSS}}", css)
|
|
113
|
-
.replace("{{JS}}", templateJs)
|
|
114
|
-
.replace("{{SESSION_DATA}}", sessionDataBase64)
|
|
115
|
-
.replace("{{MARKED_JS}}", markedJs)
|
|
116
|
-
.replace("{{HIGHLIGHT_JS}}", hljsJs);
|
|
112
|
+
.replace("{{CSS}}", () => css)
|
|
113
|
+
.replace("{{JS}}", () => templateJs)
|
|
114
|
+
.replace("{{SESSION_DATA}}", () => sessionDataBase64)
|
|
115
|
+
.replace("{{MARKED_JS}}", () => markedJs)
|
|
116
|
+
.replace("{{HIGHLIGHT_JS}}", () => hljsJs);
|
|
117
117
|
}
|
|
118
118
|
/** Tools rendered directly by the HTML template (not pre-rendered via TUI→ANSI→HTML pipeline) */
|
|
119
119
|
const TEMPLATE_RENDERED_TOOLS = new Set(["bash", "read", "write", "edit", "ls"]);
|
|
@@ -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 \"@caupulican/pi-agent-core\";\nimport type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.ts\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.ts\";\nimport { normalizePath, resolvePath } from \"../../utils/paths.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { openSession } from \"../session-manager-factory.ts\";\n\n/**\n * Interface for rendering custom tools to HTML.\n * Used by agent-session to pre-render extension tool output.\n */\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to HTML. Returns collapsed/expanded or undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolCallId: string,\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): { collapsed?: string; expanded?: string } | undefined;\n}\n\n/** Pre-rendered HTML for a custom tool call and result */\ninterface RenderedToolHtml {\n\tcallHtml?: string;\n\tresultHtmlCollapsed?: string;\n\tresultHtmlExpanded?: string;\n}\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n\t/** Optional tool renderer for custom tools */\n\ttoolRenderer?: ToolHtmlRenderer;\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\ttools?: Array<Pick<ToolDefinition, \"name\" | \"description\" | \"parameters\">>;\n\t/** Pre-rendered HTML for custom tool calls/results, keyed by tool call ID */\n\trenderedTools?: Record<string, RenderedToolHtml>;\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst derivedExportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = themeExport.pageBg ?? derivedExportColors.pageBg;\n\tconst containerBg = themeExport.cardBg ?? derivedExportColors.cardBg;\n\tconst infoBg = themeExport.infoBg ?? derivedExportColors.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/** Tools rendered directly by the HTML template (not pre-rendered via TUI→ANSI→HTML pipeline) */\nconst TEMPLATE_RENDERED_TOOLS = new Set([\"bash\", \"read\", \"write\", \"edit\", \"ls\"]);\n\n/**\n * Pre-render custom tools to HTML using their TUI renderers.\n */\nfunction preRenderCustomTools(\n\tentries: SessionEntry[],\n\ttoolRenderer: ToolHtmlRenderer,\n): Record<string, RenderedToolHtml> {\n\tconst renderedTools: Record<string, RenderedToolHtml> = {};\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = entry.message;\n\n\t\t// Find tool calls in assistant messages\n\t\tif (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"toolCall\" && !TEMPLATE_RENDERED_TOOLS.has(block.name)) {\n\t\t\t\t\tconst callHtml = toolRenderer.renderCall(block.id, block.name, block.arguments);\n\t\t\t\t\tif (callHtml) {\n\t\t\t\t\t\trenderedTools[block.id] = { callHtml };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find tool results\n\t\tif (msg.role === \"toolResult\" && msg.toolCallId) {\n\t\t\tconst toolName = msg.toolName || \"\";\n\t\t\t// Only render if we have a pre-rendered call OR it's not template-rendered\n\t\t\tconst existing = renderedTools[msg.toolCallId];\n\t\t\tif (existing || !TEMPLATE_RENDERED_TOOLS.has(toolName)) {\n\t\t\t\tconst rendered = toolRenderer.renderResult(\n\t\t\t\t\tmsg.toolCallId,\n\t\t\t\t\ttoolName,\n\t\t\t\t\tmsg.content,\n\t\t\t\t\tmsg.details,\n\t\t\t\t\tmsg.isError || false,\n\t\t\t\t);\n\t\t\t\tif (rendered) {\n\t\t\t\t\trenderedTools[msg.toolCallId] = {\n\t\t\t\t\t\t...existing,\n\t\t\t\t\t\tresultHtmlCollapsed: rendered.collapsed,\n\t\t\t\t\t\tresultHtmlExpanded: rendered.expanded,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn renderedTools;\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst entries = sm.getEntries();\n\n\t// Pre-render custom tools if a tool renderer is provided\n\tlet renderedTools: Record<string, RenderedToolHtml> | undefined;\n\tif (opts.toolRenderer) {\n\t\trenderedTools = preRenderCustomTools(entries, opts.toolRenderer);\n\t\t// Only include if we actually rendered something\n\t\tif (Object.keys(renderedTools).length === 0) {\n\t\t\trenderedTools = undefined;\n\t\t}\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries,\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })),\n\t\trenderedTools,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath ? normalizePath(opts.outputPath) : undefined;\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\tconst resolvedInputPath = resolvePath(inputPath);\n\n\tif (!existsSync(resolvedInputPath)) {\n\t\tthrow new Error(`File not found: ${resolvedInputPath}`);\n\t}\n\n\tconst sm = openSession(resolvedInputPath);\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 ? normalizePath(opts.outputPath) : undefined;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(resolvedInputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/export-html/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentState } from \"@caupulican/pi-agent-core\";\nimport type { SessionEntry, SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport { existsSync, readFileSync, writeFileSync } from \"fs\";\nimport { basename, join } from \"path\";\nimport { APP_NAME, getExportTemplateDir } from \"../../config.ts\";\nimport { getResolvedThemeColors, getThemeExportColors } from \"../../modes/interactive/theme/theme.ts\";\nimport { normalizePath, resolvePath } from \"../../utils/paths.ts\";\nimport type { ToolDefinition } from \"../extensions/types.ts\";\nimport { openSession } from \"../session-manager-factory.ts\";\n\n/**\n * Interface for rendering custom tools to HTML.\n * Used by agent-session to pre-render extension tool output.\n */\nexport interface ToolHtmlRenderer {\n\t/** Render a tool call to HTML. Returns undefined if tool has no custom renderer. */\n\trenderCall(toolCallId: string, toolName: string, args: unknown): string | undefined;\n\t/** Render a tool result to HTML. Returns collapsed/expanded or undefined if tool has no custom renderer. */\n\trenderResult(\n\t\ttoolCallId: string,\n\t\ttoolName: string,\n\t\tresult: Array<{ type: string; text?: string; data?: string; mimeType?: string }>,\n\t\tdetails: unknown,\n\t\tisError: boolean,\n\t): { collapsed?: string; expanded?: string } | undefined;\n}\n\n/** Pre-rendered HTML for a custom tool call and result */\ninterface RenderedToolHtml {\n\tcallHtml?: string;\n\tresultHtmlCollapsed?: string;\n\tresultHtmlExpanded?: string;\n}\n\nexport interface ExportOptions {\n\toutputPath?: string;\n\tthemeName?: string;\n\t/** Optional tool renderer for custom tools */\n\ttoolRenderer?: ToolHtmlRenderer;\n}\n\n/** Parse a color string to RGB values. Supports hex (#RRGGBB) and rgb(r,g,b) formats. */\nfunction parseColor(color: string): { r: number; g: number; b: number } | undefined {\n\tconst hexMatch = color.match(/^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (hexMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(hexMatch[1], 16),\n\t\t\tg: Number.parseInt(hexMatch[2], 16),\n\t\t\tb: Number.parseInt(hexMatch[3], 16),\n\t\t};\n\t}\n\tconst rgbMatch = color.match(/^rgb\\s*\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n\tif (rgbMatch) {\n\t\treturn {\n\t\t\tr: Number.parseInt(rgbMatch[1], 10),\n\t\t\tg: Number.parseInt(rgbMatch[2], 10),\n\t\t\tb: Number.parseInt(rgbMatch[3], 10),\n\t\t};\n\t}\n\treturn undefined;\n}\n\n/** Calculate relative luminance of a color (0-1, higher = lighter). */\nfunction getLuminance(r: number, g: number, b: number): number {\n\tconst toLinear = (c: number) => {\n\t\tconst s = c / 255;\n\t\treturn s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n\t};\n\treturn 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);\n}\n\n/** Adjust color brightness. Factor > 1 lightens, < 1 darkens. */\nfunction adjustBrightness(color: string, factor: number): string {\n\tconst parsed = parseColor(color);\n\tif (!parsed) return color;\n\tconst adjust = (c: number) => Math.min(255, Math.max(0, Math.round(c * factor)));\n\treturn `rgb(${adjust(parsed.r)}, ${adjust(parsed.g)}, ${adjust(parsed.b)})`;\n}\n\n/** Derive export background colors from a base color (e.g., userMessageBg). */\nfunction deriveExportColors(baseColor: string): { pageBg: string; cardBg: string; infoBg: string } {\n\tconst parsed = parseColor(baseColor);\n\tif (!parsed) {\n\t\treturn {\n\t\t\tpageBg: \"rgb(24, 24, 30)\",\n\t\t\tcardBg: \"rgb(30, 30, 36)\",\n\t\t\tinfoBg: \"rgb(60, 55, 40)\",\n\t\t};\n\t}\n\n\tconst luminance = getLuminance(parsed.r, parsed.g, parsed.b);\n\tconst isLight = luminance > 0.5;\n\n\tif (isLight) {\n\t\treturn {\n\t\t\tpageBg: adjustBrightness(baseColor, 0.96),\n\t\t\tcardBg: baseColor,\n\t\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 10)}, ${Math.min(255, parsed.g + 5)}, ${Math.max(0, parsed.b - 20)})`,\n\t\t};\n\t}\n\treturn {\n\t\tpageBg: adjustBrightness(baseColor, 0.7),\n\t\tcardBg: adjustBrightness(baseColor, 0.85),\n\t\tinfoBg: `rgb(${Math.min(255, parsed.r + 20)}, ${Math.min(255, parsed.g + 15)}, ${parsed.b})`,\n\t};\n}\n\n/**\n * Generate CSS custom property declarations from theme colors.\n */\nfunction generateThemeVars(themeName?: string): string {\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst lines: string[] = [];\n\tfor (const [key, value] of Object.entries(colors)) {\n\t\tlines.push(`--${key}: ${value};`);\n\t}\n\n\t// Use explicit theme export colors if available, otherwise derive from userMessageBg\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst userMessageBg = colors.userMessageBg || \"#343541\";\n\tconst derivedColors = deriveExportColors(userMessageBg);\n\n\tlines.push(`--exportPageBg: ${themeExport.pageBg ?? derivedColors.pageBg};`);\n\tlines.push(`--exportCardBg: ${themeExport.cardBg ?? derivedColors.cardBg};`);\n\tlines.push(`--exportInfoBg: ${themeExport.infoBg ?? derivedColors.infoBg};`);\n\n\treturn lines.join(\"\\n \");\n}\n\ninterface SessionData {\n\theader: ReturnType<SessionManager[\"getHeader\"]>;\n\tentries: ReturnType<SessionManager[\"getEntries\"]>;\n\tleafId: string | null;\n\tsystemPrompt?: string;\n\ttools?: Array<Pick<ToolDefinition, \"name\" | \"description\" | \"parameters\">>;\n\t/** Pre-rendered HTML for custom tool calls/results, keyed by tool call ID */\n\trenderedTools?: Record<string, RenderedToolHtml>;\n}\n\n/**\n * Core HTML generation logic shared by both export functions.\n */\nfunction generateHtml(sessionData: SessionData, themeName?: string): string {\n\tconst templateDir = getExportTemplateDir();\n\tconst template = readFileSync(join(templateDir, \"template.html\"), \"utf-8\");\n\tconst templateCss = readFileSync(join(templateDir, \"template.css\"), \"utf-8\");\n\tconst templateJs = readFileSync(join(templateDir, \"template.js\"), \"utf-8\");\n\tconst markedJs = readFileSync(join(templateDir, \"vendor\", \"marked.min.js\"), \"utf-8\");\n\tconst hljsJs = readFileSync(join(templateDir, \"vendor\", \"highlight.min.js\"), \"utf-8\");\n\n\tconst themeVars = generateThemeVars(themeName);\n\tconst colors = getResolvedThemeColors(themeName);\n\tconst themeExport = getThemeExportColors(themeName);\n\tconst derivedExportColors = deriveExportColors(colors.userMessageBg || \"#343541\");\n\tconst bodyBg = themeExport.pageBg ?? derivedExportColors.pageBg;\n\tconst containerBg = themeExport.cardBg ?? derivedExportColors.cardBg;\n\tconst infoBg = themeExport.infoBg ?? derivedExportColors.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/** Tools rendered directly by the HTML template (not pre-rendered via TUI→ANSI→HTML pipeline) */\nconst TEMPLATE_RENDERED_TOOLS = new Set([\"bash\", \"read\", \"write\", \"edit\", \"ls\"]);\n\n/**\n * Pre-render custom tools to HTML using their TUI renderers.\n */\nfunction preRenderCustomTools(\n\tentries: SessionEntry[],\n\ttoolRenderer: ToolHtmlRenderer,\n): Record<string, RenderedToolHtml> {\n\tconst renderedTools: Record<string, RenderedToolHtml> = {};\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"message\") continue;\n\t\tconst msg = entry.message;\n\n\t\t// Find tool calls in assistant messages\n\t\tif (msg.role === \"assistant\" && Array.isArray(msg.content)) {\n\t\t\tfor (const block of msg.content) {\n\t\t\t\tif (block.type === \"toolCall\" && !TEMPLATE_RENDERED_TOOLS.has(block.name)) {\n\t\t\t\t\tconst callHtml = toolRenderer.renderCall(block.id, block.name, block.arguments);\n\t\t\t\t\tif (callHtml) {\n\t\t\t\t\t\trenderedTools[block.id] = { callHtml };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Find tool results\n\t\tif (msg.role === \"toolResult\" && msg.toolCallId) {\n\t\t\tconst toolName = msg.toolName || \"\";\n\t\t\t// Only render if we have a pre-rendered call OR it's not template-rendered\n\t\t\tconst existing = renderedTools[msg.toolCallId];\n\t\t\tif (existing || !TEMPLATE_RENDERED_TOOLS.has(toolName)) {\n\t\t\t\tconst rendered = toolRenderer.renderResult(\n\t\t\t\t\tmsg.toolCallId,\n\t\t\t\t\ttoolName,\n\t\t\t\t\tmsg.content,\n\t\t\t\t\tmsg.details,\n\t\t\t\t\tmsg.isError || false,\n\t\t\t\t);\n\t\t\t\tif (rendered) {\n\t\t\t\t\trenderedTools[msg.toolCallId] = {\n\t\t\t\t\t\t...existing,\n\t\t\t\t\t\tresultHtmlCollapsed: rendered.collapsed,\n\t\t\t\t\t\tresultHtmlExpanded: rendered.expanded,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn renderedTools;\n}\n\n/**\n * Export session to HTML using SessionManager and AgentState.\n * Used by TUI's /export command.\n */\nexport async function exportSessionToHtml(\n\tsm: SessionManager,\n\tstate?: AgentState,\n\toptions?: ExportOptions | string,\n): Promise<string> {\n\tconst opts: ExportOptions = typeof options === \"string\" ? { outputPath: options } : options || {};\n\n\tconst sessionFile = sm.getSessionFile();\n\tif (!sessionFile) {\n\t\tthrow new Error(\"Cannot export in-memory session to HTML\");\n\t}\n\tif (!existsSync(sessionFile)) {\n\t\tthrow new Error(\"Nothing to export yet - start a conversation first\");\n\t}\n\n\tconst entries = sm.getEntries();\n\n\t// Pre-render custom tools if a tool renderer is provided\n\tlet renderedTools: Record<string, RenderedToolHtml> | undefined;\n\tif (opts.toolRenderer) {\n\t\trenderedTools = preRenderCustomTools(entries, opts.toolRenderer);\n\t\t// Only include if we actually rendered something\n\t\tif (Object.keys(renderedTools).length === 0) {\n\t\t\trenderedTools = undefined;\n\t\t}\n\t}\n\n\tconst sessionData: SessionData = {\n\t\theader: sm.getHeader(),\n\t\tentries,\n\t\tleafId: sm.getLeafId(),\n\t\tsystemPrompt: state?.systemPrompt,\n\t\ttools: state?.tools?.map((t) => ({ name: t.name, description: t.description, parameters: t.parameters })),\n\t\trenderedTools,\n\t};\n\n\tconst html = generateHtml(sessionData, opts.themeName);\n\n\tlet outputPath = opts.outputPath ? normalizePath(opts.outputPath) : undefined;\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\tconst resolvedInputPath = resolvePath(inputPath);\n\n\tif (!existsSync(resolvedInputPath)) {\n\t\tthrow new Error(`File not found: ${resolvedInputPath}`);\n\t}\n\n\tconst sm = openSession(resolvedInputPath);\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 ? normalizePath(opts.outputPath) : undefined;\n\tif (!outputPath) {\n\t\tconst inputBasename = basename(resolvedInputPath, \".jsonl\");\n\t\toutputPath = `${APP_NAME}-session-${inputBasename}.html`;\n\t}\n\n\twriteFileSync(outputPath, html, \"utf8\");\n\treturn outputPath;\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ClassifiedError } from "@caupulican/pi-agent-core";
|
|
2
|
+
export interface FailureCorpusRecord {
|
|
3
|
+
ts: string;
|
|
4
|
+
provider?: string;
|
|
5
|
+
modelId?: string;
|
|
6
|
+
reason: ClassifiedError["reason"];
|
|
7
|
+
retryable: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export interface FailureCorpusStats {
|
|
11
|
+
total: number;
|
|
12
|
+
unknown: number;
|
|
13
|
+
}
|
|
14
|
+
export interface FailureCorpusFs {
|
|
15
|
+
existsSync(path: string): boolean;
|
|
16
|
+
mkdirSync(path: string, options: {
|
|
17
|
+
recursive: true;
|
|
18
|
+
}): void;
|
|
19
|
+
appendFileSync(path: string, data: string, encoding: "utf-8"): void;
|
|
20
|
+
readFileSync(path: string, encoding: "utf-8"): string;
|
|
21
|
+
writeFileSync(path: string, data: string, encoding: "utf-8"): void;
|
|
22
|
+
statSync(path: string): {
|
|
23
|
+
size: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare class FailureCorpusRecorder {
|
|
27
|
+
private total;
|
|
28
|
+
private unknown;
|
|
29
|
+
private readonly filePath;
|
|
30
|
+
private readonly fs;
|
|
31
|
+
private readonly now;
|
|
32
|
+
private readonly debug;
|
|
33
|
+
constructor(args: {
|
|
34
|
+
filePath: string;
|
|
35
|
+
fs?: FailureCorpusFs;
|
|
36
|
+
now?: () => Date;
|
|
37
|
+
debug?: (message: string) => void;
|
|
38
|
+
});
|
|
39
|
+
record(args: {
|
|
40
|
+
provider?: string;
|
|
41
|
+
modelId?: string;
|
|
42
|
+
message: string;
|
|
43
|
+
classified: ClassifiedError;
|
|
44
|
+
}): void;
|
|
45
|
+
stats(): FailureCorpusStats;
|
|
46
|
+
private rotateIfNeeded;
|
|
47
|
+
}
|
|
48
|
+
export declare function redactSecrets(message: string): string;
|
|
49
|
+
//# sourceMappingURL=failure-corpus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-corpus.d.ts","sourceRoot":"","sources":["../../src/core/failure-corpus.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,MAAM,WAAW,mBAAmB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC/B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,SAAS,EAAE,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACpE,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IACtD,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAOD,qBAAa,qBAAqB;IACjC,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAkB;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAa;IACjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAElD,YAAY,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,eAAe,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,EAKhH;IAED,MAAM,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,eAAe,CAAA;KAAE,GAAG,IAAI,CAkBxG;IAED,KAAK,IAAI,kBAAkB,CAE1B;IAED,OAAO,CAAC,cAAc;CAStB;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKrD","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { ClassifiedError } from \"@caupulican/pi-agent-core\";\n\nexport interface FailureCorpusRecord {\n\tts: string;\n\tprovider?: string;\n\tmodelId?: string;\n\treason: ClassifiedError[\"reason\"];\n\tretryable: boolean;\n\tmessage: string;\n}\n\nexport interface FailureCorpusStats {\n\ttotal: number;\n\tunknown: number;\n}\n\nexport interface FailureCorpusFs {\n\texistsSync(path: string): boolean;\n\tmkdirSync(path: string, options: { recursive: true }): void;\n\tappendFileSync(path: string, data: string, encoding: \"utf-8\"): void;\n\treadFileSync(path: string, encoding: \"utf-8\"): string;\n\twriteFileSync(path: string, data: string, encoding: \"utf-8\"): void;\n\tstatSync(path: string): { size: number };\n}\n\nconst DEFAULT_FS: FailureCorpusFs = { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, statSync };\nconst MAX_MESSAGE_CHARS = 500;\nconst MAX_FILE_BYTES = 512 * 1024;\nconst MAX_ROTATED_RECORDS = 1000;\n\nexport class FailureCorpusRecorder {\n\tprivate total = 0;\n\tprivate unknown = 0;\n\tprivate readonly filePath: string;\n\tprivate readonly fs: FailureCorpusFs;\n\tprivate readonly now: () => Date;\n\tprivate readonly debug: (message: string) => void;\n\n\tconstructor(args: { filePath: string; fs?: FailureCorpusFs; now?: () => Date; debug?: (message: string) => void }) {\n\t\tthis.filePath = args.filePath;\n\t\tthis.fs = args.fs ?? DEFAULT_FS;\n\t\tthis.now = args.now ?? (() => new Date());\n\t\tthis.debug = args.debug ?? (() => {});\n\t}\n\n\trecord(args: { provider?: string; modelId?: string; message: string; classified: ClassifiedError }): void {\n\t\tthis.total += 1;\n\t\tif (args.classified.reason === \"unknown\") this.unknown += 1;\n\t\ttry {\n\t\t\tthis.fs.mkdirSync(dirname(this.filePath), { recursive: true });\n\t\t\tconst record: FailureCorpusRecord = {\n\t\t\t\tts: this.now().toISOString(),\n\t\t\t\tprovider: args.provider,\n\t\t\t\tmodelId: args.modelId,\n\t\t\t\treason: args.classified.reason,\n\t\t\t\tretryable: args.classified.retryable,\n\t\t\t\tmessage: redactSecrets(args.message).slice(0, MAX_MESSAGE_CHARS),\n\t\t\t};\n\t\t\tthis.fs.appendFileSync(this.filePath, `${JSON.stringify(record)}\\n`, \"utf-8\");\n\t\t\tthis.rotateIfNeeded();\n\t\t} catch (error) {\n\t\t\tthis.debug(`failure corpus write skipped: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\tstats(): FailureCorpusStats {\n\t\treturn { total: this.total, unknown: this.unknown };\n\t}\n\n\tprivate rotateIfNeeded(): void {\n\t\tif (!this.fs.existsSync(this.filePath) || this.fs.statSync(this.filePath).size <= MAX_FILE_BYTES) return;\n\t\tconst lines = this.fs\n\t\t\t.readFileSync(this.filePath, \"utf-8\")\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.trim().length > 0)\n\t\t\t.slice(-MAX_ROTATED_RECORDS);\n\t\tthis.fs.writeFileSync(this.filePath, `${lines.join(\"\\n\")}\\n`, \"utf-8\");\n\t}\n}\n\nexport function redactSecrets(message: string): string {\n\treturn message\n\t\t.replace(/sk-[A-Za-z0-9]{8,}/g, \"[redacted]\")\n\t\t.replace(/Bearer\\s+[A-Za-z0-9._~+/=-]+/gi, \"Bearer [redacted]\")\n\t\t.replace(/[A-Za-z0-9+/]{40,}={0,2}/g, \"[redacted]\");\n}\n"]}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
const DEFAULT_FS = { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, statSync };
|
|
4
|
+
const MAX_MESSAGE_CHARS = 500;
|
|
5
|
+
const MAX_FILE_BYTES = 512 * 1024;
|
|
6
|
+
const MAX_ROTATED_RECORDS = 1000;
|
|
7
|
+
export class FailureCorpusRecorder {
|
|
8
|
+
total = 0;
|
|
9
|
+
unknown = 0;
|
|
10
|
+
filePath;
|
|
11
|
+
fs;
|
|
12
|
+
now;
|
|
13
|
+
debug;
|
|
14
|
+
constructor(args) {
|
|
15
|
+
this.filePath = args.filePath;
|
|
16
|
+
this.fs = args.fs ?? DEFAULT_FS;
|
|
17
|
+
this.now = args.now ?? (() => new Date());
|
|
18
|
+
this.debug = args.debug ?? (() => { });
|
|
19
|
+
}
|
|
20
|
+
record(args) {
|
|
21
|
+
this.total += 1;
|
|
22
|
+
if (args.classified.reason === "unknown")
|
|
23
|
+
this.unknown += 1;
|
|
24
|
+
try {
|
|
25
|
+
this.fs.mkdirSync(dirname(this.filePath), { recursive: true });
|
|
26
|
+
const record = {
|
|
27
|
+
ts: this.now().toISOString(),
|
|
28
|
+
provider: args.provider,
|
|
29
|
+
modelId: args.modelId,
|
|
30
|
+
reason: args.classified.reason,
|
|
31
|
+
retryable: args.classified.retryable,
|
|
32
|
+
message: redactSecrets(args.message).slice(0, MAX_MESSAGE_CHARS),
|
|
33
|
+
};
|
|
34
|
+
this.fs.appendFileSync(this.filePath, `${JSON.stringify(record)}\n`, "utf-8");
|
|
35
|
+
this.rotateIfNeeded();
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
this.debug(`failure corpus write skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
stats() {
|
|
42
|
+
return { total: this.total, unknown: this.unknown };
|
|
43
|
+
}
|
|
44
|
+
rotateIfNeeded() {
|
|
45
|
+
if (!this.fs.existsSync(this.filePath) || this.fs.statSync(this.filePath).size <= MAX_FILE_BYTES)
|
|
46
|
+
return;
|
|
47
|
+
const lines = this.fs
|
|
48
|
+
.readFileSync(this.filePath, "utf-8")
|
|
49
|
+
.split("\n")
|
|
50
|
+
.filter((line) => line.trim().length > 0)
|
|
51
|
+
.slice(-MAX_ROTATED_RECORDS);
|
|
52
|
+
this.fs.writeFileSync(this.filePath, `${lines.join("\n")}\n`, "utf-8");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function redactSecrets(message) {
|
|
56
|
+
return message
|
|
57
|
+
.replace(/sk-[A-Za-z0-9]{8,}/g, "[redacted]")
|
|
58
|
+
.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]")
|
|
59
|
+
.replace(/[A-Za-z0-9+/]{40,}={0,2}/g, "[redacted]");
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=failure-corpus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-corpus.js","sourceRoot":"","sources":["../../src/core/failure-corpus.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { ClassifiedError } from \"@caupulican/pi-agent-core\";\n\nexport interface FailureCorpusRecord {\n\tts: string;\n\tprovider?: string;\n\tmodelId?: string;\n\treason: ClassifiedError[\"reason\"];\n\tretryable: boolean;\n\tmessage: string;\n}\n\nexport interface FailureCorpusStats {\n\ttotal: number;\n\tunknown: number;\n}\n\nexport interface FailureCorpusFs {\n\texistsSync(path: string): boolean;\n\tmkdirSync(path: string, options: { recursive: true }): void;\n\tappendFileSync(path: string, data: string, encoding: \"utf-8\"): void;\n\treadFileSync(path: string, encoding: \"utf-8\"): string;\n\twriteFileSync(path: string, data: string, encoding: \"utf-8\"): void;\n\tstatSync(path: string): { size: number };\n}\n\nconst DEFAULT_FS: FailureCorpusFs = { existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync, statSync };\nconst MAX_MESSAGE_CHARS = 500;\nconst MAX_FILE_BYTES = 512 * 1024;\nconst MAX_ROTATED_RECORDS = 1000;\n\nexport class FailureCorpusRecorder {\n\tprivate total = 0;\n\tprivate unknown = 0;\n\tprivate readonly filePath: string;\n\tprivate readonly fs: FailureCorpusFs;\n\tprivate readonly now: () => Date;\n\tprivate readonly debug: (message: string) => void;\n\n\tconstructor(args: { filePath: string; fs?: FailureCorpusFs; now?: () => Date; debug?: (message: string) => void }) {\n\t\tthis.filePath = args.filePath;\n\t\tthis.fs = args.fs ?? DEFAULT_FS;\n\t\tthis.now = args.now ?? (() => new Date());\n\t\tthis.debug = args.debug ?? (() => {});\n\t}\n\n\trecord(args: { provider?: string; modelId?: string; message: string; classified: ClassifiedError }): void {\n\t\tthis.total += 1;\n\t\tif (args.classified.reason === \"unknown\") this.unknown += 1;\n\t\ttry {\n\t\t\tthis.fs.mkdirSync(dirname(this.filePath), { recursive: true });\n\t\t\tconst record: FailureCorpusRecord = {\n\t\t\t\tts: this.now().toISOString(),\n\t\t\t\tprovider: args.provider,\n\t\t\t\tmodelId: args.modelId,\n\t\t\t\treason: args.classified.reason,\n\t\t\t\tretryable: args.classified.retryable,\n\t\t\t\tmessage: redactSecrets(args.message).slice(0, MAX_MESSAGE_CHARS),\n\t\t\t};\n\t\t\tthis.fs.appendFileSync(this.filePath, `${JSON.stringify(record)}\\n`, \"utf-8\");\n\t\t\tthis.rotateIfNeeded();\n\t\t} catch (error) {\n\t\t\tthis.debug(`failure corpus write skipped: ${error instanceof Error ? error.message : String(error)}`);\n\t\t}\n\t}\n\n\tstats(): FailureCorpusStats {\n\t\treturn { total: this.total, unknown: this.unknown };\n\t}\n\n\tprivate rotateIfNeeded(): void {\n\t\tif (!this.fs.existsSync(this.filePath) || this.fs.statSync(this.filePath).size <= MAX_FILE_BYTES) return;\n\t\tconst lines = this.fs\n\t\t\t.readFileSync(this.filePath, \"utf-8\")\n\t\t\t.split(\"\\n\")\n\t\t\t.filter((line) => line.trim().length > 0)\n\t\t\t.slice(-MAX_ROTATED_RECORDS);\n\t\tthis.fs.writeFileSync(this.filePath, `${lines.join(\"\\n\")}\\n`, \"utf-8\");\n\t}\n}\n\nexport function redactSecrets(message: string): string {\n\treturn message\n\t\t.replace(/sk-[A-Za-z0-9]{8,}/g, \"[redacted]\")\n\t\t.replace(/Bearer\\s+[A-Za-z0-9._~+/=-]+/gi, \"Bearer [redacted]\")\n\t\t.replace(/[A-Za-z0-9+/]{40,}={0,2}/g, \"[redacted]\");\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ModelRegistry } from "../model-registry.ts";
|
|
2
2
|
import type { ModelRouterStatusSettings } from "./status.ts";
|
|
3
|
-
export declare function collectModelRouterConfigDiagnostics(settings: ModelRouterStatusSettings, modelRegistry: ModelRegistry): string[];
|
|
3
|
+
export declare function collectModelRouterConfigDiagnostics(settings: ModelRouterStatusSettings, modelRegistry: ModelRegistry, agentDir?: string): string[];
|
|
4
4
|
//# sourceMappingURL=config-diagnostics.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-diagnostics.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/config-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"config-diagnostics.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/config-diagnostics.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AA+C7D,wBAAgB,mCAAmC,CAClD,QAAQ,EAAE,yBAAyB,EACnC,aAAa,EAAE,aAAa,EAC5B,QAAQ,CAAC,EAAE,MAAM,GACf,MAAM,EAAE,CA+CV","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"../model-registry.ts\";\nimport { resolveCliModel } from \"../model-resolver.ts\";\nimport { FitnessStore } from \"../models/fitness-store.ts\";\nimport { evaluateSurfaceFitness, type FitnessGatedSurface } from \"./fitness-gate.ts\";\nimport type { ModelRouterStatusSettings } from \"./status.ts\";\n\nfunction formatModel(model: Model<any>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction collectModelRouterModelDiagnostics(\n\tlabel: \"cheap model\" | \"medium model\" | \"expensive model\",\n\tsettingKey: \"modelRouter.cheapModel\" | \"modelRouter.mediumModel\" | \"modelRouter.expensiveModel\",\n\tmodelPattern: string | undefined,\n\tmodelRegistry: ModelRegistry,\n): string[] {\n\tif (!modelPattern) {\n\t\treturn [`Model router ${label} is unset; configure ${settingKey} or disable modelRouter.enabled.`];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [`Model router ${label} is unresolved: ${modelPattern}.`];\n\t}\n\tif (!modelRegistry.hasConfiguredAuth(resolved.model)) {\n\t\treturn [`Model router ${label} is missing auth: ${formatModel(resolved.model)}.`];\n\t}\n\treturn [];\n}\n\nfunction collectFitnessGateDiagnostic(\n\tlabel: \"cheap model\" | \"medium model\" | \"expensive model\",\n\tsurface: FitnessGatedSurface,\n\tmodelPattern: string | undefined,\n\tmodelRegistry: ModelRegistry,\n\tagentDir: string | undefined,\n): string[] {\n\tif (!agentDir || !modelPattern) return [];\n\tconst resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });\n\tif (!resolved.model || !modelRegistry.hasConfiguredAuth(resolved.model)) return [];\n\tconst modelRef = formatModel(resolved.model);\n\tconst fitness = FitnessStore.forAgentDir(agentDir)\n\t\t.getForHost()\n\t\t.find((entry) => entry.model === modelRef);\n\tconst verdict = evaluateSurfaceFitness(surface, fitness?.report);\n\tif (verdict.fit) return [];\n\tif (verdict.reason === \"unprobed\") return [`Model router ${label} is unprobed for the fitness gate: ${modelRef}.`];\n\treturn [\n\t\t`Model router ${label} is unfit for the fitness gate: ${modelRef} (${verdict.lane} ${verdict.succeeded}/${verdict.total}).`,\n\t];\n}\n\nexport function collectModelRouterConfigDiagnostics(\n\tsettings: ModelRouterStatusSettings,\n\tmodelRegistry: ModelRegistry,\n\tagentDir?: string,\n): string[] {\n\tif (!settings.enabled) return [];\n\treturn [\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"cheap model\",\n\t\t\t\"modelRouter.cheapModel\",\n\t\t\tsettings.cheapModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"medium model\",\n\t\t\t\"modelRouter.mediumModel\",\n\t\t\tsettings.mediumModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"expensive model\",\n\t\t\t\"modelRouter.expensiveModel\",\n\t\t\tsettings.expensiveModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...(settings.fitnessGate\n\t\t\t? [\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"cheap model\",\n\t\t\t\t\t\t\"router_cheap\",\n\t\t\t\t\t\tsettings.cheapModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"medium model\",\n\t\t\t\t\t\t\"router_medium\",\n\t\t\t\t\t\tsettings.mediumModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"expensive model\",\n\t\t\t\t\t\t\"router_expensive\",\n\t\t\t\t\t\tsettings.expensiveModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t]\n\t\t\t: []),\n\t];\n}\n"]}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { resolveCliModel } from "../model-resolver.js";
|
|
2
|
+
import { FitnessStore } from "../models/fitness-store.js";
|
|
3
|
+
import { evaluateSurfaceFitness } from "./fitness-gate.js";
|
|
2
4
|
function formatModel(model) {
|
|
3
5
|
return `${model.provider}/${model.id}`;
|
|
4
6
|
}
|
|
@@ -15,13 +17,39 @@ function collectModelRouterModelDiagnostics(label, settingKey, modelPattern, mod
|
|
|
15
17
|
}
|
|
16
18
|
return [];
|
|
17
19
|
}
|
|
18
|
-
|
|
20
|
+
function collectFitnessGateDiagnostic(label, surface, modelPattern, modelRegistry, agentDir) {
|
|
21
|
+
if (!agentDir || !modelPattern)
|
|
22
|
+
return [];
|
|
23
|
+
const resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });
|
|
24
|
+
if (!resolved.model || !modelRegistry.hasConfiguredAuth(resolved.model))
|
|
25
|
+
return [];
|
|
26
|
+
const modelRef = formatModel(resolved.model);
|
|
27
|
+
const fitness = FitnessStore.forAgentDir(agentDir)
|
|
28
|
+
.getForHost()
|
|
29
|
+
.find((entry) => entry.model === modelRef);
|
|
30
|
+
const verdict = evaluateSurfaceFitness(surface, fitness?.report);
|
|
31
|
+
if (verdict.fit)
|
|
32
|
+
return [];
|
|
33
|
+
if (verdict.reason === "unprobed")
|
|
34
|
+
return [`Model router ${label} is unprobed for the fitness gate: ${modelRef}.`];
|
|
35
|
+
return [
|
|
36
|
+
`Model router ${label} is unfit for the fitness gate: ${modelRef} (${verdict.lane} ${verdict.succeeded}/${verdict.total}).`,
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
export function collectModelRouterConfigDiagnostics(settings, modelRegistry, agentDir) {
|
|
19
40
|
if (!settings.enabled)
|
|
20
41
|
return [];
|
|
21
42
|
return [
|
|
22
43
|
...collectModelRouterModelDiagnostics("cheap model", "modelRouter.cheapModel", settings.cheapModel, modelRegistry),
|
|
23
44
|
...collectModelRouterModelDiagnostics("medium model", "modelRouter.mediumModel", settings.mediumModel, modelRegistry),
|
|
24
45
|
...collectModelRouterModelDiagnostics("expensive model", "modelRouter.expensiveModel", settings.expensiveModel, modelRegistry),
|
|
46
|
+
...(settings.fitnessGate
|
|
47
|
+
? [
|
|
48
|
+
...collectFitnessGateDiagnostic("cheap model", "router_cheap", settings.cheapModel, modelRegistry, agentDir),
|
|
49
|
+
...collectFitnessGateDiagnostic("medium model", "router_medium", settings.mediumModel, modelRegistry, agentDir),
|
|
50
|
+
...collectFitnessGateDiagnostic("expensive model", "router_expensive", settings.expensiveModel, modelRegistry, agentDir),
|
|
51
|
+
]
|
|
52
|
+
: []),
|
|
25
53
|
];
|
|
26
54
|
}
|
|
27
55
|
//# sourceMappingURL=config-diagnostics.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-diagnostics.js","sourceRoot":"","sources":["../../../src/core/model-router/config-diagnostics.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"config-diagnostics.js","sourceRoot":"","sources":["../../../src/core/model-router/config-diagnostics.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"../model-registry.ts\";\nimport { resolveCliModel } from \"../model-resolver.ts\";\nimport { FitnessStore } from \"../models/fitness-store.ts\";\nimport { evaluateSurfaceFitness, type FitnessGatedSurface } from \"./fitness-gate.ts\";\nimport type { ModelRouterStatusSettings } from \"./status.ts\";\n\nfunction formatModel(model: Model<any>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction collectModelRouterModelDiagnostics(\n\tlabel: \"cheap model\" | \"medium model\" | \"expensive model\",\n\tsettingKey: \"modelRouter.cheapModel\" | \"modelRouter.mediumModel\" | \"modelRouter.expensiveModel\",\n\tmodelPattern: string | undefined,\n\tmodelRegistry: ModelRegistry,\n): string[] {\n\tif (!modelPattern) {\n\t\treturn [`Model router ${label} is unset; configure ${settingKey} or disable modelRouter.enabled.`];\n\t}\n\tconst resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });\n\tif (!resolved.model) {\n\t\treturn [`Model router ${label} is unresolved: ${modelPattern}.`];\n\t}\n\tif (!modelRegistry.hasConfiguredAuth(resolved.model)) {\n\t\treturn [`Model router ${label} is missing auth: ${formatModel(resolved.model)}.`];\n\t}\n\treturn [];\n}\n\nfunction collectFitnessGateDiagnostic(\n\tlabel: \"cheap model\" | \"medium model\" | \"expensive model\",\n\tsurface: FitnessGatedSurface,\n\tmodelPattern: string | undefined,\n\tmodelRegistry: ModelRegistry,\n\tagentDir: string | undefined,\n): string[] {\n\tif (!agentDir || !modelPattern) return [];\n\tconst resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });\n\tif (!resolved.model || !modelRegistry.hasConfiguredAuth(resolved.model)) return [];\n\tconst modelRef = formatModel(resolved.model);\n\tconst fitness = FitnessStore.forAgentDir(agentDir)\n\t\t.getForHost()\n\t\t.find((entry) => entry.model === modelRef);\n\tconst verdict = evaluateSurfaceFitness(surface, fitness?.report);\n\tif (verdict.fit) return [];\n\tif (verdict.reason === \"unprobed\") return [`Model router ${label} is unprobed for the fitness gate: ${modelRef}.`];\n\treturn [\n\t\t`Model router ${label} is unfit for the fitness gate: ${modelRef} (${verdict.lane} ${verdict.succeeded}/${verdict.total}).`,\n\t];\n}\n\nexport function collectModelRouterConfigDiagnostics(\n\tsettings: ModelRouterStatusSettings,\n\tmodelRegistry: ModelRegistry,\n\tagentDir?: string,\n): string[] {\n\tif (!settings.enabled) return [];\n\treturn [\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"cheap model\",\n\t\t\t\"modelRouter.cheapModel\",\n\t\t\tsettings.cheapModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"medium model\",\n\t\t\t\"modelRouter.mediumModel\",\n\t\t\tsettings.mediumModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...collectModelRouterModelDiagnostics(\n\t\t\t\"expensive model\",\n\t\t\t\"modelRouter.expensiveModel\",\n\t\t\tsettings.expensiveModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t\t...(settings.fitnessGate\n\t\t\t? [\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"cheap model\",\n\t\t\t\t\t\t\"router_cheap\",\n\t\t\t\t\t\tsettings.cheapModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"medium model\",\n\t\t\t\t\t\t\"router_medium\",\n\t\t\t\t\t\tsettings.mediumModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t\t...collectFitnessGateDiagnostic(\n\t\t\t\t\t\t\"expensive model\",\n\t\t\t\t\t\t\"router_expensive\",\n\t\t\t\t\t\tsettings.expensiveModel,\n\t\t\t\t\t\tmodelRegistry,\n\t\t\t\t\t\tagentDir,\n\t\t\t\t\t),\n\t\t\t\t]\n\t\t\t: []),\n\t];\n}\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ModelFitnessReport } from "../research/model-fitness.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Fitness applicability doctrine: Class A autonomous adoption requires proof; Class B routed turns
|
|
4
|
+
* are subtractive-only and pass unprobed models. Compaction is always-on subtractive composition:
|
|
5
|
+
* digest-failed probed cheap summarizers fall back, while unprobed models pass. Class C explicit
|
|
6
|
+
* user choices are deliberately not selection-gated here. The `search` lane is probed and reported
|
|
7
|
+
* but has no selection-time consumer yet.
|
|
8
|
+
*/
|
|
9
|
+
export type FitnessGatedSurface = "compaction" | "router_cheap" | "router_medium" | "router_expensive" | "router_judge" | "executor" | "curation" | "scout_auto";
|
|
10
|
+
export type FitnessGateVerdict = {
|
|
11
|
+
fit: true;
|
|
12
|
+
probed: boolean;
|
|
13
|
+
} | {
|
|
14
|
+
fit: false;
|
|
15
|
+
reason: "unprobed";
|
|
16
|
+
} | {
|
|
17
|
+
fit: false;
|
|
18
|
+
reason: "lane_failed";
|
|
19
|
+
lane: string;
|
|
20
|
+
succeeded: number;
|
|
21
|
+
total: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function laneMeetsFitnessBar(succeeded: number, total: number): boolean;
|
|
24
|
+
export declare function evaluateSurfaceFitness(surface: FitnessGatedSurface, report: ModelFitnessReport | undefined): FitnessGateVerdict;
|
|
25
|
+
//# sourceMappingURL=fitness-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fitness-gate.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/fitness-gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAC5B,YAAY,GACZ,cAAc,GACd,eAAe,GACf,kBAAkB,GAClB,cAAc,GACd,UAAU,GACV,UAAU,GACV,YAAY,CAAC;AAEhB,MAAM,MAAM,kBAAkB,GAC3B;IAAE,GAAG,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC9B;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,GAAG,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAiBzF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,wBAAgB,sBAAsB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,kBAAkB,GAAG,SAAS,GACpC,kBAAkB,CAmBpB","sourcesContent":["import type { ModelFitnessReport } from \"../research/model-fitness.ts\";\n\n/**\n * Fitness applicability doctrine: Class A autonomous adoption requires proof; Class B routed turns\n * are subtractive-only and pass unprobed models. Compaction is always-on subtractive composition:\n * digest-failed probed cheap summarizers fall back, while unprobed models pass. Class C explicit\n * user choices are deliberately not selection-gated here. The `search` lane is probed and reported\n * but has no selection-time consumer yet.\n */\nexport type FitnessGatedSurface =\n\t| \"compaction\"\n\t| \"router_cheap\"\n\t| \"router_medium\"\n\t| \"router_expensive\"\n\t| \"router_judge\"\n\t| \"executor\"\n\t| \"curation\"\n\t| \"scout_auto\";\n\nexport type FitnessGateVerdict =\n\t| { fit: true; probed: boolean }\n\t| { fit: false; reason: \"unprobed\" }\n\t| { fit: false; reason: \"lane_failed\"; lane: string; succeeded: number; total: number };\n\ntype FitnessLane = \"research\" | \"worker\" | \"judge\" | \"toolCall\" | \"digest\";\n\nconst CLASS_A_SURFACES = new Set<FitnessGatedSurface>([\"executor\", \"curation\", \"scout_auto\"]);\n\nconst SURFACE_LANES: Record<FitnessGatedSurface, readonly FitnessLane[]> = {\n\tcompaction: [\"digest\"],\n\trouter_cheap: [\"research\", \"toolCall\"],\n\trouter_medium: [\"worker\", \"toolCall\"],\n\trouter_expensive: [\"worker\", \"toolCall\"],\n\trouter_judge: [\"judge\"],\n\texecutor: [\"toolCall\"],\n\tcuration: [\"digest\"],\n\tscout_auto: [\"research\", \"toolCall\"],\n};\n\nexport function laneMeetsFitnessBar(succeeded: number, total: number): boolean {\n\treturn total > 0 && succeeded >= Math.ceil(total * (2 / 3));\n}\n\nexport function evaluateSurfaceFitness(\n\tsurface: FitnessGatedSurface,\n\treport: ModelFitnessReport | undefined,\n): FitnessGateVerdict {\n\tif (!report) {\n\t\treturn CLASS_A_SURFACES.has(surface) ? { fit: false, reason: \"unprobed\" } : { fit: true, probed: false };\n\t}\n\n\tfor (const lane of SURFACE_LANES[surface]) {\n\t\tif (lane === \"judge\") {\n\t\t\tconst score = report.judge;\n\t\t\tif (!laneMeetsFitnessBar(score.parsed, score.total)) {\n\t\t\t\treturn { fit: false, reason: \"lane_failed\", lane, succeeded: score.parsed, total: score.total };\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = report[lane];\n\t\tif (!laneMeetsFitnessBar(score.succeeded, score.total)) {\n\t\t\treturn { fit: false, reason: \"lane_failed\", lane, succeeded: score.succeeded, total: score.total };\n\t\t}\n\t}\n\treturn { fit: true, probed: true };\n}\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const CLASS_A_SURFACES = new Set(["executor", "curation", "scout_auto"]);
|
|
2
|
+
const SURFACE_LANES = {
|
|
3
|
+
compaction: ["digest"],
|
|
4
|
+
router_cheap: ["research", "toolCall"],
|
|
5
|
+
router_medium: ["worker", "toolCall"],
|
|
6
|
+
router_expensive: ["worker", "toolCall"],
|
|
7
|
+
router_judge: ["judge"],
|
|
8
|
+
executor: ["toolCall"],
|
|
9
|
+
curation: ["digest"],
|
|
10
|
+
scout_auto: ["research", "toolCall"],
|
|
11
|
+
};
|
|
12
|
+
export function laneMeetsFitnessBar(succeeded, total) {
|
|
13
|
+
return total > 0 && succeeded >= Math.ceil(total * (2 / 3));
|
|
14
|
+
}
|
|
15
|
+
export function evaluateSurfaceFitness(surface, report) {
|
|
16
|
+
if (!report) {
|
|
17
|
+
return CLASS_A_SURFACES.has(surface) ? { fit: false, reason: "unprobed" } : { fit: true, probed: false };
|
|
18
|
+
}
|
|
19
|
+
for (const lane of SURFACE_LANES[surface]) {
|
|
20
|
+
if (lane === "judge") {
|
|
21
|
+
const score = report.judge;
|
|
22
|
+
if (!laneMeetsFitnessBar(score.parsed, score.total)) {
|
|
23
|
+
return { fit: false, reason: "lane_failed", lane, succeeded: score.parsed, total: score.total };
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const score = report[lane];
|
|
28
|
+
if (!laneMeetsFitnessBar(score.succeeded, score.total)) {
|
|
29
|
+
return { fit: false, reason: "lane_failed", lane, succeeded: score.succeeded, total: score.total };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { fit: true, probed: true };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=fitness-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fitness-gate.js","sourceRoot":"","sources":["../../../src/core/model-router/fitness-gate.ts"],"names":[],"mappings":"AA0BA,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAsB,CAAC,UAAU,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;AAE9F,MAAM,aAAa,GAAwD;IAC1E,UAAU,EAAE,CAAC,QAAQ,CAAC;IACtB,YAAY,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;IACtC,aAAa,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IACrC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;IACxC,YAAY,EAAE,CAAC,OAAO,CAAC;IACvB,QAAQ,EAAE,CAAC,UAAU,CAAC;IACtB,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,UAAU,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC;CACpC,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,KAAa,EAAW;IAC9E,OAAO,KAAK,GAAG,CAAC,IAAI,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAAA,CAC5D;AAED,MAAM,UAAU,sBAAsB,CACrC,OAA4B,EAC5B,MAAsC,EACjB;IACrB,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,gBAAgB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1G,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACtB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;YAC3B,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;YACjG,CAAC;YACD,SAAS;QACV,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3B,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACxD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;QACpG,CAAC;IACF,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAAA,CACnC","sourcesContent":["import type { ModelFitnessReport } from \"../research/model-fitness.ts\";\n\n/**\n * Fitness applicability doctrine: Class A autonomous adoption requires proof; Class B routed turns\n * are subtractive-only and pass unprobed models. Compaction is always-on subtractive composition:\n * digest-failed probed cheap summarizers fall back, while unprobed models pass. Class C explicit\n * user choices are deliberately not selection-gated here. The `search` lane is probed and reported\n * but has no selection-time consumer yet.\n */\nexport type FitnessGatedSurface =\n\t| \"compaction\"\n\t| \"router_cheap\"\n\t| \"router_medium\"\n\t| \"router_expensive\"\n\t| \"router_judge\"\n\t| \"executor\"\n\t| \"curation\"\n\t| \"scout_auto\";\n\nexport type FitnessGateVerdict =\n\t| { fit: true; probed: boolean }\n\t| { fit: false; reason: \"unprobed\" }\n\t| { fit: false; reason: \"lane_failed\"; lane: string; succeeded: number; total: number };\n\ntype FitnessLane = \"research\" | \"worker\" | \"judge\" | \"toolCall\" | \"digest\";\n\nconst CLASS_A_SURFACES = new Set<FitnessGatedSurface>([\"executor\", \"curation\", \"scout_auto\"]);\n\nconst SURFACE_LANES: Record<FitnessGatedSurface, readonly FitnessLane[]> = {\n\tcompaction: [\"digest\"],\n\trouter_cheap: [\"research\", \"toolCall\"],\n\trouter_medium: [\"worker\", \"toolCall\"],\n\trouter_expensive: [\"worker\", \"toolCall\"],\n\trouter_judge: [\"judge\"],\n\texecutor: [\"toolCall\"],\n\tcuration: [\"digest\"],\n\tscout_auto: [\"research\", \"toolCall\"],\n};\n\nexport function laneMeetsFitnessBar(succeeded: number, total: number): boolean {\n\treturn total > 0 && succeeded >= Math.ceil(total * (2 / 3));\n}\n\nexport function evaluateSurfaceFitness(\n\tsurface: FitnessGatedSurface,\n\treport: ModelFitnessReport | undefined,\n): FitnessGateVerdict {\n\tif (!report) {\n\t\treturn CLASS_A_SURFACES.has(surface) ? { fit: false, reason: \"unprobed\" } : { fit: true, probed: false };\n\t}\n\n\tfor (const lane of SURFACE_LANES[surface]) {\n\t\tif (lane === \"judge\") {\n\t\t\tconst score = report.judge;\n\t\t\tif (!laneMeetsFitnessBar(score.parsed, score.total)) {\n\t\t\t\treturn { fit: false, reason: \"lane_failed\", lane, succeeded: score.parsed, total: score.total };\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst score = report[lane];\n\t\tif (!laneMeetsFitnessBar(score.succeeded, score.total)) {\n\t\t\treturn { fit: false, reason: \"lane_failed\", lane, succeeded: score.succeeded, total: score.total };\n\t\t}\n\t}\n\treturn { fit: true, probed: true };\n}\n"]}
|
|
@@ -1,13 +1,41 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@caupulican/pi-agent-core";
|
|
1
2
|
import type { SessionEntry } from "@caupulican/pi-agent-core/node";
|
|
2
3
|
import type { RouteDecision } from "../autonomy/contracts.ts";
|
|
3
4
|
import type { ModelRouterIntent } from "./intent-classifier.ts";
|
|
4
5
|
export declare const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
6
|
+
export type ModelRouterTierFitnessStatus = {
|
|
7
|
+
status: "fit";
|
|
8
|
+
} | {
|
|
9
|
+
status: "unprobed";
|
|
10
|
+
} | {
|
|
11
|
+
status: "unfit";
|
|
12
|
+
lane: string;
|
|
13
|
+
succeeded: number;
|
|
14
|
+
total: number;
|
|
15
|
+
};
|
|
16
|
+
export type ModelRouterFitnessStatuses = Partial<Record<"cheap" | "medium" | "expensive", ModelRouterTierFitnessStatus>>;
|
|
17
|
+
export type ModelRouterFailoverStatus = {
|
|
18
|
+
exhausted: string[];
|
|
19
|
+
lastNotice?: string;
|
|
20
|
+
failureStats?: {
|
|
21
|
+
total: number;
|
|
22
|
+
unknown: number;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
5
25
|
export type ModelRouterStatusSettings = {
|
|
6
26
|
enabled: boolean;
|
|
27
|
+
fitnessGate?: boolean;
|
|
7
28
|
cheapModel?: string;
|
|
29
|
+
cheapThinking?: ThinkingLevel;
|
|
8
30
|
mediumModel?: string;
|
|
31
|
+
mediumThinking?: ThinkingLevel;
|
|
9
32
|
expensiveModel?: string;
|
|
33
|
+
expensiveThinking?: ThinkingLevel;
|
|
10
34
|
learningModel?: string;
|
|
35
|
+
executorModel?: string;
|
|
36
|
+
executorThinking?: ThinkingLevel;
|
|
37
|
+
judgeModel?: string;
|
|
38
|
+
judgeThinking?: ThinkingLevel;
|
|
11
39
|
};
|
|
12
40
|
export type ModelRouterDecisionStatus = {
|
|
13
41
|
route: RouteDecision;
|
|
@@ -17,5 +45,5 @@ export type ModelRouterDecisionStatus = {
|
|
|
17
45
|
intent?: ModelRouterIntent;
|
|
18
46
|
};
|
|
19
47
|
export declare function getRecentModelRouterDecisions(entries: SessionEntry[], limit?: number): ModelRouterDecisionStatus[];
|
|
20
|
-
export declare function formatModelRouterStatus(settings: ModelRouterStatusSettings, lastDecision?: ModelRouterDecisionStatus, formatLabel?: (label: string) => string, recentDecisions?: ModelRouterDecisionStatus[], lastSkipReason?: string, latestIntent?: ModelRouterIntent): string;
|
|
48
|
+
export declare function formatModelRouterStatus(settings: ModelRouterStatusSettings, lastDecision?: ModelRouterDecisionStatus, formatLabel?: (label: string) => string, recentDecisions?: ModelRouterDecisionStatus[], lastSkipReason?: string, latestIntent?: ModelRouterIntent, fitnessStatuses?: ModelRouterFitnessStatuses, failoverStatus?: ModelRouterFailoverStatus): string;
|
|
21
49
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAa,aAAa,EAAa,MAAM,0BAA0B,CAAC;AACpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,eAAO,MAAM,iCAAiC,0BAA0B,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAa,aAAa,EAAa,MAAM,0BAA0B,CAAC;AACpF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,eAAO,MAAM,iCAAiC,0BAA0B,CAAC;AAEzE,MAAM,MAAM,4BAA4B,GACrC;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,UAAU,CAAA;CAAE,GACtB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE,MAAM,MAAM,0BAA0B,GAAG,OAAO,CAC/C,MAAM,CAAC,OAAO,GAAG,QAAQ,GAAG,WAAW,EAAE,4BAA4B,CAAC,CACtE,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,aAAa,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,aAAa,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,aAAa,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC9B,CAAC;AAwBF,MAAM,MAAM,yBAAyB,GAAG;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAiDF,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,KAAK,SAAI,GAAG,yBAAyB,EAAE,CAQ7G;AAED,wBAAgB,uBAAuB,CACtC,QAAQ,EAAE,yBAAyB,EACnC,YAAY,CAAC,EAAE,yBAAyB,EACxC,WAAW,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAyB,EACzD,eAAe,GAAE,yBAAyB,EAAO,EACjD,cAAc,CAAC,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,iBAAiB,EAChC,eAAe,CAAC,EAAE,0BAA0B,EAC5C,cAAc,CAAC,EAAE,yBAAyB,GACxC,MAAM,CA4DR","sourcesContent":["import type { ThinkingLevel } from \"@caupulican/pi-agent-core\";\nimport type { SessionEntry } from \"@caupulican/pi-agent-core/node\";\nimport type { ModelTier, RouteDecision, RouteRisk } from \"../autonomy/contracts.ts\";\nimport type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nexport const MODEL_ROUTER_DECISION_CUSTOM_TYPE = \"model_router_decision\";\n\nexport type ModelRouterTierFitnessStatus =\n\t| { status: \"fit\" }\n\t| { status: \"unprobed\" }\n\t| { status: \"unfit\"; lane: string; succeeded: number; total: number };\n\nexport type ModelRouterFitnessStatuses = Partial<\n\tRecord<\"cheap\" | \"medium\" | \"expensive\", ModelRouterTierFitnessStatus>\n>;\n\nexport type ModelRouterFailoverStatus = {\n\texhausted: string[];\n\tlastNotice?: string;\n\tfailureStats?: { total: number; unknown: number };\n};\n\nexport type ModelRouterStatusSettings = {\n\tenabled: boolean;\n\tfitnessGate?: boolean;\n\tcheapModel?: string;\n\tcheapThinking?: ThinkingLevel;\n\tmediumModel?: string;\n\tmediumThinking?: ThinkingLevel;\n\texpensiveModel?: string;\n\texpensiveThinking?: ThinkingLevel;\n\tlearningModel?: string;\n\texecutorModel?: string;\n\texecutorThinking?: ThinkingLevel;\n\tjudgeModel?: string;\n\tjudgeThinking?: ThinkingLevel;\n};\n\nconst MODEL_ROUTER_INHERIT_THINKING_LABEL = \"(inherit)\";\n\nfunction getThinkingLabel(level: ThinkingLevel | undefined): string {\n\treturn level ?? MODEL_ROUTER_INHERIT_THINKING_LABEL;\n}\n\nfunction formatModelWithThinking(\n\tlabel: string,\n\tmodel: string | undefined,\n\tlevel: ThinkingLevel | undefined,\n\tfitness?: ModelRouterTierFitnessStatus,\n): string {\n\tconst fitnessSuffix = fitness ? ` · fitness ${formatFitness(fitness)}` : \"\";\n\treturn `${label} ${model ?? \"unset\"} · thinking ${getThinkingLabel(level)}${fitnessSuffix}`;\n}\n\nfunction formatFitness(fitness: ModelRouterTierFitnessStatus): string {\n\tif (fitness.status === \"fit\") return \"fit\";\n\tif (fitness.status === \"unprobed\") return \"unprobed\";\n\treturn `UNFIT (${fitness.lane} ${fitness.succeeded}/${fitness.total})`;\n}\n\nexport type ModelRouterDecisionStatus = {\n\troute: RouteDecision;\n\troutedModel: string;\n\toutcome: \"routed\" | \"escalated\" | \"failed\";\n\tretryModel?: string;\n\tintent?: ModelRouterIntent;\n};\n\nfunction isRouteDecision(value: unknown): value is RouteDecision {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst route = value as Partial<RouteDecision>;\n\tconst validTiers = new Set<ModelTier>([\"cheap\", \"medium\", \"expensive\", \"learning\"]);\n\tconst validRisks = new Set<RouteRisk>([\"read-only\", \"scoped-write\", \"high-impact\", \"approval-required\"]);\n\n\treturn (\n\t\ttypeof route.confidence === \"number\" &&\n\t\ttypeof route.reasonCode === \"string\" &&\n\t\tArray.isArray(route.reasons) &&\n\t\troute.reasons.every((r) => typeof r === \"string\") &&\n\t\tvalidTiers.has(route.tier as ModelTier) &&\n\t\tvalidRisks.has(route.risk as RouteRisk) &&\n\t\t(route.model === undefined || typeof route.model === \"string\") &&\n\t\t(route.fallbackFrom === undefined ||\n\t\t\troute.fallbackFrom === \"cheap\" ||\n\t\t\troute.fallbackFrom === \"medium\" ||\n\t\t\troute.fallbackFrom === \"expensive\" ||\n\t\t\troute.fallbackFrom === \"learning\") &&\n\t\t(route.createdAt === undefined || typeof route.createdAt === \"string\")\n\t);\n}\n\nfunction isModelRouterDecisionStatus(data: unknown): data is ModelRouterDecisionStatus {\n\tif (!data || typeof data !== \"object\") return false;\n\tconst record = data as Partial<ModelRouterDecisionStatus>;\n\treturn (\n\t\tisRouteDecision(record.route) &&\n\t\trecord.route.tier !== \"learning\" && // Validate user prompt decisions never need learning tier in runtime\n\t\ttypeof record.routedModel === \"string\" &&\n\t\t(record.outcome === \"routed\" || record.outcome === \"escalated\" || record.outcome === \"failed\") &&\n\t\t(record.retryModel === undefined || typeof record.retryModel === \"string\") &&\n\t\t(record.intent === undefined || record.intent === \"research\" || record.intent === \"modify\")\n\t);\n}\n\nfunction formatDecision(decision: ModelRouterDecisionStatus): string {\n\tconst { tier, risk, reasonCode } = decision.route;\n\tlet outcomeText: string = decision.outcome;\n\tif (decision.outcome === \"escalated\" && decision.retryModel) {\n\t\toutcomeText = `escalated -> ${decision.retryModel}`;\n\t} else if (decision.outcome === \"failed\") {\n\t\toutcomeText = \"failed\";\n\t}\n\treturn `${tier}/${risk} -> ${decision.routedModel} (${reasonCode}, ${outcomeText})`;\n}\n\nexport function getRecentModelRouterDecisions(entries: SessionEntry[], limit = 3): ModelRouterDecisionStatus[] {\n\tconst decisions: ModelRouterDecisionStatus[] = [];\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== MODEL_ROUTER_DECISION_CUSTOM_TYPE) continue;\n\t\tif (!isModelRouterDecisionStatus(entry.data)) continue;\n\t\tdecisions.push(entry.data);\n\t}\n\treturn decisions.slice(-limit);\n}\n\nexport function formatModelRouterStatus(\n\tsettings: ModelRouterStatusSettings,\n\tlastDecision?: ModelRouterDecisionStatus,\n\tformatLabel: (label: string) => string = (label) => label,\n\trecentDecisions: ModelRouterDecisionStatus[] = [],\n\tlastSkipReason?: string,\n\tlatestIntent?: ModelRouterIntent,\n\tfitnessStatuses?: ModelRouterFitnessStatuses,\n\tfailoverStatus?: ModelRouterFailoverStatus,\n): string {\n\tconst effectiveLastDecision = lastSkipReason ? undefined : lastDecision;\n\tconst lines = [\n\t\t`${formatLabel(\"Status:\")} ${settings.enabled ? \"enabled\" : \"disabled\"}`,\n\t\tformatModelWithThinking(\n\t\t\tformatLabel(\"Cheap model:\"),\n\t\t\tsettings.cheapModel,\n\t\t\tsettings.cheapThinking,\n\t\t\tsettings.fitnessGate ? fitnessStatuses?.cheap : undefined,\n\t\t),\n\t\tformatModelWithThinking(\n\t\t\tformatLabel(\"Medium model:\"),\n\t\t\tsettings.mediumModel,\n\t\t\tsettings.mediumThinking,\n\t\t\tsettings.fitnessGate ? fitnessStatuses?.medium : undefined,\n\t\t),\n\t\tformatModelWithThinking(\n\t\t\tformatLabel(\"Expensive model:\"),\n\t\t\tsettings.expensiveModel,\n\t\t\tsettings.expensiveThinking,\n\t\t\tsettings.fitnessGate ? fitnessStatuses?.expensive : undefined,\n\t\t),\n\t\tformatModelWithThinking(formatLabel(\"Executor model:\"), settings.executorModel, settings.executorThinking),\n\t\tformatModelWithThinking(formatLabel(\"Judge model:\"), settings.judgeModel, settings.judgeThinking),\n\t\t`${formatLabel(\"Learning model:\")} ${settings.learningModel ?? \"active\"}`,\n\t];\n\tif (!settings.enabled) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} inactive (disabled)`);\n\t} else if (lastSkipReason) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} skipped (${lastSkipReason})`);\n\t} else if (effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Routing:\")} active`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Routing:\")} waiting for prompt`);\n\t}\n\tlines.push(`${formatLabel(\"Latest intent:\")} ${latestIntent ?? \"none\"}`);\n\tif (!effectiveLastDecision) {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} none`);\n\t} else {\n\t\tlines.push(`${formatLabel(\"Last decision:\")} ${formatDecision(effectiveLastDecision)}`);\n\t}\n\tif (failoverStatus?.exhausted.length) {\n\t\tlines.push(`${formatLabel(\"Exhausted models:\")} ${failoverStatus.exhausted.join(\", \")}`);\n\t}\n\tif (failoverStatus?.lastNotice) {\n\t\tlines.push(`${formatLabel(\"Last failover:\")} ${failoverStatus.lastNotice}`);\n\t}\n\tif (failoverStatus?.failureStats && failoverStatus.failureStats.total > 0) {\n\t\tconst { total, unknown } = failoverStatus.failureStats;\n\t\tlines.push(\n\t\t\t`${formatLabel(\"Provider failures:\")} ${total} provider failures this session (${unknown} unclassified — see failure corpus)`,\n\t\t);\n\t}\n\tif (recentDecisions.length > 0) {\n\t\tlines.push(formatLabel(\"Recent decisions:\"));\n\t\tfor (const decision of recentDecisions) {\n\t\t\tlines.push(`- ${formatDecision(decision)}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n"]}
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
export const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
2
|
+
const MODEL_ROUTER_INHERIT_THINKING_LABEL = "(inherit)";
|
|
3
|
+
function getThinkingLabel(level) {
|
|
4
|
+
return level ?? MODEL_ROUTER_INHERIT_THINKING_LABEL;
|
|
5
|
+
}
|
|
6
|
+
function formatModelWithThinking(label, model, level, fitness) {
|
|
7
|
+
const fitnessSuffix = fitness ? ` · fitness ${formatFitness(fitness)}` : "";
|
|
8
|
+
return `${label} ${model ?? "unset"} · thinking ${getThinkingLabel(level)}${fitnessSuffix}`;
|
|
9
|
+
}
|
|
10
|
+
function formatFitness(fitness) {
|
|
11
|
+
if (fitness.status === "fit")
|
|
12
|
+
return "fit";
|
|
13
|
+
if (fitness.status === "unprobed")
|
|
14
|
+
return "unprobed";
|
|
15
|
+
return `UNFIT (${fitness.lane} ${fitness.succeeded}/${fitness.total})`;
|
|
16
|
+
}
|
|
2
17
|
function isRouteDecision(value) {
|
|
3
18
|
if (!value || typeof value !== "object")
|
|
4
19
|
return false;
|
|
@@ -52,13 +67,15 @@ export function getRecentModelRouterDecisions(entries, limit = 3) {
|
|
|
52
67
|
}
|
|
53
68
|
return decisions.slice(-limit);
|
|
54
69
|
}
|
|
55
|
-
export function formatModelRouterStatus(settings, lastDecision, formatLabel = (label) => label, recentDecisions = [], lastSkipReason, latestIntent) {
|
|
70
|
+
export function formatModelRouterStatus(settings, lastDecision, formatLabel = (label) => label, recentDecisions = [], lastSkipReason, latestIntent, fitnessStatuses, failoverStatus) {
|
|
56
71
|
const effectiveLastDecision = lastSkipReason ? undefined : lastDecision;
|
|
57
72
|
const lines = [
|
|
58
73
|
`${formatLabel("Status:")} ${settings.enabled ? "enabled" : "disabled"}`,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
formatModelWithThinking(formatLabel("Cheap model:"), settings.cheapModel, settings.cheapThinking, settings.fitnessGate ? fitnessStatuses?.cheap : undefined),
|
|
75
|
+
formatModelWithThinking(formatLabel("Medium model:"), settings.mediumModel, settings.mediumThinking, settings.fitnessGate ? fitnessStatuses?.medium : undefined),
|
|
76
|
+
formatModelWithThinking(formatLabel("Expensive model:"), settings.expensiveModel, settings.expensiveThinking, settings.fitnessGate ? fitnessStatuses?.expensive : undefined),
|
|
77
|
+
formatModelWithThinking(formatLabel("Executor model:"), settings.executorModel, settings.executorThinking),
|
|
78
|
+
formatModelWithThinking(formatLabel("Judge model:"), settings.judgeModel, settings.judgeThinking),
|
|
62
79
|
`${formatLabel("Learning model:")} ${settings.learningModel ?? "active"}`,
|
|
63
80
|
];
|
|
64
81
|
if (!settings.enabled) {
|
|
@@ -80,6 +97,16 @@ export function formatModelRouterStatus(settings, lastDecision, formatLabel = (l
|
|
|
80
97
|
else {
|
|
81
98
|
lines.push(`${formatLabel("Last decision:")} ${formatDecision(effectiveLastDecision)}`);
|
|
82
99
|
}
|
|
100
|
+
if (failoverStatus?.exhausted.length) {
|
|
101
|
+
lines.push(`${formatLabel("Exhausted models:")} ${failoverStatus.exhausted.join(", ")}`);
|
|
102
|
+
}
|
|
103
|
+
if (failoverStatus?.lastNotice) {
|
|
104
|
+
lines.push(`${formatLabel("Last failover:")} ${failoverStatus.lastNotice}`);
|
|
105
|
+
}
|
|
106
|
+
if (failoverStatus?.failureStats && failoverStatus.failureStats.total > 0) {
|
|
107
|
+
const { total, unknown } = failoverStatus.failureStats;
|
|
108
|
+
lines.push(`${formatLabel("Provider failures:")} ${total} provider failures this session (${unknown} unclassified — see failure corpus)`);
|
|
109
|
+
}
|
|
83
110
|
if (recentDecisions.length > 0) {
|
|
84
111
|
lines.push(formatLabel("Recent decisions:"));
|
|
85
112
|
for (const decision of recentDecisions) {
|