bashkit 0.5.0 → 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 -255
- package/dist/tools/index.d.ts +3 -1
- package/dist/tools/task.d.ts +3 -0
- 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,98 +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
|
-
return { error: errorMessage };
|
|
2747
2785
|
}
|
|
2786
|
+
return result;
|
|
2748
2787
|
}
|
|
2749
2788
|
});
|
|
2750
2789
|
}
|
|
@@ -2939,31 +2978,39 @@ async function createAgentTools(sandbox, config) {
|
|
|
2939
2978
|
}
|
|
2940
2979
|
}
|
|
2941
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
|
+
}
|
|
2942
2997
|
let budget;
|
|
2943
2998
|
if (config?.budget) {
|
|
2944
|
-
const {
|
|
2945
|
-
if (!
|
|
2946
|
-
throw new Error("[bashkit] Budget requires either pricingProvider or modelPricing
|
|
2947
|
-
}
|
|
2948
|
-
let openRouterPricing;
|
|
2949
|
-
if (pricingProvider === "openRouter") {
|
|
2950
|
-
try {
|
|
2951
|
-
openRouterPricing = await fetchOpenRouterPricing(apiKey);
|
|
2952
|
-
} catch (err) {
|
|
2953
|
-
if (!modelPricing) {
|
|
2954
|
-
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)}`);
|
|
2955
|
-
}
|
|
2956
|
-
}
|
|
2999
|
+
const { modelPricing, maxUsd } = config.budget;
|
|
3000
|
+
if (!openRouterPricing && !modelPricing) {
|
|
3001
|
+
throw new Error("[bashkit] Budget requires either modelRegistry, pricingProvider, or modelPricing.");
|
|
2957
3002
|
}
|
|
2958
3003
|
budget = createBudgetTracker(maxUsd, {
|
|
2959
3004
|
modelPricing,
|
|
2960
3005
|
openRouterPricing
|
|
2961
3006
|
});
|
|
2962
3007
|
}
|
|
2963
|
-
return { tools, planModeState, budget };
|
|
3008
|
+
return { tools, planModeState, budget, openRouterModels };
|
|
2964
3009
|
}
|
|
2965
3010
|
// src/utils/compact-conversation.ts
|
|
2966
|
-
import {
|
|
3011
|
+
import {
|
|
3012
|
+
generateText as generateText3
|
|
3013
|
+
} from "ai";
|
|
2967
3014
|
|
|
2968
3015
|
// src/utils/prune-messages.ts
|
|
2969
3016
|
var DEFAULT_CONFIG2 = {
|
|
@@ -2984,10 +3031,10 @@ function estimateMessageTokens(message) {
|
|
|
2984
3031
|
tokens += estimateTokens(part);
|
|
2985
3032
|
} else if ("text" in part && typeof part.text === "string") {
|
|
2986
3033
|
tokens += estimateTokens(part.text);
|
|
2987
|
-
} else if (
|
|
2988
|
-
tokens += estimateTokens(JSON.stringify(part.
|
|
2989
|
-
} else if (
|
|
2990
|
-
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));
|
|
2991
3038
|
} else {
|
|
2992
3039
|
tokens += estimateTokens(JSON.stringify(part));
|
|
2993
3040
|
}
|
|
@@ -3029,10 +3076,10 @@ function pruneMessageContent(message) {
|
|
|
3029
3076
|
return message;
|
|
3030
3077
|
}
|
|
3031
3078
|
const prunedContent = message.content.map((part) => {
|
|
3032
|
-
if (
|
|
3079
|
+
if (isToolCallPart(part)) {
|
|
3033
3080
|
return {
|
|
3034
3081
|
...part,
|
|
3035
|
-
|
|
3082
|
+
input: { _pruned: true, toolName: part.toolName }
|
|
3036
3083
|
};
|
|
3037
3084
|
}
|
|
3038
3085
|
return part;
|
|
@@ -3047,10 +3094,10 @@ function pruneToolMessage(message) {
|
|
|
3047
3094
|
return message;
|
|
3048
3095
|
}
|
|
3049
3096
|
const prunedContent = message.content.map((part) => {
|
|
3050
|
-
if (
|
|
3097
|
+
if (isToolResultPart(part)) {
|
|
3051
3098
|
return {
|
|
3052
3099
|
...part,
|
|
3053
|
-
|
|
3100
|
+
output: { type: "text", value: "pruned" }
|
|
3054
3101
|
};
|
|
3055
3102
|
}
|
|
3056
3103
|
return part;
|
|
@@ -3099,7 +3146,53 @@ function pruneMessagesByTokens(messages, config) {
|
|
|
3099
3146
|
return prunedMessages;
|
|
3100
3147
|
}
|
|
3101
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
|
+
|
|
3102
3192
|
// src/utils/compact-conversation.ts
|
|
3193
|
+
class CompactionError extends Error {
|
|
3194
|
+
name = "CompactionError";
|
|
3195
|
+
}
|
|
3103
3196
|
async function compactConversation(messages, config, state2 = { conversationSummary: "" }) {
|
|
3104
3197
|
const currentTokens = estimateMessagesTokens(messages);
|
|
3105
3198
|
const threshold = config.compactionThreshold ?? 0.85;
|
|
@@ -3108,12 +3201,14 @@ async function compactConversation(messages, config, state2 = { conversationSumm
|
|
|
3108
3201
|
return { messages, state: state2, didCompact: false };
|
|
3109
3202
|
}
|
|
3110
3203
|
const protectCount = config.protectRecentMessages ?? 10;
|
|
3111
|
-
const
|
|
3112
|
-
const oldMessages = messages.slice(0,
|
|
3204
|
+
const splitAt = findSafeSplitIndex(messages, protectCount);
|
|
3205
|
+
const oldMessages = messages.slice(0, splitAt);
|
|
3206
|
+
const recentMessages = messages.slice(splitAt);
|
|
3113
3207
|
if (oldMessages.length === 0) {
|
|
3114
3208
|
return { messages, state: state2, didCompact: false };
|
|
3115
3209
|
}
|
|
3116
|
-
const
|
|
3210
|
+
const fileOps = extractFileOps(oldMessages);
|
|
3211
|
+
const newSummary = await summarizeMessages(oldMessages, config.summarizerModel, config.taskContext, state2.conversationSummary, fileOps, config.summaryInstructions);
|
|
3117
3212
|
const compactedMessages = [
|
|
3118
3213
|
{
|
|
3119
3214
|
role: "user",
|
|
@@ -3151,6 +3246,8 @@ Create a structured summary of the conversation below. This summary will replace
|
|
|
3151
3246
|
{{PREVIOUS_SUMMARY}}
|
|
3152
3247
|
</previous-summary>
|
|
3153
3248
|
|
|
3249
|
+
{{FILE_OPERATIONS}}
|
|
3250
|
+
|
|
3154
3251
|
<conversation-to-summarize>
|
|
3155
3252
|
{{CONVERSATION}}
|
|
3156
3253
|
</conversation-to-summarize>
|
|
@@ -3197,9 +3294,44 @@ Brief description of what the user asked for and the current goal.
|
|
|
3197
3294
|
- Maintain the user's original terminology and naming.
|
|
3198
3295
|
- Do not editorialize or add suggestions - just capture what happened.
|
|
3199
3296
|
- Omit sections that have no relevant information.
|
|
3200
|
-
</instructions>`;
|
|
3201
|
-
async function summarizeMessages(messages, model, taskContext, previousSummary) {
|
|
3202
|
-
|
|
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] ?? "");
|
|
3203
3335
|
const result = await generateText3({
|
|
3204
3336
|
model,
|
|
3205
3337
|
messages: [
|
|
@@ -3211,6 +3343,12 @@ async function summarizeMessages(messages, model, taskContext, previousSummary)
|
|
|
3211
3343
|
});
|
|
3212
3344
|
return result.text;
|
|
3213
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
|
+
}
|
|
3214
3352
|
function formatMessagesForSummary(messages) {
|
|
3215
3353
|
return messages.map((msg, index) => {
|
|
3216
3354
|
const role = msg.role.toUpperCase();
|
|
@@ -3227,16 +3365,17 @@ ${msg.content}
|
|
|
3227
3365
|
if ("text" in part && typeof part.text === "string") {
|
|
3228
3366
|
return part.text;
|
|
3229
3367
|
}
|
|
3230
|
-
if (
|
|
3368
|
+
if (isToolCallPart(part)) {
|
|
3369
|
+
const inputStr = truncate(JSON.stringify(part.input));
|
|
3231
3370
|
return `[Tool Call: ${part.toolName}]
|
|
3232
|
-
|
|
3371
|
+
Input: ${inputStr}`;
|
|
3233
3372
|
}
|
|
3234
|
-
if (
|
|
3235
|
-
const
|
|
3373
|
+
if (isToolResultPart(part)) {
|
|
3374
|
+
const outputStr = typeof part.output === "string" ? part.output : JSON.stringify(part.output);
|
|
3236
3375
|
return `[Tool Result]
|
|
3237
|
-
${
|
|
3376
|
+
${truncate(outputStr)}`;
|
|
3238
3377
|
}
|
|
3239
|
-
return JSON.stringify(part
|
|
3378
|
+
return truncate(JSON.stringify(part));
|
|
3240
3379
|
}).join(`
|
|
3241
3380
|
|
|
3242
3381
|
`);
|
|
@@ -3245,12 +3384,117 @@ ${parts}
|
|
|
3245
3384
|
</message>`;
|
|
3246
3385
|
}
|
|
3247
3386
|
return `<message index="${index}" role="${role}">
|
|
3248
|
-
${JSON.stringify(msg.content
|
|
3387
|
+
${truncate(JSON.stringify(msg.content))}
|
|
3249
3388
|
</message>`;
|
|
3250
3389
|
}).join(`
|
|
3251
3390
|
|
|
3252
3391
|
`);
|
|
3253
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
|
+
}
|
|
3254
3498
|
var MODEL_CONTEXT_LIMITS = {
|
|
3255
3499
|
"claude-opus-4-5": 200000,
|
|
3256
3500
|
"claude-sonnet-4-5": 200000,
|
|
@@ -3270,47 +3514,16 @@ function createCompactConfig(modelId, summarizerModel, overrides) {
|
|
|
3270
3514
|
...overrides
|
|
3271
3515
|
};
|
|
3272
3516
|
}
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
criticalThreshold: 0.85
|
|
3278
|
-
};
|
|
3279
|
-
function defaultHighGuidance(metrics) {
|
|
3280
|
-
const used = Math.round(metrics.usagePercent * 100);
|
|
3281
|
-
const remaining = Math.round((1 - metrics.usagePercent) * 100);
|
|
3282
|
-
return `Context usage: ${used}%. You still have ${remaining}% remaining—no need to rush. Continue working thoroughly.`;
|
|
3283
|
-
}
|
|
3284
|
-
function defaultCriticalGuidance(metrics) {
|
|
3285
|
-
const used = Math.round(metrics.usagePercent * 100);
|
|
3286
|
-
return `Context usage: ${used}%. Consider wrapping up the current task or summarizing progress before continuing.`;
|
|
3287
|
-
}
|
|
3288
|
-
function getContextStatus(messages, maxTokens, config) {
|
|
3289
|
-
const { elevatedThreshold, highThreshold, criticalThreshold } = {
|
|
3290
|
-
...DEFAULT_CONFIG3,
|
|
3291
|
-
...config
|
|
3292
|
-
};
|
|
3293
|
-
const usedTokens = estimateMessagesTokens(messages);
|
|
3294
|
-
const usagePercent = usedTokens / maxTokens;
|
|
3295
|
-
const baseStatus = { usedTokens, maxTokens, usagePercent };
|
|
3296
|
-
if (usagePercent < elevatedThreshold) {
|
|
3297
|
-
return { ...baseStatus, status: "comfortable" };
|
|
3298
|
-
}
|
|
3299
|
-
if (usagePercent < highThreshold) {
|
|
3300
|
-
return { ...baseStatus, status: "elevated" };
|
|
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.`);
|
|
3301
3521
|
}
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
return { ...baseStatus, status: "critical", guidance };
|
|
3308
|
-
}
|
|
3309
|
-
function contextNeedsAttention(status) {
|
|
3310
|
-
return status.status === "high" || status.status === "critical";
|
|
3311
|
-
}
|
|
3312
|
-
function contextNeedsCompaction(status) {
|
|
3313
|
-
return status.status === "critical";
|
|
3522
|
+
return {
|
|
3523
|
+
maxTokens: contextLength,
|
|
3524
|
+
summarizerModel,
|
|
3525
|
+
...overrides
|
|
3526
|
+
};
|
|
3314
3527
|
}
|
|
3315
3528
|
// src/skills/discovery.ts
|
|
3316
3529
|
import { readdir as readdir2, readFile as readFile2, stat } from "node:fs/promises";
|
|
@@ -3654,10 +3867,12 @@ export {
|
|
|
3654
3867
|
loadSkillBundles,
|
|
3655
3868
|
loadSkillBundle,
|
|
3656
3869
|
isDebugEnabled,
|
|
3870
|
+
getModelContextLength,
|
|
3657
3871
|
getDebugLogs,
|
|
3658
3872
|
getContextStatus,
|
|
3659
3873
|
fetchSkills,
|
|
3660
3874
|
fetchSkill,
|
|
3875
|
+
fetchOpenRouterModels,
|
|
3661
3876
|
estimateTokens,
|
|
3662
3877
|
estimateMessagesTokens,
|
|
3663
3878
|
estimateMessageTokens,
|
|
@@ -3679,9 +3894,11 @@ export {
|
|
|
3679
3894
|
createEnterPlanModeTool,
|
|
3680
3895
|
createEditTool,
|
|
3681
3896
|
createE2BSandbox,
|
|
3897
|
+
createCompactConfigFromModels,
|
|
3682
3898
|
createCompactConfig,
|
|
3683
3899
|
createBudgetTracker,
|
|
3684
3900
|
createBashTool,
|
|
3901
|
+
createAutoCompaction,
|
|
3685
3902
|
createAskUserTool,
|
|
3686
3903
|
createAgentTools,
|
|
3687
3904
|
contextNeedsCompaction,
|
|
@@ -3693,5 +3910,6 @@ export {
|
|
|
3693
3910
|
anthropicPromptCacheMiddleware,
|
|
3694
3911
|
MODEL_CONTEXT_LIMITS,
|
|
3695
3912
|
LRUCacheStore,
|
|
3696
|
-
DEFAULT_CONFIG
|
|
3913
|
+
DEFAULT_CONFIG,
|
|
3914
|
+
CompactionError
|
|
3697
3915
|
};
|