@tyvm/knowhow 0.0.153 → 0.0.154

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 (42) hide show
  1. package/package.json +1 -1
  2. package/src/agents/base/base.ts +31 -5
  3. package/src/agents/tools/list.ts +31 -28
  4. package/src/clients/anthropic.ts +3 -1
  5. package/src/clients/contextLimits.ts +3 -1
  6. package/src/clients/gemini.ts +16 -4
  7. package/src/clients/http.ts +8 -5
  8. package/src/clients/knowhowMcp.ts +2 -2
  9. package/src/clients/openai.ts +7 -4
  10. package/src/clients/pricing/google.ts +11 -2
  11. package/src/clients/pricing/xai.ts +5 -1
  12. package/src/clients/xai.ts +6 -3
  13. package/tests/clients/reasoningEffort.test.ts +5 -1
  14. package/ts_build/package.json +1 -1
  15. package/ts_build/src/agents/base/base.d.ts +1 -1
  16. package/ts_build/src/agents/base/base.js +13 -5
  17. package/ts_build/src/agents/base/base.js.map +1 -1
  18. package/ts_build/src/agents/tools/list.js +0 -26
  19. package/ts_build/src/agents/tools/list.js.map +1 -1
  20. package/ts_build/src/clients/anthropic.js +3 -1
  21. package/ts_build/src/clients/anthropic.js.map +1 -1
  22. package/ts_build/src/clients/contextLimits.js +2 -0
  23. package/ts_build/src/clients/contextLimits.js.map +1 -1
  24. package/ts_build/src/clients/gemini.js +14 -3
  25. package/ts_build/src/clients/gemini.js.map +1 -1
  26. package/ts_build/src/clients/http.js +8 -5
  27. package/ts_build/src/clients/http.js.map +1 -1
  28. package/ts_build/src/clients/knowhowMcp.js +2 -2
  29. package/ts_build/src/clients/knowhowMcp.js.map +1 -1
  30. package/ts_build/src/clients/openai.js +7 -4
  31. package/ts_build/src/clients/openai.js.map +1 -1
  32. package/ts_build/src/clients/pricing/google.d.ts +1 -0
  33. package/ts_build/src/clients/pricing/google.js +7 -0
  34. package/ts_build/src/clients/pricing/google.js.map +1 -1
  35. package/ts_build/src/clients/pricing/xai.d.ts +1 -0
  36. package/ts_build/src/clients/pricing/xai.js +4 -0
  37. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  38. package/ts_build/src/clients/xai.js +6 -3
  39. package/ts_build/src/clients/xai.js.map +1 -1
  40. package/ts_build/src/types.d.ts +2 -0
  41. package/ts_build/tests/clients/reasoningEffort.test.js +2 -1
  42. package/ts_build/tests/clients/reasoningEffort.test.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.153",
3
+ "version": "0.0.154",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -1107,6 +1107,14 @@ export abstract class BaseAgent implements IAgent {
1107
1107
 
1108
1108
  // Typically, there's only one choice in the array, but you could have many
1109
1109
  // If you set `n` to more than 1, you will get multiple choices
1110
+ // Collect all tool calls across all choices up front.
1111
+ // This is used by detectTruncatedToolCalls so that the size heuristic
1112
+ // compares the full completion_tokens against ALL argument content,
1113
+ // rather than just the current choice's single tool call (Anthropic maps
1114
+ // each parallel tool_use block to a separate choice).
1115
+ const allResponseToolCalls = response.choices.flatMap(
1116
+ (c) => c.message.tool_calls ?? []
1117
+ );
1110
1118
  for (const choice of response.choices) {
1111
1119
  messages.push(choice.message);
1112
1120
 
@@ -1136,7 +1144,8 @@ export abstract class BaseAgent implements IAgent {
1136
1144
 
1137
1145
  const truncationWarning = this.detectTruncatedToolCalls(
1138
1146
  toolCalls,
1139
- response
1147
+ response,
1148
+ allResponseToolCalls
1140
1149
  );
1141
1150
  if (truncationWarning) {
1142
1151
  messages.push(truncationWarning as Message);
@@ -1589,11 +1598,14 @@ export abstract class BaseAgent implements IAgent {
1589
1598
  * 2. The model reported many output tokens but the total argument content received is tiny
1590
1599
  * relative to what those tokens should represent (soft/silent truncation).
1591
1600
  *
1601
+ * @param toolCalls - The tool calls from the CURRENT choice being checked.
1602
+ * @param allToolCalls - ALL tool calls across ALL choices in the response (used for the size heuristic).
1592
1603
  * Returns a warning system message if truncation is detected, or null otherwise.
1593
1604
  */
1594
1605
  detectTruncatedToolCalls(
1595
1606
  toolCalls: ToolCall[],
1596
- response: CompletionResponse
1607
+ response: CompletionResponse,
1608
+ allToolCalls?: ToolCall[]
1597
1609
  ): { role: string; content: string } | null {
1598
1610
  // Subtract thinking/reasoning tokens — they're billed as output tokens but
1599
1611
  // don't produce visible argument content, so including them inflates the heuristic.
@@ -1602,7 +1614,13 @@ export abstract class BaseAgent implements IAgent {
1602
1614
  (response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
1603
1615
  (response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
1604
1616
  const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
1605
- const totalArgLength = toolCalls.reduce(
1617
+ // Use allToolCalls for the size heuristic so that parallel tool calls from
1618
+ // Anthropic (each mapped to a separate choice) are counted together. Without
1619
+ // this, a response with N parallel tool calls would compare the full
1620
+ // completion_tokens count against only 1/N of the actual argument content,
1621
+ // causing false-positive truncation warnings.
1622
+ const callsForSizeCheck = allToolCalls ?? toolCalls;
1623
+ const totalArgLength = callsForSizeCheck.reduce(
1606
1624
  (sum, tc) => sum + (tc.function?.arguments?.length || 0),
1607
1625
  0
1608
1626
  );
@@ -1688,9 +1706,13 @@ export abstract class BaseAgent implements IAgent {
1688
1706
  .filter((s): s is string => typeof s === "string" && s.trim().length > 0)
1689
1707
  .join("\n\n");
1690
1708
 
1691
- this.taskBreakdown = breakdownContent || "";
1709
+ if (!breakdownContent) {
1710
+ throw new Error("Compaction task-breakdown request returned no text content");
1711
+ }
1712
+
1713
+ this.taskBreakdown = breakdownContent;
1692
1714
  this.log(`task breakdown cost: ${response.usd_cost}`);
1693
- return this.taskBreakdown || undefined;
1715
+ return this.taskBreakdown;
1694
1716
  }
1695
1717
 
1696
1718
  async compressMessages(messages: Message[]) {
@@ -1742,6 +1764,10 @@ export abstract class BaseAgent implements IAgent {
1742
1764
  const summaries = response.choices
1743
1765
  .map((c) => c.message.content)
1744
1766
  .filter((s): s is string => typeof s === "string" && s.trim().length > 0);
1767
+ if (summaries.length === 0) {
1768
+ throw new Error("Compaction summary request returned no text content");
1769
+ }
1770
+
1745
1771
  this.summaries.push(...summaries);
1746
1772
 
1747
1773
  const startMessages = [
@@ -31,34 +31,37 @@ function getPluginNames(): string {
31
31
  }
32
32
 
33
33
  export const includedTools = [
34
- {
35
- type: "function",
36
- function: {
37
- name: "agentCall",
38
- description:
39
- "Run another registered agent in-process as a blocking call and get its final answer back as a string. Unlike a plain llm() call, the target agent can use tools, loop, and maintain its own context. Useful as a graph node that delegates to a specialized agent (e.g. Researcher, Patcher). For long-running / parallel / detached work prefer startAgentTask.",
40
- parameters: {
41
- type: "object",
42
- positional: true,
43
- properties: {
44
- agentName: {
45
- type: "string",
46
- description:
47
- "The name of the agent to call (e.g. Researcher, Developer, Patcher).",
48
- },
49
- query: {
50
- type: "string",
51
- description: "The query / task to send to the agent.",
52
- },
53
- },
54
- required: ["agentName", "query"],
55
- },
56
- returns: {
57
- type: "string",
58
- description: "The agent's final answer text.",
59
- },
60
- },
61
- },
34
+ /*
35
+ * This doesn't use messageProcessors etc, needs rewrite
36
+ *{
37
+ * type: "function",
38
+ * function: {
39
+ * name: "agentCall",
40
+ * description:
41
+ * "Run another registered agent in-process as a blocking call and get its final answer back as a string. Unlike a plain llm() call, the target agent can use tools, loop, and maintain its own context. Useful as a graph node that delegates to a specialized agent (e.g. Researcher, Patcher). For long-running / parallel / detached work prefer startAgentTask.",
42
+ * parameters: {
43
+ * type: "object",
44
+ * positional: true,
45
+ * properties: {
46
+ * agentName: {
47
+ * type: "string",
48
+ * description:
49
+ * "The name of the agent to call (e.g. Researcher, Developer, Patcher).",
50
+ * },
51
+ * query: {
52
+ * type: "string",
53
+ * description: "The query / task to send to the agent.",
54
+ * },
55
+ * },
56
+ * required: ["agentName", "query"],
57
+ * },
58
+ * returns: {
59
+ * type: "string",
60
+ * description: "The agent's final answer text.",
61
+ * },
62
+ * },
63
+ *},
64
+ */
62
65
  {
63
66
  type: "function",
64
67
  function: {
@@ -472,7 +472,9 @@ export class GenericAnthropicClient implements GenericClient {
472
472
  ...(thinkingParam && { thinking: thinkingParam }),
473
473
  ...(effortOutputConfig && { output_config: effortOutputConfig }),
474
474
  ...(tools.length && {
475
- tool_choice: { type: "auto" },
475
+ tool_choice: {
476
+ type: options.tool_choice ?? "auto",
477
+ },
476
478
  tools,
477
479
  }),
478
480
  }, { signal: options.signal });
@@ -123,7 +123,8 @@ export const ContextLimits: Record<string, number> = {
123
123
  [Models.anthropic.Sonnet4_0]: 200_000,
124
124
 
125
125
  // ─── Google ───────────────────────────────────────────────────────────────
126
- // Gemini 3.6 / 3.5 (text)
126
+ // Gemini 3.7 / 3.6 / 3.5 (text)
127
+ [Models.google.Gemini_37_Flash]: 1_048_576,
127
128
  [Models.google.Gemini_36_Flash]: 1_000_000,
128
129
  [Models.google.Gemini_35_Flash]: 1_000_000,
129
130
  [Models.google.Gemini_35_Flash_Lite]: 1_000_000,
@@ -178,6 +179,7 @@ export const ContextLimits: Record<string, number> = {
178
179
  [Models.google.Gemini_15_Pro]: 2_000_000,
179
180
 
180
181
  // ─── xAI ──────────────────────────────────────────────────────────────────
182
+ [Models.xai.Grok4_6]: 500_000,
181
183
  [Models.xai.Grok4_1_Fast_Reasoning]: 2_000_000,
182
184
  [Models.xai.Grok4_1_Fast_NonReasoning]: 2_000_000,
183
185
  [Models.xai.Grok_4_20_Reasoning]: 131_072,
@@ -420,7 +420,7 @@ export class GenericGeminiClient implements GenericClient {
420
420
 
421
421
  /**
422
422
  * Builds the thinkingConfig for Gemini models that support it.
423
- * - Gemini 3.x models use thinkingLevel: "minimal" | "low" | "medium" | "high"
423
+ * - Gemini 3.x models use thinkingLevel (supported levels vary by model)
424
424
  * - Gemini 2.5 models use thinkingBudget: number (0 = off, -1 = dynamic)
425
425
  *
426
426
  * Maps CompletionOptions.reasoning_effort to provider-specific values.
@@ -433,9 +433,11 @@ export class GenericGeminiClient implements GenericClient {
433
433
 
434
434
  // Gemini 3.x — use thinkingLevel
435
435
  if (GoogleThinkingLevelModels.includes(model)) {
436
+ // Gemini 3.7 Flash rejects "minimal"; low is its least intensive level.
437
+ const minimumLevel = model === Models.google.Gemini_37_Flash ? "low" : "minimal";
436
438
  const levelMap: Record<string, string> = {
437
- none: "minimal",
438
- minimal: "minimal",
439
+ none: minimumLevel,
440
+ minimal: minimumLevel,
439
441
  low: "low",
440
442
  medium: "medium",
441
443
  high: "high",
@@ -474,6 +476,7 @@ export class GenericGeminiClient implements GenericClient {
474
476
  const { systemInstruction, contents } = this.transformMessages(
475
477
  options.messages
476
478
  );
479
+ const tools = this.transformTools(options.tools);
477
480
 
478
481
  try {
479
482
  await wait(2000);
@@ -483,7 +486,16 @@ export class GenericGeminiClient implements GenericClient {
483
486
  config: {
484
487
  systemInstruction,
485
488
  thinkingConfig,
486
- tools: this.transformTools(options.tools),
489
+ tools,
490
+ ...(tools.length && {
491
+ toolConfig: {
492
+ functionCallingConfig: {
493
+ mode: options.tool_choice === "none"
494
+ ? FunctionCallingConfigMode.NONE
495
+ : FunctionCallingConfigMode.AUTO,
496
+ },
497
+ },
498
+ }),
487
499
  maxOutputTokens: options.max_tokens,
488
500
  },
489
501
  });
@@ -247,9 +247,9 @@ export class HttpClient implements GenericClient {
247
247
  max_tokens: options.max_tokens || 4000,
248
248
  ...this.extra_body,
249
249
 
250
- ...(options.tools && {
250
+ ...(options.tools?.length && {
251
251
  tools: options.tools,
252
- tool_choice: "auto",
252
+ tool_choice: options.tool_choice ?? "auto",
253
253
  }),
254
254
  };
255
255
 
@@ -297,9 +297,9 @@ export class HttpClient implements GenericClient {
297
297
  ...this.extra_body,
298
298
  stream: true,
299
299
  stream_options: { include_usage: true },
300
- ...(options.tools && {
300
+ ...(options.tools?.length && {
301
301
  tools: options.tools,
302
- tool_choice: "auto",
302
+ tool_choice: options.tool_choice ?? "auto",
303
303
  }),
304
304
  };
305
305
 
@@ -442,7 +442,10 @@ export class HttpClient implements GenericClient {
442
442
  input,
443
443
  ...(instructions && { instructions }),
444
444
  ...(options.max_tokens && { max_output_tokens: options.max_tokens }),
445
- ...(tools?.length && { tools, tool_choice: "auto" }),
445
+ ...(tools?.length && {
446
+ tools,
447
+ tool_choice: options.tool_choice ?? "auto",
448
+ }),
446
449
  // Reasoning persistence: store server-side when requested, otherwise
447
450
  // ask the provider to return encrypted reasoning content so we can
448
451
  // thread the reasoning items back into the next request ourselves.
@@ -62,9 +62,9 @@ export class KnowhowMcpClient implements GenericClient {
62
62
  ...options,
63
63
  max_tokens: options.max_tokens || 3000,
64
64
 
65
- ...(options.tools && {
65
+ ...(options.tools?.length && {
66
66
  tools: options.tools,
67
- tool_choice: "auto",
67
+ tool_choice: options.tool_choice ?? "auto",
68
68
  }),
69
69
  },
70
70
  }
@@ -235,9 +235,9 @@ export class GenericOpenAiClient implements GenericClient {
235
235
  max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
236
236
  reasoning_effort: this.resolveReasoningEffort(options),
237
237
  }),
238
- ...(options.tools && {
238
+ ...(options.tools?.length && {
239
239
  tools: options.tools,
240
- tool_choice: "auto",
240
+ tool_choice: options.tool_choice ?? "auto",
241
241
  }),
242
242
  }, { signal: options.signal });
243
243
 
@@ -296,7 +296,10 @@ export class GenericOpenAiClient implements GenericClient {
296
296
  max_tokens: options.max_tokens,
297
297
  stream: true,
298
298
  stream_options: { include_usage: true },
299
- ...(options.tools && { tools: options.tools, tool_choice: "auto" }),
299
+ ...(options.tools?.length && {
300
+ tools: options.tools,
301
+ tool_choice: options.tool_choice ?? "auto",
302
+ }),
300
303
  }, { signal: options.signal });
301
304
 
302
305
  let usage: CompletionResponse['usage'] | undefined;
@@ -501,7 +504,7 @@ export class GenericOpenAiClient implements GenericClient {
501
504
  }),
502
505
  ...(tools?.length && {
503
506
  tools,
504
- tool_choice: "auto",
507
+ tool_choice: options.tool_choice ?? "auto",
505
508
  }),
506
509
  } as any);
507
510
 
@@ -7,7 +7,8 @@ import { completions, embeddings, images, videos, audios, liveApi, ModelCatalogE
7
7
  // ─── Model IDs ────────────────────────────────────────────────────────────────
8
8
 
9
9
  export const GoogleModels = {
10
- // Gemini 3.6 / 3.5
10
+ // Gemini 3.7 / 3.6 / 3.5
11
+ Gemini_37_Flash: "gemini-3.7-flash",
11
12
  Gemini_36_Flash: "gemini-3.6-flash",
12
13
  Gemini_35_Flash: "gemini-3.5-flash",
13
14
  Gemini_35_Flash_Lite: "gemini-3.5-flash-lite",
@@ -86,6 +87,7 @@ export const GoogleEmbeddingModels: Record<string, string> = {
86
87
  // ─── Modality arrays ──────────────────────────────────────────────────────────
87
88
 
88
89
  export const GoogleTextModels: string[] = [
90
+ GoogleModels.Gemini_37_Flash,
89
91
  GoogleModels.Gemini_36_Flash,
90
92
  GoogleModels.Gemini_35_Flash,
91
93
  GoogleModels.Gemini_35_Flash_Lite,
@@ -101,6 +103,7 @@ export const GoogleTextModels: string[] = [
101
103
 
102
104
  // Models that support thinkingLevel (Gemini 3.x series)
103
105
  export const GoogleThinkingLevelModels: string[] = [
106
+ GoogleModels.Gemini_37_Flash,
104
107
  GoogleModels.Gemini_36_Flash,
105
108
  GoogleModels.Gemini_35_Flash,
106
109
  GoogleModels.Gemini_35_Flash_Lite,
@@ -214,7 +217,13 @@ export interface GeminiModelPricing extends ModelPricing {
214
217
  }
215
218
 
216
219
  export const GeminiPricing: Record<string, GeminiModelPricing> = {
217
- // ── Gemini 3.6 / 3.5 ──────────────────────────────────────────────────────
220
+ // ── Gemini 3.7 / 3.6 / 3.5 ────────────────────────────────────────────────
221
+ // Introductory pricing through December 31, 2026. Standard pricing beginning
222
+ // January 1, 2027 is $1.50 input, $7.50 output, and $0.15 cached input.
223
+ [GoogleModels.Gemini_37_Flash]: {
224
+ input: 0.75, output: 3.75,
225
+ context_caching: 0.075, context_caching_storage: 0.5,
226
+ },
218
227
  [GoogleModels.Gemini_36_Flash]: {
219
228
  input: 1.5, output: 7.5,
220
229
  context_caching: 0.15, context_caching_storage: 1.0,
@@ -7,6 +7,7 @@ import { completions, images, videos, ModelCatalogEntry, ModelPricing } from "./
7
7
  // ─── Model IDs ────────────────────────────────────────────────────────────────
8
8
 
9
9
  export const XaiModels = {
10
+ Grok4_6: "grok-4.6",
10
11
  Grok_4_20_Reasoning: "grok-4.20-0309-reasoning",
11
12
  Grok_4_20_NonReasoning: "grok-4.20-0309-non-reasoning",
12
13
  Grok_4_20_MultiAgent: "grok-4.20-multi-agent-0309",
@@ -42,6 +43,7 @@ export const XaiModels = {
42
43
  // ─── Modality arrays ──────────────────────────────────────────────────────────
43
44
 
44
45
  export const XaiTextModels: string[] = [
46
+ XaiModels.Grok4_6,
45
47
  XaiModels.Grok_4_20_Reasoning, XaiModels.Grok_4_20_NonReasoning,
46
48
  XaiModels.Grok_4_20_MultiAgent,
47
49
  XaiModels.Grok4_1_Fast_Reasoning, XaiModels.Grok4_1_Fast_NonReasoning,
@@ -61,8 +63,9 @@ export const XaiResponsesOnlyModels: string[] = [
61
63
  ];
62
64
 
63
65
  // Models that support the reasoning_effort parameter
64
- // grok-3-mini variants support reasoning_effort; grok-3-beta, grok-4 etc. do NOT
66
+ // Grok 4.6, Grok 4.20 multi-agent, and grok-3-mini variants support reasoning_effort
65
67
  export const XaiReasoningModels: string[] = [
68
+ XaiModels.Grok4_6,
66
69
  XaiModels.Grok_4_20_MultiAgent,
67
70
  XaiModels.Grok3MiniBeta,
68
71
  XaiModels.Grok3MiniFastBeta,
@@ -84,6 +87,7 @@ export const XaiVideoModels: string[] = [XaiModels.GrokImagineVideo];
84
87
  // ─── Pricing (USD per 1M tokens / per-image / per-second) ────────────────────
85
88
 
86
89
  export const XaiTextPricing: Record<string, ModelPricing> = {
90
+ [XaiModels.Grok4_6]: { input: 2.0, input_gt_200k: 4.0, cache_hit: 0.50, output: 6.0, output_gt_200k: 12.0, reasoningLevels: ["low", "medium", "high", "xhigh"] },
87
91
  [XaiModels.Grok_4_20_Reasoning]: { input: 2.0, cache_hit: 0.20, output: 6.0, useResponsesApi: true },
88
92
  [XaiModels.Grok_4_20_NonReasoning]: { input: 2.0, cache_hit: 0.20, output: 6.0, useResponsesApi: true },
89
93
  [XaiModels.Grok_4_20_MultiAgent]: { input: 2.0, cache_hit: 0.20, output: 6.0, reasoningLevels: ["low", "medium", "high", "xhigh"], useResponsesApi: true },
@@ -90,9 +90,9 @@ export class GenericXAIClient implements GenericClient {
90
90
  // grok-3-mini models support reasoning_effort: "low" | "medium" | "high"
91
91
  reasoning_effort: reasoningEffort,
92
92
  }),
93
- ...(options.tools && {
93
+ ...(options.tools?.length && {
94
94
  tools: options.tools,
95
- tool_choice: "auto",
95
+ tool_choice: options.tool_choice ?? "auto",
96
96
  }),
97
97
  });
98
98
 
@@ -238,7 +238,10 @@ export class GenericXAIClient implements GenericClient {
238
238
  ...(instructions && { instructions }),
239
239
  ...(options.max_tokens && { max_output_tokens: Math.max(options.max_tokens, 16_000) }),
240
240
  ...(reasoningEffort && { reasoning: { effort: reasoningEffort } }),
241
- ...(tools?.length && { tools, tool_choice: "auto" }),
241
+ ...(tools?.length && {
242
+ tools,
243
+ tool_choice: options.tool_choice ?? "auto",
244
+ }),
242
245
  // Reasoning persistence (same construct as OpenAI's Responses API):
243
246
  // - store === true → xAI persists the response; next turn passes
244
247
  // previous_response_id to continue the reasoning chain server-side.
@@ -10,6 +10,7 @@ import {
10
10
  import {
11
11
  GoogleThinkingBudgetModels,
12
12
  GoogleThinkingLevelModels,
13
+ Models,
13
14
  } from "../../src/types";
14
15
  import { GenericXAIClient } from "../../src/clients/xai";
15
16
 
@@ -46,13 +47,16 @@ describe("reasoning effort values", () => {
46
47
 
47
48
  it("maps all values to valid Gemini thinking levels and budgets", () => {
48
49
  const client = new GenericGeminiClient("fake-key");
49
- const levelModel = GoogleThinkingLevelModels[0];
50
+ const levelModel = GoogleThinkingLevelModels.find(
51
+ (model) => model !== Models.google.Gemini_37_Flash
52
+ )!;
50
53
  const budgetModel = GoogleThinkingBudgetModels[0];
51
54
 
52
55
  expect(client.buildThinkingConfig(options(levelModel, "none"))).toEqual({ thinkingLevel: "minimal" });
53
56
  expect(client.buildThinkingConfig(options(levelModel, "minimal"))).toEqual({ thinkingLevel: "minimal" });
54
57
  expect(client.buildThinkingConfig(options(levelModel, "xhigh"))).toEqual({ thinkingLevel: "high" });
55
58
  expect(client.buildThinkingConfig(options(levelModel, "max"))).toEqual({ thinkingLevel: "high" });
59
+ expect(client.buildThinkingConfig(options(Models.google.Gemini_37_Flash, "minimal"))).toEqual({ thinkingLevel: "low" });
56
60
  expect(client.buildThinkingConfig(options(budgetModel, "none"))).toEqual({ thinkingBudget: 0 });
57
61
  expect(client.buildThinkingConfig(options(budgetModel, "minimal"))).toEqual({ thinkingBudget: 512 });
58
62
  expect(client.buildThinkingConfig(options(budgetModel, "medium"))).toEqual({ thinkingBudget: 8192 });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.153",
3
+ "version": "0.0.154",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -187,7 +187,7 @@ export declare abstract class BaseAgent implements IAgent {
187
187
  stopObserving(id?: string, reason?: string): string;
188
188
  getMessagesLength(messages: Message[]): number;
189
189
  getContextTokenCount(messages: Message[]): number;
190
- detectTruncatedToolCalls(toolCalls: ToolCall[], response: CompletionResponse): {
190
+ detectTruncatedToolCalls(toolCalls: ToolCall[], response: CompletionResponse, allToolCalls?: ToolCall[]): {
191
191
  role: string;
192
192
  content: string;
193
193
  } | null;
@@ -691,6 +691,7 @@ let BaseAgent = class BaseAgent {
691
691
  }
692
692
  }
693
693
  this.log("agent response cost: " + response?.usd_cost);
694
+ const allResponseToolCalls = response.choices.flatMap((c) => c.message.tool_calls ?? []);
694
695
  for (const choice of response.choices) {
695
696
  messages.push(choice.message);
696
697
  messages = await this.messageProcessor.processMessages(messages, "post_call");
@@ -700,7 +701,7 @@ let BaseAgent = class BaseAgent {
700
701
  messages = await this.messageProcessor.processMessages(messages, "pre_tools");
701
702
  const toolCalls = messages[messages.length - 1].tool_calls;
702
703
  this.updateCurrentThread(messages);
703
- const truncationWarning = this.detectTruncatedToolCalls(toolCalls, response);
704
+ const truncationWarning = this.detectTruncatedToolCalls(toolCalls, response, allResponseToolCalls);
704
705
  if (truncationWarning) {
705
706
  messages.push(truncationWarning);
706
707
  this.updateCurrentThread(messages);
@@ -984,12 +985,13 @@ let BaseAgent = class BaseAgent {
984
985
  }
985
986
  return this.getMessagesLength(messages);
986
987
  }
987
- detectTruncatedToolCalls(toolCalls, response) {
988
+ detectTruncatedToolCalls(toolCalls, response, allToolCalls) {
988
989
  const rawOutputTokens = response?.usage?.completion_tokens || 0;
989
990
  const thinkingTokens = (response?.usage?.output_tokens_details?.thinking_tokens ?? 0) +
990
991
  (response?.usage?.output_tokens_details?.reasoning_tokens ?? 0);
991
992
  const outputTokens = Math.max(0, rawOutputTokens - thinkingTokens);
992
- const totalArgLength = toolCalls.reduce((sum, tc) => sum + (tc.function?.arguments?.length || 0), 0);
993
+ const callsForSizeCheck = allToolCalls ?? toolCalls;
994
+ const totalArgLength = callsForSizeCheck.reduce((sum, tc) => sum + (tc.function?.arguments?.length || 0), 0);
993
995
  const expectedArgChars = outputTokens * 4;
994
996
  const suspiciouslySmallArgs = outputTokens > 1000 && totalArgLength < expectedArgChars * 0.1;
995
997
  for (const toolCall of toolCalls) {
@@ -1052,9 +1054,12 @@ let BaseAgent = class BaseAgent {
1052
1054
  .map((c) => c.message.content)
1053
1055
  .filter((s) => typeof s === "string" && s.trim().length > 0)
1054
1056
  .join("\n\n");
1055
- this.taskBreakdown = breakdownContent || "";
1057
+ if (!breakdownContent) {
1058
+ throw new Error("Compaction task-breakdown request returned no text content");
1059
+ }
1060
+ this.taskBreakdown = breakdownContent;
1056
1061
  this.log(`task breakdown cost: ${response.usd_cost}`);
1057
- return this.taskBreakdown || undefined;
1062
+ return this.taskBreakdown;
1058
1063
  }
1059
1064
  async compressMessages(messages) {
1060
1065
  let resumeIndex = messages.length - 1;
@@ -1094,6 +1099,9 @@ let BaseAgent = class BaseAgent {
1094
1099
  const summaries = response.choices
1095
1100
  .map((c) => c.message.content)
1096
1101
  .filter((s) => typeof s === "string" && s.trim().length > 0);
1102
+ if (summaries.length === 0) {
1103
+ throw new Error("Compaction summary request returned no text content");
1104
+ }
1097
1105
  this.summaries.push(...summaries);
1098
1106
  const startMessages = [
1099
1107
  {