@probelabs/probe 0.6.0-rc161 → 0.6.0-rc163

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",
@@ -53941,6 +54264,8 @@ __export(schemaUtils_exports, {
53941
54264
  createSchemaDefinitionCorrectionPrompt: () => createSchemaDefinitionCorrectionPrompt,
53942
54265
  decodeHtmlEntities: () => decodeHtmlEntities,
53943
54266
  extractMermaidFromMarkdown: () => extractMermaidFromMarkdown,
54267
+ generateExampleFromSchema: () => generateExampleFromSchema,
54268
+ generateSchemaInstructions: () => generateSchemaInstructions,
53944
54269
  isJsonSchema: () => isJsonSchema,
53945
54270
  isJsonSchemaDefinition: () => isJsonSchemaDefinition,
53946
54271
  isMermaidSchema: () => isMermaidSchema,
@@ -53953,6 +54278,63 @@ __export(schemaUtils_exports, {
53953
54278
  validateMermaidResponse: () => validateMermaidResponse,
53954
54279
  validateXmlResponse: () => validateXmlResponse
53955
54280
  });
54281
+ function generateExampleFromSchema(schema, options = {}) {
54282
+ const { debug = false } = options;
54283
+ try {
54284
+ const parsedSchema = typeof schema === "string" ? JSON.parse(schema) : schema;
54285
+ if (parsedSchema.type !== "object" || !parsedSchema.properties) {
54286
+ return null;
54287
+ }
54288
+ const exampleObj = {};
54289
+ for (const [key, value] of Object.entries(parsedSchema.properties)) {
54290
+ if (value.type === "boolean") {
54291
+ exampleObj[key] = false;
54292
+ } else if (value.type === "number") {
54293
+ exampleObj[key] = 0;
54294
+ } else if (value.type === "string") {
54295
+ exampleObj[key] = value.description || "your answer here";
54296
+ } else if (value.type === "array") {
54297
+ exampleObj[key] = [];
54298
+ } else {
54299
+ exampleObj[key] = {};
54300
+ }
54301
+ }
54302
+ return exampleObj;
54303
+ } catch (e) {
54304
+ if (debug) {
54305
+ console.error("[DEBUG] generateExampleFromSchema: Failed to parse schema:", e.message);
54306
+ }
54307
+ return null;
54308
+ }
54309
+ }
54310
+ function generateSchemaInstructions(schema, options = {}) {
54311
+ const { debug = false } = options;
54312
+ let instructions = "\n\nIMPORTANT: When you provide your final answer using attempt_completion, you MUST format it as valid JSON matching this schema:\n\n";
54313
+ try {
54314
+ const parsedSchema = typeof schema === "string" ? JSON.parse(schema) : schema;
54315
+ instructions += `${JSON.stringify(parsedSchema, null, 2)}
54316
+
54317
+ `;
54318
+ const exampleObj = generateExampleFromSchema(parsedSchema, { debug });
54319
+ if (exampleObj) {
54320
+ instructions += `Example:
54321
+ <attempt_completion>
54322
+ ${JSON.stringify(exampleObj, null, 2)}
54323
+ </attempt_completion>
54324
+
54325
+ `;
54326
+ }
54327
+ } catch (e) {
54328
+ if (debug) {
54329
+ console.error("[DEBUG] generateSchemaInstructions: Failed to parse schema:", e.message);
54330
+ }
54331
+ instructions += `${schema}
54332
+
54333
+ `;
54334
+ }
54335
+ instructions += "Your response inside attempt_completion must be ONLY valid JSON - no plain text, no explanations, no markdown.";
54336
+ return instructions;
54337
+ }
53956
54338
  function enforceNoAdditionalProperties(schema) {
53957
54339
  if (!schema || typeof schema !== "object") {
53958
54340
  return schema;
@@ -56656,6 +57038,170 @@ var init_FallbackManager = __esm({
56656
57038
  }
56657
57039
  });
56658
57040
 
57041
+ // src/agent/contextCompactor.js
57042
+ var contextCompactor_exports = {};
57043
+ __export(contextCompactor_exports, {
57044
+ calculateCompactionStats: () => calculateCompactionStats,
57045
+ compactMessages: () => compactMessages,
57046
+ handleContextLimitError: () => handleContextLimitError,
57047
+ identifyMessageSegments: () => identifyMessageSegments,
57048
+ isContextLimitError: () => isContextLimitError
57049
+ });
57050
+ function isContextLimitError(error) {
57051
+ if (!error) return false;
57052
+ const errorMessage = (typeof error === "string" ? error : error?.message || "").toLowerCase();
57053
+ const errorString = error.toString().toLowerCase();
57054
+ for (const pattern of CONTEXT_LIMIT_ERROR_PATTERNS) {
57055
+ const lowerPattern = pattern.toLowerCase();
57056
+ if (errorMessage.includes(lowerPattern) || errorString.includes(lowerPattern)) {
57057
+ return true;
57058
+ }
57059
+ }
57060
+ return false;
57061
+ }
57062
+ function identifyMessageSegments(messages) {
57063
+ const segments = [];
57064
+ let currentSegment = null;
57065
+ for (let i = 0; i < messages.length; i++) {
57066
+ const msg = messages[i];
57067
+ if (msg.role === "system") {
57068
+ continue;
57069
+ }
57070
+ if (msg.role === "user") {
57071
+ const content = typeof msg.content === "string" ? msg.content : "";
57072
+ const isToolResult = content.includes("<tool_result>");
57073
+ if (isToolResult && currentSegment) {
57074
+ currentSegment.finalIndex = i;
57075
+ segments.push(currentSegment);
57076
+ currentSegment = null;
57077
+ } else {
57078
+ if (currentSegment) {
57079
+ segments.push(currentSegment);
57080
+ }
57081
+ currentSegment = {
57082
+ userIndex: i,
57083
+ monologueIndices: [],
57084
+ finalIndex: null
57085
+ };
57086
+ }
57087
+ }
57088
+ if (msg.role === "assistant" && currentSegment) {
57089
+ const content = typeof msg.content === "string" ? msg.content : "";
57090
+ if (content.includes("<attempt_completion>") || content.includes("attempt_completion")) {
57091
+ currentSegment.monologueIndices.push(i);
57092
+ currentSegment.finalIndex = i;
57093
+ segments.push(currentSegment);
57094
+ currentSegment = null;
57095
+ } else {
57096
+ currentSegment.monologueIndices.push(i);
57097
+ }
57098
+ }
57099
+ }
57100
+ if (currentSegment) {
57101
+ segments.push(currentSegment);
57102
+ }
57103
+ return segments;
57104
+ }
57105
+ function compactMessages(messages, options = {}) {
57106
+ const {
57107
+ keepLastSegment = true,
57108
+ minSegmentsToKeep = 1
57109
+ } = options;
57110
+ if (!messages || messages.length === 0) {
57111
+ return messages;
57112
+ }
57113
+ const segments = identifyMessageSegments(messages);
57114
+ if (segments.length === 0) {
57115
+ return messages;
57116
+ }
57117
+ const segmentsToPreserve = keepLastSegment ? Math.max(minSegmentsToKeep, 1) : minSegmentsToKeep;
57118
+ const compactableSegments = segments.slice(0, -segmentsToPreserve);
57119
+ const preservedSegments = segments.slice(-segmentsToPreserve);
57120
+ const indicesToKeep = /* @__PURE__ */ new Set();
57121
+ messages.forEach((msg, idx) => {
57122
+ if (msg.role === "system") {
57123
+ indicesToKeep.add(idx);
57124
+ }
57125
+ });
57126
+ compactableSegments.forEach((segment) => {
57127
+ indicesToKeep.add(segment.userIndex);
57128
+ if (segment.finalIndex !== null) {
57129
+ indicesToKeep.add(segment.finalIndex);
57130
+ }
57131
+ });
57132
+ preservedSegments.forEach((segment) => {
57133
+ indicesToKeep.add(segment.userIndex);
57134
+ segment.monologueIndices.forEach((idx) => indicesToKeep.add(idx));
57135
+ if (segment.finalIndex !== null) {
57136
+ indicesToKeep.add(segment.finalIndex);
57137
+ }
57138
+ });
57139
+ const compactedMessages = messages.filter((_, idx) => indicesToKeep.has(idx));
57140
+ return compactedMessages;
57141
+ }
57142
+ function calculateCompactionStats(originalMessages, compactedMessages) {
57143
+ const originalCount = originalMessages.length;
57144
+ const compactedCount = compactedMessages.length;
57145
+ const removed = originalCount - compactedCount;
57146
+ const reductionPercent = originalCount > 0 ? (removed / originalCount * 100).toFixed(1) : 0;
57147
+ const estimateTokens = (msgs) => {
57148
+ return msgs.reduce((sum, msg) => {
57149
+ const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
57150
+ return sum + Math.ceil(content.length / 4);
57151
+ }, 0);
57152
+ };
57153
+ const originalTokens = estimateTokens(originalMessages);
57154
+ const compactedTokens = estimateTokens(compactedMessages);
57155
+ const tokensSaved = originalTokens - compactedTokens;
57156
+ return {
57157
+ originalCount,
57158
+ compactedCount,
57159
+ removed,
57160
+ reductionPercent: parseFloat(reductionPercent),
57161
+ originalTokens,
57162
+ compactedTokens,
57163
+ tokensSaved
57164
+ };
57165
+ }
57166
+ function handleContextLimitError(error, messages, options = {}) {
57167
+ if (!isContextLimitError(error)) {
57168
+ return null;
57169
+ }
57170
+ const compactedMessages = compactMessages(messages, options);
57171
+ const stats = calculateCompactionStats(messages, compactedMessages);
57172
+ return {
57173
+ compacted: true,
57174
+ messages: compactedMessages,
57175
+ stats
57176
+ };
57177
+ }
57178
+ var CONTEXT_LIMIT_ERROR_PATTERNS;
57179
+ var init_contextCompactor = __esm({
57180
+ "src/agent/contextCompactor.js"() {
57181
+ "use strict";
57182
+ CONTEXT_LIMIT_ERROR_PATTERNS = [
57183
+ // Anthropic
57184
+ "context_length_exceeded",
57185
+ "prompt is too long",
57186
+ // OpenAI
57187
+ "maximum context length",
57188
+ "context length is",
57189
+ // Google/Gemini
57190
+ "input token count exceeds",
57191
+ "token limit exceeded",
57192
+ // Generic patterns
57193
+ "context window",
57194
+ "too many tokens",
57195
+ "token limit",
57196
+ "context limit",
57197
+ "exceed",
57198
+ // Catches "exceeds", "exceed maximum", etc.
57199
+ "over the limit",
57200
+ "maximum tokens"
57201
+ ];
57202
+ }
57203
+ });
57204
+
56659
57205
  // src/agent/ProbeAgent.js
56660
57206
  var ProbeAgent_exports = {};
56661
57207
  __export(ProbeAgent_exports, {
@@ -56690,6 +57236,7 @@ var init_ProbeAgent = __esm({
56690
57236
  init_mcp();
56691
57237
  init_RetryManager();
56692
57238
  init_FallbackManager();
57239
+ init_contextCompactor();
56693
57240
  dotenv2.config();
56694
57241
  MAX_TOOL_ITERATIONS = (() => {
56695
57242
  const val = parseInt(process.env.MAX_TOOL_ITERATIONS || "30", 10);
@@ -56725,6 +57272,8 @@ var init_ProbeAgent = __esm({
56725
57272
  * @param {Array} [options.mcpServers] - Deprecated, use mcpConfig instead
56726
57273
  * @param {Object} [options.storageAdapter] - Custom storage adapter for history management
56727
57274
  * @param {Object} [options.hooks] - Hook callbacks for events (e.g., {'tool:start': callback})
57275
+ * @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'])
57276
+ * @param {boolean} [options.disableTools=false] - Convenience flag to disable all tools (equivalent to allowedTools: []). Takes precedence over allowedTools if set.
56728
57277
  * @param {Object} [options.retry] - Retry configuration
56729
57278
  * @param {number} [options.retry.maxRetries=3] - Maximum retry attempts per provider
56730
57279
  * @param {number} [options.retry.initialDelay=1000] - Initial delay in ms
@@ -56758,6 +57307,8 @@ var init_ProbeAgent = __esm({
56758
57307
  this.maxIterations = options.maxIterations || null;
56759
57308
  this.disableMermaidValidation = !!options.disableMermaidValidation;
56760
57309
  this.disableJsonValidation = !!options.disableJsonValidation;
57310
+ const effectiveAllowedTools = options.disableTools ? [] : options.allowedTools;
57311
+ this.allowedTools = this._parseAllowedTools(effectiveAllowedTools);
56761
57312
  this.storageAdapter = options.storageAdapter || new InMemoryStorageAdapter();
56762
57313
  this.hooks = new HookManager();
56763
57314
  if (options.hooks) {
@@ -56801,6 +57352,61 @@ var init_ProbeAgent = __esm({
56801
57352
  this.fallbackManager = null;
56802
57353
  this.initializeModel();
56803
57354
  }
57355
+ /**
57356
+ * Parse allowedTools configuration
57357
+ * @param {Array<string>|null|undefined} allowedTools - Tool filtering configuration
57358
+ * @returns {Object} Parsed configuration with isEnabled method
57359
+ * @private
57360
+ */
57361
+ _parseAllowedTools(allowedTools) {
57362
+ const matchesPattern2 = (toolName, pattern) => {
57363
+ if (!pattern.includes("*")) {
57364
+ return toolName === pattern;
57365
+ }
57366
+ const regexPattern = pattern.replace(/\*/g, ".*");
57367
+ return new RegExp(`^${regexPattern}$`).test(toolName);
57368
+ };
57369
+ if (!allowedTools || Array.isArray(allowedTools) && allowedTools.includes("*")) {
57370
+ const exclusions = Array.isArray(allowedTools) ? allowedTools.filter((t) => t.startsWith("!")).map((t) => t.slice(1)) : [];
57371
+ return {
57372
+ mode: "all",
57373
+ exclusions,
57374
+ isEnabled: (toolName) => !exclusions.some((pattern) => matchesPattern2(toolName, pattern))
57375
+ };
57376
+ }
57377
+ if (Array.isArray(allowedTools) && allowedTools.length === 0) {
57378
+ return {
57379
+ mode: "none",
57380
+ isEnabled: () => false
57381
+ };
57382
+ }
57383
+ const allowedPatterns = allowedTools.filter((t) => !t.startsWith("!"));
57384
+ return {
57385
+ mode: "whitelist",
57386
+ allowed: allowedPatterns,
57387
+ isEnabled: (toolName) => allowedPatterns.some((pattern) => matchesPattern2(toolName, pattern))
57388
+ };
57389
+ }
57390
+ /**
57391
+ * Check if an MCP tool is allowed based on allowedTools configuration
57392
+ * Uses mcp__ prefix convention (like Claude Code)
57393
+ * @param {string} toolName - The MCP tool name (without mcp__ prefix)
57394
+ * @returns {boolean} - Whether the tool is allowed
57395
+ * @private
57396
+ */
57397
+ _isMcpToolAllowed(toolName) {
57398
+ const mcpToolName = `mcp__${toolName}`;
57399
+ return this.allowedTools.isEnabled(mcpToolName) || this.allowedTools.isEnabled(toolName);
57400
+ }
57401
+ /**
57402
+ * Filter MCP tools based on allowedTools configuration
57403
+ * @param {string[]} mcpToolNames - Array of MCP tool names
57404
+ * @returns {string[]} - Filtered array of allowed MCP tool names
57405
+ * @private
57406
+ */
57407
+ _filterMcpTools(mcpToolNames) {
57408
+ return mcpToolNames.filter((toolName) => this._isMcpToolAllowed(toolName));
57409
+ }
56804
57410
  /**
56805
57411
  * Initialize the agent asynchronously (must be called after constructor)
56806
57412
  * This method initializes MCP and merges MCP tools into the tool list, and loads history from storage
@@ -56827,7 +57433,11 @@ var init_ProbeAgent = __esm({
56827
57433
  if (this.mcpBridge) {
56828
57434
  const mcpTools = this.mcpBridge.mcpTools || {};
56829
57435
  for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
56830
- this.toolImplementations[toolName] = toolImpl;
57436
+ if (this._isMcpToolAllowed(toolName)) {
57437
+ this.toolImplementations[toolName] = toolImpl;
57438
+ } else if (this.debug) {
57439
+ console.error(`[DEBUG] MCP tool '${toolName}' filtered out by allowedTools`);
57440
+ }
56831
57441
  }
56832
57442
  }
56833
57443
  if (this.debug) {
@@ -57447,7 +58057,11 @@ var init_ProbeAgent = __esm({
57447
58057
  if (this.mcpBridge) {
57448
58058
  const mcpTools = this.mcpBridge.mcpTools || {};
57449
58059
  for (const [toolName, toolImpl] of Object.entries(mcpTools)) {
57450
- this.toolImplementations[toolName] = toolImpl;
58060
+ if (this._isMcpToolAllowed(toolName)) {
58061
+ this.toolImplementations[toolName] = toolImpl;
58062
+ } else if (this.debug) {
58063
+ console.error(`[DEBUG] MCP tool '${toolName}' filtered out by allowedTools`);
58064
+ }
57451
58065
  }
57452
58066
  }
57453
58067
  } catch (error) {
@@ -57457,27 +58071,49 @@ var init_ProbeAgent = __esm({
57457
58071
  }
57458
58072
  }
57459
58073
  }
57460
- let toolDefinitions = `
57461
- ${searchToolDefinition}
57462
- ${queryToolDefinition}
57463
- ${extractToolDefinition}
57464
- ${listFilesToolDefinition}
57465
- ${searchFilesToolDefinition}
57466
- ${attemptCompletionToolDefinition}
58074
+ let toolDefinitions = "";
58075
+ const isToolAllowed = (toolName) => this.allowedTools.isEnabled(toolName);
58076
+ if (isToolAllowed("search")) {
58077
+ toolDefinitions += `${searchToolDefinition}
57467
58078
  `;
57468
- if (this.allowEdit) {
58079
+ }
58080
+ if (isToolAllowed("query")) {
58081
+ toolDefinitions += `${queryToolDefinition}
58082
+ `;
58083
+ }
58084
+ if (isToolAllowed("extract")) {
58085
+ toolDefinitions += `${extractToolDefinition}
58086
+ `;
58087
+ }
58088
+ if (isToolAllowed("listFiles")) {
58089
+ toolDefinitions += `${listFilesToolDefinition}
58090
+ `;
58091
+ }
58092
+ if (isToolAllowed("searchFiles")) {
58093
+ toolDefinitions += `${searchFilesToolDefinition}
58094
+ `;
58095
+ }
58096
+ if (this.allowEdit && isToolAllowed("implement")) {
57469
58097
  toolDefinitions += `${implementToolDefinition}
57470
58098
  `;
58099
+ }
58100
+ if (this.allowEdit && isToolAllowed("edit")) {
57471
58101
  toolDefinitions += `${editToolDefinition}
57472
58102
  `;
58103
+ }
58104
+ if (this.allowEdit && isToolAllowed("create")) {
57473
58105
  toolDefinitions += `${createToolDefinition}
57474
58106
  `;
57475
58107
  }
57476
- if (this.enableBash) {
58108
+ if (this.enableBash && isToolAllowed("bash")) {
57477
58109
  toolDefinitions += `${bashToolDefinition}
57478
58110
  `;
57479
58111
  }
57480
- if (this.enableDelegate) {
58112
+ if (isToolAllowed("attempt_completion")) {
58113
+ toolDefinitions += `${attemptCompletionToolDefinition}
58114
+ `;
58115
+ }
58116
+ if (this.enableDelegate && isToolAllowed("delegate")) {
57481
58117
  toolDefinitions += `${delegateToolDefinition}
57482
58118
  `;
57483
58119
  }
@@ -57639,11 +58275,14 @@ ${xmlToolGuidelines}
57639
58275
  ${toolDefinitions}
57640
58276
  `;
57641
58277
  if (this.mcpBridge && this.mcpBridge.getToolNames().length > 0) {
57642
- systemMessage += `
58278
+ const allMcpTools = this.mcpBridge.getToolNames();
58279
+ const allowedMcpTools = this._filterMcpTools(allMcpTools);
58280
+ if (allowedMcpTools.length > 0) {
58281
+ systemMessage += `
57643
58282
  ## MCP Tools (JSON parameters in <params> tag)
57644
58283
  `;
57645
- systemMessage += this.mcpBridge.getXmlToolDefinitions();
57646
- systemMessage += `
58284
+ systemMessage += this.mcpBridge.getXmlToolDefinitions(allowedMcpTools);
58285
+ systemMessage += `
57647
58286
 
57648
58287
  For MCP tools, use JSON format within the params tag, e.g.:
57649
58288
  <mcp_tool>
@@ -57652,6 +58291,7 @@ For MCP tools, use JSON format within the params tag, e.g.:
57652
58291
  </params>
57653
58292
  </mcp_tool>
57654
58293
  `;
58294
+ }
57655
58295
  }
57656
58296
  const searchDirectory = this.allowedFolders.length > 0 ? this.allowedFolders[0] : process.cwd();
57657
58297
  if (this.debug) {
@@ -57724,9 +58364,14 @@ You are working with a repository located at: ${searchDirectory}
57724
58364
  });
57725
58365
  const systemMessage = await this.getSystemMessage();
57726
58366
  let userMessage = { role: "user", content: message.trim() };
58367
+ if (options.schema && !options._schemaFormatted) {
58368
+ const schemaInstructions = generateSchemaInstructions(options.schema, { debug: this.debug });
58369
+ userMessage.content = message.trim() + schemaInstructions;
58370
+ }
57727
58371
  if (images && images.length > 0) {
58372
+ const textContent = userMessage.content;
57728
58373
  userMessage.content = [
57729
- { type: "text", text: message.trim() },
58374
+ { type: "text", text: textContent },
57730
58375
  ...images.map((image) => ({
57731
58376
  type: "image",
57732
58377
  image
@@ -57808,44 +58453,83 @@ You are working with a repository located at: ${searchDirectory}
57808
58453
  }
57809
58454
  }
57810
58455
  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);
58456
+ let compactionAttempted = false;
58457
+ while (true) {
58458
+ try {
58459
+ const executeAIRequest = async () => {
58460
+ const messagesForAI = this.prepareMessagesWithImages(currentMessages);
58461
+ const result = await this.streamTextWithRetryAndFallback({
58462
+ model: this.provider(this.model),
58463
+ messages: messagesForAI,
58464
+ maxTokens: maxResponseTokens,
58465
+ temperature: 0.3
58466
+ });
58467
+ const usagePromise = result.usage;
58468
+ for await (const delta of result.textStream) {
58469
+ assistantResponseContent += delta;
58470
+ if (options.onStream) {
58471
+ options.onStream(delta);
58472
+ }
57825
58473
  }
58474
+ const usage = await usagePromise;
58475
+ if (usage) {
58476
+ this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
58477
+ }
58478
+ return result;
58479
+ };
58480
+ if (this.tracer) {
58481
+ await this.tracer.withSpan("ai.request", executeAIRequest, {
58482
+ "ai.model": this.model,
58483
+ "ai.provider": this.clientApiProvider || "auto",
58484
+ "iteration": currentIteration,
58485
+ "max_tokens": maxResponseTokens,
58486
+ "temperature": 0.3,
58487
+ "message_count": currentMessages.length
58488
+ });
58489
+ } else {
58490
+ await executeAIRequest();
57826
58491
  }
57827
- const usage = await usagePromise;
57828
- if (usage) {
57829
- this.tokenCounter.recordUsage(usage, result.experimental_providerMetadata);
58492
+ break;
58493
+ } catch (error) {
58494
+ if (!compactionAttempted && handleContextLimitError) {
58495
+ const compactionResult = handleContextLimitError(error, currentMessages, {
58496
+ keepLastSegment: true,
58497
+ minSegmentsToKeep: 1
58498
+ });
58499
+ if (compactionResult) {
58500
+ const { messages: compactedMessages, stats } = compactionResult;
58501
+ if (stats.removed === 0) {
58502
+ console.error(`[ERROR] Context window exceeded but no messages can be compacted.`);
58503
+ console.error(`[ERROR] The conversation history is already minimal (${stats.originalCount} messages).`);
58504
+ finalResult = `Error: Context window limit exceeded and conversation cannot be compacted further. Consider starting a new session or reducing system message size.`;
58505
+ throw new Error(finalResult);
58506
+ }
58507
+ compactionAttempted = true;
58508
+ console.log(`[INFO] Context window limit exceeded. Compacting conversation...`);
58509
+ console.log(`[INFO] Removed ${stats.removed} messages (${stats.reductionPercent}% reduction)`);
58510
+ console.log(`[INFO] Estimated token savings: ${stats.tokensSaved} tokens`);
58511
+ if (this.debug) {
58512
+ console.log(`[DEBUG] Compaction stats:`, stats);
58513
+ console.log(`[DEBUG] Original message count: ${stats.originalCount}`);
58514
+ console.log(`[DEBUG] Compacted message count: ${stats.compactedCount}`);
58515
+ }
58516
+ currentMessages = [...compactedMessages];
58517
+ if (this.tracer) {
58518
+ this.tracer.addEvent("context.compacted", {
58519
+ "iteration": currentIteration,
58520
+ "original_count": stats.originalCount,
58521
+ "compacted_count": stats.compactedCount,
58522
+ "reduction_percent": stats.reductionPercent,
58523
+ "tokens_saved": stats.tokensSaved
58524
+ });
58525
+ }
58526
+ continue;
58527
+ }
57830
58528
  }
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();
58529
+ console.error(`Error during streamText (Iter ${currentIteration}):`, error);
58530
+ finalResult = `Error: Failed to get response from AI model during iteration ${currentIteration}. ${error.message}`;
58531
+ throw new Error(finalResult);
57844
58532
  }
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
58533
  }
57850
58534
  if (this.debug && assistantResponseContent) {
57851
58535
  const assistantPreview = createMessagePreview(assistantResponseContent);
@@ -58116,15 +58800,7 @@ Remember: Use proper XML format with BOTH opening and closing tags:
58116
58800
  <tool_name>
58117
58801
  <parameter>value</parameter>
58118
58802
  </tool_name>
58119
-
58120
- IMPORTANT: A schema was provided for the final output format.
58121
-
58122
- You MUST use attempt_completion to provide your answer:
58123
- <attempt_completion>
58124
- [Your complete answer here - provide in natural language, it will be automatically formatted to match the schema]
58125
- </attempt_completion>
58126
-
58127
- Your response will be automatically formatted to JSON. You can provide your answer in natural language or as JSON - either will work.`;
58803
+ ` + generateSchemaInstructions(options.schema, { debug: this.debug }).trim();
58128
58804
  } else {
58129
58805
  reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
58130
58806
 
@@ -58210,7 +58886,6 @@ Convert your previous response content into actual JSON data that follows this s
58210
58886
  ...options,
58211
58887
  _schemaFormatted: true
58212
58888
  });
58213
- finalResult = cleanSchemaResponse(finalResult);
58214
58889
  if (!this.disableMermaidValidation) {
58215
58890
  try {
58216
58891
  if (this.debug) {
@@ -58266,14 +58941,11 @@ Convert your previous response content into actual JSON data that follows this s
58266
58941
  } else if (this.debug) {
58267
58942
  console.log(`[DEBUG] Mermaid validation: Skipped due to disableMermaidValidation option`);
58268
58943
  }
58944
+ finalResult = cleanSchemaResponse(finalResult);
58269
58945
  if (isJsonSchema(options.schema)) {
58270
58946
  if (this.debug) {
58271
58947
  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`);
58948
+ console.log(`[DEBUG] JSON validation: Cleaned response length: ${finalResult.length} chars`);
58277
58949
  }
58278
58950
  if (this.tracer) {
58279
58951
  this.tracer.recordJsonValidationEvent("started", {
@@ -58363,10 +59035,9 @@ Convert your previous response content into actual JSON data that follows this s
58363
59035
  console.log("[DEBUG] Skipping schema formatting due to max iterations reached without completion");
58364
59036
  } else if (completionAttempted && options.schema && !options._schemaFormatted && !options._skipValidation) {
58365
59037
  try {
58366
- finalResult = cleanSchemaResponse(finalResult);
58367
59038
  if (!this.disableMermaidValidation) {
58368
59039
  if (this.debug) {
58369
- console.log(`[DEBUG] Mermaid validation: Validating attempt_completion result...`);
59040
+ console.log(`[DEBUG] Mermaid validation: Validating attempt_completion result BEFORE schema cleaning...`);
58370
59041
  }
58371
59042
  const mermaidValidation = await validateAndFixMermaidResponse(finalResult, {
58372
59043
  debug: this.debug,
@@ -58389,6 +59060,7 @@ Convert your previous response content into actual JSON data that follows this s
58389
59060
  } else if (this.debug) {
58390
59061
  console.log(`[DEBUG] Mermaid validation: Skipped for attempt_completion result due to disableMermaidValidation option`);
58391
59062
  }
59063
+ finalResult = cleanSchemaResponse(finalResult);
58392
59064
  if (isJsonSchema(options.schema)) {
58393
59065
  if (this.debug) {
58394
59066
  console.log(`[DEBUG] JSON validation: Starting validation process for attempt_completion result`);
@@ -58562,6 +59234,56 @@ Convert your previous response content into actual JSON data that follows this s
58562
59234
  console.log(`[DEBUG] Cleared conversation history and reset counters for session ${this.sessionId}`);
58563
59235
  }
58564
59236
  }
59237
+ /**
59238
+ * Manually compact conversation history
59239
+ * Removes intermediate monologues from older segments while preserving
59240
+ * user messages, final answers, and the most recent segment
59241
+ *
59242
+ * @param {Object} options - Compaction options
59243
+ * @param {boolean} [options.keepLastSegment=true] - Keep the most recent segment intact
59244
+ * @param {number} [options.minSegmentsToKeep=1] - Number of recent segments to preserve fully
59245
+ * @returns {Object} Compaction statistics
59246
+ */
59247
+ async compactHistory(options = {}) {
59248
+ const { compactMessages: compactMessages2, calculateCompactionStats: calculateCompactionStats2 } = await Promise.resolve().then(() => (init_contextCompactor(), contextCompactor_exports));
59249
+ if (this.history.length === 0) {
59250
+ if (this.debug) {
59251
+ console.log(`[DEBUG] No history to compact for session ${this.sessionId}`);
59252
+ }
59253
+ return {
59254
+ originalCount: 0,
59255
+ compactedCount: 0,
59256
+ removed: 0,
59257
+ reductionPercent: 0,
59258
+ originalTokens: 0,
59259
+ compactedTokens: 0,
59260
+ tokensSaved: 0
59261
+ };
59262
+ }
59263
+ const compactedMessages = compactMessages2(this.history, options);
59264
+ const stats = calculateCompactionStats2(this.history, compactedMessages);
59265
+ this.history = compactedMessages;
59266
+ try {
59267
+ await this.storageAdapter.clearHistory(this.sessionId);
59268
+ for (const message of compactedMessages) {
59269
+ await this.storageAdapter.saveMessage(this.sessionId, message);
59270
+ }
59271
+ } catch (error) {
59272
+ console.error(`[ERROR] Failed to save compacted messages to storage:`, error);
59273
+ }
59274
+ console.log(`[INFO] Manually compacted conversation history`);
59275
+ console.log(`[INFO] Removed ${stats.removed} messages (${stats.reductionPercent}% reduction)`);
59276
+ console.log(`[INFO] Estimated token savings: ${stats.tokensSaved} tokens`);
59277
+ if (this.debug) {
59278
+ console.log(`[DEBUG] Compaction stats:`, stats);
59279
+ }
59280
+ await this.hooks.emit(HOOK_TYPES.STORAGE_SAVE, {
59281
+ sessionId: this.sessionId,
59282
+ compacted: true,
59283
+ stats
59284
+ });
59285
+ return stats;
59286
+ }
58565
59287
  /**
58566
59288
  * Clone this agent's session to create a new agent with shared conversation history
58567
59289
  * @param {Object} options - Clone options
@@ -58584,6 +59306,14 @@ Convert your previous response content into actual JSON data that follows this s
58584
59306
  if (stripInternalMessages) {
58585
59307
  clonedHistory = this._stripInternalMessages(clonedHistory, keepSystemMessage);
58586
59308
  }
59309
+ let allowedToolsArray = null;
59310
+ if (this.allowedTools.mode === "whitelist") {
59311
+ allowedToolsArray = [...this.allowedTools.allowed];
59312
+ } else if (this.allowedTools.mode === "none") {
59313
+ allowedToolsArray = [];
59314
+ } else if (this.allowedTools.mode === "all" && this.allowedTools.exclusions.length > 0) {
59315
+ allowedToolsArray = ["*", ...this.allowedTools.exclusions.map((t) => "!" + t)];
59316
+ }
58587
59317
  const clonedAgent = new _ProbeAgent({
58588
59318
  // Copy current agent's config
58589
59319
  customPrompt: this.customPrompt,
@@ -58601,6 +59331,7 @@ Convert your previous response content into actual JSON data that follows this s
58601
59331
  maxIterations: this.maxIterations,
58602
59332
  disableMermaidValidation: this.disableMermaidValidation,
58603
59333
  disableJsonValidation: this.disableJsonValidation,
59334
+ allowedTools: allowedToolsArray,
58604
59335
  enableMcp: !!this.mcpBridge,
58605
59336
  mcpConfig: this.mcpConfig,
58606
59337
  enableBash: this.enableBash,
@@ -59535,6 +60266,10 @@ function parseArgs() {
59535
60266
  // New flag to enable outline format
59536
60267
  noMermaidValidation: false,
59537
60268
  // New flag to disable mermaid validation
60269
+ allowedTools: null,
60270
+ // Tool filtering: ['*'] = all, [] = none, ['tool1', 'tool2'] = specific
60271
+ disableTools: false,
60272
+ // Convenience flag to disable all tools
59538
60273
  // Bash tool configuration
59539
60274
  enableBash: false,
59540
60275
  bashAllow: null,
@@ -59588,6 +60323,17 @@ function parseArgs() {
59588
60323
  config.outline = true;
59589
60324
  } else if (arg === "--no-mermaid-validation") {
59590
60325
  config.noMermaidValidation = true;
60326
+ } else if (arg === "--allowed-tools" && i + 1 < args.length) {
60327
+ const toolsArg = args[++i];
60328
+ if (toolsArg === "*" || toolsArg === "all") {
60329
+ config.allowedTools = ["*"];
60330
+ } else if (toolsArg === "none" || toolsArg === "") {
60331
+ config.allowedTools = [];
60332
+ } else {
60333
+ config.allowedTools = toolsArg.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
60334
+ }
60335
+ } else if (arg === "--disable-tools") {
60336
+ config.disableTools = true;
59591
60337
  } else if (arg === "--enable-bash") {
59592
60338
  config.enableBash = true;
59593
60339
  } else if (arg === "--bash-allow" && i + 1 < args.length) {
@@ -59632,6 +60378,13 @@ Options:
59632
60378
  --model <name> Override model name
59633
60379
  --allow-edit Enable code modification capabilities
59634
60380
  --enable-delegate Enable delegate tool for task distribution to subagents
60381
+ --allowed-tools <tools> Filter available tools (comma-separated list)
60382
+ Use '*' or 'all' for all tools (default)
60383
+ Use 'none' or '' for no tools (raw AI mode)
60384
+ Specific tools: search,query,extract,listFiles,searchFiles
60385
+ Supports exclusion: '*,!bash' (all except bash)
60386
+ --disable-tools Disable all tools (raw AI mode, no code analysis)
60387
+ Convenience flag equivalent to --allowed-tools none
59635
60388
  --verbose Enable verbose output
59636
60389
  --outline Use outline-xml format for code search results
59637
60390
  --mcp Run as MCP server
@@ -59673,6 +60426,9 @@ Examples:
59673
60426
  probe agent "Analyze codebase" --schema schema.json # Schema from file
59674
60427
  probe agent "Debug issue" --trace-file ./debug.jsonl --verbose
59675
60428
  probe agent "Analyze code" --trace-remote http://localhost:4318/v1/traces
60429
+ probe agent "Explain this code" --allowed-tools search,extract # Only search and extract
60430
+ probe agent "What is this project about?" --allowed-tools none # Raw AI mode (no tools)
60431
+ probe agent "Tell me about this project" --disable-tools # Raw AI mode (convenience flag)
59676
60432
  probe agent --mcp # Start MCP server mode
59677
60433
  probe agent --acp # Start ACP server mode
59678
60434
 
@@ -59799,7 +60555,9 @@ var ProbeAgentMcpServer = class {
59799
60555
  allowEdit: !!args.allow_edit,
59800
60556
  debug: process.env.DEBUG === "1",
59801
60557
  maxResponseTokens: args.max_response_tokens,
59802
- disableMermaidValidation: !!args.no_mermaid_validation
60558
+ disableMermaidValidation: !!args.no_mermaid_validation,
60559
+ allowedTools: args.allowed_tools,
60560
+ disableTools: args.disable_tools
59803
60561
  };
59804
60562
  this.agent = new ProbeAgent(agentConfig2);
59805
60563
  await this.agent.initialize();
@@ -60037,6 +60795,8 @@ async function main() {
60037
60795
  outline: config.outline,
60038
60796
  maxResponseTokens: config.maxResponseTokens,
60039
60797
  disableMermaidValidation: config.noMermaidValidation,
60798
+ allowedTools: config.allowedTools,
60799
+ disableTools: config.disableTools,
60040
60800
  enableBash: config.enableBash,
60041
60801
  bashConfig
60042
60802
  };