@tyvm/knowhow 0.0.129 → 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 (96) hide show
  1. package/package.json +4 -4
  2. package/src/agents/base/base.ts +40 -5
  3. package/src/agents/tools/index.ts +1 -0
  4. package/src/agents/tools/list.ts +2 -0
  5. package/src/agents/tools/skills.ts +79 -0
  6. package/src/chat/modules/AgentModule.ts +84 -1
  7. package/src/cli.ts +4 -0
  8. package/src/clients/anthropic.ts +59 -1
  9. package/src/clients/openai.ts +28 -5
  10. package/src/clients/pricing/openai.ts +3 -0
  11. package/src/clients/types.ts +27 -4
  12. package/src/clients/xai.ts +2 -2
  13. package/src/commands/agent.ts +19 -0
  14. package/src/commands/behaviors.ts +47 -0
  15. package/src/commands/services.ts +6 -2
  16. package/src/commands/skills.ts +71 -0
  17. package/src/index.ts +2 -0
  18. package/src/plugins/PluginBase.ts +0 -2
  19. package/src/plugins/SkillsPlugin.ts +11 -124
  20. package/src/plugins/language.ts +0 -1
  21. package/src/plugins/plugins.ts +24 -13
  22. package/src/plugins/types.ts +8 -2
  23. package/src/services/BehaviorsService.ts +432 -0
  24. package/src/services/KnowhowClient.ts +61 -0
  25. package/src/services/SkillsService.ts +622 -0
  26. package/src/services/Tools.ts +2 -0
  27. package/src/services/index.ts +8 -2
  28. package/src/services/modules/types.ts +2 -0
  29. package/ts_build/package.json +4 -4
  30. package/ts_build/src/agents/base/base.d.ts +9 -1
  31. package/ts_build/src/agents/base/base.js +29 -4
  32. package/ts_build/src/agents/base/base.js.map +1 -1
  33. package/ts_build/src/agents/tools/index.d.ts +1 -0
  34. package/ts_build/src/agents/tools/index.js +1 -0
  35. package/ts_build/src/agents/tools/index.js.map +1 -1
  36. package/ts_build/src/agents/tools/list.js +2 -0
  37. package/ts_build/src/agents/tools/list.js.map +1 -1
  38. package/ts_build/src/agents/tools/skills.d.ts +33 -0
  39. package/ts_build/src/agents/tools/skills.js +62 -0
  40. package/ts_build/src/agents/tools/skills.js.map +1 -0
  41. package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
  42. package/ts_build/src/chat/modules/AgentModule.js +69 -0
  43. package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
  44. package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
  45. package/ts_build/src/cli.js +4 -0
  46. package/ts_build/src/cli.js.map +1 -1
  47. package/ts_build/src/clients/anthropic.d.ts +2 -0
  48. package/ts_build/src/clients/anthropic.js +35 -0
  49. package/ts_build/src/clients/anthropic.js.map +1 -1
  50. package/ts_build/src/clients/openai.d.ts +1 -0
  51. package/ts_build/src/clients/openai.js +25 -5
  52. package/ts_build/src/clients/openai.js.map +1 -1
  53. package/ts_build/src/clients/pricing/openai.js +3 -0
  54. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  55. package/ts_build/src/clients/types.d.ts +6 -1
  56. package/ts_build/src/clients/xai.js +1 -1
  57. package/ts_build/src/clients/xai.js.map +1 -1
  58. package/ts_build/src/commands/agent.js +16 -0
  59. package/ts_build/src/commands/agent.js.map +1 -1
  60. package/ts_build/src/commands/behaviors.d.ts +2 -0
  61. package/ts_build/src/commands/behaviors.js +43 -0
  62. package/ts_build/src/commands/behaviors.js.map +1 -0
  63. package/ts_build/src/commands/services.js +4 -2
  64. package/ts_build/src/commands/services.js.map +1 -1
  65. package/ts_build/src/commands/skills.d.ts +2 -0
  66. package/ts_build/src/commands/skills.js +61 -0
  67. package/ts_build/src/commands/skills.js.map +1 -0
  68. package/ts_build/src/index.d.ts +2 -0
  69. package/ts_build/src/index.js +5 -1
  70. package/ts_build/src/index.js.map +1 -1
  71. package/ts_build/src/plugins/PluginBase.d.ts +0 -2
  72. package/ts_build/src/plugins/PluginBase.js.map +1 -1
  73. package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
  74. package/ts_build/src/plugins/SkillsPlugin.js +6 -135
  75. package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
  76. package/ts_build/src/plugins/language.js.map +1 -1
  77. package/ts_build/src/plugins/plugins.d.ts +4 -1
  78. package/ts_build/src/plugins/plugins.js +17 -11
  79. package/ts_build/src/plugins/plugins.js.map +1 -1
  80. package/ts_build/src/plugins/types.d.ts +3 -2
  81. package/ts_build/src/services/BehaviorsService.d.ts +46 -0
  82. package/ts_build/src/services/BehaviorsService.js +358 -0
  83. package/ts_build/src/services/BehaviorsService.js.map +1 -0
  84. package/ts_build/src/services/KnowhowClient.d.ts +8 -0
  85. package/ts_build/src/services/KnowhowClient.js +20 -0
  86. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  87. package/ts_build/src/services/SkillsService.d.ts +53 -0
  88. package/ts_build/src/services/SkillsService.js +478 -0
  89. package/ts_build/src/services/SkillsService.js.map +1 -0
  90. package/ts_build/src/services/Tools.d.ts +2 -0
  91. package/ts_build/src/services/Tools.js.map +1 -1
  92. package/ts_build/src/services/index.d.ts +3 -0
  93. package/ts_build/src/services/index.js +8 -2
  94. package/ts_build/src/services/index.js.map +1 -1
  95. package/ts_build/src/services/modules/index.d.ts +4 -2
  96. package/ts_build/src/services/modules/types.d.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.129",
3
+ "version": "0.0.130",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -35,7 +35,6 @@
35
35
  "license": "MIT",
36
36
  "devDependencies": {
37
37
  "@babel/preset-typescript": "^7.23.3",
38
- "typescript": "^5.0.0",
39
38
  "@types/diff": "^5.2.1",
40
39
  "@types/express": "^4.17.13",
41
40
  "@types/jest": "^29.5.13",
@@ -47,10 +46,11 @@
47
46
  "ts-jest": "^29.1.1",
48
47
  "ts-node": "^10.7.0",
49
48
  "tslint": "^6.1.3",
50
- "tslint-config-prettier": "^1.18.0"
49
+ "tslint-config-prettier": "^1.18.0",
50
+ "typescript": "^5.0.0"
51
51
  },
52
52
  "dependencies": {
53
- "@anthropic-ai/sdk": "^0.39.0",
53
+ "@anthropic-ai/sdk": "^0.111.0",
54
54
  "@google/genai": "^0.14.1",
55
55
  "@inquirer/editor": "^4.2.18",
56
56
  "@modelcontextprotocol/sdk": "^1.13.3",
@@ -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) {
@@ -715,10 +742,10 @@ export abstract class BaseAgent implements IAgent {
715
742
  return new Promise<T>((resolve, reject) => {
716
743
  let interrupted = false;
717
744
 
718
- this._interruptResolve = (value: any) => {
745
+ this._interruptResolve = () => {
719
746
  interrupted = true;
720
747
  this._interruptResolve = null;
721
- resolve(value);
748
+ resolve(interruptValue);
722
749
  };
723
750
 
724
751
  promise
@@ -751,7 +778,7 @@ export abstract class BaseAgent implements IAgent {
751
778
  interrupt(message = "User interrupted this action you were waiting on") {
752
779
  this.log(`Interrupting current operation: ${message}`);
753
780
  if (this._interruptResolve) {
754
- this._interruptResolve(message);
781
+ this._interruptResolve();
755
782
  } else {
756
783
  this.log("No active interruptible operation to interrupt", "warn");
757
784
  }
@@ -848,6 +875,8 @@ export abstract class BaseAgent implements IAgent {
848
875
  tools: this.getEnabledTools(),
849
876
  tool_choice: "auto",
850
877
  long_ttl_cache: this.runTime() > 300_000,
878
+ ...(this.reasoningEffort !== undefined && { reasoning_effort: this.reasoningEffort }),
879
+ ...(this.summarizeReasoning !== undefined && { reasoning_summary: this.summarizeReasoning }),
851
880
  },
852
881
  { interruptValue: interruptResponse }
853
882
  );
@@ -1230,7 +1259,13 @@ export abstract class BaseAgent implements IAgent {
1230
1259
  toolCalls: ToolCall[],
1231
1260
  response: CompletionResponse
1232
1261
  ): { role: string; content: string } | null {
1233
- 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);
1234
1269
  const totalArgLength = toolCalls.reduce(
1235
1270
  (sum, tc) => sum + (tc.function?.arguments?.length || 0),
1236
1271
  0
@@ -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,79 @@
1
+ import { ToolsService } from "../../services/Tools";
2
+ import { services } from "../../services";
3
+
4
+ /**
5
+ * List all skills currently loaded in memory.
6
+ * Returns a formatted string of name: description lines.
7
+ */
8
+ export async function listSkills(this: ToolsService): Promise<string> {
9
+ const toolService =
10
+ this instanceof ToolsService ? (this as ToolsService) : services().Tools;
11
+
12
+ const { Behaviors } = toolService.getContext();
13
+
14
+ const skills = Behaviors.listSkills();
15
+ if (skills.length === 0) {
16
+ return "No skills available. Run `knowhow behaviors download --skills-only` to fetch skills.";
17
+ }
18
+ return skills.map((s) => `- ${s.name}: ${s.description}`).join("\n");
19
+ }
20
+
21
+ /**
22
+ * Load full instructions for a named skill from memory.
23
+ */
24
+ export async function loadSkill(
25
+ this: ToolsService,
26
+ name: string
27
+ ): Promise<string> {
28
+ const toolService =
29
+ this instanceof ToolsService ? (this as ToolsService) : services().Tools;
30
+
31
+ const { Behaviors } = toolService.getContext();
32
+
33
+ const skill = Behaviors.findSkill(name);
34
+ if (!skill) {
35
+ const names = Behaviors.listSkills()
36
+ .map((s) => s.name)
37
+ .join(", ");
38
+ return `Skill "${name}" not found. Available skills: ${names || "none"}`;
39
+ }
40
+ return `## Skill: ${skill.name}\n\n${skill.instructions}`;
41
+ }
42
+
43
+ /** Tool definitions for listSkills + loadSkill */
44
+ export const skillsToolDefinitions = [
45
+ {
46
+ type: "function" as const,
47
+ function: {
48
+ name: "listSkills",
49
+ description:
50
+ "List all available skills by name and description. Call this to discover what skills are available before loading one.",
51
+ parameters: {
52
+ type: "object" as const,
53
+ positional: true,
54
+ properties: {} as Record<string, never>,
55
+ required: [] as string[],
56
+ },
57
+ },
58
+ },
59
+ {
60
+ type: "function" as const,
61
+ function: {
62
+ name: "loadSkill",
63
+ description:
64
+ "Load the full instructions for a named skill. Use this when you need to follow a specific skill's guidance.",
65
+ parameters: {
66
+ type: "object" as const,
67
+ positional: true,
68
+ properties: {
69
+ name: {
70
+ type: "string",
71
+ description:
72
+ "The skill name to load (must match exactly or approximately)",
73
+ },
74
+ },
75
+ required: ["name"] as string[],
76
+ },
77
+ },
78
+ },
79
+ ];
@@ -18,7 +18,7 @@ import { BaseChatModule } from "./BaseChatModule";
18
18
  import { services } from "../../services/index";
19
19
  import { BaseAgent } from "../../agents/index";
20
20
  import { ChatCommand, ChatMode, ChatContext, ChatService } from "../types";
21
- import { Message } from "../../clients/types";
21
+ import { Message, CompletionOptions } from "../../clients/types";
22
22
  import { ChatInteraction } from "../../types";
23
23
  import { Marked } from "../../utils/index";
24
24
  import { TokenCompressor } from "../../processors/TokenCompressor";
@@ -52,6 +52,11 @@ export class AgentModule extends BaseChatModule {
52
52
  /** Currently active synced agent watcher (for FS or Web agents) */
53
53
  private activeSyncedWatcher: SyncedAgentWatcher | undefined;
54
54
 
55
+ /** Reasoning effort to apply to agents started from this module */
56
+ private reasoningEffort: CompletionOptions["reasoning_effort"] | undefined = undefined;
57
+ /** Whether to request a reasoning summary from supporting models */
58
+ private summarizeReasoning: boolean = true;
59
+
55
60
  /** Stored wire params for rewireAgentRendering */
56
61
  private _wireAgentEvents: EventService | undefined;
57
62
  private _wireTaskId: string | undefined;
@@ -138,6 +143,78 @@ export class AgentModule extends BaseChatModule {
138
143
  }
139
144
  },
140
145
  },
146
+ {
147
+ name: "reasoning_effort",
148
+ description:
149
+ "Set the reasoning effort for the agent (none, low, medium, high). Use 'none' to disable thinking on models that support it.",
150
+ handler: async (args: string[]): Promise<void> => {
151
+ const validEfforts: CompletionOptions["reasoning_effort"][] = [
152
+ "none",
153
+ "low",
154
+ "medium",
155
+ "high",
156
+ ];
157
+ const effort = args[0] as CompletionOptions["reasoning_effort"];
158
+ if (!effort || !validEfforts.includes(effort)) {
159
+ console.log(
160
+ `Current reasoning effort: ${this.reasoningEffort ?? "(not set — model default)"}`
161
+ );
162
+ console.log(
163
+ `Usage: /reasoning_effort <${validEfforts.join("|")}>`
164
+ );
165
+ return;
166
+ }
167
+ this.reasoningEffort = effort;
168
+ const agent = (this.chatService?.getContext()?.selectedAgent as BaseAgent);
169
+ if (agent) agent.setReasoningEffort(effort);
170
+ console.log(`Reasoning effort set to: ${effort}`);
171
+ },
172
+ },
173
+ {
174
+ name: "effort",
175
+ description: "Alias for /reasoning_effort",
176
+ handler: async (args: string[]): Promise<void> => {
177
+ const validEfforts: CompletionOptions["reasoning_effort"][] = [
178
+ "none",
179
+ "low",
180
+ "medium",
181
+ "high",
182
+ ];
183
+ const effort = args[0] as CompletionOptions["reasoning_effort"];
184
+ if (!effort || !validEfforts.includes(effort)) {
185
+ console.log(
186
+ `Current reasoning effort: ${this.reasoningEffort ?? "(not set — model default)"}`
187
+ );
188
+ console.log(
189
+ `Usage: /effort <${validEfforts.join("|")}>`
190
+ );
191
+ return;
192
+ }
193
+ this.reasoningEffort = effort;
194
+ const agent = (this.chatService?.getContext()?.selectedAgent as BaseAgent);
195
+ if (agent) agent.setReasoningEffort(effort);
196
+ console.log(`Reasoning effort set to: ${effort}`);
197
+ },
198
+ },
199
+ {
200
+ name: "reasoning_summary",
201
+ description:
202
+ "Toggle whether the agent displays a reasoning summary (true/false). Supported by reasoning models only.",
203
+ handler: async (args: string[]): Promise<void> => {
204
+ const val = args[0];
205
+ if (val !== "true" && val !== "false") {
206
+ console.log(
207
+ `Current reasoning summary: ${this.summarizeReasoning}`
208
+ );
209
+ console.log(`Usage: /reasoning_summary <true|false>`);
210
+ return;
211
+ }
212
+ this.summarizeReasoning = val === "true";
213
+ const agent = (this.chatService?.getContext()?.selectedAgent as BaseAgent);
214
+ if (agent) agent.setSummarizeReasoning(this.summarizeReasoning);
215
+ console.log(`Reasoning summary set to: ${this.summarizeReasoning}`);
216
+ },
217
+ },
141
218
  {
142
219
  name: "detach",
143
220
  description: "Detach from the currently attached agent",
@@ -754,6 +831,12 @@ export class AgentModule extends BaseChatModule {
754
831
  ]);
755
832
  }
756
833
 
834
+ // Apply module-level reasoning settings to the agent
835
+ if (this.reasoningEffort !== undefined) {
836
+ agent.setReasoningEffort(this.reasoningEffort);
837
+ }
838
+ agent.setSummarizeReasoning(this.summarizeReasoning);
839
+
757
840
  // Set up message processors like in original startAgent
758
841
 
759
842
  // Register an override for askHuman to use CLI input method
package/src/cli.ts CHANGED
@@ -38,6 +38,8 @@ import {
38
38
  } from "./commands/misc";
39
39
  import { addConvertCommand } from "./commands/convert";
40
40
  import { addReplayCommand } from "./commands/replay";
41
+ import { addBehaviorsCommand } from "./commands/behaviors";
42
+ import { addSkillsCommand } from "./commands/skills";
41
43
 
42
44
  // Handle unhandled promise rejections gracefully — particularly from MCP SDK
43
45
  // which fires errors via event emitters that can bypass Promise.allSettled.
@@ -102,6 +104,8 @@ async function main() {
102
104
  addMcpCommands(program);
103
105
  addConvertCommand(program);
104
106
  addReplayCommand(program);
107
+ addBehaviorsCommand(program);
108
+ addSkillsCommand(program);
105
109
 
106
110
  // Load global modules early (before parse) so they can register CLI subcommands.
107
111
  // We pass only the Program in context — no services are spun up at this stage.
@@ -1,6 +1,6 @@
1
1
  import Anthropic from "@anthropic-ai/sdk";
2
2
  import { wait } from "../utils";
3
- import { AnthropicTextPricing } from "./pricing";
3
+ import { AnthropicTextPricing, AnthropicModels } from "./pricing";
4
4
  import { ContextLimits } from "./contextLimits";
5
5
  import { ModelModality } from "./types";
6
6
  import { Models } from "../types";
@@ -24,6 +24,16 @@ import {
24
24
 
25
25
  type MessageParam = Anthropic.MessageParam;
26
26
  type Usage = Anthropic.Usage;
27
+ type ThinkingParam = Anthropic.Messages.ThinkingConfigParam;
28
+
29
+ const alwaysOnModels: readonly string[] = [
30
+ AnthropicModels.Sonnet5, AnthropicModels.Fable5,
31
+ AnthropicModels.Mythos5, AnthropicModels.MythosPreview,
32
+ ];
33
+ const optInModels: readonly string[] = [
34
+ AnthropicModels.Opus4_8, AnthropicModels.Opus4_7,
35
+ AnthropicModels.Opus4_6, AnthropicModels.Sonnet4_6,
36
+ ];
27
37
 
28
38
  export class GenericAnthropicClient implements GenericClient {
29
39
  private client: Anthropic;
@@ -48,6 +58,36 @@ export class GenericAnthropicClient implements GenericClient {
48
58
  }
49
59
  }
50
60
 
61
+ /**
62
+ * Determine the Anthropic `thinking` param from CompletionOptions.
63
+ * Returns undefined if the model does not support thinking.
64
+ */
65
+ getThinkingParam(
66
+ model: string,
67
+ reasoning_effort?: CompletionOptions["reasoning_effort"],
68
+ reasoning_summary?: boolean,
69
+ ): ThinkingParam | undefined {
70
+ const effortIsNone = reasoning_effort === "none";
71
+ const withSummary = reasoning_summary ? ({ display: "summarized" } as const) : {};
72
+
73
+ if (alwaysOnModels.includes(model)) {
74
+ return effortIsNone
75
+ ? { type: "disabled" }
76
+ : { type: "adaptive", ...withSummary };
77
+ }
78
+
79
+ if (optInModels.includes(model)) {
80
+ // Only enable when explicitly requested (not "none" and not undefined).
81
+ if (!effortIsNone && reasoning_effort !== undefined) {
82
+ return { type: "adaptive", ...withSummary };
83
+ }
84
+ return undefined;
85
+ }
86
+
87
+ // Model does not support thinking.
88
+ return undefined;
89
+ }
90
+
51
91
  /**
52
92
  * Clean JSON Schema for Anthropic API compatibility.
53
93
  * Removes unsupported fields like additionalProperties, $ref, $defs, positional.
@@ -360,6 +400,13 @@ export class GenericAnthropicClient implements GenericClient {
360
400
  options: CompletionOptions
361
401
  ): Promise<CompletionResponse> {
362
402
  const longTtl = !!options.long_ttl_cache;
403
+
404
+ const thinkingParam = this.getThinkingParam(
405
+ options.model,
406
+ options.reasoning_effort,
407
+ options.reasoning_summary,
408
+ );
409
+
363
410
  const systemMessage = options.messages
364
411
  .filter((msg) => msg.role === "system")
365
412
  .map((msg) => msg.content || "")
@@ -382,6 +429,7 @@ export class GenericAnthropicClient implements GenericClient {
382
429
  ]
383
430
  : undefined,
384
431
  max_tokens: options.max_tokens || 8000,
432
+ ...(thinkingParam && { thinking: thinkingParam }),
385
433
  ...(tools.length && {
386
434
  tool_choice: { type: "auto" },
387
435
  tools,
@@ -431,6 +479,16 @@ export class GenericAnthropicClient implements GenericClient {
431
479
  total_tokens: (response.usage.input_tokens ?? 0) + (response.usage.output_tokens ?? 0),
432
480
  cache_creation_input_tokens: response.usage.cache_creation_input_tokens ?? 0,
433
481
  cache_read_input_tokens: response.usage.cache_read_input_tokens ?? 0,
482
+ ...((response.usage as any).thinking_input_tokens != null ||
483
+ (response.usage as any).output_tokens_details?.thinking_tokens != null
484
+ ? {
485
+ output_tokens_details: {
486
+ thinking_tokens:
487
+ (response.usage as any).thinking_input_tokens ??
488
+ (response.usage as any).output_tokens_details?.thinking_tokens ?? 0,
489
+ },
490
+ }
491
+ : {}),
434
492
  } : undefined,
435
493
  usd_cost: this.calculateCost(options.model, response.usage),
436
494
  };
@@ -107,7 +107,11 @@ export class GenericOpenAiClient implements GenericClient {
107
107
  }
108
108
 
109
109
  resolveReasoningEffort(options: CompletionOptions): "low" | "medium" | "high" {
110
- return options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
110
+ const effort = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
111
+ // "none" is not a valid SDK ReasoningEffort — callers that need "none" should
112
+ // check isReasoningDisabled() separately and skip the reasoning params.
113
+ if (effort === "none") return "low";
114
+ return effort;
111
115
  }
112
116
 
113
117
  /**
@@ -116,7 +120,8 @@ export class GenericOpenAiClient implements GenericClient {
116
120
  * If the requested level is not supported, picks the lowest supported level.
117
121
  */
118
122
  resolveReasoningEffortForModel(options: CompletionOptions): string {
119
- const requested = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
123
+ const raw = options.reasoning_effort ?? this.detectReasoningEffort(options.messages);
124
+ const requested = raw === "none" ? "none" : raw;
120
125
  const pricing = OpenAiTextPricing[options.model];
121
126
  const supportedLevels = pricing?.reasoningLevels;
122
127
  if (!supportedLevels || supportedLevels.length === 0) {
@@ -130,6 +135,11 @@ export class GenericOpenAiClient implements GenericClient {
130
135
  return supportedLevels[0];
131
136
  }
132
137
 
138
+ /** Returns true when the caller explicitly requested thinking/reasoning to be disabled. */
139
+ isReasoningDisabled(options: CompletionOptions): boolean {
140
+ return options.reasoning_effort === "none";
141
+ }
142
+
133
143
  async createChatCompletion(
134
144
  options: CompletionOptions
135
145
  ): Promise<CompletionResponse> {
@@ -154,7 +164,7 @@ export class GenericOpenAiClient implements GenericClient {
154
164
  model: options.model,
155
165
  messages: openaiMessages,
156
166
  max_tokens: options.max_tokens,
157
- ...(OpenAiReasoningModels.includes(options.model) && {
167
+ ...(OpenAiReasoningModels.includes(options.model) && !this.isReasoningDisabled(options) && {
158
168
  max_tokens: undefined,
159
169
  max_completion_tokens: Math.max(options.max_tokens ?? 0, 16_000),
160
170
  reasoning_effort: this.resolveReasoningEffort(options),
@@ -186,6 +196,11 @@ export class GenericOpenAiClient implements GenericClient {
186
196
  prompt_tokens_details: {
187
197
  cached_tokens: response.usage.prompt_tokens_details?.cached_tokens ?? 0,
188
198
  },
199
+ ...(response.usage.completion_tokens_details?.reasoning_tokens != null && {
200
+ output_tokens_details: {
201
+ reasoning_tokens: response.usage.completion_tokens_details.reasoning_tokens,
202
+ },
203
+ }),
189
204
  } : undefined,
190
205
  usd_cost: usdCost,
191
206
  };
@@ -350,9 +365,12 @@ export class GenericOpenAiClient implements GenericClient {
350
365
  input,
351
366
  ...(instructions && { instructions }),
352
367
  // Don't limit max_output_tokens for Responses API - codex truncates tool call arguments when limited
353
- ...(OpenAiReasoningModels.includes(options.model) && {
368
+ ...(OpenAiReasoningModels.includes(options.model) && !this.isReasoningDisabled(options) && {
354
369
  max_output_tokens: Math.max(options.max_tokens || 0, 16000),
355
- reasoning: { effort: this.resolveReasoningEffortForModel(options) },
370
+ reasoning: {
371
+ effort: this.resolveReasoningEffortForModel(options),
372
+ ...(options.reasoning_summary && { summary: "auto" }),
373
+ },
356
374
  }),
357
375
  ...(tools?.length && {
358
376
  tools,
@@ -372,6 +390,11 @@ export class GenericOpenAiClient implements GenericClient {
372
390
  cached_tokens:
373
391
  response.usage.input_tokens_details?.cached_tokens ?? 0,
374
392
  },
393
+ ...((response.usage as any).output_tokens_details?.reasoning_tokens != null && {
394
+ output_tokens_details: {
395
+ reasoning_tokens: (response.usage as any).output_tokens_details.reasoning_tokens,
396
+ },
397
+ }),
375
398
  }
376
399
  : undefined;
377
400
 
@@ -167,6 +167,9 @@ export const OpenAiResponsesOnlyModels: string[] = [
167
167
  OpenAiModels.GPT_55_Pro,
168
168
  OpenAiModels.GPT_55,
169
169
  OpenAiModels.GPT_5_Pro,
170
+ OpenAiModels.GPT_56_Sol,
171
+ OpenAiModels.GPT_56_Terra,
172
+ OpenAiModels.GPT_56_Luna,
170
173
  OpenAiModels.o1,
171
174
  OpenAiModels.o1_Pro,
172
175
  ];
@@ -86,10 +86,26 @@ export interface CompletionOptions extends RetryOptions {
86
86
  tools?: Tool[];
87
87
  tool_choice?: "auto" | "none";
88
88
  max_tokens?: number;
89
- /** Reasoning effort level for models that support it.
90
- * Maps to: OpenAI reasoning_effort, xAI reasoning.effort, Gemini thinkingLevel/thinkingBudget, Anthropic thinking budget.
91
- * "low" = minimal thinking, "medium" = balanced, "high" = maximum reasoning */
92
- reasoning_effort?: "low" | "medium" | "high";
89
+ /**
90
+ * Reasoning effort level for models that support it.
91
+ * Maps to: OpenAI reasoning_effort/reasoning.effort, xAI reasoning.effort,
92
+ * Gemini thinkingLevel/thinkingBudget, Anthropic thinking mode.
93
+ *
94
+ * "none" = disable thinking entirely (Anthropic: thinking.type="disabled",
95
+ * OpenAI: reasoning.effort="none"). Only supported by models that
96
+ * allow disabling thinking (e.g. claude-sonnet-5).
97
+ * "low" = minimal thinking
98
+ * "medium" = balanced (default for most models)
99
+ * "high" = maximum reasoning
100
+ */
101
+ reasoning_effort?: "none" | "low" | "medium" | "high";
102
+ /**
103
+ * When true, request a summarized view of the model's thinking process.
104
+ * - Anthropic: thinking.display = "summarized"
105
+ * - OpenAI: reasoning.summary = "auto"
106
+ * Defaults to false (no summary displayed).
107
+ */
108
+ reasoning_summary?: boolean;
93
109
  /**
94
110
  * When true, hints to the client that this task is long-running and it should
95
111
  * use a long-TTL cache where available.
@@ -115,6 +131,13 @@ export interface TokenUsage {
115
131
  output_tokens?: number;
116
132
  /** Convenience total (prompt + completion) */
117
133
  total_tokens?: number;
134
+ /** Breakdown of output tokens — thinking/reasoning tokens are billed as output
135
+ * but should NOT be counted toward the visible-content heuristics. */
136
+ output_tokens_details?: {
137
+ /** Anthropic: thinking_tokens, OpenAI: reasoning_tokens */
138
+ thinking_tokens?: number;
139
+ reasoning_tokens?: number;
140
+ };
118
141
  /** Cache details */
119
142
  prompt_tokens_details?: {
120
143
  /** Tokens served from the prompt cache (reduces cost) */
@@ -76,9 +76,9 @@ export class GenericXAIClient implements GenericClient {
76
76
  model: options.model,
77
77
  messages: xaiMessages,
78
78
  max_tokens: options.max_tokens,
79
- ...(XaiReasoningModels.includes(options.model) && options.reasoning_effort && {
79
+ ...(XaiReasoningModels.includes(options.model) && options.reasoning_effort && options.reasoning_effort !== "none" && {
80
80
  // grok-3-mini models support reasoning_effort: "low" | "medium" | "high"
81
- reasoning_effort: options.reasoning_effort,
81
+ reasoning_effort: options.reasoning_effort as "low" | "medium" | "high",
82
82
  }),
83
83
  ...(options.tools && {
84
84
  tools: options.tools,
@@ -1,6 +1,7 @@
1
1
  import { Command } from "commander";
2
2
  import { readPromptFile } from "../ai";
3
3
  import { AgentModule } from "../chat/modules/AgentModule";
4
+ import { BehaviorsService, Behavior } from "../services/BehaviorsService";
4
5
  import { AskModule } from "../chat/modules/AskModule";
5
6
  import { SearchModule } from "../chat/modules/SearchModule";
6
7
  import { SessionsModule } from "../chat/modules/SessionsModule";
@@ -131,11 +132,29 @@ export function addAgentCommand(program: Command, getChatService: () => any): vo
131
132
  }
132
133
 
133
134
  await agentModule.initialize(chatService);
135
+
136
+ // Match a behavior from local disk (.knowhow/behaviors/)
137
+ let behaviorSystemPrompt: string | undefined;
138
+ let behaviorModel: string | undefined;
139
+ const behaviorsSvc = new BehaviorsService();
140
+ const matchedBehavior = behaviorsSvc.matchBehaviorLocal(input);
141
+ if (matchedBehavior) {
142
+ console.log(`🎯 Matched behavior: ${matchedBehavior.name}`);
143
+ if (matchedBehavior.instructions) {
144
+ behaviorSystemPrompt = matchedBehavior.instructions;
145
+ }
146
+ if (matchedBehavior.model) {
147
+ behaviorModel = matchedBehavior.model;
148
+ }
149
+ }
150
+
134
151
  const { taskCompleted } = await agentModule.setupAgent({
135
152
  ...options,
136
153
  input,
137
154
  maxTimeLimit: parseInt(options.maxTimeLimit, 10),
138
155
  maxSpendLimit: parseFloat(options.maxSpendLimit),
156
+ ...(behaviorSystemPrompt ? { systemPrompt: behaviorSystemPrompt } : {}),
157
+ ...(behaviorModel ? { model: behaviorModel } : {}),
139
158
  run: true,
140
159
  });
141
160
  await taskCompleted;