@probelabs/probe 0.6.0-rc161 → 0.6.0-rc162

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.
@@ -552,6 +552,162 @@ var init_BytePairEncodingCore = __esm({
552
552
  }
553
553
  });
554
554
 
555
+ // node_modules/gpt-tokenizer/esm/functionCalling.js
556
+ function countMessageTokens(message, countStringTokens) {
557
+ let tokens = 0;
558
+ if (message.role) {
559
+ tokens += countStringTokens(message.role);
560
+ }
561
+ if (message.content) {
562
+ tokens += countStringTokens(message.content);
563
+ }
564
+ if (message.name) {
565
+ tokens += countStringTokens(message.name) + MESSAGE_NAME_TOKEN_OVERHEAD;
566
+ }
567
+ if (message.function_call) {
568
+ const { name, arguments: args } = message.function_call;
569
+ if (name) {
570
+ tokens += countStringTokens(name);
571
+ }
572
+ if (args) {
573
+ tokens += countStringTokens(args);
574
+ }
575
+ tokens += FUNCTION_CALL_METADATA_TOKEN_OVERHEAD;
576
+ }
577
+ tokens += MESSAGE_TOKEN_OVERHEAD;
578
+ if (message.role === "function") {
579
+ tokens -= FUNCTION_ROLE_TOKEN_DISCOUNT;
580
+ }
581
+ return tokens;
582
+ }
583
+ function formatObjectProperties(obj, indent, formatType) {
584
+ if (!obj.properties) {
585
+ return "";
586
+ }
587
+ const lines = [];
588
+ const requiredParams = new Set(obj.required ?? []);
589
+ const indentString = " ".repeat(indent);
590
+ for (const [name, param] of Object.entries(obj.properties)) {
591
+ if (param.description && indent < 2) {
592
+ lines.push(`${indentString}// ${param.description}`);
593
+ }
594
+ const isRequired = requiredParams.has(name);
595
+ const formattedType = formatType(param, indent);
596
+ lines.push(`${indentString}${name}${isRequired ? "" : "?"}: ${formattedType},`);
597
+ }
598
+ return lines.join("\n");
599
+ }
600
+ function formatFunctionType(param, indent) {
601
+ switch (param.type) {
602
+ case "string":
603
+ return param.enum?.map((value) => JSON.stringify(value)).join(" | ") ?? "string";
604
+ case "integer":
605
+ case "number":
606
+ return param.enum?.map((value) => `${value}`).join(" | ") ?? "number";
607
+ case "boolean":
608
+ return "boolean";
609
+ case "null":
610
+ return "null";
611
+ case "array":
612
+ return param.items ? `${formatFunctionType(param.items, indent)}[]` : "any[]";
613
+ case "object": {
614
+ const inner = formatObjectProperties(param, indent + 2, formatFunctionType);
615
+ const closingIndent = " ".repeat(indent);
616
+ return `{
617
+ ${inner}
618
+ ${closingIndent}}`;
619
+ }
620
+ default:
621
+ return "any";
622
+ }
623
+ }
624
+ function formatFunctionDefinitions(functions) {
625
+ const lines = ["namespace functions {", ""];
626
+ for (const fn of functions) {
627
+ if (fn.description) {
628
+ lines.push(`// ${fn.description}`);
629
+ }
630
+ const { parameters } = fn;
631
+ const properties = parameters?.properties;
632
+ if (!parameters || !properties || Object.keys(properties).length === 0) {
633
+ lines.push(`type ${fn.name} = () => any;`);
634
+ } else {
635
+ lines.push(`type ${fn.name} = (_: {`);
636
+ const formattedProperties = formatObjectProperties(parameters, 0, formatFunctionType);
637
+ if (formattedProperties.length > 0) {
638
+ lines.push(formattedProperties);
639
+ }
640
+ lines.push("}) => any;");
641
+ }
642
+ lines.push("");
643
+ }
644
+ lines.push("} // namespace functions");
645
+ return lines.join("\n");
646
+ }
647
+ function estimateTokensInFunctions(functions, countStringTokens) {
648
+ const formatted = formatFunctionDefinitions(functions);
649
+ let tokens = countStringTokens(formatted);
650
+ tokens += FUNCTION_DEFINITION_TOKEN_OVERHEAD;
651
+ return tokens;
652
+ }
653
+ function padSystemMessage(message, hasFunctions, isSystemPadded) {
654
+ if (!hasFunctions || isSystemPadded || message.role !== "system") {
655
+ return message;
656
+ }
657
+ if (!message.content || message.content.endsWith(NEWLINE)) {
658
+ return message;
659
+ }
660
+ return {
661
+ ...message,
662
+ content: `${message.content}${NEWLINE}`
663
+ };
664
+ }
665
+ function computeChatCompletionTokenCount(request, countStringTokens) {
666
+ const { messages, functions, function_call: functionCall } = request;
667
+ const hasFunctions = Boolean(functions && functions.length > 0);
668
+ let paddedSystem = false;
669
+ let total = 0;
670
+ for (const message of messages) {
671
+ const messageToCount = padSystemMessage(message, hasFunctions, paddedSystem);
672
+ if (messageToCount !== message && message.role === "system") {
673
+ paddedSystem = true;
674
+ } else if (message.role === "system" && hasFunctions && !paddedSystem) {
675
+ paddedSystem = true;
676
+ }
677
+ total += countMessageTokens(messageToCount, countStringTokens);
678
+ }
679
+ total += COMPLETION_REQUEST_TOKEN_OVERHEAD;
680
+ if (hasFunctions && functions) {
681
+ total += estimateTokensInFunctions(functions, countStringTokens);
682
+ if (messages.some((message) => message.role === "system")) {
683
+ total -= SYSTEM_FUNCTION_TOKEN_DEDUCTION;
684
+ }
685
+ }
686
+ if (functionCall && functionCall !== "auto") {
687
+ if (functionCall === "none") {
688
+ total += FUNCTION_CALL_NONE_TOKEN_OVERHEAD;
689
+ } else if (typeof functionCall === "object" && functionCall.name) {
690
+ total += countStringTokens(functionCall.name) + FUNCTION_CALL_NAME_TOKEN_OVERHEAD;
691
+ }
692
+ }
693
+ return total;
694
+ }
695
+ var MESSAGE_TOKEN_OVERHEAD, MESSAGE_NAME_TOKEN_OVERHEAD, FUNCTION_ROLE_TOKEN_DISCOUNT, FUNCTION_CALL_METADATA_TOKEN_OVERHEAD, FUNCTION_DEFINITION_TOKEN_OVERHEAD, COMPLETION_REQUEST_TOKEN_OVERHEAD, FUNCTION_CALL_NAME_TOKEN_OVERHEAD, FUNCTION_CALL_NONE_TOKEN_OVERHEAD, SYSTEM_FUNCTION_TOKEN_DEDUCTION, NEWLINE;
696
+ var init_functionCalling = __esm({
697
+ "node_modules/gpt-tokenizer/esm/functionCalling.js"() {
698
+ MESSAGE_TOKEN_OVERHEAD = 3;
699
+ MESSAGE_NAME_TOKEN_OVERHEAD = 1;
700
+ FUNCTION_ROLE_TOKEN_DISCOUNT = 2;
701
+ FUNCTION_CALL_METADATA_TOKEN_OVERHEAD = 3;
702
+ FUNCTION_DEFINITION_TOKEN_OVERHEAD = 9;
703
+ COMPLETION_REQUEST_TOKEN_OVERHEAD = 3;
704
+ FUNCTION_CALL_NAME_TOKEN_OVERHEAD = 4;
705
+ FUNCTION_CALL_NONE_TOKEN_OVERHEAD = 1;
706
+ SYSTEM_FUNCTION_TOKEN_DEDUCTION = 4;
707
+ NEWLINE = "\n";
708
+ }
709
+ });
710
+
555
711
  // node_modules/gpt-tokenizer/esm/modelsChatEnabled.gen.js
556
712
  var chatEnabledModels;
557
713
  var init_modelsChatEnabled_gen = __esm({
@@ -565,11 +721,12 @@ var modelsMap_exports = {};
565
721
  __export(modelsMap_exports, {
566
722
  cl100k_base: () => cl100k_base,
567
723
  o200k_base: () => o200k_base,
724
+ o200k_harmony: () => o200k_harmony,
568
725
  p50k_base: () => p50k_base,
569
726
  p50k_edit: () => p50k_edit,
570
727
  r50k_base: () => r50k_base
571
728
  });
572
- var p50k_base, r50k_base, p50k_edit, cl100k_base, o200k_base;
729
+ var p50k_base, r50k_base, p50k_edit, cl100k_base, o200k_base, o200k_harmony;
573
730
  var init_modelsMap = __esm({
574
731
  "node_modules/gpt-tokenizer/esm/modelsMap.js"() {
575
732
  p50k_base = [
@@ -644,11 +801,15 @@ var init_modelsMap = __esm({
644
801
  "davinci-002"
645
802
  ];
646
803
  o200k_base = [];
804
+ o200k_harmony = [
805
+ "gpt-oss-20b",
806
+ "gpt-oss-120b"
807
+ ];
647
808
  }
648
809
  });
649
810
 
650
811
  // node_modules/gpt-tokenizer/esm/specialTokens.js
651
- var EndOfText, FimPrefix, FimMiddle, FimSuffix, ImStart, ImEnd, ImSep, EndOfPrompt;
812
+ var EndOfText, FimPrefix, FimMiddle, FimSuffix, ImStart, ImEnd, ImSep, EndOfPrompt, HarmonyStartOfText, HarmonyStart, HarmonyEnd, HarmonyMessage, HarmonyChannel, HarmonyReturn, HarmonyConstrain, HarmonyCall;
652
813
  var init_specialTokens = __esm({
653
814
  "node_modules/gpt-tokenizer/esm/specialTokens.js"() {
654
815
  EndOfText = "<|endoftext|>";
@@ -659,6 +820,14 @@ var init_specialTokens = __esm({
659
820
  ImEnd = "<|im_end|>";
660
821
  ImSep = "<|im_sep|>";
661
822
  EndOfPrompt = "<|endofprompt|>";
823
+ HarmonyStartOfText = "<|startoftext|>";
824
+ HarmonyStart = "<|start|>";
825
+ HarmonyEnd = "<|end|>";
826
+ HarmonyMessage = "<|message|>";
827
+ HarmonyChannel = "<|channel|>";
828
+ HarmonyReturn = "<|return|>";
829
+ HarmonyConstrain = "<|constrain|>";
830
+ HarmonyCall = "<|call|>";
662
831
  }
663
832
  });
664
833
 
@@ -685,11 +854,16 @@ var init_mapping = __esm({
685
854
  });
686
855
 
687
856
  // node_modules/gpt-tokenizer/esm/encodingParams/constants.js
688
- var R50K_TOKEN_SPLIT_REGEX, CL_AND_O_TOKEN_SPLIT_PATTERN;
857
+ var R50K_TOKEN_SPLIT_REGEX, CONTRACTION_SUFFIX_PATTERN, OPTIONAL_CONTRACTION_SUFFIX, CL100K_TOKEN_SPLIT_PATTERN, CL100K_TOKEN_SPLIT_REGEX, O200K_TOKEN_SPLIT_PATTERN, O200K_TOKEN_SPLIT_REGEX;
689
858
  var init_constants2 = __esm({
690
859
  "node_modules/gpt-tokenizer/esm/encodingParams/constants.js"() {
691
860
  R50K_TOKEN_SPLIT_REGEX = new RegExp("'s|'t|'re|'ve|'m|'ll|'d| ?\\p{L}+| ?\\p{N}+| ?[^\\s\\p{L}\\p{N}]+|\\s+(?!\\S)|\\s+", "gu");
692
- CL_AND_O_TOKEN_SPLIT_PATTERN = new RegExp("(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?\\p{L}+|\\p{N}{1,3}| ?[^\\s\\p{L}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+", "giu");
861
+ CONTRACTION_SUFFIX_PATTERN = String.raw`'(?:[sS]|[dD]|[mM]|[tT]|[lL][lL]|[vV][eE]|[rR][eE])`;
862
+ OPTIONAL_CONTRACTION_SUFFIX = String.raw`(?:${CONTRACTION_SUFFIX_PATTERN})?`;
863
+ CL100K_TOKEN_SPLIT_PATTERN = String.raw`${CONTRACTION_SUFFIX_PATTERN}|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s+$|\s*[\r\n]|\s+(?!\S)|\s`;
864
+ CL100K_TOKEN_SPLIT_REGEX = new RegExp(CL100K_TOKEN_SPLIT_PATTERN, "gu");
865
+ O200K_TOKEN_SPLIT_PATTERN = String.raw`[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]*[\p{Ll}\p{Lm}\p{Lo}\p{M}]+${OPTIONAL_CONTRACTION_SUFFIX}|[^\r\n\p{L}\p{N}]?[\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{M}]+[\p{Ll}\p{Lm}\p{Lo}\p{M}]*${OPTIONAL_CONTRACTION_SUFFIX}|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n/]*|\s*[\r\n]+|\s+(?!\S)|\s+`;
866
+ O200K_TOKEN_SPLIT_REGEX = new RegExp(O200K_TOKEN_SPLIT_PATTERN, "gu");
693
867
  }
694
868
  });
695
869
 
@@ -706,7 +880,7 @@ function Cl100KBase(bytePairRankDecoder) {
706
880
  [EndOfPrompt, 100276]
707
881
  ]);
708
882
  return {
709
- tokenSplitRegex: CL_AND_O_TOKEN_SPLIT_PATTERN,
883
+ tokenSplitRegex: CL100K_TOKEN_SPLIT_REGEX,
710
884
  bytePairRankDecoder,
711
885
  specialTokensEncoder: specialTokenMapping
712
886
  };
@@ -720,26 +894,69 @@ var init_cl100k_base = __esm({
720
894
 
721
895
  // node_modules/gpt-tokenizer/esm/encodingParams/o200k_base.js
722
896
  function O200KBase(bytePairRankDecoder) {
723
- const specialTokenMapping = /* @__PURE__ */ new Map([
724
- [EndOfText, 199999],
725
- [FimPrefix, 2e5],
726
- [FimMiddle, 200001],
727
- [FimSuffix, 200002],
728
- [ImStart, 200003],
729
- [ImEnd, 200004],
730
- [ImSep, 200005],
731
- [EndOfPrompt, 200006]
732
- ]);
733
897
  return {
734
- tokenSplitRegex: CL_AND_O_TOKEN_SPLIT_PATTERN,
898
+ tokenSplitRegex: O200K_TOKEN_SPLIT_REGEX,
735
899
  bytePairRankDecoder,
736
- specialTokensEncoder: specialTokenMapping
900
+ specialTokensEncoder: createO200KSpecialTokenMap()
737
901
  };
738
902
  }
903
+ var O200K_BASE_SPECIAL_TOKEN_ENTRIES, createO200KSpecialTokenMap;
739
904
  var init_o200k_base2 = __esm({
740
905
  "node_modules/gpt-tokenizer/esm/encodingParams/o200k_base.js"() {
741
906
  init_specialTokens();
742
907
  init_constants2();
908
+ O200K_BASE_SPECIAL_TOKEN_ENTRIES = [
909
+ [EndOfText, 199999],
910
+ [FimPrefix, 2e5],
911
+ [FimMiddle, 200001],
912
+ [FimSuffix, 200002],
913
+ [ImStart, 200003],
914
+ [ImEnd, 200004],
915
+ [ImSep, 200005],
916
+ [EndOfPrompt, 200006]
917
+ ];
918
+ createO200KSpecialTokenMap = () => new Map(O200K_BASE_SPECIAL_TOKEN_ENTRIES);
919
+ }
920
+ });
921
+
922
+ // node_modules/gpt-tokenizer/esm/encodingParams/o200k_harmony.js
923
+ function O200KHarmony(bytePairRankDecoder) {
924
+ const specialTokensEncoder = new Map(STATIC_SPECIAL_TOKEN_ENTRIES);
925
+ for (let tokenId = RESERVED_TOKEN_RANGE_START; tokenId < RESERVED_TOKEN_RANGE_END; tokenId += 1) {
926
+ specialTokensEncoder.set(`<|reserved_${tokenId}|>`, tokenId);
927
+ }
928
+ specialTokensEncoder.set(EndOfPrompt, 200018);
929
+ return {
930
+ tokenSplitRegex: O200K_TOKEN_SPLIT_REGEX,
931
+ bytePairRankDecoder,
932
+ specialTokensEncoder,
933
+ chatFormatter: "harmony"
934
+ };
935
+ }
936
+ var RESERVED_TOKEN_RANGE_START, RESERVED_TOKEN_RANGE_END, STATIC_SPECIAL_TOKEN_ENTRIES;
937
+ var init_o200k_harmony = __esm({
938
+ "node_modules/gpt-tokenizer/esm/encodingParams/o200k_harmony.js"() {
939
+ init_specialTokens();
940
+ init_constants2();
941
+ RESERVED_TOKEN_RANGE_START = 200013;
942
+ RESERVED_TOKEN_RANGE_END = 201088;
943
+ STATIC_SPECIAL_TOKEN_ENTRIES = [
944
+ [HarmonyStartOfText, 199998],
945
+ [EndOfText, 199999],
946
+ ["<|reserved_200000|>", 2e5],
947
+ ["<|reserved_200001|>", 200001],
948
+ [HarmonyReturn, 200002],
949
+ [HarmonyConstrain, 200003],
950
+ ["<|reserved_200004|>", 200004],
951
+ [HarmonyChannel, 200005],
952
+ [HarmonyStart, 200006],
953
+ [HarmonyEnd, 200007],
954
+ [HarmonyMessage, 200008],
955
+ ["<|reserved_200009|>", 200009],
956
+ ["<|reserved_200010|>", 200010],
957
+ ["<|reserved_200011|>", 200011],
958
+ [HarmonyCall, 200012]
959
+ ];
743
960
  }
744
961
  });
745
962
 
@@ -813,6 +1030,8 @@ function getEncodingParams(encodingName, getMergeableRanks) {
813
1030
  return Cl100KBase(mergeableBytePairRanks);
814
1031
  case "o200k_base":
815
1032
  return O200KBase(mergeableBytePairRanks);
1033
+ case "o200k_harmony":
1034
+ return O200KHarmony(mergeableBytePairRanks);
816
1035
  default:
817
1036
  throw new Error(`Unknown encoding name: ${encodingName}`);
818
1037
  }
@@ -821,6 +1040,7 @@ var init_modelParams = __esm({
821
1040
  "node_modules/gpt-tokenizer/esm/modelParams.js"() {
822
1041
  init_cl100k_base();
823
1042
  init_o200k_base2();
1043
+ init_o200k_harmony();
824
1044
  init_p50k_base();
825
1045
  init_p50k_edit();
826
1046
  init_r50k_base();
@@ -833,6 +1053,7 @@ var init_GptEncoding = __esm({
833
1053
  "node_modules/gpt-tokenizer/esm/GptEncoding.js"() {
834
1054
  init_BytePairEncodingCore();
835
1055
  init_constants();
1056
+ init_functionCalling();
836
1057
  init_mapping();
837
1058
  init_modelParams();
838
1059
  init_specialTokens();
@@ -851,8 +1072,10 @@ var init_GptEncoding = __esm({
851
1072
  specialTokensSet;
852
1073
  allSpecialTokenRegex;
853
1074
  defaultSpecialTokenConfig;
1075
+ chatFormatter;
1076
+ countChatCompletionTokens;
854
1077
  vocabularySize;
855
- constructor({ bytePairRankDecoder: mergeableBytePairRanks, specialTokensEncoder, expectedVocabularySize, modelName, modelSpec, ...rest }) {
1078
+ constructor({ bytePairRankDecoder: mergeableBytePairRanks, specialTokensEncoder, expectedVocabularySize, modelName, modelSpec, chatFormatter, ...rest }) {
856
1079
  this.specialTokensEncoder = specialTokensEncoder;
857
1080
  this.specialTokensSet = new Set(this.specialTokensEncoder.keys());
858
1081
  this.allSpecialTokenRegex = getSpecialTokenRegex(this.specialTokensSet);
@@ -885,8 +1108,69 @@ var init_GptEncoding = __esm({
885
1108
  this.setMergeCacheSize = this.setMergeCacheSize.bind(this);
886
1109
  this.clearMergeCache = this.clearMergeCache.bind(this);
887
1110
  this.estimateCost = this.estimateCost.bind(this);
1111
+ if (modelSpec?.supported_features?.includes("function_calling")) {
1112
+ this.countChatCompletionTokens = this.countChatCompletionTokensInternal.bind(this);
1113
+ }
888
1114
  this.modelName = modelName;
889
1115
  this.modelSpec = modelSpec;
1116
+ this.chatFormatter = chatFormatter ?? "chatml";
1117
+ }
1118
+ *encodeHarmonyChatGenerator(chat, encodeOptions) {
1119
+ const harmonyStart = this.specialTokensEncoder.get(HarmonyStart);
1120
+ const harmonyMessage = this.specialTokensEncoder.get(HarmonyMessage);
1121
+ const harmonyEnd = this.specialTokensEncoder.get(HarmonyEnd);
1122
+ const harmonyReturn = this.specialTokensEncoder.get(HarmonyReturn);
1123
+ const harmonyCall = this.specialTokensEncoder.get(HarmonyCall);
1124
+ const harmonyChannel = this.specialTokensEncoder.get(HarmonyChannel);
1125
+ const harmonyConstrain = this.specialTokensEncoder.get(HarmonyConstrain);
1126
+ if (harmonyStart === void 0 || harmonyMessage === void 0 || harmonyEnd === void 0 || harmonyReturn === void 0 || harmonyCall === void 0 || harmonyChannel === void 0 || harmonyConstrain === void 0) {
1127
+ throw new Error("Harmony chat format requires dedicated special tokens.");
1128
+ }
1129
+ const encodeHeaderText = (text) => text.length > 0 ? this.encode(text) : [];
1130
+ const resolveTerminatorToken = (terminator) => {
1131
+ switch (terminator) {
1132
+ case "<|return|>":
1133
+ return harmonyReturn;
1134
+ case "<|call|>":
1135
+ return harmonyCall;
1136
+ // eslint-disable-next-line unicorn/no-useless-switch-case
1137
+ case "<|end|>":
1138
+ default:
1139
+ return harmonyEnd;
1140
+ }
1141
+ };
1142
+ for (const message of chat) {
1143
+ if (message.content === void 0) {
1144
+ throw new Error("Content must be defined for all messages.");
1145
+ }
1146
+ const roleOrName = message.name ?? message.role ?? "user";
1147
+ yield [harmonyStart];
1148
+ yield encodeHeaderText(roleOrName);
1149
+ const recipientInRole = message.recipient && (message.recipientPlacement === "role" || !message.channel);
1150
+ const recipientInChannel = message.recipient && !recipientInRole;
1151
+ if (recipientInRole) {
1152
+ yield encodeHeaderText(` to=${message.recipient}`);
1153
+ }
1154
+ if (message.channel) {
1155
+ yield [harmonyChannel];
1156
+ yield encodeHeaderText(message.channel);
1157
+ if (recipientInChannel) {
1158
+ yield encodeHeaderText(` to=${message.recipient}`);
1159
+ }
1160
+ }
1161
+ if (message.constraint) {
1162
+ yield [harmonyConstrain];
1163
+ yield encodeHeaderText(message.constraint);
1164
+ }
1165
+ yield [harmonyMessage];
1166
+ yield* this.encodeGenerator(message.content, encodeOptions);
1167
+ yield [resolveTerminatorToken(message.terminator)];
1168
+ }
1169
+ const assistantPrime = encodeOptions?.primeWithAssistantResponse ?? "assistant";
1170
+ if (assistantPrime.length > 0) {
1171
+ yield [harmonyStart];
1172
+ yield encodeHeaderText(assistantPrime);
1173
+ }
890
1174
  }
891
1175
  static getEncodingApi(encodingName, getMergeableRanks) {
892
1176
  const modelParams = getEncodingParams(encodingName, getMergeableRanks);
@@ -959,9 +1243,16 @@ var init_GptEncoding = __esm({
959
1243
  throw new Error("Model name must be provided either during initialization or passed in to the method.");
960
1244
  }
961
1245
  const params = chatModelParams[model];
1246
+ if (!params) {
1247
+ throw new Error(`Model '${model}' does not support chat.`);
1248
+ }
1249
+ if (this.chatFormatter === "harmony") {
1250
+ yield* this.encodeHarmonyChatGenerator(chat, encodeOptions);
1251
+ return;
1252
+ }
962
1253
  const chatStartToken = this.specialTokensEncoder.get(ImStart);
963
1254
  const chatEndToken = this.specialTokensEncoder.get(ImEnd);
964
- if (!params || chatStartToken === void 0 || chatEndToken === void 0) {
1255
+ if (chatStartToken === void 0 || chatEndToken === void 0) {
965
1256
  throw new Error(`Model '${model}' does not support chat.`);
966
1257
  }
967
1258
  const allowedSpecial = /* @__PURE__ */ new Set([ImSep]);
@@ -984,8 +1275,11 @@ var init_GptEncoding = __esm({
984
1275
  yield [chatEndToken];
985
1276
  yield encodedMessageSeparator;
986
1277
  }
987
- yield [chatStartToken];
988
- yield* this.encodeGenerator("assistant", encodeOptions);
1278
+ const assistantPrime = encodeOptions?.primeWithAssistantResponse ?? "assistant";
1279
+ if (assistantPrime.length > 0) {
1280
+ yield [chatStartToken];
1281
+ yield* this.encodeGenerator(assistantPrime, encodeOptions);
1282
+ }
989
1283
  if (encodedRoleSeparator.length > 0) {
990
1284
  yield encodedRoleSeparator;
991
1285
  }
@@ -1043,6 +1337,15 @@ var init_GptEncoding = __esm({
1043
1337
  }
1044
1338
  return count;
1045
1339
  }
1340
+ countStringTokens(text) {
1341
+ if (!text) {
1342
+ return 0;
1343
+ }
1344
+ return this.bytePairEncodingCoreProcessor.countNative(text);
1345
+ }
1346
+ countChatCompletionTokensInternal(request) {
1347
+ return computeChatCompletionTokenCount(request, (text) => this.countStringTokens(text));
1348
+ }
1046
1349
  setMergeCacheSize(size) {
1047
1350
  this.bytePairEncodingCoreProcessor.setMergeCacheSize(size);
1048
1351
  }
@@ -7483,7 +7786,15 @@ function parseXmlToolCall(xmlString, validTools = DEFAULT_VALID_TOOLS) {
7483
7786
  if (openIndex === -1) {
7484
7787
  continue;
7485
7788
  }
7486
- let closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
7789
+ let closeIndex;
7790
+ if (toolName === "attempt_completion") {
7791
+ closeIndex = xmlString.lastIndexOf(closeTag);
7792
+ if (closeIndex !== -1 && closeIndex <= openIndex + openTag.length) {
7793
+ closeIndex = -1;
7794
+ }
7795
+ } else {
7796
+ closeIndex = xmlString.indexOf(closeTag, openIndex + openTag.length);
7797
+ }
7487
7798
  let hasClosingTag = closeIndex !== -1;
7488
7799
  if (closeIndex === -1) {
7489
7800
  closeIndex = xmlString.length;
@@ -17148,10 +17459,22 @@ function extractThinkingContent(xmlString) {
17148
17459
  return thinkingMatch ? thinkingMatch[1].trim() : null;
17149
17460
  }
17150
17461
  function checkAttemptCompleteRecovery(cleanedXmlString, validTools = []) {
17151
- const attemptCompletionMatch = cleanedXmlString.match(/<attempt_completion>([\s\S]*?)(?:<\/attempt_completion>|$)/);
17152
- if (attemptCompletionMatch) {
17153
- const content = attemptCompletionMatch[1].trim();
17154
- const hasClosingTag = cleanedXmlString.includes("</attempt_completion>");
17462
+ const openTagIndex = cleanedXmlString.indexOf("<attempt_completion>");
17463
+ if (openTagIndex !== -1) {
17464
+ const afterOpenTag = cleanedXmlString.substring(openTagIndex + "<attempt_completion>".length);
17465
+ const closeTagIndex = cleanedXmlString.lastIndexOf("</attempt_completion>");
17466
+ let content;
17467
+ let hasClosingTag = false;
17468
+ if (closeTagIndex !== -1 && closeTagIndex >= openTagIndex + "<attempt_completion>".length) {
17469
+ content = cleanedXmlString.substring(
17470
+ openTagIndex + "<attempt_completion>".length,
17471
+ closeTagIndex
17472
+ ).trim();
17473
+ hasClosingTag = true;
17474
+ } else {
17475
+ content = afterOpenTag.trim();
17476
+ hasClosingTag = false;
17477
+ }
17155
17478
  if (content) {
17156
17479
  return {
17157
17480
  toolName: "attempt_completion",
@@ -56656,6 +56979,170 @@ var init_FallbackManager = __esm({
56656
56979
  }
56657
56980
  });
56658
56981
 
56982
+ // src/agent/contextCompactor.js
56983
+ var contextCompactor_exports = {};
56984
+ __export(contextCompactor_exports, {
56985
+ calculateCompactionStats: () => calculateCompactionStats,
56986
+ compactMessages: () => compactMessages,
56987
+ handleContextLimitError: () => handleContextLimitError,
56988
+ identifyMessageSegments: () => identifyMessageSegments,
56989
+ isContextLimitError: () => isContextLimitError
56990
+ });
56991
+ function isContextLimitError(error) {
56992
+ if (!error) return false;
56993
+ const errorMessage = (typeof error === "string" ? error : error?.message || "").toLowerCase();
56994
+ const errorString = error.toString().toLowerCase();
56995
+ for (const pattern of CONTEXT_LIMIT_ERROR_PATTERNS) {
56996
+ const lowerPattern = pattern.toLowerCase();
56997
+ if (errorMessage.includes(lowerPattern) || errorString.includes(lowerPattern)) {
56998
+ return true;
56999
+ }
57000
+ }
57001
+ return false;
57002
+ }
57003
+ function identifyMessageSegments(messages) {
57004
+ const segments = [];
57005
+ let currentSegment = null;
57006
+ for (let i = 0; i < messages.length; i++) {
57007
+ const msg = messages[i];
57008
+ if (msg.role === "system") {
57009
+ continue;
57010
+ }
57011
+ if (msg.role === "user") {
57012
+ const content = typeof msg.content === "string" ? msg.content : "";
57013
+ const isToolResult = content.includes("<tool_result>");
57014
+ if (isToolResult && currentSegment) {
57015
+ currentSegment.finalIndex = i;
57016
+ segments.push(currentSegment);
57017
+ currentSegment = null;
57018
+ } else {
57019
+ if (currentSegment) {
57020
+ segments.push(currentSegment);
57021
+ }
57022
+ currentSegment = {
57023
+ userIndex: i,
57024
+ monologueIndices: [],
57025
+ finalIndex: null
57026
+ };
57027
+ }
57028
+ }
57029
+ if (msg.role === "assistant" && currentSegment) {
57030
+ const content = typeof msg.content === "string" ? msg.content : "";
57031
+ if (content.includes("<attempt_completion>") || content.includes("attempt_completion")) {
57032
+ currentSegment.monologueIndices.push(i);
57033
+ currentSegment.finalIndex = i;
57034
+ segments.push(currentSegment);
57035
+ currentSegment = null;
57036
+ } else {
57037
+ currentSegment.monologueIndices.push(i);
57038
+ }
57039
+ }
57040
+ }
57041
+ if (currentSegment) {
57042
+ segments.push(currentSegment);
57043
+ }
57044
+ return segments;
57045
+ }
57046
+ function compactMessages(messages, options = {}) {
57047
+ const {
57048
+ keepLastSegment = true,
57049
+ minSegmentsToKeep = 1
57050
+ } = options;
57051
+ if (!messages || messages.length === 0) {
57052
+ return messages;
57053
+ }
57054
+ const segments = identifyMessageSegments(messages);
57055
+ if (segments.length === 0) {
57056
+ return messages;
57057
+ }
57058
+ const segmentsToPreserve = keepLastSegment ? Math.max(minSegmentsToKeep, 1) : minSegmentsToKeep;
57059
+ const compactableSegments = segments.slice(0, -segmentsToPreserve);
57060
+ const preservedSegments = segments.slice(-segmentsToPreserve);
57061
+ const indicesToKeep = /* @__PURE__ */ new Set();
57062
+ messages.forEach((msg, idx) => {
57063
+ if (msg.role === "system") {
57064
+ indicesToKeep.add(idx);
57065
+ }
57066
+ });
57067
+ compactableSegments.forEach((segment) => {
57068
+ indicesToKeep.add(segment.userIndex);
57069
+ if (segment.finalIndex !== null) {
57070
+ indicesToKeep.add(segment.finalIndex);
57071
+ }
57072
+ });
57073
+ preservedSegments.forEach((segment) => {
57074
+ indicesToKeep.add(segment.userIndex);
57075
+ segment.monologueIndices.forEach((idx) => indicesToKeep.add(idx));
57076
+ if (segment.finalIndex !== null) {
57077
+ indicesToKeep.add(segment.finalIndex);
57078
+ }
57079
+ });
57080
+ const compactedMessages = messages.filter((_, idx) => indicesToKeep.has(idx));
57081
+ return compactedMessages;
57082
+ }
57083
+ function calculateCompactionStats(originalMessages, compactedMessages) {
57084
+ const originalCount = originalMessages.length;
57085
+ const compactedCount = compactedMessages.length;
57086
+ const removed = originalCount - compactedCount;
57087
+ const reductionPercent = originalCount > 0 ? (removed / originalCount * 100).toFixed(1) : 0;
57088
+ const estimateTokens = (msgs) => {
57089
+ return msgs.reduce((sum, msg) => {
57090
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
57091
+ return sum + Math.ceil(content.length / 4);
57092
+ }, 0);
57093
+ };
57094
+ const originalTokens = estimateTokens(originalMessages);
57095
+ const compactedTokens = estimateTokens(compactedMessages);
57096
+ const tokensSaved = originalTokens - compactedTokens;
57097
+ return {
57098
+ originalCount,
57099
+ compactedCount,
57100
+ removed,
57101
+ reductionPercent: parseFloat(reductionPercent),
57102
+ originalTokens,
57103
+ compactedTokens,
57104
+ tokensSaved
57105
+ };
57106
+ }
57107
+ function handleContextLimitError(error, messages, options = {}) {
57108
+ if (!isContextLimitError(error)) {
57109
+ return null;
57110
+ }
57111
+ const compactedMessages = compactMessages(messages, options);
57112
+ const stats = calculateCompactionStats(messages, compactedMessages);
57113
+ return {
57114
+ compacted: true,
57115
+ messages: compactedMessages,
57116
+ stats
57117
+ };
57118
+ }
57119
+ var CONTEXT_LIMIT_ERROR_PATTERNS;
57120
+ var init_contextCompactor = __esm({
57121
+ "src/agent/contextCompactor.js"() {
57122
+ "use strict";
57123
+ CONTEXT_LIMIT_ERROR_PATTERNS = [
57124
+ // Anthropic
57125
+ "context_length_exceeded",
57126
+ "prompt is too long",
57127
+ // OpenAI
57128
+ "maximum context length",
57129
+ "context length is",
57130
+ // Google/Gemini
57131
+ "input token count exceeds",
57132
+ "token limit exceeded",
57133
+ // Generic patterns
57134
+ "context window",
57135
+ "too many tokens",
57136
+ "token limit",
57137
+ "context limit",
57138
+ "exceed",
57139
+ // Catches "exceeds", "exceed maximum", etc.
57140
+ "over the limit",
57141
+ "maximum tokens"
57142
+ ];
57143
+ }
57144
+ });
57145
+
56659
57146
  // src/agent/ProbeAgent.js
56660
57147
  var ProbeAgent_exports = {};
56661
57148
  __export(ProbeAgent_exports, {
@@ -56690,6 +57177,7 @@ var init_ProbeAgent = __esm({
56690
57177
  init_mcp();
56691
57178
  init_RetryManager();
56692
57179
  init_FallbackManager();
57180
+ init_contextCompactor();
56693
57181
  dotenv2.config();
56694
57182
  MAX_TOOL_ITERATIONS = (() => {
56695
57183
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
@@ -56725,6 +57213,8 @@ var init_ProbeAgent = __esm({
56725
57213
  * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
56726
57214
  * @param {Object} [options.storageAdapter] - Custom storage adapter for history management
56727
57215
  * @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
57216
+ * @param {Array<string>|null} [options.allowedTools] - List of allowed tool names. Use ['*'] for all tools (default), [] or null for no tools (raw AI mode), or specific tool names like ['search', 'query', 'extract']. Supports exclusion with '!' prefix (e.g., ['*', '!bash'])
57217
+ * @param {boolean} [options.disableTools=false] - Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set.
56728
57218
  * @param {Object} [options.retry] - Retry configuration
56729
57219
  * @param {number} [options.retry.maxRetries=3] - Maximum retry attempts per provider
56730
57220
  * @param {number} [options.retry.initialDelay=1000] - Initial delay in ms
@@ -56758,6 +57248,8 @@ var init_ProbeAgent = __esm({
56758
57248
  this.maxIterations = options.maxIterations || null;
56759
57249
  this.disableMermaidValidation = !!options.disableMermaidValidation;
56760
57250
  this.disableJsonValidation = !!options.disableJsonValidation;
57251
+ const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
57252
+ this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
56761
57253
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
56762
57254
  this.hooks = new HookManager();
56763
57255
  if (options.hooks) {
@@ -56801,6 +57293,61 @@ var init_ProbeAgent = __esm({
56801
57293
  this.fallbackManager = null;
56802
57294
  this.initializeModel();
56803
57295
  }
57296
+ /**
57297
+ * Parse allowedTools configuration
57298
+ * @param {Array<string>|null|undefined} allowedTools - Tool filtering configuration
57299
+ * @returns {Object} Parsed configuration with isEnabled method
57300
+ * @private
57301
+ */
57302
+ _parseAllowedTools(allowedTools) {
57303
+ const matchesPattern2 = (toolName, pattern) => {
57304
+ if (!pattern.includes("*")) {
57305
+ return toolName === pattern;
57306
+ }
57307
+ const regexPattern = pattern.replace(/\*/g, ".*");
57308
+ return new RegExp(`^${regexPattern}$`).test(toolName);
57309
+ };
57310
+ if (!allowedTools || Array.isArray(allowedTools) && allowedTools.includes("*")) {
57311
+ const exclusions = Array.isArray(allowedTools) ? allowedTools.filter((t) => t.startsWith("!")).map((t) => t.slice(1)) : [];
57312
+ return {
57313
+ mode: "all",
57314
+ exclusions,
57315
+ isEnabled: (toolName) => !exclusions.some((pattern) => matchesPattern2(toolName, pattern))
57316
+ };
57317
+ }
57318
+ if (Array.isArray(allowedTools) && allowedTools.length === 0) {
57319
+ return {
57320
+ mode: "none",
57321
+ isEnabled: () => false
57322
+ };
57323
+ }
57324
+ const allowedPatterns = allowedTools.filter((t) => !t.startsWith("!"));
57325
+ return {
57326
+ mode: "whitelist",
57327
+ allowed: allowedPatterns,
57328
+ isEnabled: (toolName) => allowedPatterns.some((pattern) => matchesPattern2(toolName, pattern))
57329
+ };
57330
+ }
57331
+ /**
57332
+ * Check if an MCP tool is allowed based on allowedTools configuration
57333
+ * Uses mcp__ prefix convention (like Claude Code)
57334
+ * @param {string} toolName - The MCP tool name (without mcp__ prefix)
57335
+ * @returns {boolean} - Whether the tool is allowed
57336
+ * @private
57337
+ */
57338
+ _isMcpToolAllowed(toolName) {
57339
+ const mcpToolName = `mcp__${toolName}`;
57340
+ return this.allowedTools.isEnabled(mcpToolName) || this.allowedTools.isEnabled(toolName);
57341
+ }
57342
+ /**
57343
+ * Filter MCP tools based on allowedTools configuration
57344
+ * @param {string[]} mcpToolNames - Array of MCP tool names
57345
+ * @returns {string[]} - Filtered array of allowed MCP tool names
57346
+ * @private
57347
+ */
57348
+ _filterMcpTools(mcpToolNames) {
57349
+ return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
57350
+ }
56804
57351
  /**
56805
57352
  * Initialize the agent asynchronously (must be called after constructor)
56806
57353
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -56827,7 +57374,11 @@ var init_ProbeAgent = __esm({
56827
57374
  if (this.mcpBridge) {
56828
57375
  const mcpTools = this.mcpBridge.mcpTools || {};
56829
57376
  for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
56830
- this.toolImplementations[toolName] = toolImpl;
57377
+ if (this._isMcpToolAllowed(toolName)) {
57378
+ this.toolImplementations[toolName] = toolImpl;
57379
+ } else if (this.debug) {
57380
+ console.error(`[DEBUG] MCP tool '${toolName}' filtered out by allowedTools`);
57381
+ }
56831
57382
  }
56832
57383
  }
56833
57384
  if (this.debug) {
@@ -57447,7 +57998,11 @@ var init_ProbeAgent = __esm({
57447
57998
  if (this.mcpBridge) {
57448
57999
  const mcpTools = this.mcpBridge.mcpTools || {};
57449
58000
  for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
57450
- this.toolImplementations[toolName] = toolImpl;
58001
+ if (this._isMcpToolAllowed(toolName)) {
58002
+ this.toolImplementations[toolName] = toolImpl;
58003
+ } else if (this.debug) {
58004
+ console.error(`[DEBUG] MCP tool '${toolName}' filtered out by allowedTools`);
58005
+ }
57451
58006
  }
57452
58007
  }
57453
58008
  } catch (error) {
@@ -57457,27 +58012,49 @@ var init_ProbeAgent = __esm({
57457
58012
  }
57458
58013
  }
57459
58014
  }
57460
- let toolDefinitions = `
57461
- ${searchToolDefinition}
57462
- ${queryToolDefinition}
57463
- ${extractToolDefinition}
57464
- ${listFilesToolDefinition}
57465
- ${searchFilesToolDefinition}
57466
- ${attemptCompletionToolDefinition}
58015
+ let toolDefinitions = "";
58016
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
58017
+ if (isToolAllowed("search")) {
58018
+ toolDefinitions += `${searchToolDefinition}
57467
58019
  `;
57468
- if (this.allowEdit) {
58020
+ }
58021
+ if (isToolAllowed("query")) {
58022
+ toolDefinitions += `${queryToolDefinition}
58023
+ `;
58024
+ }
58025
+ if (isToolAllowed("extract")) {
58026
+ toolDefinitions += `${extractToolDefinition}
58027
+ `;
58028
+ }
58029
+ if (isToolAllowed("listFiles")) {
58030
+ toolDefinitions += `${listFilesToolDefinition}
58031
+ `;
58032
+ }
58033
+ if (isToolAllowed("searchFiles")) {
58034
+ toolDefinitions += `${searchFilesToolDefinition}
58035
+ `;
58036
+ }
58037
+ if (this.allowEdit && isToolAllowed("implement")) {
57469
58038
  toolDefinitions += `${implementToolDefinition}
57470
58039
  `;
58040
+ }
58041
+ if (this.allowEdit && isToolAllowed("edit")) {
57471
58042
  toolDefinitions += `${editToolDefinition}
57472
58043
  `;
58044
+ }
58045
+ if (this.allowEdit && isToolAllowed("create")) {
57473
58046
  toolDefinitions += `${createToolDefinition}
57474
58047
  `;
57475
58048
  }
57476
- if (this.enableBash) {
58049
+ if (this.enableBash && isToolAllowed("bash")) {
57477
58050
  toolDefinitions += `${bashToolDefinition}
57478
58051
  `;
57479
58052
  }
57480
- if (this.enableDelegate) {
58053
+ if (isToolAllowed("attempt_completion")) {
58054
+ toolDefinitions += `${attemptCompletionToolDefinition}
58055
+ `;
58056
+ }
58057
+ if (this.enableDelegate && isToolAllowed("delegate")) {
57481
58058
  toolDefinitions += `${delegateToolDefinition}
57482
58059
  `;
57483
58060
  }
@@ -57639,11 +58216,14 @@ ${xmlToolGuidelines}
57639
58216
  ${toolDefinitions}
57640
58217
  `;
57641
58218
  if (this.mcpBridge && this.mcpBridge.getToolNames().length > 0) {
57642
- systemMessage += `
58219
+ const allMcpTools = this.mcpBridge.getToolNames();
58220
+ const allowedMcpTools = this._filterMcpTools(allMcpTools);
58221
+ if (allowedMcpTools.length > 0) {
58222
+ systemMessage += `
57643
58223
  ## MCP Tools (JSON parameters in <params> tag)
57644
58224
  `;
57645
- systemMessage += this.mcpBridge.getXmlToolDefinitions();
57646
- systemMessage += `
58225
+ systemMessage += this.mcpBridge.getXmlToolDefinitions(allowedMcpTools);
58226
+ systemMessage += `
57647
58227
 
57648
58228
  For MCP tools, use JSON format within the params tag, e.g.:
57649
58229
  <mcp_tool>
@@ -57652,6 +58232,7 @@ For MCP tools, use JSON format within the params tag, e.g.:
57652
58232
  </params>
57653
58233
  </mcp_tool>
57654
58234
  `;
58235
+ }
57655
58236
  }
57656
58237
  const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
57657
58238
  if (this.debug) {
@@ -57808,44 +58389,83 @@ You are working with a repository located at: ${searchDirectory}
57808
58389
  }
57809
58390
  }
57810
58391
  let assistantResponseContent = "";
57811
- try {
57812
- const executeAIRequest = async () => {
57813
- const messagesForAI = this.prepareMessagesWithImages(currentMessages);
57814
- const result = await this.streamTextWithRetryAndFallback({
57815
- model: this.provider(this.model),
57816
- messages: messagesForAI,
57817
- maxTokens: maxResponseTokens,
57818
- temperature: 0.3
57819
- });
57820
- const usagePromise = result.usage;
57821
- for await (const delta of result.textStream) {
57822
- assistantResponseContent += delta;
57823
- if (options.onStream) {
57824
- options.onStream(delta);
58392
+ let compactionAttempted = false;
58393
+ while (true) {
58394
+ try {
58395
+ const executeAIRequest = async () => {
58396
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
58397
+ const result = await this.streamTextWithRetryAndFallback({
58398
+ model: this.provider(this.model),
58399
+ messages: messagesForAI,
58400
+ maxTokens: maxResponseTokens,
58401
+ temperature: 0.3
58402
+ });
58403
+ const usagePromise = result.usage;
58404
+ for await (const delta of result.textStream) {
58405
+ assistantResponseContent += delta;
58406
+ if (options.onStream) {
58407
+ options.onStream(delta);
58408
+ }
58409
+ }
58410
+ const usage = await usagePromise;
58411
+ if (usage) {
58412
+ this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
57825
58413
  }
58414
+ return result;
58415
+ };
58416
+ if (this.tracer) {
58417
+ await this.tracer.withSpan("ai.request", executeAIRequest, {
58418
+ "ai.model": this.model,
58419
+ "ai.provider": this.clientApiProvider || "auto",
58420
+ "iteration": currentIteration,
58421
+ "max_tokens": maxResponseTokens,
58422
+ "temperature": 0.3,
58423
+ "message_count": currentMessages.length
58424
+ });
58425
+ } else {
58426
+ await executeAIRequest();
57826
58427
  }
57827
- const usage = await usagePromise;
57828
- if (usage) {
57829
- this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
58428
+ break;
58429
+ } catch (error) {
58430
+ if (!compactionAttempted && handleContextLimitError) {
58431
+ const compactionResult = handleContextLimitError(error, currentMessages, {
58432
+ keepLastSegment: true,
58433
+ minSegmentsToKeep: 1
58434
+ });
58435
+ if (compactionResult) {
58436
+ const { messages: compactedMessages, stats } = compactionResult;
58437
+ if (stats.removed === 0) {
58438
+ console.error(`[ERROR] Context window exceeded but no messages can be compacted.`);
58439
+ console.error(`[ERROR] The conversation history is already minimal (${stats.originalCount} messages).`);
58440
+ finalResult = `Error: Context window limit exceeded and conversation cannot be compacted further. Consider starting a new session or reducing system message size.`;
58441
+ throw new Error(finalResult);
58442
+ }
58443
+ compactionAttempted = true;
58444
+ console.log(`[INFO] Context window limit exceeded. Compacting conversation...`);
58445
+ console.log(`[INFO] Removed ${stats.removed} messages (${stats.reductionPercent}% reduction)`);
58446
+ console.log(`[INFO] Estimated token savings: ${stats.tokensSaved} tokens`);
58447
+ if (this.debug) {
58448
+ console.log(`[DEBUG] Compaction stats:`, stats);
58449
+ console.log(`[DEBUG] Original message count: ${stats.originalCount}`);
58450
+ console.log(`[DEBUG] Compacted message count: ${stats.compactedCount}`);
58451
+ }
58452
+ currentMessages = [...compactedMessages];
58453
+ if (this.tracer) {
58454
+ this.tracer.addEvent("context.compacted", {
58455
+ "iteration": currentIteration,
58456
+ "original_count": stats.originalCount,
58457
+ "compacted_count": stats.compactedCount,
58458
+ "reduction_percent": stats.reductionPercent,
58459
+ "tokens_saved": stats.tokensSaved
58460
+ });
58461
+ }
58462
+ continue;
58463
+ }
57830
58464
  }
57831
- return result;
57832
- };
57833
- if (this.tracer) {
57834
- await this.tracer.withSpan("ai.request", executeAIRequest, {
57835
- "ai.model": this.model,
57836
- "ai.provider": this.clientApiProvider || "auto",
57837
- "iteration": currentIteration,
57838
- "max_tokens": maxResponseTokens,
57839
- "temperature": 0.3,
57840
- "message_count": currentMessages.length
57841
- });
57842
- } else {
57843
- await executeAIRequest();
58465
+ console.error(`Error during streamText (Iter ${currentIteration}):`, error);
58466
+ finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
58467
+ throw new Error(finalResult);
57844
58468
  }
57845
- } catch (error) {
57846
- console.error(`Error during streamText (Iter ${currentIteration}):`, error);
57847
- finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
57848
- throw new Error(finalResult);
57849
58469
  }
57850
58470
  if (this.debug && assistantResponseContent) {
57851
58471
  const assistantPreview = createMessagePreview(assistantResponseContent);
@@ -58210,7 +58830,6 @@ Convert your previous response content into actual JSON data that follows this s
58210
58830
  ...options,
58211
58831
  _schemaFormatted: true
58212
58832
  });
58213
- finalResult = cleanSchemaResponse(finalResult);
58214
58833
  if (!this.disableMermaidValidation) {
58215
58834
  try {
58216
58835
  if (this.debug) {
@@ -58266,14 +58885,11 @@ Convert your previous response content into actual JSON data that follows this s
58266
58885
  } else if (this.debug) {
58267
58886
  console.log(`[DEBUG] Mermaid validation: Skipped due to disableMermaidValidation option`);
58268
58887
  }
58888
+ finalResult = cleanSchemaResponse(finalResult);
58269
58889
  if (isJsonSchema(options.schema)) {
58270
58890
  if (this.debug) {
58271
58891
  console.log(`[DEBUG] JSON validation: Starting validation process for schema response`);
58272
- console.log(`[DEBUG] JSON validation: Response length: ${finalResult.length} chars`);
58273
- }
58274
- finalResult = cleanSchemaResponse(finalResult);
58275
- if (this.debug) {
58276
- console.log(`[DEBUG] JSON validation: After cleaning, length: ${finalResult.length} chars`);
58892
+ console.log(`[DEBUG] JSON validation: Cleaned response length: ${finalResult.length} chars`);
58277
58893
  }
58278
58894
  if (this.tracer) {
58279
58895
  this.tracer.recordJsonValidationEvent("started", {
@@ -58363,10 +58979,9 @@ Convert your previous response content into actual JSON data that follows this s
58363
58979
  console.log("[DEBUG] Skipping schema formatting due to max iterations reached without completion");
58364
58980
  } else if (completionAttempted && options.schema && !options._schemaFormatted && !options._skipValidation) {
58365
58981
  try {
58366
- finalResult = cleanSchemaResponse(finalResult);
58367
58982
  if (!this.disableMermaidValidation) {
58368
58983
  if (this.debug) {
58369
- console.log(`[DEBUG] Mermaid validation: Validating attempt_completion result...`);
58984
+ console.log(`[DEBUG] Mermaid validation: Validating attempt_completion result BEFORE schema cleaning...`);
58370
58985
  }
58371
58986
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
58372
58987
  debug: this.debug,
@@ -58389,6 +59004,7 @@ Convert your previous response content into actual JSON data that follows this s
58389
59004
  } else if (this.debug) {
58390
59005
  console.log(`[DEBUG] Mermaid validation: Skipped for attempt_completion result due to disableMermaidValidation option`);
58391
59006
  }
59007
+ finalResult = cleanSchemaResponse(finalResult);
58392
59008
  if (isJsonSchema(options.schema)) {
58393
59009
  if (this.debug) {
58394
59010
  console.log(`[DEBUG] JSON validation: Starting validation process for attempt_completion result`);
@@ -58562,6 +59178,56 @@ Convert your previous response content into actual JSON data that follows this s
58562
59178
  console.log(`[DEBUG] Cleared conversation history and reset counters for session ${this.sessionId}`);
58563
59179
  }
58564
59180
  }
59181
+ /**
59182
+ * Manually compact conversation history
59183
+ * Removes intermediate monologues from older segments while preserving
59184
+ * user messages, final answers, and the most recent segment
59185
+ *
59186
+ * @param {Object} options - Compaction options
59187
+ * @param {boolean} [options.keepLastSegment=true] - Keep the most recent segment intact
59188
+ * @param {number} [options.minSegmentsToKeep=1] - Number of recent segments to preserve fully
59189
+ * @returns {Object} Compaction statistics
59190
+ */
59191
+ async compactHistory(options = {}) {
59192
+ const { compactMessages: compactMessages2, calculateCompactionStats: calculateCompactionStats2 } = await Promise.resolve().then(() => (init_contextCompactor(), contextCompactor_exports));
59193
+ if (this.history.length === 0) {
59194
+ if (this.debug) {
59195
+ console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
59196
+ }
59197
+ return {
59198
+ originalCount: 0,
59199
+ compactedCount: 0,
59200
+ removed: 0,
59201
+ reductionPercent: 0,
59202
+ originalTokens: 0,
59203
+ compactedTokens: 0,
59204
+ tokensSaved: 0
59205
+ };
59206
+ }
59207
+ const compactedMessages = compactMessages2(this.history, options);
59208
+ const stats = calculateCompactionStats2(this.history, compactedMessages);
59209
+ this.history = compactedMessages;
59210
+ try {
59211
+ await this.storageAdapter.clearHistory(this.sessionId);
59212
+ for (const message of compactedMessages) {
59213
+ await this.storageAdapter.saveMessage(this.sessionId, message);
59214
+ }
59215
+ } catch (error) {
59216
+ console.error(`[ERROR] Failed to save compacted messages to storage:`, error);
59217
+ }
59218
+ console.log(`[INFO] Manually compacted conversation history`);
59219
+ console.log(`[INFO] Removed ${stats.removed} messages (${stats.reductionPercent}% reduction)`);
59220
+ console.log(`[INFO] Estimated token savings: ${stats.tokensSaved} tokens`);
59221
+ if (this.debug) {
59222
+ console.log(`[DEBUG] Compaction stats:`, stats);
59223
+ }
59224
+ await this.hooks.emit(HOOK_TYPES.STORAGE_SAVE, {
59225
+ sessionId: this.sessionId,
59226
+ compacted: true,
59227
+ stats
59228
+ });
59229
+ return stats;
59230
+ }
58565
59231
  /**
58566
59232
  * Clone this agent's session to create a new agent with shared conversation history
58567
59233
  * @param {Object} options - Clone options
@@ -58584,6 +59250,14 @@ Convert your previous response content into actual JSON data that follows this s
58584
59250
  if (stripInternalMessages) {
58585
59251
  clonedHistory = this._stripInternalMessages(clonedHistory, keepSystemMessage);
58586
59252
  }
59253
+ let allowedToolsArray = null;
59254
+ if (this.allowedTools.mode === "whitelist") {
59255
+ allowedToolsArray = [...this.allowedTools.allowed];
59256
+ } else if (this.allowedTools.mode === "none") {
59257
+ allowedToolsArray = [];
59258
+ } else if (this.allowedTools.mode === "all" && this.allowedTools.exclusions.length > 0) {
59259
+ allowedToolsArray = ["*", ...this.allowedTools.exclusions.map((t) => "!" + t)];
59260
+ }
58587
59261
  const clonedAgent = new _ProbeAgent({
58588
59262
  // Copy current agent's config
58589
59263
  customPrompt: this.customPrompt,
@@ -58601,6 +59275,7 @@ Convert your previous response content into actual JSON data that follows this s
58601
59275
  maxIterations: this.maxIterations,
58602
59276
  disableMermaidValidation: this.disableMermaidValidation,
58603
59277
  disableJsonValidation: this.disableJsonValidation,
59278
+ allowedTools: allowedToolsArray,
58604
59279
  enableMcp: !!this.mcpBridge,
58605
59280
  mcpConfig: this.mcpConfig,
58606
59281
  enableBash: this.enableBash,
@@ -59535,6 +60210,10 @@ function parseArgs() {
59535
60210
  // New flag to enable outline format
59536
60211
  noMermaidValidation: false,
59537
60212
  // New flag to disable mermaid validation
60213
+ allowedTools: null,
60214
+ // Tool filtering: ['*'] = all, [] = none, ['tool1', 'tool2'] = specific
60215
+ disableTools: false,
60216
+ // Convenience flag to disable all tools
59538
60217
  // Bash tool configuration
59539
60218
  enableBash: false,
59540
60219
  bashAllow: null,
@@ -59588,6 +60267,17 @@ function parseArgs() {
59588
60267
  config.outline = true;
59589
60268
  } else if (arg === "--no-mermaid-validation") {
59590
60269
  config.noMermaidValidation = true;
60270
+ } else if (arg === "--allowed-tools" && i + 1 < args.length) {
60271
+ const toolsArg = args[++i];
60272
+ if (toolsArg === "*" || toolsArg === "all") {
60273
+ config.allowedTools = ["*"];
60274
+ } else if (toolsArg === "none" || toolsArg === "") {
60275
+ config.allowedTools = [];
60276
+ } else {
60277
+ config.allowedTools = toolsArg.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
60278
+ }
60279
+ } else if (arg === "--disable-tools") {
60280
+ config.disableTools = true;
59591
60281
  } else if (arg === "--enable-bash") {
59592
60282
  config.enableBash = true;
59593
60283
  } else if (arg === "--bash-allow" && i + 1 < args.length) {
@@ -59632,6 +60322,13 @@ Options:
59632
60322
  --model <name> Override model name
59633
60323
  --allow-edit Enable code modification capabilities
59634
60324
  --enable-delegate Enable delegate tool for task distribution to subagents
60325
+ --allowed-tools <tools> Filter available tools (comma-separated list)
60326
+ Use '*' or 'all' for all tools (default)
60327
+ Use 'none' or '' for no tools (raw AI mode)
60328
+ Specific tools: search,query,extract,listFiles,searchFiles
60329
+ Supports exclusion: '*,!bash' (all except bash)
60330
+ --disable-tools Disable all tools (raw AI mode, no code analysis)
60331
+ Convenience flag equivalent to --allowed-tools none
59635
60332
  --verbose Enable verbose output
59636
60333
  --outline Use outline-xml format for code search results
59637
60334
  --mcp Run as MCP server
@@ -59673,6 +60370,9 @@ Examples:
59673
60370
  probe agent "Analyze codebase" --schema schema.json # Schema from file
59674
60371
  probe agent "Debug issue" --trace-file ./debug.jsonl --verbose
59675
60372
  probe agent "Analyze code" --trace-remote http://localhost:4318/v1/traces
60373
+ probe agent "Explain this code" --allowed-tools search,extract # Only search and extract
60374
+ probe agent "What is this project about?" --allowed-tools none # Raw AI mode (no tools)
60375
+ probe agent "Tell me about this project" --disable-tools # Raw AI mode (convenience flag)
59676
60376
  probe agent --mcp # Start MCP server mode
59677
60377
  probe agent --acp # Start ACP server mode
59678
60378
 
@@ -59799,7 +60499,9 @@ var ProbeAgentMcpServer = class {
59799
60499
  allowEdit: !!args.allow_edit,
59800
60500
  debug: process.env.DEBUG === "1",
59801
60501
  maxResponseTokens: args.max_response_tokens,
59802
- disableMermaidValidation: !!args.no_mermaid_validation
60502
+ disableMermaidValidation: !!args.no_mermaid_validation,
60503
+ allowedTools: args.allowed_tools,
60504
+ disableTools: args.disable_tools
59803
60505
  };
59804
60506
  this.agent = new ProbeAgent(agentConfig2);
59805
60507
  await this.agent.initialize();
@@ -60037,6 +60739,8 @@ async function main() {
60037
60739
  outline: config.outline,
60038
60740
  maxResponseTokens: config.maxResponseTokens,
60039
60741
  disableMermaidValidation: config.noMermaidValidation,
60742
+ allowedTools: config.allowedTools,
60743
+ disableTools: config.disableTools,
60040
60744
  enableBash: config.enableBash,
60041
60745
  bashConfig
60042
60746
  };