@caupulican/pi-adaptative 0.80.80 → 0.80.82
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 +55 -0
- package/dist/core/agent-session.d.ts +15 -14
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +202 -9
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/cost/daily-usage.d.ts +23 -0
- package/dist/core/cost/daily-usage.d.ts.map +1 -0
- package/dist/core/cost/daily-usage.js +135 -0
- package/dist/core/cost/daily-usage.js.map +1 -0
- package/dist/core/model-router/config-diagnostics.d.ts +4 -0
- package/dist/core/model-router/config-diagnostics.d.ts.map +1 -0
- package/dist/core/model-router/config-diagnostics.js +26 -0
- package/dist/core/model-router/config-diagnostics.js.map +1 -0
- package/dist/core/model-router/intent-classifier.d.ts +3 -0
- package/dist/core/model-router/intent-classifier.d.ts.map +1 -0
- package/dist/core/model-router/intent-classifier.js +12 -0
- package/dist/core/model-router/intent-classifier.js.map +1 -0
- package/dist/core/model-router/session-buffer.d.ts +21 -0
- package/dist/core/model-router/session-buffer.d.ts.map +1 -0
- package/dist/core/model-router/session-buffer.js +20 -0
- package/dist/core/model-router/session-buffer.js.map +1 -0
- package/dist/core/model-router/status.d.ts +18 -0
- package/dist/core/model-router/status.d.ts.map +1 -0
- package/dist/core/model-router/status.js +67 -0
- package/dist/core/model-router/status.js.map +1 -0
- package/dist/core/model-router/tool-escalation.d.ts +9 -0
- package/dist/core/model-router/tool-escalation.d.ts.map +1 -0
- package/dist/core/model-router/tool-escalation.js +97 -0
- package/dist/core/model-router/tool-escalation.js.map +1 -0
- package/dist/core/profile-registry.d.ts +2 -1
- package/dist/core/profile-registry.d.ts.map +1 -1
- package/dist/core/profile-registry.js +15 -0
- package/dist/core/profile-registry.js.map +1 -1
- package/dist/core/resource-profile-equality.d.ts +5 -0
- package/dist/core/resource-profile-equality.d.ts.map +1 -0
- package/dist/core/resource-profile-equality.js +18 -0
- package/dist/core/resource-profile-equality.js.map +1 -0
- package/dist/core/settings-manager.d.ts +16 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +74 -0
- 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 +2 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/config-selector.js +3 -3
- package/dist/modes/interactive/components/config-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +6 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +4 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +102 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +96 -15
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/docs/resources.md +7 -1
- package/docs/settings.md +24 -2
- 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/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SessionEntry } from "../session-manager.ts";
|
|
2
|
+
export type DailyUsageWindow = {
|
|
3
|
+
startMs: number;
|
|
4
|
+
endMs: number;
|
|
5
|
+
};
|
|
6
|
+
export type DailyUsageTotals = {
|
|
7
|
+
ownCost: number;
|
|
8
|
+
spawnedCost: number;
|
|
9
|
+
totalCost: number;
|
|
10
|
+
input: number;
|
|
11
|
+
output: number;
|
|
12
|
+
cacheRead: number;
|
|
13
|
+
cacheWrite: number;
|
|
14
|
+
totalTokens: number;
|
|
15
|
+
sessions: number;
|
|
16
|
+
reports: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function aggregateDailyUsageFromEntries(entries: SessionEntry[], window: DailyUsageWindow): DailyUsageTotals;
|
|
19
|
+
export declare function aggregateDailyUsageFromSessionFiles(sessionDir: string, window: DailyUsageWindow): DailyUsageTotals;
|
|
20
|
+
export declare function aggregateDailyUsageFromSessionRoot(sessionRoot: string, window: DailyUsageWindow): DailyUsageTotals;
|
|
21
|
+
export declare function formatDailyUsageBreakdown(totals: DailyUsageTotals, formatLabel?: (label: string) => string): string;
|
|
22
|
+
export declare function getLocalDayWindow(now?: Date): DailyUsageWindow;
|
|
23
|
+
//# sourceMappingURL=daily-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daily-usage.d.ts","sourceRoot":"","sources":["../../../src/core/cost/daily-usage.ts"],"names":[],"mappings":"AAIA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE/E,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB,CAAC;AA8EF,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAKlH;AAED,wBAAgB,mCAAmC,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAclH;AAED,wBAAgB,kCAAkC,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAoBlH;AAED,wBAAgB,yBAAyB,CACxC,MAAM,EAAE,gBAAgB,EACxB,WAAW,GAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAyB,GACvD,MAAM,CAQR;AAED,wBAAgB,iBAAiB,CAAC,GAAG,OAAa,GAAG,gBAAgB,CAMpE","sourcesContent":["import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AssistantMessage, Usage } from \"@caupulican/pi-ai\";\nimport { SPAWNED_USAGE_CUSTOM_TYPE, type SpawnedUsageReport } from \"../agent-session.ts\";\nimport { loadEntriesFromFile, type SessionEntry } from \"../session-manager.ts\";\n\nexport type DailyUsageWindow = {\n\tstartMs: number;\n\tendMs: number;\n};\n\nexport type DailyUsageTotals = {\n\townCost: number;\n\tspawnedCost: number;\n\ttotalCost: number;\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\ttotalTokens: number;\n\tsessions: number;\n\treports: number;\n};\n\nfunction createZeroTotals(): DailyUsageTotals {\n\treturn {\n\t\townCost: 0,\n\t\tspawnedCost: 0,\n\t\ttotalCost: 0,\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t\ttotalTokens: 0,\n\t\tsessions: 0,\n\t\treports: 0,\n\t};\n}\n\nfunction isInsideWindow(timestamp: number, window: DailyUsageWindow): boolean {\n\treturn timestamp >= window.startMs && timestamp < window.endMs;\n}\n\nfunction addUsage(\n\ttotals: Pick<DailyUsageTotals, \"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\" | \"totalTokens\">,\n\tusage: Usage,\n): void {\n\ttotals.input += usage.input;\n\ttotals.output += usage.output;\n\ttotals.cacheRead += usage.cacheRead;\n\ttotals.cacheWrite += usage.cacheWrite;\n\ttotals.totalTokens += usage.totalTokens;\n}\n\nfunction addDailyUsageFromEntries(\n\ttotals: DailyUsageTotals,\n\tentries: SessionEntry[],\n\twindow: DailyUsageWindow,\n\tseenSpawnedReportIds: Set<string>,\n): boolean {\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (!isInsideWindow(Date.parse(entry.timestamp), window)) continue;\n\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tconst usage = (entry.message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\ttotals.ownCost += usage.cost.total;\n\t\t\taddUsage(totals, usage);\n\t\t\thasUsage = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type === \"custom\" && entry.customType === SPAWNED_USAGE_CUSTOM_TYPE) {\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\ttotals.spawnedCost += data.usage.cost.total;\n\t\t\ttotals.reports += 1;\n\t\t\taddUsage(totals, data.usage);\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage;\n}\n\nfunction finishTotals(totals: DailyUsageTotals): DailyUsageTotals {\n\ttotals.totalCost = totals.ownCost + totals.spawnedCost;\n\treturn totals;\n}\n\nfunction shouldReadSessionFile(filePath: string, window: DailyUsageWindow): boolean {\n\ttry {\n\t\treturn statSync(filePath).mtimeMs >= window.startMs;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function aggregateDailyUsageFromEntries(entries: SessionEntry[], window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tconst hasUsage = addDailyUsageFromEntries(totals, entries, window, new Set());\n\ttotals.sessions = hasUsage ? 1 : 0;\n\treturn finishTotals(totals);\n}\n\nexport function aggregateDailyUsageFromSessionFiles(sessionDir: string, window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tif (!sessionDir || !existsSync(sessionDir)) return totals;\n\tconst seenSpawnedReportIds = new Set<string>();\n\tfor (const name of readdirSync(sessionDir)) {\n\t\tif (!name.endsWith(\".jsonl\")) continue;\n\t\tconst filePath = join(sessionDir, name);\n\t\tif (!shouldReadSessionFile(filePath, window)) continue;\n\t\tconst entries = loadEntriesFromFile(filePath).filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t\tif (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {\n\t\t\ttotals.sessions += 1;\n\t\t}\n\t}\n\treturn finishTotals(totals);\n}\n\nexport function aggregateDailyUsageFromSessionRoot(sessionRoot: string, window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tif (!sessionRoot || !existsSync(sessionRoot)) return totals;\n\tconst seenSpawnedReportIds = new Set<string>();\n\tfor (const name of readdirSync(sessionRoot)) {\n\t\tconst dir = join(sessionRoot, name);\n\t\tif (!existsSync(dir) || !statSync(dir).isDirectory()) continue;\n\t\tfor (const fileName of readdirSync(dir)) {\n\t\t\tif (!fileName.endsWith(\".jsonl\")) continue;\n\t\t\tconst filePath = join(dir, fileName);\n\t\t\tif (!shouldReadSessionFile(filePath, window)) continue;\n\t\t\tconst entries = loadEntriesFromFile(filePath).filter(\n\t\t\t\t(entry): entry is SessionEntry => entry.type !== \"session\",\n\t\t\t);\n\t\t\tif (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {\n\t\t\t\ttotals.sessions += 1;\n\t\t\t}\n\t\t}\n\t}\n\treturn finishTotals(totals);\n}\n\nexport function formatDailyUsageBreakdown(\n\ttotals: DailyUsageTotals,\n\tformatLabel: (label: string) => string = (label) => label,\n): string {\n\treturn [\n\t\t`${formatLabel(\"Today:\")} $${totals.totalCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Own/session messages:\")} $${totals.ownCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Spawned/background reports:\")} $${totals.spawnedCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Sessions scanned:\")} ${totals.sessions}`,\n\t\t`${formatLabel(\"Spawned/background report count:\")} ${totals.reports}`,\n\t].join(\"\\n\");\n}\n\nexport function getLocalDayWindow(now = new Date()): DailyUsageWindow {\n\tconst start = new Date(now);\n\tstart.setHours(0, 0, 0, 0);\n\tconst end = new Date(start);\n\tend.setDate(end.getDate() + 1);\n\treturn { startMs: start.getTime(), endMs: end.getTime() };\n}\n"]}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { SPAWNED_USAGE_CUSTOM_TYPE } from "../agent-session.js";
|
|
4
|
+
import { loadEntriesFromFile } from "../session-manager.js";
|
|
5
|
+
function createZeroTotals() {
|
|
6
|
+
return {
|
|
7
|
+
ownCost: 0,
|
|
8
|
+
spawnedCost: 0,
|
|
9
|
+
totalCost: 0,
|
|
10
|
+
input: 0,
|
|
11
|
+
output: 0,
|
|
12
|
+
cacheRead: 0,
|
|
13
|
+
cacheWrite: 0,
|
|
14
|
+
totalTokens: 0,
|
|
15
|
+
sessions: 0,
|
|
16
|
+
reports: 0,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function isInsideWindow(timestamp, window) {
|
|
20
|
+
return timestamp >= window.startMs && timestamp < window.endMs;
|
|
21
|
+
}
|
|
22
|
+
function addUsage(totals, usage) {
|
|
23
|
+
totals.input += usage.input;
|
|
24
|
+
totals.output += usage.output;
|
|
25
|
+
totals.cacheRead += usage.cacheRead;
|
|
26
|
+
totals.cacheWrite += usage.cacheWrite;
|
|
27
|
+
totals.totalTokens += usage.totalTokens;
|
|
28
|
+
}
|
|
29
|
+
function addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds) {
|
|
30
|
+
let hasUsage = false;
|
|
31
|
+
for (const entry of entries) {
|
|
32
|
+
if (!isInsideWindow(Date.parse(entry.timestamp), window))
|
|
33
|
+
continue;
|
|
34
|
+
if (entry.type === "message" && entry.message.role === "assistant") {
|
|
35
|
+
const usage = entry.message.usage;
|
|
36
|
+
if (!usage)
|
|
37
|
+
continue;
|
|
38
|
+
totals.ownCost += usage.cost.total;
|
|
39
|
+
addUsage(totals, usage);
|
|
40
|
+
hasUsage = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (entry.type === "custom" && entry.customType === SPAWNED_USAGE_CUSTOM_TYPE) {
|
|
44
|
+
const data = entry.data;
|
|
45
|
+
if (!data?.usage)
|
|
46
|
+
continue;
|
|
47
|
+
if (data.reportId) {
|
|
48
|
+
if (seenSpawnedReportIds.has(data.reportId))
|
|
49
|
+
continue;
|
|
50
|
+
seenSpawnedReportIds.add(data.reportId);
|
|
51
|
+
}
|
|
52
|
+
totals.spawnedCost += data.usage.cost.total;
|
|
53
|
+
totals.reports += 1;
|
|
54
|
+
addUsage(totals, data.usage);
|
|
55
|
+
hasUsage = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return hasUsage;
|
|
59
|
+
}
|
|
60
|
+
function finishTotals(totals) {
|
|
61
|
+
totals.totalCost = totals.ownCost + totals.spawnedCost;
|
|
62
|
+
return totals;
|
|
63
|
+
}
|
|
64
|
+
function shouldReadSessionFile(filePath, window) {
|
|
65
|
+
try {
|
|
66
|
+
return statSync(filePath).mtimeMs >= window.startMs;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function aggregateDailyUsageFromEntries(entries, window) {
|
|
73
|
+
const totals = createZeroTotals();
|
|
74
|
+
const hasUsage = addDailyUsageFromEntries(totals, entries, window, new Set());
|
|
75
|
+
totals.sessions = hasUsage ? 1 : 0;
|
|
76
|
+
return finishTotals(totals);
|
|
77
|
+
}
|
|
78
|
+
export function aggregateDailyUsageFromSessionFiles(sessionDir, window) {
|
|
79
|
+
const totals = createZeroTotals();
|
|
80
|
+
if (!sessionDir || !existsSync(sessionDir))
|
|
81
|
+
return totals;
|
|
82
|
+
const seenSpawnedReportIds = new Set();
|
|
83
|
+
for (const name of readdirSync(sessionDir)) {
|
|
84
|
+
if (!name.endsWith(".jsonl"))
|
|
85
|
+
continue;
|
|
86
|
+
const filePath = join(sessionDir, name);
|
|
87
|
+
if (!shouldReadSessionFile(filePath, window))
|
|
88
|
+
continue;
|
|
89
|
+
const entries = loadEntriesFromFile(filePath).filter((entry) => entry.type !== "session");
|
|
90
|
+
if (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {
|
|
91
|
+
totals.sessions += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return finishTotals(totals);
|
|
95
|
+
}
|
|
96
|
+
export function aggregateDailyUsageFromSessionRoot(sessionRoot, window) {
|
|
97
|
+
const totals = createZeroTotals();
|
|
98
|
+
if (!sessionRoot || !existsSync(sessionRoot))
|
|
99
|
+
return totals;
|
|
100
|
+
const seenSpawnedReportIds = new Set();
|
|
101
|
+
for (const name of readdirSync(sessionRoot)) {
|
|
102
|
+
const dir = join(sessionRoot, name);
|
|
103
|
+
if (!existsSync(dir) || !statSync(dir).isDirectory())
|
|
104
|
+
continue;
|
|
105
|
+
for (const fileName of readdirSync(dir)) {
|
|
106
|
+
if (!fileName.endsWith(".jsonl"))
|
|
107
|
+
continue;
|
|
108
|
+
const filePath = join(dir, fileName);
|
|
109
|
+
if (!shouldReadSessionFile(filePath, window))
|
|
110
|
+
continue;
|
|
111
|
+
const entries = loadEntriesFromFile(filePath).filter((entry) => entry.type !== "session");
|
|
112
|
+
if (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {
|
|
113
|
+
totals.sessions += 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return finishTotals(totals);
|
|
118
|
+
}
|
|
119
|
+
export function formatDailyUsageBreakdown(totals, formatLabel = (label) => label) {
|
|
120
|
+
return [
|
|
121
|
+
`${formatLabel("Today:")} $${totals.totalCost.toFixed(4)}`,
|
|
122
|
+
`${formatLabel("Own/session messages:")} $${totals.ownCost.toFixed(4)}`,
|
|
123
|
+
`${formatLabel("Spawned/background reports:")} $${totals.spawnedCost.toFixed(4)}`,
|
|
124
|
+
`${formatLabel("Sessions scanned:")} ${totals.sessions}`,
|
|
125
|
+
`${formatLabel("Spawned/background report count:")} ${totals.reports}`,
|
|
126
|
+
].join("\n");
|
|
127
|
+
}
|
|
128
|
+
export function getLocalDayWindow(now = new Date()) {
|
|
129
|
+
const start = new Date(now);
|
|
130
|
+
start.setHours(0, 0, 0, 0);
|
|
131
|
+
const end = new Date(start);
|
|
132
|
+
end.setDate(end.getDate() + 1);
|
|
133
|
+
return { startMs: start.getTime(), endMs: end.getTime() };
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=daily-usage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daily-usage.js","sourceRoot":"","sources":["../../../src/core/cost/daily-usage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,yBAAyB,EAA2B,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAqB,MAAM,uBAAuB,CAAC;AAoB/E,SAAS,gBAAgB,GAAqB;IAC7C,OAAO;QACN,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,CAAC;QACd,SAAS,EAAE,CAAC;QACZ,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,CAAC;QACZ,UAAU,EAAE,CAAC;QACb,WAAW,EAAE,CAAC;QACd,QAAQ,EAAE,CAAC;QACX,OAAO,EAAE,CAAC;KACV,CAAC;AAAA,CACF;AAED,SAAS,cAAc,CAAC,SAAiB,EAAE,MAAwB,EAAW;IAC7E,OAAO,SAAS,IAAI,MAAM,CAAC,OAAO,IAAI,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC;AAAA,CAC/D;AAED,SAAS,QAAQ,CAChB,MAA+F,EAC/F,KAAY,EACL;IACP,MAAM,CAAC,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;IAC5B,MAAM,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC;IAC9B,MAAM,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;IACpC,MAAM,CAAC,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;IACtC,MAAM,CAAC,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC;AAAA,CACxC;AAED,SAAS,wBAAwB,CAChC,MAAwB,EACxB,OAAuB,EACvB,MAAwB,EACxB,oBAAiC,EACvB;IACV,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;YAAE,SAAS;QACnE,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACpE,MAAM,KAAK,GAAI,KAAK,CAAC,OAA4B,CAAC,KAAK,CAAC;YACxD,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,MAAM,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YACnC,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YACxB,QAAQ,GAAG,IAAI,CAAC;YAChB,SAAS;QACV,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,yBAAyB,EAAE,CAAC;YAC/E,MAAM,IAAI,GAAG,KAAK,CAAC,IAAsC,CAAC;YAC1D,IAAI,CAAC,IAAI,EAAE,KAAK;gBAAE,SAAS;YAC3B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnB,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACtD,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzC,CAAC;YACD,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC5C,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC;YACpB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,QAAQ,GAAG,IAAI,CAAC;QACjB,CAAC;IACF,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,SAAS,YAAY,CAAC,MAAwB,EAAoB;IACjE,MAAM,CAAC,SAAS,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC;IACvD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,qBAAqB,CAAC,QAAgB,EAAE,MAAwB,EAAW;IACnF,IAAI,CAAC;QACJ,OAAO,QAAQ,CAAC,QAAQ,CAAC,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AAAA,CACD;AAED,MAAM,UAAU,8BAA8B,CAAC,OAAuB,EAAE,MAAwB,EAAoB;IACnH,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,MAAM,QAAQ,GAAG,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;IAC9E,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,mCAAmC,CAAC,UAAkB,EAAE,MAAwB,EAAoB;IACnH,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,MAAM,CAAC;IAC1D,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,SAAS;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC;YAAE,SAAS;QACvD,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAyB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;QACjH,IAAI,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,CAAC;YAC7E,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACtB,CAAC;IACF,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,kCAAkC,CAAC,WAAmB,EAAE,MAAwB,EAAoB;IACnH,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,IAAI,CAAC,WAAW,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5D,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE;YAAE,SAAS;QAC/D,KAAK,MAAM,QAAQ,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAAE,SAAS;YAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;YACrC,IAAI,CAAC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAAE,SAAS;YACvD,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC,MAAM,CACnD,CAAC,KAAK,EAAyB,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAC1D,CAAC;YACF,IAAI,wBAAwB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,oBAAoB,CAAC,EAAE,CAAC;gBAC7E,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;YACtB,CAAC;QACF,CAAC;IACF,CAAC;IACD,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,yBAAyB,CACxC,MAAwB,EACxB,WAAW,GAA8B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EAChD;IACT,OAAO;QACN,GAAG,WAAW,CAAC,QAAQ,CAAC,KAAK,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QAC1D,GAAG,WAAW,CAAC,uBAAuB,CAAC,KAAK,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACvE,GAAG,WAAW,CAAC,6BAA6B,CAAC,KAAK,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;QACjF,GAAG,WAAW,CAAC,mBAAmB,CAAC,IAAI,MAAM,CAAC,QAAQ,EAAE;QACxD,GAAG,WAAW,CAAC,kCAAkC,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE;KACtE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACb;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,EAAoB;IACrE,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5B,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5B,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;AAAA,CAC1D","sourcesContent":["import { existsSync, readdirSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { AssistantMessage, Usage } from \"@caupulican/pi-ai\";\nimport { SPAWNED_USAGE_CUSTOM_TYPE, type SpawnedUsageReport } from \"../agent-session.ts\";\nimport { loadEntriesFromFile, type SessionEntry } from \"../session-manager.ts\";\n\nexport type DailyUsageWindow = {\n\tstartMs: number;\n\tendMs: number;\n};\n\nexport type DailyUsageTotals = {\n\townCost: number;\n\tspawnedCost: number;\n\ttotalCost: number;\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\ttotalTokens: number;\n\tsessions: number;\n\treports: number;\n};\n\nfunction createZeroTotals(): DailyUsageTotals {\n\treturn {\n\t\townCost: 0,\n\t\tspawnedCost: 0,\n\t\ttotalCost: 0,\n\t\tinput: 0,\n\t\toutput: 0,\n\t\tcacheRead: 0,\n\t\tcacheWrite: 0,\n\t\ttotalTokens: 0,\n\t\tsessions: 0,\n\t\treports: 0,\n\t};\n}\n\nfunction isInsideWindow(timestamp: number, window: DailyUsageWindow): boolean {\n\treturn timestamp >= window.startMs && timestamp < window.endMs;\n}\n\nfunction addUsage(\n\ttotals: Pick<DailyUsageTotals, \"input\" | \"output\" | \"cacheRead\" | \"cacheWrite\" | \"totalTokens\">,\n\tusage: Usage,\n): void {\n\ttotals.input += usage.input;\n\ttotals.output += usage.output;\n\ttotals.cacheRead += usage.cacheRead;\n\ttotals.cacheWrite += usage.cacheWrite;\n\ttotals.totalTokens += usage.totalTokens;\n}\n\nfunction addDailyUsageFromEntries(\n\ttotals: DailyUsageTotals,\n\tentries: SessionEntry[],\n\twindow: DailyUsageWindow,\n\tseenSpawnedReportIds: Set<string>,\n): boolean {\n\tlet hasUsage = false;\n\tfor (const entry of entries) {\n\t\tif (!isInsideWindow(Date.parse(entry.timestamp), window)) continue;\n\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\tconst usage = (entry.message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\ttotals.ownCost += usage.cost.total;\n\t\t\taddUsage(totals, usage);\n\t\t\thasUsage = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (entry.type === \"custom\" && entry.customType === SPAWNED_USAGE_CUSTOM_TYPE) {\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\ttotals.spawnedCost += data.usage.cost.total;\n\t\t\ttotals.reports += 1;\n\t\t\taddUsage(totals, data.usage);\n\t\t\thasUsage = true;\n\t\t}\n\t}\n\treturn hasUsage;\n}\n\nfunction finishTotals(totals: DailyUsageTotals): DailyUsageTotals {\n\ttotals.totalCost = totals.ownCost + totals.spawnedCost;\n\treturn totals;\n}\n\nfunction shouldReadSessionFile(filePath: string, window: DailyUsageWindow): boolean {\n\ttry {\n\t\treturn statSync(filePath).mtimeMs >= window.startMs;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nexport function aggregateDailyUsageFromEntries(entries: SessionEntry[], window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tconst hasUsage = addDailyUsageFromEntries(totals, entries, window, new Set());\n\ttotals.sessions = hasUsage ? 1 : 0;\n\treturn finishTotals(totals);\n}\n\nexport function aggregateDailyUsageFromSessionFiles(sessionDir: string, window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tif (!sessionDir || !existsSync(sessionDir)) return totals;\n\tconst seenSpawnedReportIds = new Set<string>();\n\tfor (const name of readdirSync(sessionDir)) {\n\t\tif (!name.endsWith(\".jsonl\")) continue;\n\t\tconst filePath = join(sessionDir, name);\n\t\tif (!shouldReadSessionFile(filePath, window)) continue;\n\t\tconst entries = loadEntriesFromFile(filePath).filter((entry): entry is SessionEntry => entry.type !== \"session\");\n\t\tif (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {\n\t\t\ttotals.sessions += 1;\n\t\t}\n\t}\n\treturn finishTotals(totals);\n}\n\nexport function aggregateDailyUsageFromSessionRoot(sessionRoot: string, window: DailyUsageWindow): DailyUsageTotals {\n\tconst totals = createZeroTotals();\n\tif (!sessionRoot || !existsSync(sessionRoot)) return totals;\n\tconst seenSpawnedReportIds = new Set<string>();\n\tfor (const name of readdirSync(sessionRoot)) {\n\t\tconst dir = join(sessionRoot, name);\n\t\tif (!existsSync(dir) || !statSync(dir).isDirectory()) continue;\n\t\tfor (const fileName of readdirSync(dir)) {\n\t\t\tif (!fileName.endsWith(\".jsonl\")) continue;\n\t\t\tconst filePath = join(dir, fileName);\n\t\t\tif (!shouldReadSessionFile(filePath, window)) continue;\n\t\t\tconst entries = loadEntriesFromFile(filePath).filter(\n\t\t\t\t(entry): entry is SessionEntry => entry.type !== \"session\",\n\t\t\t);\n\t\t\tif (addDailyUsageFromEntries(totals, entries, window, seenSpawnedReportIds)) {\n\t\t\t\ttotals.sessions += 1;\n\t\t\t}\n\t\t}\n\t}\n\treturn finishTotals(totals);\n}\n\nexport function formatDailyUsageBreakdown(\n\ttotals: DailyUsageTotals,\n\tformatLabel: (label: string) => string = (label) => label,\n): string {\n\treturn [\n\t\t`${formatLabel(\"Today:\")} $${totals.totalCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Own/session messages:\")} $${totals.ownCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Spawned/background reports:\")} $${totals.spawnedCost.toFixed(4)}`,\n\t\t`${formatLabel(\"Sessions scanned:\")} ${totals.sessions}`,\n\t\t`${formatLabel(\"Spawned/background report count:\")} ${totals.reports}`,\n\t].join(\"\\n\");\n}\n\nexport function getLocalDayWindow(now = new Date()): DailyUsageWindow {\n\tconst start = new Date(now);\n\tstart.setHours(0, 0, 0, 0);\n\tconst end = new Date(start);\n\tend.setDate(end.getDate() + 1);\n\treturn { startMs: start.getTime(), endMs: end.getTime() };\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ModelRegistry } from "../model-registry.ts";
|
|
2
|
+
import type { ModelRouterStatusSettings } from "./status.ts";
|
|
3
|
+
export declare function collectModelRouterConfigDiagnostics(settings: ModelRouterStatusSettings, modelRegistry: ModelRegistry): string[];
|
|
4
|
+
//# sourceMappingURL=config-diagnostics.d.ts.map
|
|
@@ -0,0 +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;AAE1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAyB7D,wBAAgB,mCAAmC,CAClD,QAAQ,EAAE,yBAAyB,EACnC,aAAa,EAAE,aAAa,GAC1B,MAAM,EAAE,CAgBV","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"../model-registry.ts\";\nimport { resolveCliModel } from \"../model-resolver.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\" | \"expensive model\",\n\tsettingKey: \"modelRouter.cheapModel\" | \"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\nexport function collectModelRouterConfigDiagnostics(\n\tsettings: ModelRouterStatusSettings,\n\tmodelRegistry: ModelRegistry,\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\"expensive model\",\n\t\t\t\"modelRouter.expensiveModel\",\n\t\t\tsettings.expensiveModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t];\n}\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { resolveCliModel } from "../model-resolver.js";
|
|
2
|
+
function formatModel(model) {
|
|
3
|
+
return `${model.provider}/${model.id}`;
|
|
4
|
+
}
|
|
5
|
+
function collectModelRouterModelDiagnostics(label, settingKey, modelPattern, modelRegistry) {
|
|
6
|
+
if (!modelPattern) {
|
|
7
|
+
return [`Model router ${label} is unset; configure ${settingKey} or disable modelRouter.enabled.`];
|
|
8
|
+
}
|
|
9
|
+
const resolved = resolveCliModel({ cliModel: modelPattern, modelRegistry });
|
|
10
|
+
if (!resolved.model) {
|
|
11
|
+
return [`Model router ${label} is unresolved: ${modelPattern}.`];
|
|
12
|
+
}
|
|
13
|
+
if (!modelRegistry.hasConfiguredAuth(resolved.model)) {
|
|
14
|
+
return [`Model router ${label} is missing auth: ${formatModel(resolved.model)}.`];
|
|
15
|
+
}
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
export function collectModelRouterConfigDiagnostics(settings, modelRegistry) {
|
|
19
|
+
if (!settings.enabled)
|
|
20
|
+
return [];
|
|
21
|
+
return [
|
|
22
|
+
...collectModelRouterModelDiagnostics("cheap model", "modelRouter.cheapModel", settings.cheapModel, modelRegistry),
|
|
23
|
+
...collectModelRouterModelDiagnostics("expensive model", "modelRouter.expensiveModel", settings.expensiveModel, modelRegistry),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=config-diagnostics.js.map
|
|
@@ -0,0 +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;AAGvD,SAAS,WAAW,CAAC,KAAiB,EAAU;IAC/C,OAAO,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,EAAE,EAAE,CAAC;AAAA,CACvC;AAED,SAAS,kCAAkC,CAC1C,KAAwC,EACxC,UAAmE,EACnE,YAAgC,EAChC,aAA4B,EACjB;IACX,IAAI,CAAC,YAAY,EAAE,CAAC;QACnB,OAAO,CAAC,gBAAgB,KAAK,wBAAwB,UAAU,kCAAkC,CAAC,CAAC;IACpG,CAAC;IACD,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACrB,OAAO,CAAC,gBAAgB,KAAK,mBAAmB,YAAY,GAAG,CAAC,CAAC;IAClE,CAAC;IACD,IAAI,CAAC,aAAa,CAAC,iBAAiB,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACtD,OAAO,CAAC,gBAAgB,KAAK,qBAAqB,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,EAAE,CAAC;AAAA,CACV;AAED,MAAM,UAAU,mCAAmC,CAClD,QAAmC,EACnC,aAA4B,EACjB;IACX,IAAI,CAAC,QAAQ,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO;QACN,GAAG,kCAAkC,CACpC,aAAa,EACb,wBAAwB,EACxB,QAAQ,CAAC,UAAU,EACnB,aAAa,CACb;QACD,GAAG,kCAAkC,CACpC,iBAAiB,EACjB,4BAA4B,EAC5B,QAAQ,CAAC,cAAc,EACvB,aAAa,CACb;KACD,CAAC;AAAA,CACF","sourcesContent":["import type { Model } from \"@caupulican/pi-ai\";\nimport type { ModelRegistry } from \"../model-registry.ts\";\nimport { resolveCliModel } from \"../model-resolver.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\" | \"expensive model\",\n\tsettingKey: \"modelRouter.cheapModel\" | \"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\nexport function collectModelRouterConfigDiagnostics(\n\tsettings: ModelRouterStatusSettings,\n\tmodelRegistry: ModelRegistry,\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\"expensive model\",\n\t\t\t\"modelRouter.expensiveModel\",\n\t\t\tsettings.expensiveModel,\n\t\t\tmodelRegistry,\n\t\t),\n\t];\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-classifier.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/intent-classifier.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,CAAC;AAUtD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,iBAAiB,CAK3E","sourcesContent":["export type ModelRouterIntent = \"research\" | \"modify\";\n\nconst MODIFY_INTENT_RE =\n\t/\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\\b|\\b(npm|pnpm|yarn|bun|git|gh|pytest|vitest|tsc|biome)\\b|[;&|]\\s*\\w+/i;\n\nconst EXPLICIT_MODIFY_REQUEST_RE =\n\t/^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+.*\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\\b/i;\n\nconst READ_ONLY_QUESTION_RE = /^(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show)\\b/i;\n\nexport function classifyModelRouterIntent(prompt: string): ModelRouterIntent {\n\tconst text = prompt.trim();\n\tif (EXPLICIT_MODIFY_REQUEST_RE.test(text)) return \"modify\";\n\tif (READ_ONLY_QUESTION_RE.test(text)) return \"research\";\n\treturn MODIFY_INTENT_RE.test(text) ? \"modify\" : \"research\";\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MODIFY_INTENT_RE = /\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\b|\b(npm|pnpm|yarn|bun|git|gh|pytest|vitest|tsc|biome)\b|[;&|]\s*\w+/i;
|
|
2
|
+
const EXPLICIT_MODIFY_REQUEST_RE = /^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\s+.*\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\b/i;
|
|
3
|
+
const READ_ONLY_QUESTION_RE = /^(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show)\b/i;
|
|
4
|
+
export function classifyModelRouterIntent(prompt) {
|
|
5
|
+
const text = prompt.trim();
|
|
6
|
+
if (EXPLICIT_MODIFY_REQUEST_RE.test(text))
|
|
7
|
+
return "modify";
|
|
8
|
+
if (READ_ONLY_QUESTION_RE.test(text))
|
|
9
|
+
return "research";
|
|
10
|
+
return MODIFY_INTENT_RE.test(text) ? "modify" : "research";
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=intent-classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent-classifier.js","sourceRoot":"","sources":["../../../src/core/model-router/intent-classifier.ts"],"names":[],"mappings":"AAEA,MAAM,gBAAgB,GACrB,6NAA6N,CAAC;AAE/N,MAAM,0BAA0B,GAC/B,wPAAwP,CAAC;AAE1P,MAAM,qBAAqB,GAAG,wFAAwF,CAAC;AAEvH,MAAM,UAAU,yBAAyB,CAAC,MAAc,EAAqB;IAC5E,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC3D,IAAI,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAC;IACxD,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC;AAAA,CAC3D","sourcesContent":["export type ModelRouterIntent = \"research\" | \"modify\";\n\nconst MODIFY_INTENT_RE =\n\t/\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\\b|\\b(npm|pnpm|yarn|bun|git|gh|pytest|vitest|tsc|biome)\\b|[;&|]\\s*\\w+/i;\n\nconst EXPLICIT_MODIFY_REQUEST_RE =\n\t/^(?:can you|could you|please|pls|go ahead and|let'?s|i need you to|we need to|you should)\\s+.*\\b(add|apply|build|change|commit|create|delete|edit|fix|generate|implement|install|modify|patch|refactor|remove|rename|replace|run|test|update|write)\\b/i;\n\nconst READ_ONLY_QUESTION_RE = /^(?:how|what|why|when|where|which|who|explain|summarize|compare|describe|list|show)\\b/i;\n\nexport function classifyModelRouterIntent(prompt: string): ModelRouterIntent {\n\tconst text = prompt.trim();\n\tif (EXPLICIT_MODIFY_REQUEST_RE.test(text)) return \"modify\";\n\tif (READ_ONLY_QUESTION_RE.test(text)) return \"research\";\n\treturn MODIFY_INTENT_RE.test(text) ? \"modify\" : \"research\";\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { AgentMessage } from "@caupulican/pi-agent-core";
|
|
2
|
+
import type { ImageContent, Message, TextContent } from "@caupulican/pi-ai";
|
|
3
|
+
export type ModelRouterBufferedSessionMessage = {
|
|
4
|
+
kind: "message";
|
|
5
|
+
message: Message;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "custom";
|
|
8
|
+
message: Extract<AgentMessage, {
|
|
9
|
+
role: "custom";
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
export type ModelRouterSessionBuffer = {
|
|
13
|
+
messages: ModelRouterBufferedSessionMessage[];
|
|
14
|
+
};
|
|
15
|
+
export declare function createModelRouterSessionBuffer(): ModelRouterSessionBuffer;
|
|
16
|
+
export declare function bufferModelRouterSessionMessage(buffer: ModelRouterSessionBuffer, message: Message): void;
|
|
17
|
+
export declare function bufferModelRouterSessionCustomMessage(buffer: ModelRouterSessionBuffer, message: Extract<AgentMessage, {
|
|
18
|
+
role: "custom";
|
|
19
|
+
}>): void;
|
|
20
|
+
export declare function flushModelRouterSessionBuffer(buffer: ModelRouterSessionBuffer, appendMessage: (message: Message) => void, appendCustomMessageEntry: (customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details?: unknown) => void): void;
|
|
21
|
+
//# sourceMappingURL=session-buffer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-buffer.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/session-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE5E,MAAM,MAAM,iCAAiC,GAC1C;IACA,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAChB,GACD;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,OAAO,CAAC,YAAY,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;CAClD,CAAC;AAEL,MAAM,MAAM,wBAAwB,GAAG;IACtC,QAAQ,EAAE,iCAAiC,EAAE,CAAC;CAC9C,CAAC;AAEF,wBAAgB,8BAA8B,IAAI,wBAAwB,CAEzE;AAED,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,wBAAwB,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAExG;AAED,wBAAgB,qCAAqC,CACpD,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,OAAO,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAChD,IAAI,CAEN;AAED,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,wBAAwB,EAChC,aAAa,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,EACzC,wBAAwB,EAAE,CACzB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,GAAG,CAAC,WAAW,GAAG,YAAY,CAAC,EAAE,EAChD,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE,OAAO,KACb,IAAI,GACP,IAAI,CAaN","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ImageContent, Message, TextContent } from \"@caupulican/pi-ai\";\n\nexport type ModelRouterBufferedSessionMessage =\n\t| {\n\t\t\tkind: \"message\";\n\t\t\tmessage: Message;\n\t }\n\t| {\n\t\t\tkind: \"custom\";\n\t\t\tmessage: Extract<AgentMessage, { role: \"custom\" }>;\n\t };\n\nexport type ModelRouterSessionBuffer = {\n\tmessages: ModelRouterBufferedSessionMessage[];\n};\n\nexport function createModelRouterSessionBuffer(): ModelRouterSessionBuffer {\n\treturn { messages: [] };\n}\n\nexport function bufferModelRouterSessionMessage(buffer: ModelRouterSessionBuffer, message: Message): void {\n\tbuffer.messages.push({ kind: \"message\", message });\n}\n\nexport function bufferModelRouterSessionCustomMessage(\n\tbuffer: ModelRouterSessionBuffer,\n\tmessage: Extract<AgentMessage, { role: \"custom\" }>,\n): void {\n\tbuffer.messages.push({ kind: \"custom\", message });\n}\n\nexport function flushModelRouterSessionBuffer(\n\tbuffer: ModelRouterSessionBuffer,\n\tappendMessage: (message: Message) => void,\n\tappendCustomMessageEntry: (\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: unknown,\n\t) => void,\n): void {\n\tfor (const entry of buffer.messages) {\n\t\tif (entry.kind === \"message\") {\n\t\t\tappendMessage(entry.message);\n\t\t} else {\n\t\t\tappendCustomMessageEntry(\n\t\t\t\tentry.message.customType,\n\t\t\t\tentry.message.content,\n\t\t\t\tentry.message.display,\n\t\t\t\tentry.message.details,\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function createModelRouterSessionBuffer() {
|
|
2
|
+
return { messages: [] };
|
|
3
|
+
}
|
|
4
|
+
export function bufferModelRouterSessionMessage(buffer, message) {
|
|
5
|
+
buffer.messages.push({ kind: "message", message });
|
|
6
|
+
}
|
|
7
|
+
export function bufferModelRouterSessionCustomMessage(buffer, message) {
|
|
8
|
+
buffer.messages.push({ kind: "custom", message });
|
|
9
|
+
}
|
|
10
|
+
export function flushModelRouterSessionBuffer(buffer, appendMessage, appendCustomMessageEntry) {
|
|
11
|
+
for (const entry of buffer.messages) {
|
|
12
|
+
if (entry.kind === "message") {
|
|
13
|
+
appendMessage(entry.message);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
appendCustomMessageEntry(entry.message.customType, entry.message.content, entry.message.display, entry.message.details);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=session-buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-buffer.js","sourceRoot":"","sources":["../../../src/core/model-router/session-buffer.ts"],"names":[],"mappings":"AAiBA,MAAM,UAAU,8BAA8B,GAA6B;IAC1E,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAAA,CACxB;AAED,MAAM,UAAU,+BAA+B,CAAC,MAAgC,EAAE,OAAgB,EAAQ;IACzG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,CACnD;AAED,MAAM,UAAU,qCAAqC,CACpD,MAAgC,EAChC,OAAkD,EAC3C;IACP,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,CAClD;AAED,MAAM,UAAU,6BAA6B,CAC5C,MAAgC,EAChC,aAAyC,EACzC,wBAKS,EACF;IACP,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,wBAAwB,CACvB,KAAK,CAAC,OAAO,CAAC,UAAU,EACxB,KAAK,CAAC,OAAO,CAAC,OAAO,EACrB,KAAK,CAAC,OAAO,CAAC,OAAO,EACrB,KAAK,CAAC,OAAO,CAAC,OAAO,CACrB,CAAC;QACH,CAAC;IACF,CAAC;AAAA,CACD","sourcesContent":["import type { AgentMessage } from \"@caupulican/pi-agent-core\";\nimport type { ImageContent, Message, TextContent } from \"@caupulican/pi-ai\";\n\nexport type ModelRouterBufferedSessionMessage =\n\t| {\n\t\t\tkind: \"message\";\n\t\t\tmessage: Message;\n\t }\n\t| {\n\t\t\tkind: \"custom\";\n\t\t\tmessage: Extract<AgentMessage, { role: \"custom\" }>;\n\t };\n\nexport type ModelRouterSessionBuffer = {\n\tmessages: ModelRouterBufferedSessionMessage[];\n};\n\nexport function createModelRouterSessionBuffer(): ModelRouterSessionBuffer {\n\treturn { messages: [] };\n}\n\nexport function bufferModelRouterSessionMessage(buffer: ModelRouterSessionBuffer, message: Message): void {\n\tbuffer.messages.push({ kind: \"message\", message });\n}\n\nexport function bufferModelRouterSessionCustomMessage(\n\tbuffer: ModelRouterSessionBuffer,\n\tmessage: Extract<AgentMessage, { role: \"custom\" }>,\n): void {\n\tbuffer.messages.push({ kind: \"custom\", message });\n}\n\nexport function flushModelRouterSessionBuffer(\n\tbuffer: ModelRouterSessionBuffer,\n\tappendMessage: (message: Message) => void,\n\tappendCustomMessageEntry: (\n\t\tcustomType: string,\n\t\tcontent: string | (TextContent | ImageContent)[],\n\t\tdisplay: boolean,\n\t\tdetails?: unknown,\n\t) => void,\n): void {\n\tfor (const entry of buffer.messages) {\n\t\tif (entry.kind === \"message\") {\n\t\t\tappendMessage(entry.message);\n\t\t} else {\n\t\t\tappendCustomMessageEntry(\n\t\t\t\tentry.message.customType,\n\t\t\t\tentry.message.content,\n\t\t\t\tentry.message.display,\n\t\t\t\tentry.message.details,\n\t\t\t);\n\t\t}\n\t}\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SessionEntry } from "../session-manager.ts";
|
|
2
|
+
import type { ModelRouterIntent } from "./intent-classifier.ts";
|
|
3
|
+
export declare const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
4
|
+
export type ModelRouterStatusSettings = {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
cheapModel?: string;
|
|
7
|
+
expensiveModel?: string;
|
|
8
|
+
learningModel?: string;
|
|
9
|
+
};
|
|
10
|
+
export type ModelRouterDecisionStatus = {
|
|
11
|
+
intent: ModelRouterIntent;
|
|
12
|
+
routedModel: string;
|
|
13
|
+
outcome: "routed" | "escalated" | "failed";
|
|
14
|
+
retryModel?: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function getRecentModelRouterDecisions(entries: SessionEntry[], limit?: number): ModelRouterDecisionStatus[];
|
|
17
|
+
export declare function formatModelRouterStatus(settings: ModelRouterStatusSettings, lastDecision?: ModelRouterDecisionStatus, formatLabel?: (label: string) => string, recentDecisions?: ModelRouterDecisionStatus[], lastSkipReason?: string, latestIntent?: ModelRouterIntent): string;
|
|
18
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +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,uBAAuB,CAAC;AAC1D,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,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,QAAQ,GAAG,WAAW,GAAG,QAAQ,CAAC;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAuBF,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,GAC9B,MAAM,CA8BR","sourcesContent":["import type { SessionEntry } from \"../session-manager.ts\";\nimport type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nexport const MODEL_ROUTER_DECISION_CUSTOM_TYPE = \"model_router_decision\";\n\nexport type ModelRouterStatusSettings = {\n\tenabled: boolean;\n\tcheapModel?: string;\n\texpensiveModel?: string;\n\tlearningModel?: string;\n};\n\nexport type ModelRouterDecisionStatus = {\n\tintent: ModelRouterIntent;\n\troutedModel: string;\n\toutcome: \"routed\" | \"escalated\" | \"failed\";\n\tretryModel?: string;\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\t(record.intent === \"research\" || record.intent === \"modify\") &&\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);\n}\n\nfunction formatDecision(decision: ModelRouterDecisionStatus): string {\n\tlet text = `${decision.intent} -> ${decision.routedModel}`;\n\tif (decision.outcome === \"escalated\" && decision.retryModel) {\n\t\ttext += ` (escalated -> ${decision.retryModel})`;\n\t} else if (decision.outcome === \"failed\") {\n\t\ttext += \" (failed)\";\n\t}\n\treturn text;\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): string {\n\tconst effectiveLastDecision = lastSkipReason ? undefined : lastDecision;\n\tconst lines = [\n\t\t`${formatLabel(\"Status:\")} ${settings.enabled ? \"enabled\" : \"disabled\"}`,\n\t\t`${formatLabel(\"Cheap model:\")} ${settings.cheapModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Expensive model:\")} ${settings.expensiveModel ?? \"unset\"}`,\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 (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"]}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const MODEL_ROUTER_DECISION_CUSTOM_TYPE = "model_router_decision";
|
|
2
|
+
function isModelRouterDecisionStatus(data) {
|
|
3
|
+
if (!data || typeof data !== "object")
|
|
4
|
+
return false;
|
|
5
|
+
const record = data;
|
|
6
|
+
return ((record.intent === "research" || record.intent === "modify") &&
|
|
7
|
+
typeof record.routedModel === "string" &&
|
|
8
|
+
(record.outcome === "routed" || record.outcome === "escalated" || record.outcome === "failed") &&
|
|
9
|
+
(record.retryModel === undefined || typeof record.retryModel === "string"));
|
|
10
|
+
}
|
|
11
|
+
function formatDecision(decision) {
|
|
12
|
+
let text = `${decision.intent} -> ${decision.routedModel}`;
|
|
13
|
+
if (decision.outcome === "escalated" && decision.retryModel) {
|
|
14
|
+
text += ` (escalated -> ${decision.retryModel})`;
|
|
15
|
+
}
|
|
16
|
+
else if (decision.outcome === "failed") {
|
|
17
|
+
text += " (failed)";
|
|
18
|
+
}
|
|
19
|
+
return text;
|
|
20
|
+
}
|
|
21
|
+
export function getRecentModelRouterDecisions(entries, limit = 3) {
|
|
22
|
+
const decisions = [];
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (entry.type !== "custom" || entry.customType !== MODEL_ROUTER_DECISION_CUSTOM_TYPE)
|
|
25
|
+
continue;
|
|
26
|
+
if (!isModelRouterDecisionStatus(entry.data))
|
|
27
|
+
continue;
|
|
28
|
+
decisions.push(entry.data);
|
|
29
|
+
}
|
|
30
|
+
return decisions.slice(-limit);
|
|
31
|
+
}
|
|
32
|
+
export function formatModelRouterStatus(settings, lastDecision, formatLabel = (label) => label, recentDecisions = [], lastSkipReason, latestIntent) {
|
|
33
|
+
const effectiveLastDecision = lastSkipReason ? undefined : lastDecision;
|
|
34
|
+
const lines = [
|
|
35
|
+
`${formatLabel("Status:")} ${settings.enabled ? "enabled" : "disabled"}`,
|
|
36
|
+
`${formatLabel("Cheap model:")} ${settings.cheapModel ?? "unset"}`,
|
|
37
|
+
`${formatLabel("Expensive model:")} ${settings.expensiveModel ?? "unset"}`,
|
|
38
|
+
`${formatLabel("Learning model:")} ${settings.learningModel ?? "active"}`,
|
|
39
|
+
];
|
|
40
|
+
if (!settings.enabled) {
|
|
41
|
+
lines.push(`${formatLabel("Routing:")} inactive (disabled)`);
|
|
42
|
+
}
|
|
43
|
+
else if (lastSkipReason) {
|
|
44
|
+
lines.push(`${formatLabel("Routing:")} skipped (${lastSkipReason})`);
|
|
45
|
+
}
|
|
46
|
+
else if (effectiveLastDecision) {
|
|
47
|
+
lines.push(`${formatLabel("Routing:")} active`);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
lines.push(`${formatLabel("Routing:")} waiting for prompt`);
|
|
51
|
+
}
|
|
52
|
+
lines.push(`${formatLabel("Latest intent:")} ${latestIntent ?? "none"}`);
|
|
53
|
+
if (!effectiveLastDecision) {
|
|
54
|
+
lines.push(`${formatLabel("Last decision:")} none`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
lines.push(`${formatLabel("Last decision:")} ${formatDecision(effectiveLastDecision)}`);
|
|
58
|
+
}
|
|
59
|
+
if (recentDecisions.length > 0) {
|
|
60
|
+
lines.push(formatLabel("Recent decisions:"));
|
|
61
|
+
for (const decision of recentDecisions) {
|
|
62
|
+
lines.push(`- ${formatDecision(decision)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return lines.join("\n");
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/core/model-router/status.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,iCAAiC,GAAG,uBAAuB,CAAC;AAgBzE,SAAS,2BAA2B,CAAC,IAAa,EAAqC;IACtF,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACpD,MAAM,MAAM,GAAG,IAA0C,CAAC;IAC1D,OAAO,CACN,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC;QAC5D,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;QACtC,CAAC,MAAM,CAAC,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,OAAO,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC;QAC9F,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAC1E,CAAC;AAAA,CACF;AAED,SAAS,cAAc,CAAC,QAAmC,EAAU;IACpE,IAAI,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,QAAQ,CAAC,OAAO,KAAK,WAAW,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC;QAC7D,IAAI,IAAI,kBAAkB,QAAQ,CAAC,UAAU,GAAG,CAAC;IAClD,CAAC;SAAM,IAAI,QAAQ,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC1C,IAAI,IAAI,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,UAAU,6BAA6B,CAAC,OAAuB,EAAE,KAAK,GAAG,CAAC,EAA+B;IAC9G,MAAM,SAAS,GAAgC,EAAE,CAAC;IAClD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,iCAAiC;YAAE,SAAS;QAChG,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QACvD,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;AAAA,CAC/B;AAED,MAAM,UAAU,uBAAuB,CACtC,QAAmC,EACnC,YAAwC,EACxC,WAAW,GAA8B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,EACzD,eAAe,GAAgC,EAAE,EACjD,cAAuB,EACvB,YAAgC,EACvB;IACT,MAAM,qBAAqB,GAAG,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC;IACxE,MAAM,KAAK,GAAG;QACb,GAAG,WAAW,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE;QACxE,GAAG,WAAW,CAAC,cAAc,CAAC,IAAI,QAAQ,CAAC,UAAU,IAAI,OAAO,EAAE;QAClE,GAAG,WAAW,CAAC,kBAAkB,CAAC,IAAI,QAAQ,CAAC,cAAc,IAAI,OAAO,EAAE;QAC1E,GAAG,WAAW,CAAC,iBAAiB,CAAC,IAAI,QAAQ,CAAC,aAAa,IAAI,QAAQ,EAAE;KACzE,CAAC;IACF,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;IAC9D,CAAC;SAAM,IAAI,cAAc,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,aAAa,cAAc,GAAG,CAAC,CAAC;IACtE,CAAC;SAAM,IAAI,qBAAqB,EAAE,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IACjD,CAAC;SAAM,CAAC;QACP,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;IAC7D,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,gBAAgB,CAAC,IAAI,YAAY,IAAI,MAAM,EAAE,CAAC,CAAC;IACzE,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrD,CAAC;SAAM,CAAC;QACP,KAAK,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,gBAAgB,CAAC,IAAI,cAAc,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAAC;QAC7C,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,KAAK,cAAc,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7C,CAAC;IACF,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB","sourcesContent":["import type { SessionEntry } from \"../session-manager.ts\";\nimport type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nexport const MODEL_ROUTER_DECISION_CUSTOM_TYPE = \"model_router_decision\";\n\nexport type ModelRouterStatusSettings = {\n\tenabled: boolean;\n\tcheapModel?: string;\n\texpensiveModel?: string;\n\tlearningModel?: string;\n};\n\nexport type ModelRouterDecisionStatus = {\n\tintent: ModelRouterIntent;\n\troutedModel: string;\n\toutcome: \"routed\" | \"escalated\" | \"failed\";\n\tretryModel?: string;\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\t(record.intent === \"research\" || record.intent === \"modify\") &&\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);\n}\n\nfunction formatDecision(decision: ModelRouterDecisionStatus): string {\n\tlet text = `${decision.intent} -> ${decision.routedModel}`;\n\tif (decision.outcome === \"escalated\" && decision.retryModel) {\n\t\ttext += ` (escalated -> ${decision.retryModel})`;\n\t} else if (decision.outcome === \"failed\") {\n\t\ttext += \" (failed)\";\n\t}\n\treturn text;\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): string {\n\tconst effectiveLastDecision = lastSkipReason ? undefined : lastDecision;\n\tconst lines = [\n\t\t`${formatLabel(\"Status:\")} ${settings.enabled ? \"enabled\" : \"disabled\"}`,\n\t\t`${formatLabel(\"Cheap model:\")} ${settings.cheapModel ?? \"unset\"}`,\n\t\t`${formatLabel(\"Expensive model:\")} ${settings.expensiveModel ?? \"unset\"}`,\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 (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"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ModelRouterIntent } from "./intent-classifier.ts";
|
|
2
|
+
type ToolEscalationOptions = {
|
|
3
|
+
intent: ModelRouterIntent;
|
|
4
|
+
toolName: string;
|
|
5
|
+
args?: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare function shouldEscalateModelRouterTool(options: ToolEscalationOptions): boolean;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=tool-escalation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-escalation.d.ts","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAqDhE,KAAK,qBAAqB,GAAG;IAAE,MAAM,EAAE,iBAAiB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAwC7F,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAUrF","sourcesContent":["import type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\ntype ToolEscalationOptions = { intent: ModelRouterIntent; toolName: string; args?: unknown };\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command)) return false;\n\tconst segments = command.split(/\\s*&&\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every(isReadOnlyShellSegment);\n}\n\nexport function shouldEscalateModelRouterTool(options: ToolEscalationOptions): boolean {\n\tif (options.intent !== \"research\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const READ_ONLY_TOOL_NAMES = new Set([
|
|
2
|
+
"read",
|
|
3
|
+
"grep",
|
|
4
|
+
"find",
|
|
5
|
+
"ls",
|
|
6
|
+
"list",
|
|
7
|
+
"search",
|
|
8
|
+
"glob",
|
|
9
|
+
"view_file",
|
|
10
|
+
"list_dir",
|
|
11
|
+
"grep_search",
|
|
12
|
+
"search_web",
|
|
13
|
+
"read_url_content",
|
|
14
|
+
"read_browser_page",
|
|
15
|
+
]);
|
|
16
|
+
const SHELL_TOOL_NAMES = new Set(["bash", "exec", "execute", "run", "run_command", "shell"]);
|
|
17
|
+
const READ_ONLY_COMMANDS = new Set([
|
|
18
|
+
"awk",
|
|
19
|
+
"cat",
|
|
20
|
+
"date",
|
|
21
|
+
"df",
|
|
22
|
+
"du",
|
|
23
|
+
"env",
|
|
24
|
+
"git",
|
|
25
|
+
"grep",
|
|
26
|
+
"head",
|
|
27
|
+
"jq",
|
|
28
|
+
"ls",
|
|
29
|
+
"node",
|
|
30
|
+
"npm",
|
|
31
|
+
"pnpm",
|
|
32
|
+
"pwd",
|
|
33
|
+
"rg",
|
|
34
|
+
"sed",
|
|
35
|
+
"tail",
|
|
36
|
+
"test",
|
|
37
|
+
"tsc",
|
|
38
|
+
"wc",
|
|
39
|
+
"which",
|
|
40
|
+
"yarn",
|
|
41
|
+
]);
|
|
42
|
+
const READ_ONLY_GIT_SUBCOMMANDS = new Set(["branch", "diff", "log", "rev-parse", "show", "status", "tag"]);
|
|
43
|
+
const READ_ONLY_NPM_SUBCOMMANDS = new Set(["info", "list", "ls", "outdated", "view", "whoami"]);
|
|
44
|
+
const MUTATING_SHELL_TOKEN_RE = /(^|\s)(>|>>|2>|&>|tee\b|rm\b|mv\b|cp\b|mkdir\b|touch\b|chmod\b|chown\b|install\b|commit\b|push\b|publish\b|deploy\b|apply\b|add\b|checkout\b|switch\b|reset\b|clean\b|stash\b|merge\b|rebase\b|npm\s+(?:i|install|ci|update|publish|run)\b|pnpm\s+(?:i|install|update|publish|run)\b|yarn\s+(?:add|install|upgrade|publish|run)\b)/i;
|
|
45
|
+
const MUTATING_TOOL_NAME_RE = /(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;
|
|
46
|
+
function getShellCommand(args) {
|
|
47
|
+
if (!args || typeof args !== "object")
|
|
48
|
+
return undefined;
|
|
49
|
+
const record = args;
|
|
50
|
+
const command = record.command ?? record.cmd ?? record.shellCommand;
|
|
51
|
+
return typeof command === "string" ? command.trim() : undefined;
|
|
52
|
+
}
|
|
53
|
+
function commandName(segment) {
|
|
54
|
+
const first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];
|
|
55
|
+
if (!first)
|
|
56
|
+
return undefined;
|
|
57
|
+
const parts = first.split("/");
|
|
58
|
+
return parts[parts.length - 1]?.toLowerCase();
|
|
59
|
+
}
|
|
60
|
+
function commandArg(segment, index) {
|
|
61
|
+
return segment.trim().split(/\s+/)[index]?.toLowerCase();
|
|
62
|
+
}
|
|
63
|
+
function isReadOnlyShellSegment(segment) {
|
|
64
|
+
const name = commandName(segment);
|
|
65
|
+
if (!name || !READ_ONLY_COMMANDS.has(name))
|
|
66
|
+
return false;
|
|
67
|
+
if (name === "git") {
|
|
68
|
+
const subcommand = commandArg(segment, 1);
|
|
69
|
+
return Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));
|
|
70
|
+
}
|
|
71
|
+
if (name === "npm" || name === "pnpm" || name === "yarn") {
|
|
72
|
+
const subcommand = commandArg(segment, 1);
|
|
73
|
+
return Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function isReadOnlyShellCommand(command) {
|
|
78
|
+
if (!command || MUTATING_SHELL_TOKEN_RE.test(command))
|
|
79
|
+
return false;
|
|
80
|
+
const segments = command.split(/\s*&&\s*/).map((segment) => segment.trim());
|
|
81
|
+
return segments.length > 0 && segments.every(isReadOnlyShellSegment);
|
|
82
|
+
}
|
|
83
|
+
export function shouldEscalateModelRouterTool(options) {
|
|
84
|
+
if (options.intent !== "research")
|
|
85
|
+
return false;
|
|
86
|
+
const toolName = options.toolName.trim().toLowerCase();
|
|
87
|
+
if (!toolName)
|
|
88
|
+
return true;
|
|
89
|
+
if (READ_ONLY_TOOL_NAMES.has(toolName))
|
|
90
|
+
return false;
|
|
91
|
+
if (SHELL_TOOL_NAMES.has(toolName)) {
|
|
92
|
+
const command = getShellCommand(options.args);
|
|
93
|
+
return command ? !isReadOnlyShellCommand(command) : true;
|
|
94
|
+
}
|
|
95
|
+
return MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith("read_");
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=tool-escalation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-escalation.js","sourceRoot":"","sources":["../../../src/core/model-router/tool-escalation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ModelRouterIntent } from \"./intent-classifier.ts\";\n\nconst READ_ONLY_TOOL_NAMES = new Set([\n\t\"read\",\n\t\"grep\",\n\t\"find\",\n\t\"ls\",\n\t\"list\",\n\t\"search\",\n\t\"glob\",\n\t\"view_file\",\n\t\"list_dir\",\n\t\"grep_search\",\n\t\"search_web\",\n\t\"read_url_content\",\n\t\"read_browser_page\",\n]);\n\nconst SHELL_TOOL_NAMES = new Set([\"bash\", \"exec\", \"execute\", \"run\", \"run_command\", \"shell\"]);\n\nconst READ_ONLY_COMMANDS = new Set([\n\t\"awk\",\n\t\"cat\",\n\t\"date\",\n\t\"df\",\n\t\"du\",\n\t\"env\",\n\t\"git\",\n\t\"grep\",\n\t\"head\",\n\t\"jq\",\n\t\"ls\",\n\t\"node\",\n\t\"npm\",\n\t\"pnpm\",\n\t\"pwd\",\n\t\"rg\",\n\t\"sed\",\n\t\"tail\",\n\t\"test\",\n\t\"tsc\",\n\t\"wc\",\n\t\"which\",\n\t\"yarn\",\n]);\n\nconst READ_ONLY_GIT_SUBCOMMANDS = new Set([\"branch\", \"diff\", \"log\", \"rev-parse\", \"show\", \"status\", \"tag\"]);\nconst READ_ONLY_NPM_SUBCOMMANDS = new Set([\"info\", \"list\", \"ls\", \"outdated\", \"view\", \"whoami\"]);\nconst MUTATING_SHELL_TOKEN_RE =\n\t/(^|\\s)(>|>>|2>|&>|tee\\b|rm\\b|mv\\b|cp\\b|mkdir\\b|touch\\b|chmod\\b|chown\\b|install\\b|commit\\b|push\\b|publish\\b|deploy\\b|apply\\b|add\\b|checkout\\b|switch\\b|reset\\b|clean\\b|stash\\b|merge\\b|rebase\\b|npm\\s+(?:i|install|ci|update|publish|run)\\b|pnpm\\s+(?:i|install|update|publish|run)\\b|yarn\\s+(?:add|install|upgrade|publish|run)\\b)/i;\nconst MUTATING_TOOL_NAME_RE =\n\t/(bash|exec|execute|run|shell|write|edit|patch|replace|delete|remove|move|rename|create|mkdir|touch|install|commit|push|publish|deploy|apply)/i;\n\ntype ToolEscalationOptions = { intent: ModelRouterIntent; toolName: string; args?: unknown };\n\nfunction getShellCommand(args: unknown): string | undefined {\n\tif (!args || typeof args !== \"object\") return undefined;\n\tconst record = args as Record<string, unknown>;\n\tconst command = record.command ?? record.cmd ?? record.shellCommand;\n\treturn typeof command === \"string\" ? command.trim() : undefined;\n}\n\nfunction commandName(segment: string): string | undefined {\n\tconst first = segment.trim().match(/^[A-Za-z0-9_./-]+/)?.[0];\n\tif (!first) return undefined;\n\tconst parts = first.split(\"/\");\n\treturn parts[parts.length - 1]?.toLowerCase();\n}\n\nfunction commandArg(segment: string, index: number): string | undefined {\n\treturn segment.trim().split(/\\s+/)[index]?.toLowerCase();\n}\n\nfunction isReadOnlyShellSegment(segment: string): boolean {\n\tconst name = commandName(segment);\n\tif (!name || !READ_ONLY_COMMANDS.has(name)) return false;\n\tif (name === \"git\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_GIT_SUBCOMMANDS.has(subcommand));\n\t}\n\tif (name === \"npm\" || name === \"pnpm\" || name === \"yarn\") {\n\t\tconst subcommand = commandArg(segment, 1);\n\t\treturn Boolean(subcommand && READ_ONLY_NPM_SUBCOMMANDS.has(subcommand));\n\t}\n\treturn true;\n}\n\nfunction isReadOnlyShellCommand(command: string): boolean {\n\tif (!command || MUTATING_SHELL_TOKEN_RE.test(command)) return false;\n\tconst segments = command.split(/\\s*&&\\s*/).map((segment) => segment.trim());\n\treturn segments.length > 0 && segments.every(isReadOnlyShellSegment);\n}\n\nexport function shouldEscalateModelRouterTool(options: ToolEscalationOptions): boolean {\n\tif (options.intent !== \"research\") return false;\n\tconst toolName = options.toolName.trim().toLowerCase();\n\tif (!toolName) return true;\n\tif (READ_ONLY_TOOL_NAMES.has(toolName)) return false;\n\tif (SHELL_TOOL_NAMES.has(toolName)) {\n\t\tconst command = getShellCommand(options.args);\n\t\treturn command ? !isReadOnlyShellCommand(command) : true;\n\t}\n\treturn MUTATING_TOOL_NAME_RE.test(toolName) || !toolName.startsWith(\"read_\");\n}\n"]}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@caupulican/pi-agent-core";
|
|
2
|
-
import type { ResourceProfileSettings, Settings } from "./settings-manager.ts";
|
|
2
|
+
import type { ModelRouterSettings, ResourceProfileSettings, Settings } from "./settings-manager.ts";
|
|
3
3
|
export type ProfileSource = "global-settings" | "project-settings" | "profile-file" | "directory-overlay" | "inline" | "embedded" | "bundle";
|
|
4
4
|
export interface NormalizedProfile {
|
|
5
5
|
name: string;
|
|
6
6
|
description?: string;
|
|
7
7
|
model?: string;
|
|
8
8
|
thinking?: ThinkingLevel;
|
|
9
|
+
modelRouter?: ModelRouterSettings;
|
|
9
10
|
/** Situational identity injected into the system prompt while this profile is active (R6). */
|
|
10
11
|
soul?: string;
|
|
11
12
|
resources: ResourceProfileSettings;
|