bashkit 0.5.1 → 0.5.2
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/dist/index.d.ts +3 -3
- package/dist/index.js +473 -261
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/web-constants.d.ts +5 -0
- package/dist/types.d.ts +27 -3
- package/dist/utils/budget-tracking.d.ts +19 -0
- package/dist/utils/compact-conversation.d.ts +72 -2
- package/dist/utils/context-status.d.ts +2 -0
- package/dist/utils/debug.d.ts +5 -6
- package/dist/utils/helpers.d.ts +21 -0
- package/dist/utils/index.d.ts +3 -2
- package/package.json +1 -1
- package/dist/cloudflare/index.d.ts +0 -20
- package/dist/cloudflare/index.js +0 -1251
- package/dist/durable/durable-session.d.ts +0 -220
- package/dist/durable/index.d.ts +0 -41
- package/dist/durable/index.js +0 -159
- package/dist/durable/schema.d.ts +0 -51
- package/dist/durable/types.d.ts +0 -208
- package/dist/react/index.d.ts +0 -42
- package/dist/react/index.js +0 -10
- package/dist/react/types.d.ts +0 -333
- package/dist/react/use-agent.d.ts +0 -33
- package/dist/react/use-durable-chat.d.ts +0 -39
- package/dist/workflow.d.ts +0 -52
- package/dist/workflow.js +0 -1051
package/dist/index.js
CHANGED
|
@@ -613,14 +613,15 @@ var DEFAULT_CONFIG = {
|
|
|
613
613
|
};
|
|
614
614
|
|
|
615
615
|
// src/utils/budget-tracking.ts
|
|
616
|
-
var
|
|
616
|
+
var openRouterPricingCache = null;
|
|
617
|
+
var openRouterModelsCache = null;
|
|
617
618
|
var openRouterCacheTimestamp = 0;
|
|
618
619
|
var openRouterFetchPromise = null;
|
|
619
620
|
var OPENROUTER_FETCH_TIMEOUT_MS = 1e4;
|
|
620
621
|
var OPENROUTER_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
621
|
-
async function
|
|
622
|
-
if (
|
|
623
|
-
return
|
|
622
|
+
async function fetchOpenRouterData(apiKey) {
|
|
623
|
+
if (openRouterModelsCache && Date.now() - openRouterCacheTimestamp < OPENROUTER_CACHE_TTL_MS) {
|
|
624
|
+
return openRouterModelsCache;
|
|
624
625
|
}
|
|
625
626
|
if (openRouterFetchPromise)
|
|
626
627
|
return openRouterFetchPromise;
|
|
@@ -653,7 +654,9 @@ async function fetchOpenRouterPricing(apiKey) {
|
|
|
653
654
|
throw new Error(`[bashkit] OpenRouter pricing response missing data array. ` + `The API may have changed. Please provide modelPricing overrides in your config.`);
|
|
654
655
|
}
|
|
655
656
|
const MAX_MODELS = 1e4;
|
|
656
|
-
const
|
|
657
|
+
const MAX_CONTEXT_LENGTH = 1e7;
|
|
658
|
+
const modelsMap = new Map;
|
|
659
|
+
const pricingMap = new Map;
|
|
657
660
|
for (const model of models.slice(0, MAX_MODELS)) {
|
|
658
661
|
if (!model.id || !model.pricing)
|
|
659
662
|
continue;
|
|
@@ -673,11 +676,19 @@ async function fetchOpenRouterPricing(apiKey) {
|
|
|
673
676
|
const cacheWrite = parseFloat(model.pricing.input_cache_write ?? "");
|
|
674
677
|
if (Number.isFinite(cacheWrite) && cacheWrite >= 0)
|
|
675
678
|
pricing.cacheWritePerToken = cacheWrite;
|
|
676
|
-
|
|
679
|
+
const key = model.id.toLowerCase();
|
|
680
|
+
pricingMap.set(key, pricing);
|
|
681
|
+
const contextLength = model.context_length;
|
|
682
|
+
if (typeof contextLength === "number" && Number.isFinite(contextLength) && contextLength > 0 && contextLength <= MAX_CONTEXT_LENGTH) {
|
|
683
|
+
modelsMap.set(key, { pricing, contextLength });
|
|
684
|
+
} else {
|
|
685
|
+
modelsMap.set(key, { pricing, contextLength: 0 });
|
|
686
|
+
}
|
|
677
687
|
}
|
|
678
|
-
|
|
688
|
+
openRouterModelsCache = modelsMap;
|
|
689
|
+
openRouterPricingCache = pricingMap;
|
|
679
690
|
openRouterCacheTimestamp = Date.now();
|
|
680
|
-
return
|
|
691
|
+
return openRouterModelsCache;
|
|
681
692
|
} finally {
|
|
682
693
|
clearTimeout(timeoutId);
|
|
683
694
|
openRouterFetchPromise = null;
|
|
@@ -685,6 +696,9 @@ async function fetchOpenRouterPricing(apiKey) {
|
|
|
685
696
|
})();
|
|
686
697
|
return openRouterFetchPromise;
|
|
687
698
|
}
|
|
699
|
+
async function fetchOpenRouterModels(apiKey) {
|
|
700
|
+
return fetchOpenRouterData(apiKey);
|
|
701
|
+
}
|
|
688
702
|
function getModelMatchVariants(model) {
|
|
689
703
|
const lower = model.toLowerCase();
|
|
690
704
|
const kebab = lower.replace(/[^a-z0-9]+/g, "-");
|
|
@@ -700,54 +714,65 @@ function getModelMatchVariants(model) {
|
|
|
700
714
|
}
|
|
701
715
|
return variants;
|
|
702
716
|
}
|
|
703
|
-
function
|
|
704
|
-
if (
|
|
717
|
+
function searchModelInMap(model, map, filter) {
|
|
718
|
+
if (map.size === 0)
|
|
705
719
|
return;
|
|
706
720
|
const modelVariants = getModelMatchVariants(model);
|
|
707
|
-
const
|
|
708
|
-
function
|
|
709
|
-
let variants =
|
|
721
|
+
const entryVariantsCache = new Map;
|
|
722
|
+
function getEntryVariants(key) {
|
|
723
|
+
let variants = entryVariantsCache.get(key);
|
|
710
724
|
if (!variants) {
|
|
711
725
|
variants = getModelMatchVariants(key);
|
|
712
|
-
|
|
726
|
+
entryVariantsCache.set(key, variants);
|
|
713
727
|
}
|
|
714
728
|
return variants;
|
|
715
729
|
}
|
|
716
730
|
for (const variant of modelVariants) {
|
|
717
|
-
const
|
|
718
|
-
if (
|
|
719
|
-
return
|
|
731
|
+
const value = map.get(variant);
|
|
732
|
+
if (value !== undefined && (!filter || filter(value)))
|
|
733
|
+
return value;
|
|
720
734
|
}
|
|
721
735
|
let bestMatch;
|
|
722
736
|
let bestMatchLength = 0;
|
|
723
|
-
for (const [
|
|
724
|
-
|
|
737
|
+
for (const [entryKey, value] of map) {
|
|
738
|
+
if (filter && !filter(value))
|
|
739
|
+
continue;
|
|
740
|
+
const entryVariants = getEntryVariants(entryKey);
|
|
725
741
|
for (const modelVariant of modelVariants) {
|
|
726
|
-
for (const
|
|
727
|
-
if (modelVariant.includes(
|
|
728
|
-
bestMatch =
|
|
729
|
-
bestMatchLength =
|
|
742
|
+
for (const entryVariant of entryVariants) {
|
|
743
|
+
if (modelVariant.includes(entryVariant) && entryVariant.length > bestMatchLength) {
|
|
744
|
+
bestMatch = value;
|
|
745
|
+
bestMatchLength = entryVariant.length;
|
|
730
746
|
}
|
|
731
747
|
}
|
|
732
748
|
}
|
|
733
749
|
}
|
|
734
|
-
if (bestMatch)
|
|
750
|
+
if (bestMatch !== undefined)
|
|
735
751
|
return bestMatch;
|
|
736
752
|
let reverseMatch;
|
|
737
753
|
let reverseMatchLength = Infinity;
|
|
738
|
-
for (const [
|
|
739
|
-
|
|
754
|
+
for (const [entryKey, value] of map) {
|
|
755
|
+
if (filter && !filter(value))
|
|
756
|
+
continue;
|
|
757
|
+
const entryVariants = getEntryVariants(entryKey);
|
|
740
758
|
for (const modelVariant of modelVariants) {
|
|
741
|
-
for (const
|
|
742
|
-
if (
|
|
743
|
-
reverseMatch =
|
|
744
|
-
reverseMatchLength =
|
|
759
|
+
for (const entryVariant of entryVariants) {
|
|
760
|
+
if (entryVariant.includes(modelVariant) && entryVariant.length < reverseMatchLength) {
|
|
761
|
+
reverseMatch = value;
|
|
762
|
+
reverseMatchLength = entryVariant.length;
|
|
745
763
|
}
|
|
746
764
|
}
|
|
747
765
|
}
|
|
748
766
|
}
|
|
749
767
|
return reverseMatch;
|
|
750
768
|
}
|
|
769
|
+
function searchModelInCosts(model, costsMap) {
|
|
770
|
+
return searchModelInMap(model, costsMap);
|
|
771
|
+
}
|
|
772
|
+
function getModelContextLength(model, modelsMap) {
|
|
773
|
+
const info = searchModelInMap(model, modelsMap, (v) => v.contextLength > 0);
|
|
774
|
+
return info?.contextLength;
|
|
775
|
+
}
|
|
751
776
|
function findPricingForModel(model, options) {
|
|
752
777
|
if (options?.overrides) {
|
|
753
778
|
const overrideMap = options.overrides instanceof Map ? options.overrides : new Map(Object.entries(options.overrides).map(([k, v]) => [
|
|
@@ -853,13 +878,14 @@ import { tool, zodSchema } from "ai";
|
|
|
853
878
|
import { z } from "zod";
|
|
854
879
|
|
|
855
880
|
// src/utils/debug.ts
|
|
881
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
856
882
|
import { appendFileSync } from "node:fs";
|
|
857
883
|
var state = {
|
|
858
884
|
mode: "off",
|
|
859
885
|
logs: [],
|
|
860
|
-
counters: new Map
|
|
861
|
-
parentStack: []
|
|
886
|
+
counters: new Map
|
|
862
887
|
};
|
|
888
|
+
var debugContext = new AsyncLocalStorage;
|
|
863
889
|
var MAX_STRING_LENGTH = 1000;
|
|
864
890
|
var MAX_ARRAY_ITEMS = 10;
|
|
865
891
|
function initDebugMode() {
|
|
@@ -947,7 +973,8 @@ function emitEvent(event) {
|
|
|
947
973
|
}
|
|
948
974
|
}
|
|
949
975
|
function formatHumanReadable(event) {
|
|
950
|
-
const
|
|
976
|
+
const ctx = debugContext.getStore();
|
|
977
|
+
const indent = " ".repeat(ctx?.depth ?? 0);
|
|
951
978
|
if (event.event === "start") {
|
|
952
979
|
const inputSummary = event.input ? Object.entries(event.input).map(([k, v]) => `${k}=${JSON.stringify(v)}`).slice(0, 3).join(" ") : "";
|
|
953
980
|
process.stderr.write(`${indent}[bashkit:${event.tool}] → ${inputSummary}
|
|
@@ -965,7 +992,8 @@ function debugStart(tool, input) {
|
|
|
965
992
|
if (state.mode === "off")
|
|
966
993
|
return "";
|
|
967
994
|
const id = generateId(tool);
|
|
968
|
-
const
|
|
995
|
+
const ctx = debugContext.getStore();
|
|
996
|
+
const parent = ctx?.parentId;
|
|
969
997
|
const event = {
|
|
970
998
|
id,
|
|
971
999
|
ts: Date.now(),
|
|
@@ -1003,15 +1031,12 @@ function debugError(id, tool, error) {
|
|
|
1003
1031
|
};
|
|
1004
1032
|
emitEvent(event);
|
|
1005
1033
|
}
|
|
1006
|
-
function
|
|
1007
|
-
if (state.mode === "off" || !
|
|
1008
|
-
return;
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (state.mode === "off")
|
|
1013
|
-
return;
|
|
1014
|
-
state.parentStack.pop();
|
|
1034
|
+
function runWithDebugParent(parentId, fn) {
|
|
1035
|
+
if (state.mode === "off" || !parentId)
|
|
1036
|
+
return fn();
|
|
1037
|
+
const current = debugContext.getStore();
|
|
1038
|
+
const depth = current ? current.depth + 1 : 1;
|
|
1039
|
+
return debugContext.run({ parentId, depth }, fn);
|
|
1015
1040
|
}
|
|
1016
1041
|
function getDebugLogs() {
|
|
1017
1042
|
return [...state.logs];
|
|
@@ -1019,12 +1044,10 @@ function getDebugLogs() {
|
|
|
1019
1044
|
function clearDebugLogs() {
|
|
1020
1045
|
state.logs = [];
|
|
1021
1046
|
state.counters.clear();
|
|
1022
|
-
state.parentStack = [];
|
|
1023
1047
|
}
|
|
1024
1048
|
function reinitDebugMode() {
|
|
1025
1049
|
state.logs = [];
|
|
1026
1050
|
state.counters.clear();
|
|
1027
|
-
state.parentStack = [];
|
|
1028
1051
|
initDebugMode();
|
|
1029
1052
|
}
|
|
1030
1053
|
|
|
@@ -1164,6 +1187,37 @@ function createAskUserTool(config) {
|
|
|
1164
1187
|
// src/tools/bash.ts
|
|
1165
1188
|
import { tool as tool2, zodSchema as zodSchema2 } from "ai";
|
|
1166
1189
|
import { z as z2 } from "zod";
|
|
1190
|
+
|
|
1191
|
+
// src/utils/helpers.ts
|
|
1192
|
+
function isToolCallPart(part) {
|
|
1193
|
+
return typeof part === "object" && part !== null && part.type === "tool-call" && "toolName" in part && "input" in part;
|
|
1194
|
+
}
|
|
1195
|
+
function isToolResultPart(part) {
|
|
1196
|
+
return typeof part === "object" && part !== null && part.type === "tool-result" && "toolName" in part && "output" in part;
|
|
1197
|
+
}
|
|
1198
|
+
function middleTruncate(text, maxLength) {
|
|
1199
|
+
if (!Number.isFinite(maxLength) || maxLength < 0)
|
|
1200
|
+
return text;
|
|
1201
|
+
if (text.length <= maxLength)
|
|
1202
|
+
return text;
|
|
1203
|
+
const headLength = Math.floor(maxLength / 2);
|
|
1204
|
+
const tailLength = maxLength - headLength;
|
|
1205
|
+
const omitted = text.length - headLength - tailLength;
|
|
1206
|
+
let totalLines = 1;
|
|
1207
|
+
for (let i = 0;i < text.length; i++) {
|
|
1208
|
+
if (text.charCodeAt(i) === 10)
|
|
1209
|
+
totalLines++;
|
|
1210
|
+
}
|
|
1211
|
+
return `[Total output lines: ${totalLines}]
|
|
1212
|
+
|
|
1213
|
+
` + text.slice(0, headLength) + `
|
|
1214
|
+
|
|
1215
|
+
…${omitted} chars truncated…
|
|
1216
|
+
|
|
1217
|
+
` + text.slice(text.length - tailLength);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/tools/bash.ts
|
|
1167
1221
|
var bashInputSchema = z2.object({
|
|
1168
1222
|
command: z2.string().describe("The command to execute"),
|
|
1169
1223
|
timeout: z2.number().nullable().default(null).describe("Optional timeout in milliseconds (max 600000)"),
|
|
@@ -1239,16 +1293,8 @@ function createBashTool(sandbox, config) {
|
|
|
1239
1293
|
const result = await sandbox.exec(command, {
|
|
1240
1294
|
timeout: effectiveTimeout
|
|
1241
1295
|
});
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
if (stdout.length > maxOutputLength) {
|
|
1245
|
-
stdout = stdout.slice(0, maxOutputLength) + `
|
|
1246
|
-
[output truncated, ${stdout.length - maxOutputLength} chars omitted]`;
|
|
1247
|
-
}
|
|
1248
|
-
if (stderr.length > maxOutputLength) {
|
|
1249
|
-
stderr = stderr.slice(0, maxOutputLength) + `
|
|
1250
|
-
[output truncated, ${stderr.length - maxOutputLength} chars omitted]`;
|
|
1251
|
-
}
|
|
1296
|
+
const stdout = middleTruncate(result.stdout, maxOutputLength);
|
|
1297
|
+
const stderr = middleTruncate(result.stderr, maxOutputLength);
|
|
1252
1298
|
const durationMs = Math.round(performance.now() - startTime);
|
|
1253
1299
|
if (debugId) {
|
|
1254
1300
|
debugEnd(debugId, "bash", {
|
|
@@ -2598,53 +2644,98 @@ function createTaskTool(config) {
|
|
|
2598
2644
|
...Object.keys(typeConfig.additionalTools ?? {})
|
|
2599
2645
|
]
|
|
2600
2646
|
}) : "";
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2647
|
+
const executeTask = async () => {
|
|
2648
|
+
try {
|
|
2649
|
+
const model = typeConfig.model || defaultModel;
|
|
2650
|
+
const tools = filterTools(allTools, customTools ?? typeConfig.tools, typeConfig.additionalTools);
|
|
2651
|
+
const systemPrompt = system_prompt ?? typeConfig.systemPrompt;
|
|
2652
|
+
const baseStopWhen = typeConfig.stopWhen ?? defaultStopWhen ?? stepCountIs(15);
|
|
2653
|
+
const effectiveStopWhen = budget ? [baseStopWhen, budget.stopWhen].flat() : baseStopWhen;
|
|
2654
|
+
const commonOptions = {
|
|
2655
|
+
model,
|
|
2656
|
+
tools,
|
|
2657
|
+
system: systemPrompt,
|
|
2658
|
+
prompt,
|
|
2659
|
+
stopWhen: effectiveStopWhen,
|
|
2660
|
+
prepareStep: typeConfig.prepareStep
|
|
2661
|
+
};
|
|
2662
|
+
if (streamWriter) {
|
|
2663
|
+
const startId = generateEventId();
|
|
2664
|
+
streamWriter.write({
|
|
2665
|
+
type: "data-subagent",
|
|
2666
|
+
id: startId,
|
|
2667
|
+
data: {
|
|
2668
|
+
event: "start",
|
|
2669
|
+
subagent: subagent_type,
|
|
2670
|
+
description
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
const result3 = streamText({
|
|
2674
|
+
...commonOptions,
|
|
2675
|
+
onStepFinish: async (step) => {
|
|
2676
|
+
budget?.onStepFinish(step);
|
|
2677
|
+
if (step.toolCalls?.length) {
|
|
2678
|
+
for (const tc of step.toolCalls) {
|
|
2679
|
+
const eventId = generateEventId();
|
|
2680
|
+
streamWriter.write({
|
|
2681
|
+
type: "data-subagent",
|
|
2682
|
+
id: eventId,
|
|
2683
|
+
data: {
|
|
2684
|
+
event: "tool-call",
|
|
2685
|
+
subagent: subagent_type,
|
|
2686
|
+
description,
|
|
2687
|
+
toolName: tc.toolName,
|
|
2688
|
+
args: tc.input
|
|
2689
|
+
}
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
}
|
|
2693
|
+
await typeConfig.onStepFinish?.(step);
|
|
2694
|
+
await defaultOnStepFinish?.({
|
|
2695
|
+
subagentType: subagent_type,
|
|
2696
|
+
description,
|
|
2697
|
+
step
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
const text = await result3.text;
|
|
2702
|
+
const usage2 = await result3.usage;
|
|
2703
|
+
const response = await result3.response;
|
|
2704
|
+
streamWriter.write({
|
|
2705
|
+
type: "data-subagent",
|
|
2706
|
+
id: generateEventId(),
|
|
2707
|
+
data: {
|
|
2708
|
+
event: "done",
|
|
2709
|
+
subagent: subagent_type,
|
|
2710
|
+
description
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
streamWriter.write({
|
|
2714
|
+
type: "data-subagent",
|
|
2715
|
+
id: generateEventId(),
|
|
2716
|
+
data: {
|
|
2717
|
+
event: "complete",
|
|
2718
|
+
subagent: subagent_type,
|
|
2719
|
+
description,
|
|
2720
|
+
messages: response.messages
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
const durationMs2 = Math.round(performance.now() - startTime);
|
|
2724
|
+
return {
|
|
2725
|
+
result: text,
|
|
2726
|
+
usage: usage2.inputTokens !== undefined && usage2.outputTokens !== undefined ? {
|
|
2727
|
+
input_tokens: usage2.inputTokens,
|
|
2728
|
+
output_tokens: usage2.outputTokens
|
|
2729
|
+
} : undefined,
|
|
2730
|
+
duration_ms: durationMs2,
|
|
2624
2731
|
subagent: subagent_type,
|
|
2625
2732
|
description
|
|
2626
|
-
}
|
|
2627
|
-
}
|
|
2628
|
-
const result2 =
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
const result2 = await generateText2({
|
|
2629
2736
|
...commonOptions,
|
|
2630
2737
|
onStepFinish: async (step) => {
|
|
2631
2738
|
budget?.onStepFinish(step);
|
|
2632
|
-
if (step.toolCalls?.length) {
|
|
2633
|
-
for (const tc of step.toolCalls) {
|
|
2634
|
-
const eventId = generateEventId();
|
|
2635
|
-
streamWriter.write({
|
|
2636
|
-
type: "data-subagent",
|
|
2637
|
-
id: eventId,
|
|
2638
|
-
data: {
|
|
2639
|
-
event: "tool-call",
|
|
2640
|
-
subagent: subagent_type,
|
|
2641
|
-
description,
|
|
2642
|
-
toolName: tc.toolName,
|
|
2643
|
-
args: tc.input
|
|
2644
|
-
}
|
|
2645
|
-
});
|
|
2646
|
-
}
|
|
2647
|
-
}
|
|
2648
2739
|
await typeConfig.onStepFinish?.(step);
|
|
2649
2740
|
await defaultOnStepFinish?.({
|
|
2650
2741
|
subagentType: subagent_type,
|
|
@@ -2653,104 +2744,46 @@ function createTaskTool(config) {
|
|
|
2653
2744
|
});
|
|
2654
2745
|
}
|
|
2655
2746
|
});
|
|
2656
|
-
const
|
|
2657
|
-
const
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
id: generateEventId(),
|
|
2662
|
-
data: {
|
|
2663
|
-
event: "done",
|
|
2664
|
-
subagent: subagent_type,
|
|
2665
|
-
description
|
|
2666
|
-
}
|
|
2667
|
-
});
|
|
2668
|
-
streamWriter.write({
|
|
2669
|
-
type: "data-subagent",
|
|
2670
|
-
id: generateEventId(),
|
|
2671
|
-
data: {
|
|
2672
|
-
event: "complete",
|
|
2673
|
-
subagent: subagent_type,
|
|
2674
|
-
description,
|
|
2675
|
-
messages: response.messages
|
|
2676
|
-
}
|
|
2677
|
-
});
|
|
2678
|
-
const durationMs2 = Math.round(performance.now() - startTime);
|
|
2679
|
-
if (debugId) {
|
|
2680
|
-
popParent();
|
|
2681
|
-
debugEnd(debugId, "task", {
|
|
2682
|
-
summary: {
|
|
2683
|
-
tokens: {
|
|
2684
|
-
input: usage2.inputTokens,
|
|
2685
|
-
output: usage2.outputTokens
|
|
2686
|
-
},
|
|
2687
|
-
steps: response.messages?.length
|
|
2688
|
-
},
|
|
2689
|
-
duration_ms: durationMs2
|
|
2690
|
-
});
|
|
2691
|
-
}
|
|
2747
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
2748
|
+
const usage = result2.usage.inputTokens !== undefined && result2.usage.outputTokens !== undefined ? {
|
|
2749
|
+
input_tokens: result2.usage.inputTokens,
|
|
2750
|
+
output_tokens: result2.usage.outputTokens
|
|
2751
|
+
} : undefined;
|
|
2692
2752
|
return {
|
|
2693
|
-
result: text,
|
|
2694
|
-
usage
|
|
2695
|
-
|
|
2696
|
-
output_tokens: usage2.outputTokens
|
|
2697
|
-
} : undefined,
|
|
2698
|
-
duration_ms: durationMs2,
|
|
2753
|
+
result: result2.text,
|
|
2754
|
+
usage,
|
|
2755
|
+
duration_ms: durationMs,
|
|
2699
2756
|
subagent: subagent_type,
|
|
2700
2757
|
description
|
|
2701
2758
|
};
|
|
2759
|
+
} catch (error) {
|
|
2760
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
2761
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
2762
|
+
return {
|
|
2763
|
+
error: errorMessage,
|
|
2764
|
+
subagent: subagent_type,
|
|
2765
|
+
description,
|
|
2766
|
+
duration_ms: durationMs
|
|
2767
|
+
};
|
|
2702
2768
|
}
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
subagentType: subagent_type,
|
|
2710
|
-
description,
|
|
2711
|
-
step
|
|
2712
|
-
});
|
|
2713
|
-
}
|
|
2714
|
-
});
|
|
2715
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
2716
|
-
const usage = result.usage.inputTokens !== undefined && result.usage.outputTokens !== undefined ? {
|
|
2717
|
-
input_tokens: result.usage.inputTokens,
|
|
2718
|
-
output_tokens: result.usage.outputTokens
|
|
2719
|
-
} : undefined;
|
|
2720
|
-
if (debugId) {
|
|
2721
|
-
popParent();
|
|
2769
|
+
};
|
|
2770
|
+
const result = await runWithDebugParent(debugId, executeTask);
|
|
2771
|
+
if (debugId) {
|
|
2772
|
+
if ("error" in result) {
|
|
2773
|
+
debugError(debugId, "task", result.error);
|
|
2774
|
+
} else {
|
|
2722
2775
|
debugEnd(debugId, "task", {
|
|
2723
2776
|
summary: {
|
|
2724
|
-
tokens: {
|
|
2725
|
-
input: result.usage.
|
|
2726
|
-
output: result.usage.
|
|
2727
|
-
}
|
|
2728
|
-
steps: result.steps?.length
|
|
2777
|
+
tokens: result.usage ? {
|
|
2778
|
+
input: result.usage.input_tokens,
|
|
2779
|
+
output: result.usage.output_tokens
|
|
2780
|
+
} : undefined
|
|
2729
2781
|
},
|
|
2730
|
-
duration_ms:
|
|
2782
|
+
duration_ms: result.duration_ms ?? Math.round(performance.now() - startTime)
|
|
2731
2783
|
});
|
|
2732
2784
|
}
|
|
2733
|
-
return {
|
|
2734
|
-
result: result.text,
|
|
2735
|
-
usage,
|
|
2736
|
-
duration_ms: durationMs,
|
|
2737
|
-
subagent: subagent_type,
|
|
2738
|
-
description
|
|
2739
|
-
};
|
|
2740
|
-
} catch (error) {
|
|
2741
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
2742
|
-
if (debugId) {
|
|
2743
|
-
popParent();
|
|
2744
|
-
debugError(debugId, "task", errorMessage);
|
|
2745
|
-
}
|
|
2746
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
2747
|
-
return {
|
|
2748
|
-
error: errorMessage,
|
|
2749
|
-
subagent: subagent_type,
|
|
2750
|
-
description,
|
|
2751
|
-
duration_ms: durationMs
|
|
2752
|
-
};
|
|
2753
2785
|
}
|
|
2786
|
+
return result;
|
|
2754
2787
|
}
|
|
2755
2788
|
});
|
|
2756
2789
|
}
|
|
@@ -2945,31 +2978,39 @@ async function createAgentTools(sandbox, config) {
|
|
|
2945
2978
|
}
|
|
2946
2979
|
}
|
|
2947
2980
|
}
|
|
2981
|
+
let openRouterModels;
|
|
2982
|
+
const shouldFetchOpenRouter = config?.modelRegistry?.provider === "openRouter" || config?.budget?.pricingProvider === "openRouter";
|
|
2983
|
+
if (shouldFetchOpenRouter) {
|
|
2984
|
+
const apiKey = config?.modelRegistry?.apiKey ?? config?.budget?.apiKey;
|
|
2985
|
+
try {
|
|
2986
|
+
openRouterModels = await fetchOpenRouterModels(apiKey);
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
if (config?.budget && !config.budget.modelPricing) {
|
|
2989
|
+
throw new Error(`[bashkit] Failed to fetch OpenRouter pricing and no modelPricing overrides provided. ` + `Either provide modelPricing in your budget config or ensure network access to OpenRouter. ` + `Original error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
let openRouterPricing;
|
|
2994
|
+
if (openRouterModels) {
|
|
2995
|
+
openRouterPricing = new Map([...openRouterModels].map(([k, v]) => [k, v.pricing]));
|
|
2996
|
+
}
|
|
2948
2997
|
let budget;
|
|
2949
2998
|
if (config?.budget) {
|
|
2950
|
-
const {
|
|
2951
|
-
if (!
|
|
2952
|
-
throw new Error("[bashkit] Budget requires either pricingProvider or modelPricing
|
|
2953
|
-
}
|
|
2954
|
-
let openRouterPricing;
|
|
2955
|
-
if (pricingProvider === "openRouter") {
|
|
2956
|
-
try {
|
|
2957
|
-
openRouterPricing = await fetchOpenRouterPricing(apiKey);
|
|
2958
|
-
} catch (err) {
|
|
2959
|
-
if (!modelPricing) {
|
|
2960
|
-
throw new Error(`[bashkit] Failed to fetch OpenRouter pricing and no modelPricing overrides provided. ` + `Either provide modelPricing in your budget config or ensure network access to OpenRouter. ` + `Original error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2961
|
-
}
|
|
2962
|
-
}
|
|
2999
|
+
const { modelPricing, maxUsd } = config.budget;
|
|
3000
|
+
if (!openRouterPricing && !modelPricing) {
|
|
3001
|
+
throw new Error("[bashkit] Budget requires either modelRegistry, pricingProvider, or modelPricing.");
|
|
2963
3002
|
}
|
|
2964
3003
|
budget = createBudgetTracker(maxUsd, {
|
|
2965
3004
|
modelPricing,
|
|
2966
3005
|
openRouterPricing
|
|
2967
3006
|
});
|
|
2968
3007
|
}
|
|
2969
|
-
return { tools, planModeState, budget };
|
|
3008
|
+
return { tools, planModeState, budget, openRouterModels };
|
|
2970
3009
|
}
|
|
2971
3010
|
// src/utils/compact-conversation.ts
|
|
2972
|
-
import {
|
|
3011
|
+
import {
|
|
3012
|
+
generateText as generateText3
|
|
3013
|
+
} from "ai";
|
|
2973
3014
|
|
|
2974
3015
|
// src/utils/prune-messages.ts
|
|
2975
3016
|
var DEFAULT_CONFIG2 = {
|
|
@@ -2990,10 +3031,10 @@ function estimateMessageTokens(message) {
|
|
|
2990
3031
|
tokens += estimateTokens(part);
|
|
2991
3032
|
} else if ("text" in part && typeof part.text === "string") {
|
|
2992
3033
|
tokens += estimateTokens(part.text);
|
|
2993
|
-
} else if (
|
|
2994
|
-
tokens += estimateTokens(JSON.stringify(part.
|
|
2995
|
-
} else if (
|
|
2996
|
-
tokens += estimateTokens(JSON.stringify(part.
|
|
3034
|
+
} else if (isToolResultPart(part)) {
|
|
3035
|
+
tokens += estimateTokens(JSON.stringify(part.output));
|
|
3036
|
+
} else if (isToolCallPart(part)) {
|
|
3037
|
+
tokens += estimateTokens(JSON.stringify(part.input));
|
|
2997
3038
|
} else {
|
|
2998
3039
|
tokens += estimateTokens(JSON.stringify(part));
|
|
2999
3040
|
}
|
|
@@ -3035,10 +3076,10 @@ function pruneMessageContent(message) {
|
|
|
3035
3076
|
return message;
|
|
3036
3077
|
}
|
|
3037
3078
|
const prunedContent = message.content.map((part) => {
|
|
3038
|
-
if (
|
|
3079
|
+
if (isToolCallPart(part)) {
|
|
3039
3080
|
return {
|
|
3040
3081
|
...part,
|
|
3041
|
-
|
|
3082
|
+
input: { _pruned: true, toolName: part.toolName }
|
|
3042
3083
|
};
|
|
3043
3084
|
}
|
|
3044
3085
|
return part;
|
|
@@ -3053,10 +3094,10 @@ function pruneToolMessage(message) {
|
|
|
3053
3094
|
return message;
|
|
3054
3095
|
}
|
|
3055
3096
|
const prunedContent = message.content.map((part) => {
|
|
3056
|
-
if (
|
|
3097
|
+
if (isToolResultPart(part)) {
|
|
3057
3098
|
return {
|
|
3058
3099
|
...part,
|
|
3059
|
-
|
|
3100
|
+
output: { type: "text", value: "pruned" }
|
|
3060
3101
|
};
|
|
3061
3102
|
}
|
|
3062
3103
|
return part;
|
|
@@ -3105,7 +3146,53 @@ function pruneMessagesByTokens(messages, config) {
|
|
|
3105
3146
|
return prunedMessages;
|
|
3106
3147
|
}
|
|
3107
3148
|
|
|
3149
|
+
// src/utils/context-status.ts
|
|
3150
|
+
var DEFAULT_CONFIG3 = {
|
|
3151
|
+
elevatedThreshold: 0.5,
|
|
3152
|
+
highThreshold: 0.7,
|
|
3153
|
+
criticalThreshold: 0.85
|
|
3154
|
+
};
|
|
3155
|
+
function defaultHighGuidance(metrics) {
|
|
3156
|
+
const used = Math.round(metrics.usagePercent * 100);
|
|
3157
|
+
const remaining = Math.round((1 - metrics.usagePercent) * 100);
|
|
3158
|
+
return `Context usage: ${used}%. You still have ${remaining}% remaining—no need to rush. Continue working thoroughly.`;
|
|
3159
|
+
}
|
|
3160
|
+
function defaultCriticalGuidance(metrics) {
|
|
3161
|
+
const used = Math.round(metrics.usagePercent * 100);
|
|
3162
|
+
return `Context usage: ${used}%. Consider wrapping up the current task or summarizing progress before continuing.`;
|
|
3163
|
+
}
|
|
3164
|
+
function getContextStatus(messages, maxTokens, config) {
|
|
3165
|
+
const { elevatedThreshold, highThreshold, criticalThreshold } = {
|
|
3166
|
+
...DEFAULT_CONFIG3,
|
|
3167
|
+
...config
|
|
3168
|
+
};
|
|
3169
|
+
const usedTokens = config?.knownTokenCount ?? estimateMessagesTokens(messages);
|
|
3170
|
+
const usagePercent = usedTokens / maxTokens;
|
|
3171
|
+
const baseStatus = { usedTokens, maxTokens, usagePercent };
|
|
3172
|
+
if (usagePercent < elevatedThreshold) {
|
|
3173
|
+
return { ...baseStatus, status: "comfortable" };
|
|
3174
|
+
}
|
|
3175
|
+
if (usagePercent < highThreshold) {
|
|
3176
|
+
return { ...baseStatus, status: "elevated" };
|
|
3177
|
+
}
|
|
3178
|
+
if (usagePercent < criticalThreshold) {
|
|
3179
|
+
const guidance2 = typeof config?.highGuidance === "function" ? config.highGuidance(baseStatus) : config?.highGuidance ?? defaultHighGuidance(baseStatus);
|
|
3180
|
+
return { ...baseStatus, status: "high", guidance: guidance2 };
|
|
3181
|
+
}
|
|
3182
|
+
const guidance = typeof config?.criticalGuidance === "function" ? config.criticalGuidance(baseStatus) : config?.criticalGuidance ?? defaultCriticalGuidance(baseStatus);
|
|
3183
|
+
return { ...baseStatus, status: "critical", guidance };
|
|
3184
|
+
}
|
|
3185
|
+
function contextNeedsAttention(status) {
|
|
3186
|
+
return status.status === "high" || status.status === "critical";
|
|
3187
|
+
}
|
|
3188
|
+
function contextNeedsCompaction(status) {
|
|
3189
|
+
return status.status === "critical";
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3108
3192
|
// src/utils/compact-conversation.ts
|
|
3193
|
+
class CompactionError extends Error {
|
|
3194
|
+
name = "CompactionError";
|
|
3195
|
+
}
|
|
3109
3196
|
async function compactConversation(messages, config, state2 = { conversationSummary: "" }) {
|
|
3110
3197
|
const currentTokens = estimateMessagesTokens(messages);
|
|
3111
3198
|
const threshold = config.compactionThreshold ?? 0.85;
|
|
@@ -3114,12 +3201,14 @@ async function compactConversation(messages, config, state2 = { conversationSumm
|
|
|
3114
3201
|
return { messages, state: state2, didCompact: false };
|
|
3115
3202
|
}
|
|
3116
3203
|
const protectCount = config.protectRecentMessages ?? 10;
|
|
3117
|
-
const
|
|
3118
|
-
const oldMessages = messages.slice(0,
|
|
3204
|
+
const splitAt = findSafeSplitIndex(messages, protectCount);
|
|
3205
|
+
const oldMessages = messages.slice(0, splitAt);
|
|
3206
|
+
const recentMessages = messages.slice(splitAt);
|
|
3119
3207
|
if (oldMessages.length === 0) {
|
|
3120
3208
|
return { messages, state: state2, didCompact: false };
|
|
3121
3209
|
}
|
|
3122
|
-
const
|
|
3210
|
+
const fileOps = extractFileOps(oldMessages);
|
|
3211
|
+
const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state2.conversationSummary, fileOps, config.summaryInstructions);
|
|
3123
3212
|
const compactedMessages = [
|
|
3124
3213
|
{
|
|
3125
3214
|
role: "user",
|
|
@@ -3157,6 +3246,8 @@ Create a structured summary of the conversation below. This summary will replace
|
|
|
3157
3246
|
{{PREVIOUS_SUMMARY}}
|
|
3158
3247
|
</previous-summary>
|
|
3159
3248
|
|
|
3249
|
+
{{FILE_OPERATIONS}}
|
|
3250
|
+
|
|
3160
3251
|
<conversation-to-summarize>
|
|
3161
3252
|
{{CONVERSATION}}
|
|
3162
3253
|
</conversation-to-summarize>
|
|
@@ -3203,9 +3294,44 @@ Brief description of what the user asked for and the current goal.
|
|
|
3203
3294
|
- Maintain the user's original terminology and naming.
|
|
3204
3295
|
- Do not editorialize or add suggestions - just capture what happened.
|
|
3205
3296
|
- Omit sections that have no relevant information.
|
|
3206
|
-
</instructions>`;
|
|
3207
|
-
async function summarizeMessages(messages, model, taskContext, previousSummary) {
|
|
3208
|
-
|
|
3297
|
+
{{SUMMARY_INSTRUCTIONS}}</instructions>`;
|
|
3298
|
+
async function summarizeMessages(messages, model, taskContext, previousSummary, fileOps, summaryInstructions) {
|
|
3299
|
+
let fileOpsBlock = "";
|
|
3300
|
+
if (fileOps) {
|
|
3301
|
+
const MAX_FILES = 50;
|
|
3302
|
+
const sanitize = (p) => p.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
3303
|
+
const readFiles = [...fileOps.read].sort().map(sanitize);
|
|
3304
|
+
const modifiedFiles = [...fileOps.modified].sort().map(sanitize);
|
|
3305
|
+
const sections = [];
|
|
3306
|
+
if (readFiles.length > 0) {
|
|
3307
|
+
const listed = readFiles.slice(0, MAX_FILES);
|
|
3308
|
+
sections.push(`Read: ${listed.join(", ")}`);
|
|
3309
|
+
if (readFiles.length > MAX_FILES) {
|
|
3310
|
+
sections.push(`... and ${readFiles.length - MAX_FILES} more read files`);
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
if (modifiedFiles.length > 0) {
|
|
3314
|
+
const listed = modifiedFiles.slice(0, MAX_FILES);
|
|
3315
|
+
sections.push(`Modified: ${listed.join(", ")}`);
|
|
3316
|
+
if (modifiedFiles.length > MAX_FILES) {
|
|
3317
|
+
sections.push(`... and ${modifiedFiles.length - MAX_FILES} more modified files`);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
if (sections.length > 0) {
|
|
3321
|
+
fileOpsBlock = `<file-operations>
|
|
3322
|
+
${sections.join(`
|
|
3323
|
+
`)}
|
|
3324
|
+
</file-operations>`;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
const templateVars = {
|
|
3328
|
+
TASK_CONTEXT: taskContext || "Not specified",
|
|
3329
|
+
PREVIOUS_SUMMARY: previousSummary || "None - this is the first compaction",
|
|
3330
|
+
CONVERSATION: formatMessagesForSummary(messages),
|
|
3331
|
+
FILE_OPERATIONS: fileOpsBlock,
|
|
3332
|
+
SUMMARY_INSTRUCTIONS: summaryInstructions ? `- ${summaryInstructions}` : ""
|
|
3333
|
+
};
|
|
3334
|
+
const prompt = SUMMARIZATION_PROMPT.replace(/\{\{(\w+)\}\}/g, (_, key) => templateVars[key] ?? "");
|
|
3209
3335
|
const result = await generateText3({
|
|
3210
3336
|
model,
|
|
3211
3337
|
messages: [
|
|
@@ -3217,6 +3343,12 @@ async function summarizeMessages(messages, model, taskContext, previousSummary)
|
|
|
3217
3343
|
});
|
|
3218
3344
|
return result.text;
|
|
3219
3345
|
}
|
|
3346
|
+
var MAX_PART_LENGTH = 500;
|
|
3347
|
+
function truncate(str) {
|
|
3348
|
+
if (str.length <= MAX_PART_LENGTH)
|
|
3349
|
+
return str;
|
|
3350
|
+
return `${str.slice(0, MAX_PART_LENGTH)}... [truncated]`;
|
|
3351
|
+
}
|
|
3220
3352
|
function formatMessagesForSummary(messages) {
|
|
3221
3353
|
return messages.map((msg, index) => {
|
|
3222
3354
|
const role = msg.role.toUpperCase();
|
|
@@ -3233,16 +3365,17 @@ ${msg.content}
|
|
|
3233
3365
|
if ("text" in part && typeof part.text === "string") {
|
|
3234
3366
|
return part.text;
|
|
3235
3367
|
}
|
|
3236
|
-
if (
|
|
3368
|
+
if (isToolCallPart(part)) {
|
|
3369
|
+
const inputStr = truncate(JSON.stringify(part.input));
|
|
3237
3370
|
return `[Tool Call: ${part.toolName}]
|
|
3238
|
-
|
|
3371
|
+
Input: ${inputStr}`;
|
|
3239
3372
|
}
|
|
3240
|
-
if (
|
|
3241
|
-
const
|
|
3373
|
+
if (isToolResultPart(part)) {
|
|
3374
|
+
const outputStr = typeof part.output === "string" ? part.output : JSON.stringify(part.output);
|
|
3242
3375
|
return `[Tool Result]
|
|
3243
|
-
${
|
|
3376
|
+
${truncate(outputStr)}`;
|
|
3244
3377
|
}
|
|
3245
|
-
return JSON.stringify(part
|
|
3378
|
+
return truncate(JSON.stringify(part));
|
|
3246
3379
|
}).join(`
|
|
3247
3380
|
|
|
3248
3381
|
`);
|
|
@@ -3251,12 +3384,117 @@ ${parts}
|
|
|
3251
3384
|
</message>`;
|
|
3252
3385
|
}
|
|
3253
3386
|
return `<message index="${index}" role="${role}">
|
|
3254
|
-
${JSON.stringify(msg.content
|
|
3387
|
+
${truncate(JSON.stringify(msg.content))}
|
|
3255
3388
|
</message>`;
|
|
3256
3389
|
}).join(`
|
|
3257
3390
|
|
|
3258
3391
|
`);
|
|
3259
3392
|
}
|
|
3393
|
+
function hasToolCalls(message) {
|
|
3394
|
+
if (message.role !== "assistant" || !Array.isArray(message.content)) {
|
|
3395
|
+
return false;
|
|
3396
|
+
}
|
|
3397
|
+
return message.content.some(isToolCallPart);
|
|
3398
|
+
}
|
|
3399
|
+
function findSafeSplitIndex(messages, protectCount) {
|
|
3400
|
+
const naiveSplit = Math.max(0, messages.length - protectCount);
|
|
3401
|
+
let splitAt = naiveSplit;
|
|
3402
|
+
while (splitAt > 0) {
|
|
3403
|
+
const msg = messages[splitAt];
|
|
3404
|
+
if (msg.role === "tool") {
|
|
3405
|
+
splitAt--;
|
|
3406
|
+
continue;
|
|
3407
|
+
}
|
|
3408
|
+
const prev = messages[splitAt - 1];
|
|
3409
|
+
if (prev?.role === "assistant" && hasToolCalls(prev)) {
|
|
3410
|
+
splitAt--;
|
|
3411
|
+
continue;
|
|
3412
|
+
}
|
|
3413
|
+
break;
|
|
3414
|
+
}
|
|
3415
|
+
if (splitAt === 0 && naiveSplit > 0) {
|
|
3416
|
+
splitAt = naiveSplit;
|
|
3417
|
+
while (splitAt < messages.length) {
|
|
3418
|
+
const msg = messages[splitAt];
|
|
3419
|
+
if (msg.role !== "tool") {
|
|
3420
|
+
const prev = messages[splitAt - 1];
|
|
3421
|
+
if (!prev || prev.role !== "assistant" || !hasToolCalls(prev)) {
|
|
3422
|
+
break;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
splitAt++;
|
|
3426
|
+
}
|
|
3427
|
+
if (splitAt >= messages.length) {
|
|
3428
|
+
splitAt = naiveSplit;
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
return splitAt;
|
|
3432
|
+
}
|
|
3433
|
+
function extractFileOps(messages) {
|
|
3434
|
+
const ops = {
|
|
3435
|
+
read: new Set,
|
|
3436
|
+
modified: new Set
|
|
3437
|
+
};
|
|
3438
|
+
for (const msg of messages) {
|
|
3439
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.content))
|
|
3440
|
+
continue;
|
|
3441
|
+
for (const part of msg.content) {
|
|
3442
|
+
if (!isToolCallPart(part))
|
|
3443
|
+
continue;
|
|
3444
|
+
const toolName = String(part.toolName).toLowerCase();
|
|
3445
|
+
const rawInput = part.input;
|
|
3446
|
+
if (typeof rawInput !== "object" || rawInput === null)
|
|
3447
|
+
continue;
|
|
3448
|
+
const input = rawInput;
|
|
3449
|
+
switch (toolName) {
|
|
3450
|
+
case "read": {
|
|
3451
|
+
const filePath = input.file_path;
|
|
3452
|
+
if (typeof filePath === "string")
|
|
3453
|
+
ops.read.add(filePath);
|
|
3454
|
+
break;
|
|
3455
|
+
}
|
|
3456
|
+
case "write":
|
|
3457
|
+
case "edit": {
|
|
3458
|
+
const filePath = input.file_path;
|
|
3459
|
+
if (typeof filePath === "string")
|
|
3460
|
+
ops.modified.add(filePath);
|
|
3461
|
+
break;
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
return ops;
|
|
3467
|
+
}
|
|
3468
|
+
function createAutoCompaction(config) {
|
|
3469
|
+
const state2 = { conversationSummary: "" };
|
|
3470
|
+
const threshold = config.compactionThreshold ?? 0.85;
|
|
3471
|
+
const prepareStep = async (args) => {
|
|
3472
|
+
const status = getContextStatus(args.messages, config.maxTokens, {
|
|
3473
|
+
criticalThreshold: threshold
|
|
3474
|
+
});
|
|
3475
|
+
if (status.status !== "critical") {
|
|
3476
|
+
return {};
|
|
3477
|
+
}
|
|
3478
|
+
let lastError;
|
|
3479
|
+
for (let attempt = 0;attempt < 2; attempt++) {
|
|
3480
|
+
try {
|
|
3481
|
+
const result = await compactConversation(args.messages, config, state2);
|
|
3482
|
+
if (result.didCompact) {
|
|
3483
|
+
state2.conversationSummary = result.state.conversationSummary;
|
|
3484
|
+
return { messages: result.messages };
|
|
3485
|
+
}
|
|
3486
|
+
return {};
|
|
3487
|
+
} catch (err) {
|
|
3488
|
+
lastError = err;
|
|
3489
|
+
if (attempt < 1) {
|
|
3490
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
throw new CompactionError("Conversation compaction failed after 2 attempts", { cause: lastError });
|
|
3495
|
+
};
|
|
3496
|
+
return { prepareStep, getState: () => ({ ...state2 }) };
|
|
3497
|
+
}
|
|
3260
3498
|
var MODEL_CONTEXT_LIMITS = {
|
|
3261
3499
|
"claude-opus-4-5": 200000,
|
|
3262
3500
|
"claude-sonnet-4-5": 200000,
|
|
@@ -3276,47 +3514,16 @@ function createCompactConfig(modelId, summarizerModel, overrides) {
|
|
|
3276
3514
|
...overrides
|
|
3277
3515
|
};
|
|
3278
3516
|
}
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
criticalThreshold: 0.85
|
|
3284
|
-
};
|
|
3285
|
-
function defaultHighGuidance(metrics) {
|
|
3286
|
-
const used = Math.round(metrics.usagePercent * 100);
|
|
3287
|
-
const remaining = Math.round((1 - metrics.usagePercent) * 100);
|
|
3288
|
-
return `Context usage: ${used}%. You still have ${remaining}% remaining—no need to rush. Continue working thoroughly.`;
|
|
3289
|
-
}
|
|
3290
|
-
function defaultCriticalGuidance(metrics) {
|
|
3291
|
-
const used = Math.round(metrics.usagePercent * 100);
|
|
3292
|
-
return `Context usage: ${used}%. Consider wrapping up the current task or summarizing progress before continuing.`;
|
|
3293
|
-
}
|
|
3294
|
-
function getContextStatus(messages, maxTokens, config) {
|
|
3295
|
-
const { elevatedThreshold, highThreshold, criticalThreshold } = {
|
|
3296
|
-
...DEFAULT_CONFIG3,
|
|
3297
|
-
...config
|
|
3298
|
-
};
|
|
3299
|
-
const usedTokens = estimateMessagesTokens(messages);
|
|
3300
|
-
const usagePercent = usedTokens / maxTokens;
|
|
3301
|
-
const baseStatus = { usedTokens, maxTokens, usagePercent };
|
|
3302
|
-
if (usagePercent < elevatedThreshold) {
|
|
3303
|
-
return { ...baseStatus, status: "comfortable" };
|
|
3304
|
-
}
|
|
3305
|
-
if (usagePercent < highThreshold) {
|
|
3306
|
-
return { ...baseStatus, status: "elevated" };
|
|
3307
|
-
}
|
|
3308
|
-
if (usagePercent < criticalThreshold) {
|
|
3309
|
-
const guidance2 = typeof config?.highGuidance === "function" ? config.highGuidance(baseStatus) : config?.highGuidance ?? defaultHighGuidance(baseStatus);
|
|
3310
|
-
return { ...baseStatus, status: "high", guidance: guidance2 };
|
|
3517
|
+
function createCompactConfigFromModels(modelId, summarizerModel, modelsMap, overrides) {
|
|
3518
|
+
const contextLength = getModelContextLength(modelId, modelsMap);
|
|
3519
|
+
if (contextLength === undefined) {
|
|
3520
|
+
throw new Error(`[bashkit] No context length found for model "${modelId}" in OpenRouter data. ` + `Provide maxTokens manually via overrides or use createCompactConfig() with a known model.`);
|
|
3311
3521
|
}
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
}
|
|
3318
|
-
function contextNeedsCompaction(status) {
|
|
3319
|
-
return status.status === "critical";
|
|
3522
|
+
return {
|
|
3523
|
+
maxTokens: contextLength,
|
|
3524
|
+
summarizerModel,
|
|
3525
|
+
...overrides
|
|
3526
|
+
};
|
|
3320
3527
|
}
|
|
3321
3528
|
// src/skills/discovery.ts
|
|
3322
3529
|
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
@@ -3660,10 +3867,12 @@ export {
|
|
|
3660
3867
|
loadSkillBundles,
|
|
3661
3868
|
loadSkillBundle,
|
|
3662
3869
|
isDebugEnabled,
|
|
3870
|
+
getModelContextLength,
|
|
3663
3871
|
getDebugLogs,
|
|
3664
3872
|
getContextStatus,
|
|
3665
3873
|
fetchSkills,
|
|
3666
3874
|
fetchSkill,
|
|
3875
|
+
fetchOpenRouterModels,
|
|
3667
3876
|
estimateTokens,
|
|
3668
3877
|
estimateMessagesTokens,
|
|
3669
3878
|
estimateMessageTokens,
|
|
@@ -3685,9 +3894,11 @@ export {
|
|
|
3685
3894
|
createEnterPlanModeTool,
|
|
3686
3895
|
createEditTool,
|
|
3687
3896
|
createE2BSandbox,
|
|
3897
|
+
createCompactConfigFromModels,
|
|
3688
3898
|
createCompactConfig,
|
|
3689
3899
|
createBudgetTracker,
|
|
3690
3900
|
createBashTool,
|
|
3901
|
+
createAutoCompaction,
|
|
3691
3902
|
createAskUserTool,
|
|
3692
3903
|
createAgentTools,
|
|
3693
3904
|
contextNeedsCompaction,
|
|
@@ -3699,5 +3910,6 @@ export {
|
|
|
3699
3910
|
anthropicPromptCacheMiddleware,
|
|
3700
3911
|
MODEL_CONTEXT_LIMITS,
|
|
3701
3912
|
LRUCacheStore,
|
|
3702
|
-
DEFAULT_CONFIG
|
|
3913
|
+
DEFAULT_CONFIG,
|
|
3914
|
+
CompactionError
|
|
3703
3915
|
};
|