@tyvm/knowhow 0.0.128 → 0.0.130

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.
Files changed (183) hide show
  1. package/package.json +5 -4
  2. package/scripts/cache-miss-debug.ts +365 -0
  3. package/src/agents/base/base.ts +159 -59
  4. package/src/agents/tools/execCommand.ts +26 -6
  5. package/src/agents/tools/index.ts +1 -0
  6. package/src/agents/tools/list.ts +2 -0
  7. package/src/agents/tools/minimal/callTool.ts +45 -0
  8. package/src/agents/tools/minimal/definitions.ts +50 -0
  9. package/src/agents/tools/minimal/index.ts +3 -0
  10. package/src/agents/tools/minimal/inspectTools.ts +27 -0
  11. package/src/agents/tools/skills.ts +79 -0
  12. package/src/chat/modules/AgentModule.ts +111 -9
  13. package/src/cli.ts +4 -0
  14. package/src/clients/anthropic.ts +59 -1
  15. package/src/clients/contextLimits.ts +9 -0
  16. package/src/clients/index.ts +6 -0
  17. package/src/clients/meta.ts +18 -0
  18. package/src/clients/openai.ts +28 -5
  19. package/src/clients/pricing/anthropic.ts +13 -0
  20. package/src/clients/pricing/fireworks.ts +18 -12
  21. package/src/clients/pricing/index.ts +1 -0
  22. package/src/clients/pricing/meta.ts +12 -0
  23. package/src/clients/pricing/openai.ts +16 -0
  24. package/src/clients/pricing/xai.ts +3 -0
  25. package/src/clients/types.ts +27 -4
  26. package/src/clients/xai.ts +2 -2
  27. package/src/commands/agent.ts +19 -0
  28. package/src/commands/behaviors.ts +47 -0
  29. package/src/commands/services.ts +7 -3
  30. package/src/commands/skills.ts +71 -0
  31. package/src/index.ts +2 -0
  32. package/src/plugins/PluginBase.ts +0 -2
  33. package/src/plugins/SkillsPlugin.ts +11 -124
  34. package/src/plugins/language.ts +0 -1
  35. package/src/plugins/plugins.ts +24 -13
  36. package/src/plugins/types.ts +8 -2
  37. package/src/processors/MinimalToolsMessageProcessor.ts +113 -0
  38. package/src/processors/TokenCompressor.ts +21 -4
  39. package/src/processors/index.ts +7 -1
  40. package/src/services/AgentSyncFs.ts +49 -0
  41. package/src/services/AgentSyncKnowhowWeb.ts +1 -1
  42. package/src/services/BehaviorsService.ts +432 -0
  43. package/src/services/KnowhowClient.ts +62 -1
  44. package/src/services/LazyToolsService.ts +12 -0
  45. package/src/services/MinimalToolsService.ts +167 -0
  46. package/src/services/SkillsService.ts +622 -0
  47. package/src/services/Tools.ts +11 -0
  48. package/src/services/index.ts +9 -2
  49. package/src/services/modules/types.ts +2 -0
  50. package/src/worker.ts +7 -0
  51. package/tests/processors/TokenCompressor.test.ts +45 -0
  52. package/tests/unit/services/MinimalToolsService.test.ts +424 -0
  53. package/ts_build/package.json +5 -4
  54. package/ts_build/src/agents/base/base.d.ts +13 -2
  55. package/ts_build/src/agents/base/base.js +91 -35
  56. package/ts_build/src/agents/base/base.js.map +1 -1
  57. package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
  58. package/ts_build/src/agents/tools/execCommand.js +18 -5
  59. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  60. package/ts_build/src/agents/tools/index.d.ts +1 -0
  61. package/ts_build/src/agents/tools/index.js +1 -0
  62. package/ts_build/src/agents/tools/index.js.map +1 -1
  63. package/ts_build/src/agents/tools/list.js +2 -0
  64. package/ts_build/src/agents/tools/list.js.map +1 -1
  65. package/ts_build/src/agents/tools/minimal/callTool.d.ts +5 -0
  66. package/ts_build/src/agents/tools/minimal/callTool.js +28 -0
  67. package/ts_build/src/agents/tools/minimal/callTool.js.map +1 -0
  68. package/ts_build/src/agents/tools/minimal/definitions.d.ts +4 -0
  69. package/ts_build/src/agents/tools/minimal/definitions.js +46 -0
  70. package/ts_build/src/agents/tools/minimal/definitions.js.map +1 -0
  71. package/ts_build/src/agents/tools/minimal/index.d.ts +3 -0
  72. package/ts_build/src/agents/tools/minimal/index.js +20 -0
  73. package/ts_build/src/agents/tools/minimal/index.js.map +1 -0
  74. package/ts_build/src/agents/tools/minimal/inspectTools.d.ts +2 -0
  75. package/ts_build/src/agents/tools/minimal/inspectTools.js +15 -0
  76. package/ts_build/src/agents/tools/minimal/inspectTools.js.map +1 -0
  77. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  78. package/ts_build/src/agents/tools/skills.js +62 -0
  79. package/ts_build/src/agents/tools/skills.js.map +1 -0
  80. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  81. package/ts_build/src/chat/modules/AgentModule.js +77 -2
  82. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  83. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  84. package/ts_build/src/cli.js +4 -0
  85. package/ts_build/src/cli.js.map +1 -1
  86. package/ts_build/src/clients/anthropic.d.ts +2 -0
  87. package/ts_build/src/clients/anthropic.js +35 -0
  88. package/ts_build/src/clients/anthropic.js.map +1 -1
  89. package/ts_build/src/clients/contextLimits.js +8 -0
  90. package/ts_build/src/clients/contextLimits.js.map +1 -1
  91. package/ts_build/src/clients/index.d.ts +1 -0
  92. package/ts_build/src/clients/index.js +6 -0
  93. package/ts_build/src/clients/index.js.map +1 -1
  94. package/ts_build/src/clients/meta.d.ts +4 -0
  95. package/ts_build/src/clients/meta.js +15 -0
  96. package/ts_build/src/clients/meta.js.map +1 -0
  97. package/ts_build/src/clients/openai.d.ts +1 -0
  98. package/ts_build/src/clients/openai.js +25 -5
  99. package/ts_build/src/clients/openai.js.map +1 -1
  100. package/ts_build/src/clients/pricing/anthropic.d.ts +4 -0
  101. package/ts_build/src/clients/pricing/anthropic.js +12 -0
  102. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  103. package/ts_build/src/clients/pricing/fireworks.d.ts +2 -5
  104. package/ts_build/src/clients/pricing/fireworks.js +13 -11
  105. package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
  106. package/ts_build/src/clients/pricing/index.d.ts +1 -0
  107. package/ts_build/src/clients/pricing/index.js +5 -2
  108. package/ts_build/src/clients/pricing/index.js.map +1 -1
  109. package/ts_build/src/clients/pricing/meta.d.ts +6 -0
  110. package/ts_build/src/clients/pricing/meta.js +8 -0
  111. package/ts_build/src/clients/pricing/meta.js.map +1 -0
  112. package/ts_build/src/clients/pricing/openai.d.ts +3 -0
  113. package/ts_build/src/clients/pricing/openai.js +15 -0
  114. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  115. package/ts_build/src/clients/pricing/xai.d.ts +1 -0
  116. package/ts_build/src/clients/pricing/xai.js +3 -0
  117. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  118. package/ts_build/src/clients/types.d.ts +6 -1
  119. package/ts_build/src/clients/xai.js +1 -1
  120. package/ts_build/src/clients/xai.js.map +1 -1
  121. package/ts_build/src/commands/agent.js +16 -0
  122. package/ts_build/src/commands/agent.js.map +1 -1
  123. package/ts_build/src/commands/behaviors.d.ts +2 -0
  124. package/ts_build/src/commands/behaviors.js +43 -0
  125. package/ts_build/src/commands/behaviors.js.map +1 -0
  126. package/ts_build/src/commands/services.js +4 -2
  127. package/ts_build/src/commands/services.js.map +1 -1
  128. package/ts_build/src/commands/skills.d.ts +2 -0
  129. package/ts_build/src/commands/skills.js +61 -0
  130. package/ts_build/src/commands/skills.js.map +1 -0
  131. package/ts_build/src/index.d.ts +2 -0
  132. package/ts_build/src/index.js +5 -1
  133. package/ts_build/src/index.js.map +1 -1
  134. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  135. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  136. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  137. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  138. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  139. package/ts_build/src/plugins/language.js.map +1 -1
  140. package/ts_build/src/plugins/plugins.d.ts +4 -1
  141. package/ts_build/src/plugins/plugins.js +17 -11
  142. package/ts_build/src/plugins/plugins.js.map +1 -1
  143. package/ts_build/src/plugins/types.d.ts +3 -2
  144. package/ts_build/src/processors/MinimalToolsMessageProcessor.d.ts +7 -0
  145. package/ts_build/src/processors/MinimalToolsMessageProcessor.js +61 -0
  146. package/ts_build/src/processors/MinimalToolsMessageProcessor.js.map +1 -0
  147. package/ts_build/src/processors/TokenCompressor.js +5 -2
  148. package/ts_build/src/processors/TokenCompressor.js.map +1 -1
  149. package/ts_build/src/processors/index.d.ts +2 -1
  150. package/ts_build/src/processors/index.js +3 -1
  151. package/ts_build/src/processors/index.js.map +1 -1
  152. package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
  153. package/ts_build/src/services/AgentSyncFs.js +30 -0
  154. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  155. package/ts_build/src/services/AgentSyncKnowhowWeb.js +1 -1
  156. package/ts_build/src/services/BehaviorsService.d.ts +46 -0
  157. package/ts_build/src/services/BehaviorsService.js +358 -0
  158. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  159. package/ts_build/src/services/KnowhowClient.d.ts +9 -1
  160. package/ts_build/src/services/KnowhowClient.js +20 -0
  161. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  162. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  163. package/ts_build/src/services/MinimalToolsService.d.ts +14 -0
  164. package/ts_build/src/services/MinimalToolsService.js +80 -0
  165. package/ts_build/src/services/MinimalToolsService.js.map +1 -0
  166. package/ts_build/src/services/SkillsService.d.ts +53 -0
  167. package/ts_build/src/services/SkillsService.js +478 -0
  168. package/ts_build/src/services/SkillsService.js.map +1 -0
  169. package/ts_build/src/services/Tools.d.ts +2 -0
  170. package/ts_build/src/services/Tools.js.map +1 -1
  171. package/ts_build/src/services/index.d.ts +4 -0
  172. package/ts_build/src/services/index.js +9 -2
  173. package/ts_build/src/services/index.js.map +1 -1
  174. package/ts_build/src/services/modules/index.d.ts +4 -2
  175. package/ts_build/src/services/modules/types.d.ts +2 -0
  176. package/ts_build/src/types.d.ts +8 -0
  177. package/ts_build/src/worker.js +5 -0
  178. package/ts_build/src/worker.js.map +1 -1
  179. package/ts_build/tests/processors/TokenCompressor.test.js +27 -0
  180. package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
  181. package/ts_build/tests/unit/services/MinimalToolsService.test.d.ts +1 -0
  182. package/ts_build/tests/unit/services/MinimalToolsService.test.js +320 -0
  183. package/ts_build/tests/unit/services/MinimalToolsService.test.js.map +1 -0
@@ -1,6 +1,7 @@
1
1
  import { EventEmitter } from "events"; // kept for reference; agentEvents now uses EventService
2
2
  import {
3
3
  CompletionResponse,
4
+ CompletionOptions,
4
5
  GenericClient,
5
6
  Message,
6
7
  MessageContent,
@@ -26,6 +27,8 @@ export { Message, Tool, ToolCall };
26
27
  export interface ModelPreference {
27
28
  model: string;
28
29
  provider: keyof typeof Clients.clients;
30
+ reasoning_effort?: CompletionOptions["reasoning_effort"];
31
+ reasoning_summary?: boolean;
29
32
  }
30
33
 
31
34
  export interface AgentContext {
@@ -60,6 +63,8 @@ export abstract class BaseAgent implements IAgent {
60
63
  protected turnCount = 0;
61
64
  protected totalCostUsd = 0;
62
65
  protected currentThread = 0;
66
+ protected reasoningEffort: CompletionOptions["reasoning_effort"] | undefined = undefined;
67
+ protected summarizeReasoning: boolean | undefined = undefined;
63
68
  protected totalInputTokens = 0;
64
69
  protected totalOutputTokens = 0;
65
70
  protected totalCacheReadTokens = 0;
@@ -69,7 +74,7 @@ export abstract class BaseAgent implements IAgent {
69
74
  protected compressMinMessages = 30;
70
75
 
71
76
  // Interrupt support: resolves the currently awaited tool call or completion
72
- private _interruptResolve: ((value: any) => void) | null = null;
77
+ private _interruptResolve: (() => void) | null = null;
73
78
 
74
79
  protected threads = [] as Message[][];
75
80
 
@@ -233,6 +238,12 @@ export abstract class BaseAgent implements IAgent {
233
238
  updatePreferences(value: ModelPreference) {
234
239
  this.setModel(value.model);
235
240
  this.setProvider(value.provider);
241
+ if (value.reasoning_effort !== undefined) {
242
+ this.reasoningEffort = value.reasoning_effort;
243
+ }
244
+ if (value.reasoning_summary !== undefined) {
245
+ this.summarizeReasoning = value.reasoning_summary;
246
+ }
236
247
  }
237
248
 
238
249
  nextModel() {
@@ -262,6 +273,22 @@ export abstract class BaseAgent implements IAgent {
262
273
  this.client = null; // Reset client to force re-fetch
263
274
  }
264
275
 
276
+ setReasoningEffort(effort: CompletionOptions["reasoning_effort"]) {
277
+ this.reasoningEffort = effort;
278
+ }
279
+
280
+ getReasoningEffort(): CompletionOptions["reasoning_effort"] {
281
+ return this.reasoningEffort;
282
+ }
283
+
284
+ setSummarizeReasoning(value: boolean) {
285
+ this.summarizeReasoning = value;
286
+ }
287
+
288
+ getSummarizeReasoning(): boolean | undefined {
289
+ return this.summarizeReasoning;
290
+ }
291
+
265
292
  getClient() {
266
293
  if (!this.client) {
267
294
  if (this.provider) {
@@ -305,14 +332,16 @@ export abstract class BaseAgent implements IAgent {
305
332
  // Short response (≤ 3 words) that matches a termination word/phrase exactly
306
333
  const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
307
334
  if (wordCount <= 3) {
308
- const terminationPattern = /^(done|complete|completed|finished|final\s*answer|task\s*complete|all\s*done|that'?s\s*(all|it)|ok(ay)?|yes)[.!]*$/i;
335
+ const terminationPattern =
336
+ /^(done|complete|completed|finished|final\s*answer|task\s*complete|all\s*done|that'?s\s*(all|it)|ok(ay)?|yes)[.!]*$/i;
309
337
  if (terminationPattern.test(trimmed)) return true;
310
338
  }
311
339
 
312
340
  // Check if the first 1-3 words indicate task completion (for longer responses)
313
341
  // e.g. "Task complete: ...", "All done.", "No further changes needed.", "Confirmed complete."
314
342
  const firstWords = trimmed.split(/\s+/).slice(0, 3).join(" ");
315
- const firstWordPattern = /^(task\s*(complete|completed|done|finished)|all\s*done|no\s*(further|more|additional|changes|action)|confirmed?\s*(complete|done|finished|one\s*last)|nothing\s*(more|further|else)|standing\s*by|everything\s*is|still\s*confirmed|acknowledged|done\s*and|complete\s*(and|\.)|completed\s*successfully|no\s*additional|verified\s*and)/i;
343
+ const firstWordPattern =
344
+ /^(task\s*(complete|completed|done|finished)|all\s*done|no\s*(further|more|additional|changes|action)|confirmed?\s*(complete|done|finished|one\s*last)|nothing\s*(more|further|else)|standing\s*by|everything\s*is|still\s*confirmed|acknowledged|done\s*and|complete\s*(and|\.)|completed\s*successfully|no\s*additional|verified\s*and)/i;
316
345
  if (firstWordPattern.test(firstWords)) return true;
317
346
 
318
347
  // If easyFinalAnswer mode is on, also match response starting with "✅" or numbered confirmation lists
@@ -418,15 +447,17 @@ export abstract class BaseAgent implements IAgent {
418
447
  return this.totalCostUsd;
419
448
  }
420
449
 
421
- adjustTokenUsage(usage: any) {
450
+ adjustTokenUsage(usage: any, messages?: Message[]) {
422
451
  if (!usage) return;
423
452
  // Support both OpenAI-style (prompt_tokens/completion_tokens) and Anthropic-style (input_tokens/output_tokens)
424
453
  const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0;
425
454
  const outputTokens = usage.output_tokens ?? usage.completion_tokens ?? 0;
426
455
 
427
456
  const cacheReadTokens =
428
- usage.cache_read_input_tokens ?? usage.cache_read_tokens ??
429
- usage.prompt_tokens_details?.cached_tokens ?? 0;
457
+ usage.cache_read_input_tokens ??
458
+ usage.cache_read_tokens ??
459
+ usage.prompt_tokens_details?.cached_tokens ??
460
+ 0;
430
461
  const cacheWriteTokens =
431
462
  usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0;
432
463
 
@@ -436,6 +467,7 @@ export abstract class BaseAgent implements IAgent {
436
467
  this.totalCacheWriteTokens += cacheWriteTokens;
437
468
 
438
469
  this.agentEvents.emit(this.eventTypes.tokenUsage, {
470
+ timestamp: new Date().toISOString(),
439
471
  inputTokens,
440
472
  outputTokens,
441
473
  cacheReadTokens,
@@ -444,6 +476,7 @@ export abstract class BaseAgent implements IAgent {
444
476
  totalOutputTokens: this.totalOutputTokens,
445
477
  totalCacheReadTokens: this.totalCacheReadTokens,
446
478
  totalCacheWriteTokens: this.totalCacheWriteTokens,
479
+ messages,
447
480
  });
448
481
  }
449
482
 
@@ -456,6 +489,37 @@ export abstract class BaseAgent implements IAgent {
456
489
  };
457
490
  }
458
491
 
492
+ /**
493
+ * Centralized helper for making an AI completion call while ensuring
494
+ * cost (totalCostUsd) and token usage (input/output/cache) are always
495
+ * updated consistently, regardless of which part of the agent triggers
496
+ * the completion (main call loop, task breakdown, compression, etc).
497
+ *
498
+ * Optionally supports being interrupted via makeInterruptible by passing
499
+ * an `interruptValue` in options - if the operation is interrupted, the
500
+ * interrupt value is returned and used for cost/usage tracking (which,
501
+ * for a typical interrupt stub with no usage/cost, is effectively a
502
+ * no-op).
503
+ */
504
+ protected async createAgentCompletion(
505
+ params: Parameters<GenericClient["createChatCompletion"]>[0],
506
+ options: {
507
+ interruptValue?: CompletionResponse;
508
+ } = {}
509
+ ): Promise<CompletionResponse> {
510
+ const { interruptValue } = options;
511
+
512
+ const callPromise = this.getClient().createChatCompletion(params);
513
+ const response = interruptValue
514
+ ? await this.makeInterruptible(callPromise, interruptValue)
515
+ : await callPromise;
516
+
517
+ this.adjustTotalCostUsd(response?.usd_cost);
518
+ this.adjustTokenUsage(response?.usage, params.messages);
519
+
520
+ return response;
521
+ }
522
+
459
523
  startNewThread(messages: Message[]) {
460
524
  this.currentThread++;
461
525
  this.agentEvents.emit(this.eventTypes.newThread, messages);
@@ -488,7 +552,14 @@ export abstract class BaseAgent implements IAgent {
488
552
  toolCallId: toolCall.id,
489
553
  functionName: toolCall.function?.name,
490
554
  functionArgs: {},
491
- toolMessages: [{ role: "tool", tool_call_id: toolCall.id, name: toolCall.function?.name, content: interruptMsg }],
555
+ toolMessages: [
556
+ {
557
+ role: "tool",
558
+ tool_call_id: toolCall.id,
559
+ name: toolCall.function?.name,
560
+ content: interruptMsg,
561
+ },
562
+ ],
492
563
  };
493
564
 
494
565
  const { functionResp, toolMessages } = await this.makeInterruptible(
@@ -671,29 +742,31 @@ export abstract class BaseAgent implements IAgent {
671
742
  return new Promise<T>((resolve, reject) => {
672
743
  let interrupted = false;
673
744
 
674
- this._interruptResolve = (value: any) => {
745
+ this._interruptResolve = () => {
675
746
  interrupted = true;
676
747
  this._interruptResolve = null;
677
- resolve(value);
748
+ resolve(interruptValue);
678
749
  };
679
750
 
680
- promise.then((result) => {
681
- if (this._interruptResolve) {
682
- this._interruptResolve = null;
683
- resolve(result);
684
- }
685
- }).catch((err) => {
686
- if (this._interruptResolve) {
687
- this._interruptResolve = null;
688
- }
689
- // Only swallow the error if interrupt() was explicitly called.
690
- // Otherwise re-throw so callers see the real error.
691
- if (interrupted) {
692
- resolve(interruptValue);
693
- } else {
694
- reject(err);
695
- }
696
- });
751
+ promise
752
+ .then((result) => {
753
+ if (this._interruptResolve) {
754
+ this._interruptResolve = null;
755
+ resolve(result);
756
+ }
757
+ })
758
+ .catch((err) => {
759
+ if (this._interruptResolve) {
760
+ this._interruptResolve = null;
761
+ }
762
+ // Only swallow the error if interrupt() was explicitly called.
763
+ // Otherwise re-throw so callers see the real error.
764
+ if (interrupted) {
765
+ resolve(interruptValue);
766
+ } else {
767
+ reject(err);
768
+ }
769
+ });
697
770
  });
698
771
  }
699
772
 
@@ -705,7 +778,7 @@ export abstract class BaseAgent implements IAgent {
705
778
  interrupt(message = "User interrupted this action you were waiting on") {
706
779
  this.log(`Interrupting current operation: ${message}`);
707
780
  if (this._interruptResolve) {
708
- this._interruptResolve(message);
781
+ this._interruptResolve();
709
782
  } else {
710
783
  this.log("No active interruptible operation to interrupt", "warn");
711
784
  }
@@ -782,20 +855,30 @@ export abstract class BaseAgent implements IAgent {
782
855
  );
783
856
 
784
857
  const interruptResponse: CompletionResponse = {
785
- choices: [{ message: { role: "assistant", content: "User interrupted this AI completion. Please continue." } }],
858
+ choices: [
859
+ {
860
+ message: {
861
+ role: "assistant",
862
+ content: "User interrupted this AI completion. Please continue.",
863
+ },
864
+ },
865
+ ],
786
866
  model,
787
867
  usage: undefined,
788
868
  usd_cost: 0,
789
869
  };
790
- const response = await this.makeInterruptible(
791
- this.getClient().createChatCompletion({
870
+
871
+ const response = await this.createAgentCompletion(
872
+ {
792
873
  model,
793
874
  messages,
794
875
  tools: this.getEnabledTools(),
795
876
  tool_choice: "auto",
796
877
  long_ttl_cache: this.runTime() > 300_000,
797
- }),
798
- interruptResponse
878
+ ...(this.reasoningEffort !== undefined && { reasoning_effort: this.reasoningEffort }),
879
+ ...(this.summarizeReasoning !== undefined && { reasoning_summary: this.summarizeReasoning }),
880
+ },
881
+ { interruptValue: interruptResponse }
799
882
  );
800
883
 
801
884
  // If the agent was paused while the completion was in-flight, wait here
@@ -829,8 +912,6 @@ export abstract class BaseAgent implements IAgent {
829
912
  }
830
913
  }
831
914
 
832
- this.adjustTotalCostUsd(response?.usd_cost);
833
- this.adjustTokenUsage(response?.usage);
834
915
  this.log("agent response cost: " + response?.usd_cost);
835
916
 
836
917
  // Typically, there's only one choice in the array, but you could have many
@@ -847,7 +928,6 @@ export abstract class BaseAgent implements IAgent {
847
928
 
848
929
  this.logMessages([lastMessage]);
849
930
 
850
- const toolCalls = lastMessage.tool_calls;
851
931
  if (lastMessage.tool_calls) {
852
932
  // About to call a tool, process the messages
853
933
  // We could add all the tool calls, and do this once
@@ -856,6 +936,11 @@ export abstract class BaseAgent implements IAgent {
856
936
  "pre_tools"
857
937
  );
858
938
 
939
+ // Re-derive toolCalls from the freshly processed messages, since
940
+ // pre_tools processors (e.g. MinimalToolsMessageProcessor) may have
941
+ // rewritten the tool_calls on a deep-copied message array.
942
+ const toolCalls = messages[messages.length - 1].tool_calls;
943
+
859
944
  this.updateCurrentThread(messages);
860
945
 
861
946
  const truncationWarning = this.detectTruncatedToolCalls(
@@ -942,7 +1027,9 @@ export abstract class BaseAgent implements IAgent {
942
1027
  firstMessage.content &&
943
1028
  this.isTerminationResponse(firstMessage.content)
944
1029
  ) {
945
- this.log(`Termination word detected: "${firstMessage.content.trim()}", treating as finalAnswer`);
1030
+ this.log(
1031
+ `Termination word detected: "${firstMessage.content.trim()}", treating as finalAnswer`
1032
+ );
946
1033
  this.status = this.eventTypes.done;
947
1034
  this.agentEvents.emit(this.eventTypes.done, firstMessage.content);
948
1035
  return firstMessage.content;
@@ -1001,7 +1088,9 @@ export abstract class BaseAgent implements IAgent {
1001
1088
  this.logStatus();
1002
1089
 
1003
1090
  const continuation = `<Workflow>
1004
- Task terminates after you call on of these tools: ${JSON.stringify(this.requiredToolNames)}.\n
1091
+ Task terminates after you call on of these tools: ${JSON.stringify(
1092
+ this.requiredToolNames
1093
+ )}.\n
1005
1094
  User likely only sees output from the required tool call.
1006
1095
  <TaskStatus>${statusMessage}</TaskStatus>
1007
1096
  </Workflow>`;
@@ -1048,7 +1137,12 @@ export abstract class BaseAgent implements IAgent {
1048
1137
 
1049
1138
  this.log(`Agent failed: ${e}`, "error");
1050
1139
 
1051
- if (e != null && typeof e === "object" && "response" in e && "data" in (e as any).response) {
1140
+ if (
1141
+ e != null &&
1142
+ typeof e === "object" &&
1143
+ "response" in e &&
1144
+ "data" in (e as any).response
1145
+ ) {
1052
1146
  this.log(
1053
1147
  `Error response data: ${JSON.stringify(e.response.data, null, 2)}`,
1054
1148
  "error"
@@ -1165,7 +1259,13 @@ export abstract class BaseAgent implements IAgent {
1165
1259
  toolCalls: ToolCall[],
1166
1260
  response: CompletionResponse
1167
1261
  ): { role: string; content: string } | null {
1168
- const outputTokens: number = response?.usage?.completion_tokens || 0;
1262
+ // Subtract thinking/reasoning tokens — they're billed as output tokens but
1263
+ // don't produce visible argument content, so including them inflates the heuristic.
1264
+ const rawOutputTokens: number = response?.usage?.completion_tokens || 0;
1265
+ const thinkingTokens: number =
1266
+ (response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
1267
+ (response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
1268
+ const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
1169
1269
  const totalArgLength = toolCalls.reduce(
1170
1270
  (sum, tc) => sum + (tc.function?.arguments?.length || 0),
1171
1271
  0
@@ -1223,20 +1323,20 @@ export abstract class BaseAgent implements IAgent {
1223
1323
 
1224
1324
  const model = this.getModel();
1225
1325
 
1226
- const response = await this.getClient().createChatCompletion({
1326
+ const taskBreakdownMessages = [
1327
+ ...messages,
1328
+ {
1329
+ role: "user",
1330
+ content: taskPrompt,
1331
+ },
1332
+ ] as Message[];
1333
+
1334
+ const response = await this.createAgentCompletion({
1227
1335
  model,
1228
- messages: [
1229
- ...messages,
1230
- {
1231
- role: "user",
1232
- content: taskPrompt,
1233
- },
1234
- ],
1336
+ messages: taskBreakdownMessages,
1235
1337
  max_tokens: 2000,
1236
1338
  });
1237
1339
 
1238
- this.adjustTotalCostUsd(response.usd_cost);
1239
-
1240
1340
  this.taskBreakdown = response.choices[0].message.content;
1241
1341
  this.log(`task breakdown cost: ${response.usd_cost}`);
1242
1342
  return this.taskBreakdown;
@@ -1268,19 +1368,19 @@ export abstract class BaseAgent implements IAgent {
1268
1368
 
1269
1369
  const model = this.getModel();
1270
1370
 
1271
- const response = await this.getClient().createChatCompletion({
1371
+ const compressMessagesPayload = [
1372
+ ...messages,
1373
+ {
1374
+ role: "user",
1375
+ content: toCompressPrompt,
1376
+ },
1377
+ ] as Message[];
1378
+
1379
+ const response = await this.createAgentCompletion({
1272
1380
  model,
1273
- messages: [
1274
- ...messages,
1275
- {
1276
- role: "user",
1277
- content: toCompressPrompt,
1278
- },
1279
- ],
1381
+ messages: compressMessagesPayload,
1280
1382
  });
1281
1383
 
1282
- this.adjustTotalCostUsd(response.usd_cost);
1283
-
1284
1384
  const summaries = response.choices.map((c) => c.message.content);
1285
1385
  this.summaries.push(...summaries);
1286
1386
 
@@ -259,7 +259,7 @@ const execWithTimeout = async (
259
259
  // Public tool
260
260
  export async function execCommand(
261
261
  command: string,
262
- timeout?: number,
262
+ timeout?: number | string,
263
263
  continueInBackground?: boolean,
264
264
  logFileName?: string
265
265
  ): Promise<string> {
@@ -272,17 +272,37 @@ export async function execCommand(
272
272
  this instanceof ToolsService ? this : services().Tools
273
273
  ) as ToolsService;
274
274
 
275
+ // Coerce timeout to a number. Agents (and some tool-call serializers) often
276
+ // pass numeric arguments as strings (e.g. "120000"). Node's exec()/setTimeout
277
+ // reject non-integer values with "The value of \"timeout\" is out of range.
278
+ // It must be an unsigned integer", so normalize here before any use.
279
+ let normalizedTimeout: number | undefined;
280
+ const rawTimeout = typeof timeout === "string" ? Number(timeout.trim()) : timeout;
281
+ if (typeof rawTimeout !== "number" || !Number.isFinite(rawTimeout)) {
282
+ normalizedTimeout = undefined;
283
+ } else if (rawTimeout === -1) {
284
+ // -1 is the sentinel for "wait indefinitely" — leave as-is.
285
+ normalizedTimeout = -1;
286
+ } else {
287
+ // Clamp to a non-negative integer so the underlying APIs never throw.
288
+ normalizedTimeout = Math.max(0, Math.floor(rawTimeout));
289
+ }
290
+
275
291
  // Detect suspiciously small timeout values — agents often pass seconds instead of ms.
276
292
  // Any value < 100 is almost certainly a mistake (100ms is still very fast for a shell cmd).
277
293
  // We auto-correct to seconds and include a warning in the output so the agent learns.
278
- let correctedTimeout = timeout;
294
+ let correctedTimeout = normalizedTimeout;
279
295
  let timeoutWarning = "";
280
- if (timeout !== undefined && timeout !== -1 && timeout < 100) {
281
- correctedTimeout = timeout * 1000;
296
+ if (
297
+ normalizedTimeout !== undefined &&
298
+ normalizedTimeout !== -1 &&
299
+ normalizedTimeout < 100
300
+ ) {
301
+ correctedTimeout = normalizedTimeout * 1000;
282
302
  timeoutWarning =
283
- `⚠️ Warning: timeout was ${timeout}ms which is extremely small and likely a mistake. ` +
303
+ `⚠️ Warning: timeout was ${normalizedTimeout}ms which is extremely small and likely a mistake. ` +
284
304
  `The timeout unit is milliseconds, not seconds. ` +
285
- `Auto-corrected to ${correctedTimeout}ms (${timeout}s).\n\n`;
305
+ `Auto-corrected to ${correctedTimeout}ms (${normalizedTimeout}s).\n\n`;
286
306
  }
287
307
 
288
308
  const context = toolService.getContext();
@@ -23,3 +23,4 @@ export * from "./stringReplace";
23
23
  export * from "./startAgentTask";
24
24
  export * from "./ycmd";
25
25
  export * from "./mcp";
26
+ export * from "./skills";
@@ -7,6 +7,7 @@ import * as language from "./language/definitions";
7
7
  import * as mcp from "./mcp/definitions";
8
8
  import { googleSearchDefinition } from "./googleSearch";
9
9
  import { startAgentTaskDefinition } from "./startAgentTask";
10
+ import { skillsToolDefinitions } from "./skills";
10
11
 
11
12
  function getPluginNames(): string {
12
13
  try {
@@ -681,4 +682,5 @@ export const includedTools = [
681
682
  ...ycmd.definitions,
682
683
  ...language.definitions,
683
684
  ...mcp.definitions,
685
+ ...skillsToolDefinitions,
684
686
  ] as Tool[];
@@ -0,0 +1,45 @@
1
+ import { minimatch } from "minimatch";
2
+ import { MinimalToolsService } from "../../../services/MinimalToolsService";
3
+ import { ToolsService } from "../../../services/Tools";
4
+
5
+ /**
6
+ * Calls any tool by name with the given args.
7
+ * This is the escape hatch that lets agents call tools outside the base tool set
8
+ * without changing the `tools:` array sent to the AI (preserving cache stability).
9
+ */
10
+ export async function callTool(
11
+ this: ToolsService,
12
+ args: { name: string; args: Record<string, any> }
13
+ ): Promise<any> {
14
+ const service = this as unknown as MinimalToolsService;
15
+ const { name, args: toolArgs } = args;
16
+
17
+ const allTools = service.getAllTools ? service.getAllTools() : service.getTools();
18
+
19
+ // Fuzzy match: exact, endsWith, or glob
20
+ const matchedTool =
21
+ allTools.find((t) => t.function.name === name) ||
22
+ allTools.find((t) => t.function.name.endsWith(name)) ||
23
+ allTools.find((t) => minimatch(t.function.name, name, { nocase: true }));
24
+
25
+ if (!matchedTool) {
26
+ const available = allTools.map((t) => t.function.name).join(", ");
27
+ return `Error: Tool "${name}" not found. Use inspectTools() to browse available tools. Available: ${available}`;
28
+ }
29
+
30
+ const toolName = matchedTool.function.name;
31
+
32
+ // Build a synthetic ToolCall object and delegate to the base callTool
33
+ const syntheticToolCall = {
34
+ id: `call_minimal_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
35
+ type: "function" as const,
36
+ function: {
37
+ name: toolName,
38
+ arguments: JSON.stringify(toolArgs),
39
+ },
40
+ };
41
+
42
+ // Use base ToolsService.callTool with the matched tool name allowed
43
+ const result = await service.callToolByName(syntheticToolCall, toolName);
44
+ return result;
45
+ }
@@ -0,0 +1,50 @@
1
+ import { Tool } from "../../../clients/types";
2
+
3
+ export const callToolDefinition: Tool = {
4
+ type: "function",
5
+ function: {
6
+ name: "callTool",
7
+ description:
8
+ "Call any available tool by name with the given arguments. Use listStoredToolResponses + inspectTools to discover available tools and their argument schemas before calling. This is the primary way to call tools that are not in the base tool set.",
9
+ parameters: {
10
+ type: "object",
11
+ positional: false,
12
+ properties: {
13
+ name: {
14
+ type: "string",
15
+ description: "The name of the tool to call.",
16
+ },
17
+ args: {
18
+ type: "object",
19
+ description:
20
+ "The arguments to pass to the tool. Should match the tool's parameter schema.",
21
+ },
22
+ },
23
+ required: ["name", "args"],
24
+ },
25
+ },
26
+ };
27
+
28
+ export const inspectToolsDefinition: Tool = {
29
+ type: "function",
30
+ function: {
31
+ name: "inspectTools",
32
+ description:
33
+ "Get the full schema/definition for tools matching the given glob patterns. Use this to discover what arguments a tool needs before calling it via callTool. Example patterns: ['read*'] finds tools starting with 'read', ['*File'] finds tools ending with 'File'.",
34
+ parameters: {
35
+ type: "object",
36
+ positional: true,
37
+ properties: {
38
+ patterns: {
39
+ type: "array",
40
+ items: { type: "string" },
41
+ description:
42
+ "Array of glob patterns to filter tool names. If omitted, returns all available tools.",
43
+ },
44
+ },
45
+ required: [],
46
+ },
47
+ },
48
+ };
49
+
50
+ export const minimalDefinitions = [callToolDefinition, inspectToolsDefinition];
@@ -0,0 +1,3 @@
1
+ export * from "./callTool";
2
+ export * from "./inspectTools";
3
+ export * from "./definitions";
@@ -0,0 +1,27 @@
1
+ import { minimatch } from "minimatch";
2
+ import { MinimalToolsService } from "../../../services/MinimalToolsService";
3
+ import { ToolsService } from "../../../services/Tools";
4
+
5
+ /**
6
+ * Returns the full tool definitions (schema) for all tools matching the given patterns.
7
+ * Used by agents to discover argument shapes before calling via callTool.
8
+ */
9
+ export function inspectTools(
10
+ this: ToolsService,
11
+ patterns?: string[]
12
+ ): object[] {
13
+ const service = this as unknown as MinimalToolsService;
14
+ const allTools = service.getAllTools ? service.getAllTools() : service.getTools();
15
+
16
+ if (!patterns || patterns.length === 0) {
17
+ return allTools.map((t) => t.function);
18
+ }
19
+
20
+ return allTools
21
+ .filter((t) =>
22
+ patterns.some((pattern) =>
23
+ minimatch(t.function.name, pattern, { nocase: true })
24
+ )
25
+ )
26
+ .map((t) => t.function);
27
+ }