@tyvm/knowhow 0.0.105 → 0.0.106

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 (209) hide show
  1. package/CONFIG.md +8 -5
  2. package/package.json +3 -2
  3. package/scripts/check-model-pricing.ts +509 -0
  4. package/scripts/compare-openrouter-coverage.ts +576 -0
  5. package/src/agents/base/base.ts +127 -2
  6. package/src/agents/tools/execCommand.ts +4 -0
  7. package/src/agents/tools/executeScript/definition.ts +1 -1
  8. package/src/agents/tools/index.ts +0 -1
  9. package/src/agents/tools/list.ts +3 -43
  10. package/src/agents/tools/writeFile.ts +1 -1
  11. package/src/auth/browserLogin.ts +9 -4
  12. package/src/chat/modules/RemoteSyncModule.ts +3 -0
  13. package/src/cli.ts +31 -1
  14. package/src/clients/cerebras.ts +10 -0
  15. package/src/clients/contextLimits.ts +7 -2
  16. package/src/clients/copilot.ts +23 -0
  17. package/src/clients/deepseek.ts +16 -0
  18. package/src/clients/fireworks.ts +15 -0
  19. package/src/clients/gemini.ts +45 -2
  20. package/src/clients/github.ts +16 -0
  21. package/src/clients/groq.ts +15 -0
  22. package/src/clients/http.ts +190 -6
  23. package/src/clients/index.ts +116 -4
  24. package/src/clients/llama.ts +16 -0
  25. package/src/clients/mistral.ts +16 -0
  26. package/src/clients/nvidia.ts +16 -0
  27. package/src/clients/openai.ts +41 -11
  28. package/src/clients/openrouter.ts +17 -0
  29. package/src/clients/pricing/anthropic.ts +105 -78
  30. package/src/clients/pricing/cerebras.ts +11 -0
  31. package/src/clients/pricing/copilot.ts +60 -0
  32. package/src/clients/pricing/deepseek.ts +15 -0
  33. package/src/clients/pricing/fireworks.ts +32 -0
  34. package/src/clients/pricing/github.ts +69 -0
  35. package/src/clients/pricing/google.ts +245 -206
  36. package/src/clients/pricing/groq.ts +56 -0
  37. package/src/clients/pricing/index.ts +42 -5
  38. package/src/clients/pricing/llama.ts +18 -0
  39. package/src/clients/pricing/mistral.ts +34 -0
  40. package/src/clients/pricing/models.ts +7 -236
  41. package/src/clients/pricing/nvidia.ts +102 -0
  42. package/src/clients/pricing/openai.ts +347 -171
  43. package/src/clients/pricing/openrouter.ts +36 -0
  44. package/src/clients/pricing/types.ts +83 -2
  45. package/src/clients/pricing/xai.ts +121 -65
  46. package/src/clients/types.ts +4 -0
  47. package/src/clients/xai.ts +150 -0
  48. package/src/fileSync.ts +8 -2
  49. package/src/login.ts +11 -3
  50. package/src/services/AgentSyncFs.ts +36 -12
  51. package/src/services/KnowhowClient.ts +11 -0
  52. package/src/services/LazyToolsService.ts +6 -0
  53. package/src/services/S3.ts +0 -7
  54. package/src/services/modules/index.ts +11 -2
  55. package/src/types.ts +56 -279
  56. package/src/worker.ts +174 -0
  57. package/tests/clients/pricing.test.ts +37 -0
  58. package/tests/manual/clients/completions.json +838 -226
  59. package/tests/manual/clients/completions.test.ts +46 -31
  60. package/ts_build/package.json +3 -2
  61. package/ts_build/src/agents/base/base.d.ts +17 -1
  62. package/ts_build/src/agents/base/base.js +82 -1
  63. package/ts_build/src/agents/base/base.js.map +1 -1
  64. package/ts_build/src/agents/tools/execCommand.js +3 -0
  65. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  66. package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
  67. package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
  68. package/ts_build/src/agents/tools/index.d.ts +0 -1
  69. package/ts_build/src/agents/tools/index.js +0 -1
  70. package/ts_build/src/agents/tools/index.js.map +1 -1
  71. package/ts_build/src/agents/tools/list.js +3 -38
  72. package/ts_build/src/agents/tools/list.js.map +1 -1
  73. package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
  74. package/ts_build/src/agents/tools/writeFile.js +1 -1
  75. package/ts_build/src/agents/tools/writeFile.js.map +1 -1
  76. package/ts_build/src/ai.d.ts +1 -1
  77. package/ts_build/src/auth/browserLogin.d.ts +2 -1
  78. package/ts_build/src/auth/browserLogin.js +10 -3
  79. package/ts_build/src/auth/browserLogin.js.map +1 -1
  80. package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
  81. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  82. package/ts_build/src/cli.js +19 -0
  83. package/ts_build/src/cli.js.map +1 -1
  84. package/ts_build/src/clients/anthropic.d.ts +1 -82
  85. package/ts_build/src/clients/cerebras.d.ts +4 -0
  86. package/ts_build/src/clients/cerebras.js +14 -0
  87. package/ts_build/src/clients/cerebras.js.map +1 -0
  88. package/ts_build/src/clients/contextLimits.js +7 -2
  89. package/ts_build/src/clients/contextLimits.js.map +1 -1
  90. package/ts_build/src/clients/copilot.d.ts +4 -0
  91. package/ts_build/src/clients/copilot.js +15 -0
  92. package/ts_build/src/clients/copilot.js.map +1 -0
  93. package/ts_build/src/clients/deepseek.d.ts +4 -0
  94. package/ts_build/src/clients/deepseek.js +15 -0
  95. package/ts_build/src/clients/deepseek.js.map +1 -0
  96. package/ts_build/src/clients/fireworks.d.ts +4 -0
  97. package/ts_build/src/clients/fireworks.js +15 -0
  98. package/ts_build/src/clients/fireworks.js.map +1 -0
  99. package/ts_build/src/clients/gemini.d.ts +1 -0
  100. package/ts_build/src/clients/gemini.js +28 -1
  101. package/ts_build/src/clients/gemini.js.map +1 -1
  102. package/ts_build/src/clients/github.d.ts +4 -0
  103. package/ts_build/src/clients/github.js +15 -0
  104. package/ts_build/src/clients/github.js.map +1 -0
  105. package/ts_build/src/clients/groq.d.ts +4 -0
  106. package/ts_build/src/clients/groq.js +15 -0
  107. package/ts_build/src/clients/groq.js.map +1 -0
  108. package/ts_build/src/clients/http.d.ts +22 -1
  109. package/ts_build/src/clients/http.js +132 -7
  110. package/ts_build/src/clients/http.js.map +1 -1
  111. package/ts_build/src/clients/index.d.ts +14 -0
  112. package/ts_build/src/clients/index.js +94 -4
  113. package/ts_build/src/clients/index.js.map +1 -1
  114. package/ts_build/src/clients/llama.d.ts +4 -0
  115. package/ts_build/src/clients/llama.js +15 -0
  116. package/ts_build/src/clients/llama.js.map +1 -0
  117. package/ts_build/src/clients/mistral.d.ts +4 -0
  118. package/ts_build/src/clients/mistral.js +15 -0
  119. package/ts_build/src/clients/mistral.js.map +1 -0
  120. package/ts_build/src/clients/nvidia.d.ts +4 -0
  121. package/ts_build/src/clients/nvidia.js +15 -0
  122. package/ts_build/src/clients/nvidia.js.map +1 -0
  123. package/ts_build/src/clients/openai.d.ts +4 -206
  124. package/ts_build/src/clients/openai.js +27 -9
  125. package/ts_build/src/clients/openai.js.map +1 -1
  126. package/ts_build/src/clients/openrouter.d.ts +4 -0
  127. package/ts_build/src/clients/openrouter.js +15 -0
  128. package/ts_build/src/clients/openrouter.js.map +1 -0
  129. package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
  130. package/ts_build/src/clients/pricing/anthropic.js +75 -78
  131. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  132. package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
  133. package/ts_build/src/clients/pricing/cerebras.js +11 -0
  134. package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
  135. package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
  136. package/ts_build/src/clients/pricing/copilot.js +35 -0
  137. package/ts_build/src/clients/pricing/copilot.js.map +1 -0
  138. package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
  139. package/ts_build/src/clients/pricing/deepseek.js +10 -0
  140. package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
  141. package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
  142. package/ts_build/src/clients/pricing/fireworks.js +21 -0
  143. package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
  144. package/ts_build/src/clients/pricing/github.d.ts +4 -0
  145. package/ts_build/src/clients/pricing/github.js +58 -0
  146. package/ts_build/src/clients/pricing/github.js.map +1 -0
  147. package/ts_build/src/clients/pricing/google.d.ts +59 -6
  148. package/ts_build/src/clients/pricing/google.js +214 -167
  149. package/ts_build/src/clients/pricing/google.js.map +1 -1
  150. package/ts_build/src/clients/pricing/groq.d.ts +5 -0
  151. package/ts_build/src/clients/pricing/groq.js +41 -0
  152. package/ts_build/src/clients/pricing/groq.js.map +1 -0
  153. package/ts_build/src/clients/pricing/index.d.ts +16 -5
  154. package/ts_build/src/clients/pricing/index.js +62 -7
  155. package/ts_build/src/clients/pricing/index.js.map +1 -1
  156. package/ts_build/src/clients/pricing/llama.d.ts +4 -0
  157. package/ts_build/src/clients/pricing/llama.js +14 -0
  158. package/ts_build/src/clients/pricing/llama.js.map +1 -0
  159. package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
  160. package/ts_build/src/clients/pricing/mistral.js +23 -0
  161. package/ts_build/src/clients/pricing/mistral.js.map +1 -0
  162. package/ts_build/src/clients/pricing/models.d.ts +5 -4
  163. package/ts_build/src/clients/pricing/models.js +8 -162
  164. package/ts_build/src/clients/pricing/models.js.map +1 -1
  165. package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
  166. package/ts_build/src/clients/pricing/nvidia.js +96 -0
  167. package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
  168. package/ts_build/src/clients/pricing/openai.d.ts +86 -197
  169. package/ts_build/src/clients/pricing/openai.js +294 -168
  170. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  171. package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
  172. package/ts_build/src/clients/pricing/openrouter.js +29 -0
  173. package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
  174. package/ts_build/src/clients/pricing/types.d.ts +27 -2
  175. package/ts_build/src/clients/pricing/types.js +46 -0
  176. package/ts_build/src/clients/pricing/types.js.map +1 -1
  177. package/ts_build/src/clients/pricing/xai.d.ts +37 -57
  178. package/ts_build/src/clients/pricing/xai.js +92 -59
  179. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  180. package/ts_build/src/clients/types.d.ts +1 -0
  181. package/ts_build/src/clients/xai.d.ts +2 -62
  182. package/ts_build/src/clients/xai.js +121 -0
  183. package/ts_build/src/clients/xai.js.map +1 -1
  184. package/ts_build/src/fileSync.js +7 -2
  185. package/ts_build/src/fileSync.js.map +1 -1
  186. package/ts_build/src/login.js +8 -2
  187. package/ts_build/src/login.js.map +1 -1
  188. package/ts_build/src/services/AgentSyncFs.js +1 -0
  189. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  190. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  191. package/ts_build/src/services/KnowhowClient.js +7 -0
  192. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  193. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  194. package/ts_build/src/services/LazyToolsService.js +3 -0
  195. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  196. package/ts_build/src/services/S3.js +0 -7
  197. package/ts_build/src/services/S3.js.map +1 -1
  198. package/ts_build/src/services/modules/index.js +41 -1
  199. package/ts_build/src/services/modules/index.js.map +1 -1
  200. package/ts_build/src/types.d.ts +163 -124
  201. package/ts_build/src/types.js +33 -213
  202. package/ts_build/src/types.js.map +1 -1
  203. package/ts_build/src/worker.d.ts +4 -0
  204. package/ts_build/src/worker.js +140 -0
  205. package/ts_build/src/worker.js.map +1 -1
  206. package/ts_build/tests/clients/pricing.test.js +21 -0
  207. package/ts_build/tests/clients/pricing.test.js.map +1 -1
  208. package/ts_build/tests/manual/clients/completions.test.js +27 -24
  209. package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from "events"; // kept for reference; agentEvents now uses EventService
2
2
  import {
3
+ CompletionResponse,
3
4
  GenericClient,
4
5
  Message,
5
6
  MessageContent,
@@ -59,6 +60,10 @@ export abstract class BaseAgent implements IAgent {
59
60
  protected turnCount = 0;
60
61
  protected totalCostUsd = 0;
61
62
  protected currentThread = 0;
63
+ protected totalInputTokens = 0;
64
+ protected totalOutputTokens = 0;
65
+ protected totalCacheReadTokens = 0;
66
+ protected totalCacheWriteTokens = 0;
62
67
 
63
68
  protected compressThreshold = 30000;
64
69
  protected compressMinMessages = 30;
@@ -95,6 +100,7 @@ export abstract class BaseAgent implements IAgent {
95
100
  agentSay: "agent:say",
96
101
  agentNewTask: "agent:newTask",
97
102
  agentTaskComplete: "agent:taskComplete",
103
+ tokenUsage: "agent:tokenUsage",
98
104
  };
99
105
 
100
106
  public tools: ToolsService;
@@ -194,6 +200,10 @@ export abstract class BaseAgent implements IAgent {
194
200
  this.taskBreakdown = "";
195
201
  this.summaries = [];
196
202
  this.totalCostUsd = 0;
203
+ this.totalInputTokens = 0;
204
+ this.totalOutputTokens = 0;
205
+ this.totalCacheReadTokens = 0;
206
+ this.totalCacheWriteTokens = 0;
197
207
  this.status = this.eventTypes.inProgress;
198
208
  this.turnCount = 0;
199
209
  this.startTimeMs = Date.now();
@@ -369,6 +379,44 @@ export abstract class BaseAgent implements IAgent {
369
379
  return this.totalCostUsd;
370
380
  }
371
381
 
382
+ adjustTokenUsage(usage: any) {
383
+ if (!usage) return;
384
+ // Support both OpenAI-style (prompt_tokens/completion_tokens) and Anthropic-style (input_tokens/output_tokens)
385
+ const inputTokens = usage.input_tokens ?? usage.prompt_tokens ?? 0;
386
+ const outputTokens = usage.output_tokens ?? usage.completion_tokens ?? 0;
387
+
388
+ const cacheReadTokens =
389
+ usage.cache_read_input_tokens ?? usage.cache_read_tokens ??
390
+ usage.prompt_tokens_details?.cached_tokens ?? 0;
391
+ const cacheWriteTokens =
392
+ usage.cache_creation_input_tokens ?? usage.cache_write_tokens ?? 0;
393
+
394
+ this.totalInputTokens += inputTokens;
395
+ this.totalOutputTokens += outputTokens;
396
+ this.totalCacheReadTokens += cacheReadTokens;
397
+ this.totalCacheWriteTokens += cacheWriteTokens;
398
+
399
+ this.agentEvents.emit(this.eventTypes.tokenUsage, {
400
+ inputTokens,
401
+ outputTokens,
402
+ cacheReadTokens,
403
+ cacheWriteTokens,
404
+ totalInputTokens: this.totalInputTokens,
405
+ totalOutputTokens: this.totalOutputTokens,
406
+ totalCacheReadTokens: this.totalCacheReadTokens,
407
+ totalCacheWriteTokens: this.totalCacheWriteTokens,
408
+ });
409
+ }
410
+
411
+ getTokenUsage() {
412
+ return {
413
+ totalInputTokens: this.totalInputTokens,
414
+ totalOutputTokens: this.totalOutputTokens,
415
+ totalCacheReadTokens: this.totalCacheReadTokens,
416
+ totalCacheWriteTokens: this.totalCacheWriteTokens,
417
+ };
418
+ }
419
+
372
420
  startNewThread(messages: Message[]) {
373
421
  this.currentThread++;
374
422
  this.agentEvents.emit(this.eventTypes.newThread, messages);
@@ -544,8 +592,14 @@ export abstract class BaseAgent implements IAgent {
544
592
 
545
593
  async kill() {
546
594
  this.log("Killing agent");
547
- if (this.status === this.eventTypes.kill || this.status === this.eventTypes.done) {
548
- this.log("Agent is already being killed or done, ignoring duplicate kill()", "warn");
595
+ if (
596
+ this.status === this.eventTypes.kill ||
597
+ this.status === this.eventTypes.done
598
+ ) {
599
+ this.log(
600
+ "Agent is already being killed or done, ignoring duplicate kill()",
601
+ "warn"
602
+ );
549
603
  return;
550
604
  }
551
605
  this.agentEvents.emit(this.eventTypes.kill, this);
@@ -657,9 +711,16 @@ export abstract class BaseAgent implements IAgent {
657
711
  "warn"
658
712
  );
659
713
  }
714
+ if (!response?.choices) {
715
+ const errMsg =
716
+ (error?.error?.message ?? error?.message) ||
717
+ JSON.stringify(response);
718
+ throw new Error(`AI response error: ${errMsg}`);
719
+ }
660
720
  }
661
721
 
662
722
  this.adjustTotalCostUsd(response?.usd_cost);
723
+ this.adjustTokenUsage(response?.usage);
663
724
  this.log("agent response cost: " + response?.usd_cost);
664
725
 
665
726
  // Typically, there's only one choice in the array, but you could have many
@@ -687,6 +748,16 @@ export abstract class BaseAgent implements IAgent {
687
748
 
688
749
  this.updateCurrentThread(messages);
689
750
 
751
+ const truncationWarning = this.detectTruncatedToolCalls(
752
+ toolCalls,
753
+ response
754
+ );
755
+ if (truncationWarning) {
756
+ messages.push(truncationWarning as Message);
757
+ this.updateCurrentThread(messages);
758
+ return this.call(userInput, messages);
759
+ }
760
+
690
761
  for (const toolCall of toolCalls) {
691
762
  if (this.status === this.eventTypes.pause) {
692
763
  this.log(
@@ -958,6 +1029,60 @@ export abstract class BaseAgent implements IAgent {
958
1029
  return JSON.stringify(messages).split(" ").length;
959
1030
  }
960
1031
 
1032
+ /**
1033
+ * Detects whether tool call arguments appear truncated due to hitting the output token limit.
1034
+ * Two signals are checked:
1035
+ * 1. Any tool call argument is empty or invalid JSON (hard truncation).
1036
+ * 2. The model reported many output tokens but the total argument content received is tiny
1037
+ * relative to what those tokens should represent (soft/silent truncation).
1038
+ *
1039
+ * Returns a warning system message if truncation is detected, or null otherwise.
1040
+ */
1041
+ detectTruncatedToolCalls(
1042
+ toolCalls: ToolCall[],
1043
+ response: CompletionResponse
1044
+ ): { role: string; content: string } | null {
1045
+ const outputTokens: number = response?.usage?.output_tokens || 0;
1046
+ const totalArgLength = toolCalls.reduce(
1047
+ (sum, tc) => sum + (tc.function?.arguments?.length || 0),
1048
+ 0
1049
+ );
1050
+
1051
+ // Percentage-based heuristic: if actual arg chars are less than ~10% of the
1052
+ // expected chars (outputTokens * 4 chars/token), the output was likely truncated.
1053
+ // Only apply when outputTokens > 1000 to avoid false positives on small responses.
1054
+ const expectedArgChars = outputTokens * 4;
1055
+ const suspiciouslySmallArgs =
1056
+ outputTokens > 1000 && totalArgLength < expectedArgChars * 0.1;
1057
+
1058
+ for (const toolCall of toolCalls) {
1059
+ const args = toolCall.function?.arguments || "";
1060
+ let isInvalidJson = false;
1061
+ try {
1062
+ JSON.parse(args);
1063
+ } catch {
1064
+ isInvalidJson = true;
1065
+ }
1066
+ if (isInvalidJson || args.trim() === "" || suspiciouslySmallArgs) {
1067
+ this.log(
1068
+ `Tool call '${toolCall.function?.name}' has malformed/truncated arguments — likely hit output token limit (outputTokens=${outputTokens}, argLength=${args.length})`,
1069
+ "warn"
1070
+ );
1071
+ return {
1072
+ role: "user",
1073
+ content:
1074
+ "⚠️ Output limit warning: Your last tool call had incomplete or missing arguments, which usually means you exceeded the output token limit mid-response. The model reported " +
1075
+ outputTokens +
1076
+ " output tokens but only " +
1077
+ totalArgLength +
1078
+ " characters of tool call arguments were received. Please write smaller, more concise content in your tool calls. Aim for no more than 4000 tokens of output per response. Break large responses into smaller pieces if needed.",
1079
+ };
1080
+ }
1081
+ }
1082
+
1083
+ return null;
1084
+ }
1085
+
961
1086
  async getTaskBreakdown(messages: Message[]) {
962
1087
  if (this.taskBreakdown) {
963
1088
  return this.taskBreakdown;
@@ -262,6 +262,10 @@ export const execCommand = async (
262
262
  continueInBackground?: boolean,
263
263
  logFileName?: string
264
264
  ): Promise<string> => {
265
+ if(!command || typeof command !== "string") {
266
+ throw new Error("Invalid command. We received a non-string value. Please ensure you are sending strings of 4k tokens or less.");
267
+ }
268
+
265
269
  const { stdout, stderr, timedOut, killed, pid, logPath } =
266
270
  await execWithTimeout(command, {
267
271
  timeout,
@@ -53,7 +53,7 @@ export const executeScriptDefinition: Tool = {
53
53
  properties: {
54
54
  script: {
55
55
  type: "string",
56
- description: "The TypeScript code to execute",
56
+ description: "The TypeScript code to execute. 4000 tokens or less",
57
57
  },
58
58
  maxToolCalls: {
59
59
  type: "number",
@@ -19,7 +19,6 @@ export * from "./language";
19
19
  export * from "./askHuman";
20
20
  export * from "./aiClient";
21
21
  export * from "./googleSearch";
22
- export * from "./loadWebpage";
23
22
  export * from "./stringReplace";
24
23
  export * from "./executeScript";
25
24
  export * from "./startAgentTask";
@@ -55,7 +55,7 @@ export const includedTools = [
55
55
  properties: {
56
56
  command: {
57
57
  type: "string",
58
- description: "The command to execute",
58
+ description: "The command to execute. 4000 tokens or less",
59
59
  },
60
60
  timeout: {
61
61
  type: "number",
@@ -346,7 +346,7 @@ export const includedTools = [
346
346
  function: {
347
347
  name: "writeFileChunk",
348
348
  description:
349
- "Update or create files by writing in small chunks of text. Suitable for larger files, this tool allows incremental writing by calling it multiple times.",
349
+ "Update or create files by writing in small chunks of text. Suitable for larger files, this tool allows incremental writing by calling it multiple times. Write chunks of around 4000 tokens",
350
350
  parameters: {
351
351
  type: "object",
352
352
  positional: true,
@@ -358,7 +358,7 @@ export const includedTools = [
358
358
  },
359
359
  content: {
360
360
  type: "string",
361
- description: "The chunk of content to write to the file",
361
+ description: "The chunk of content to write to the file. 4000 tokens or less",
362
362
  },
363
363
  isContinuing: {
364
364
  type: "boolean",
@@ -631,46 +631,6 @@ export const includedTools = [
631
631
  },
632
632
  },
633
633
  },
634
- {
635
- type: "function",
636
- function: {
637
- name: "loadWebpage",
638
- description:
639
- "Load a webpage using a stealth browser to avoid bot detection. Can return either text content with console logs or a screenshot.",
640
- parameters: {
641
- type: "object",
642
- positional: true,
643
- properties: {
644
- url: {
645
- type: "string",
646
- description: "The URL of the webpage to load",
647
- },
648
- mode: {
649
- type: "string",
650
- description:
651
- "The mode for content extraction: 'text' for text content with console logs, 'screenshot' for a base64 encoded screenshot",
652
- enum: ["text", "screenshot"],
653
- },
654
- waitForSelector: {
655
- type: "string",
656
- description:
657
- "Optional CSS selector to wait for before extracting content",
658
- },
659
- timeout: {
660
- type: "number",
661
- description:
662
- "Timeout in milliseconds for page loading (default: 30000)",
663
- },
664
- },
665
- required: ["url"],
666
- },
667
- returns: {
668
- type: "string",
669
- description:
670
- "The webpage content as text with console logs, or a base64 encoded screenshot",
671
- },
672
- },
673
- },
674
634
  {
675
635
  type: "function",
676
636
  function: {
@@ -28,7 +28,7 @@ export async function writeFileChunk(
28
28
  `File path and content are both required. We received: ${JSON.stringify({
29
29
  filePath,
30
30
  content,
31
- })}. Make sure you write small chunks of content, otherwise you will hit output limits, resulting in content being empty.`
31
+ })}. Make sure you write small chunks of content (4k tokens), otherwise you will hit output limits, resulting in content being empty.`
32
32
  );
33
33
  }
34
34
 
@@ -26,7 +26,7 @@ interface RetrieveTokenResponse {
26
26
  export class BrowserLoginService {
27
27
  private baseUrl: string;
28
28
 
29
- constructor(baseUrl: string = KNOWHOW_API_URL) {
29
+ constructor(baseUrl: string = KNOWHOW_API_URL, private orgId?: string) {
30
30
  if (!baseUrl) {
31
31
  throw new BrowserLoginError(
32
32
  "KNOWHOW_API_URL environment variable not set"
@@ -52,11 +52,16 @@ export class BrowserLoginService {
52
52
  spinner.start("Opening browser for authentication");
53
53
 
54
54
  // Step 2: Open browser
55
- await openBrowser(sessionData.browserUrl);
55
+ let browserUrl = sessionData.browserUrl;
56
+ // Append orgId as query string so the frontend can pre-select the correct organization
57
+ if (this.orgId) {
58
+ const separator = browserUrl.includes("?") ? "&" : "?";
59
+ browserUrl = `${browserUrl}${separator}orgId=${encodeURIComponent(this.orgId)}`;
60
+ }
61
+ await openBrowser(browserUrl);
56
62
  console.log(
57
- `\nIf the browser didn't open automatically, please visit: ${sessionData.browserUrl}\n`
63
+ `\nIf the browser didn't open automatically, please visit: ${browserUrl}\n`
58
64
  );
59
-
60
65
  spinner.stop();
61
66
  spinner.start("Waiting for browser authentication");
62
67
 
@@ -247,6 +247,9 @@ export class RemoteSyncModule extends BaseChatModule {
247
247
  const registry = this.agentModule.getTaskRegistry();
248
248
  const taskInfo = registry.get(taskId);
249
249
 
250
+ // Refresh JWT in case it was updated since client was instantiated (e.g. after knowhow login)
251
+ this.client.refreshJwt();
252
+
250
253
  if (!taskInfo) {
251
254
  console.log(
252
255
  `⚠️ Task "${taskId}" not found in registry.`
package/src/cli.ts CHANGED
@@ -14,9 +14,10 @@ import { includedTools } from "./agents/tools/list";
14
14
  import * as allTools from "./agents/tools";
15
15
  import { LazyToolsService, services } from "./services";
16
16
  import { login } from "./login";
17
- import { worker } from "./worker";
17
+ import { worker, tunnel } from "./worker";
18
18
  import { fileSync } from "./fileSync";
19
19
  import { KnowhowSimpleClient } from "./services/KnowhowClient";
20
+ import { ModulesService } from "./services/modules";
20
21
  import {
21
22
  startAllWorkers,
22
23
  listWorkerPaths,
@@ -56,6 +57,7 @@ async function setupServices() {
56
57
  const { Agents, Mcp, Clients, Tools: OldTools } = services();
57
58
  const Tools = new LazyToolsService(); // eslint-disable-line no-shadow
58
59
 
60
+ // Load modules from config first so module-provided tools/agents/plugins are available
59
61
  // We need to wireup the LazyTools to be connected to the same singletons that are in services()
60
62
  Tools.setContext({
61
63
  ...OldTools.getContext(),
@@ -100,6 +102,19 @@ async function setupServices() {
100
102
  await Clients.registerConfiguredModels();
101
103
  console.log("✓ Services are set up and ready to go!");
102
104
 
105
+ // Load modules (tools, plugins, agents) from knowhow.json config
106
+ console.log("📦 Loading modules from config...");
107
+ const modulesService = new ModulesService();
108
+ await modulesService.loadModulesFromConfig({
109
+ Agents,
110
+ Embeddings: services().Embeddings,
111
+ Plugins: services().Plugins,
112
+ Clients,
113
+ // Use LazyToolsService so module-provided tools are visible to agents and scripts
114
+ Tools: Tools as any,
115
+ MediaProcessor: services().MediaProcessor,
116
+ });
117
+
103
118
  // Return both LazyToolsService (for agents) and OldTools (plain ToolsService with all tools for scripts)
104
119
  return { Tools, Clients, PlainTools: OldTools };
105
120
  }
@@ -527,6 +542,21 @@ async function main() {
527
542
  }
528
543
  });
529
544
 
545
+ program
546
+ .command("tunnel")
547
+ .description(
548
+ "Start tunnel-only mode: expose local ports to the cloud without registering any tools"
549
+ )
550
+ .option(
551
+ "--share",
552
+ "Share this tunnel with your organization (allows other users to use it)"
553
+ )
554
+ .option("--unshare", "Make this tunnel private (only you can use it)")
555
+ .action(async (options) => {
556
+ await tunnel(options);
557
+ });
558
+
559
+
530
560
  program
531
561
  .command("script")
532
562
  .description("Run a local tool script file using the executeScript sandbox")
@@ -0,0 +1,10 @@
1
+ import { HttpClient } from "./http";
2
+ import { CerebrasTextPricing } from "./pricing/cerebras";
3
+
4
+ export class GenericCerebrasClient extends HttpClient {
5
+ constructor(apiKey: string) {
6
+ super("https://api.cerebras.ai");
7
+ this.setJwt(apiKey);
8
+ this.setPrices(CerebrasTextPricing);
9
+ }
10
+ }
@@ -19,8 +19,8 @@ export const ContextLimits: Record<string, number> = {
19
19
  [Models.openai.GPT_5]: 1_000_000,
20
20
  [Models.openai.GPT_5_Mini]: 1_000_000,
21
21
  [Models.openai.GPT_5_Nano]: 1_000_000,
22
- [Models.openai.GPT_5_1]: 1_000_000,
23
- [Models.openai.GPT_5_2]: 1_000_000,
22
+ [Models.openai.GPT_51]: 1_000_000,
23
+ [Models.openai.GPT_52]: 1_000_000,
24
24
  [Models.openai.GPT_41]: 1_047_576,
25
25
  [Models.openai.GPT_41_Mini]: 1_047_576,
26
26
  [Models.openai.GPT_41_Nano]: 1_047_576,
@@ -43,6 +43,7 @@ export const ContextLimits: Record<string, number> = {
43
43
 
44
44
  // ─── Anthropic ────────────────────────────────────────────────────────────
45
45
  [Models.anthropic.Opus4_6]: 1_000_000,
46
+ [Models.anthropic.Opus4_6Fast]: 1_000_000,
46
47
  [Models.anthropic.Sonnet4_6]: 1_000_000,
47
48
  [Models.anthropic.Opus4_5]: 1_000_000,
48
49
  [Models.anthropic.Opus4]: 200_000,
@@ -54,6 +55,7 @@ export const ContextLimits: Record<string, number> = {
54
55
  [Models.anthropic.Sonnet3_5]: 200_000,
55
56
  [Models.anthropic.Opus3]: 200_000,
56
57
  [Models.anthropic.Haiku3]: 200_000,
58
+ [Models.anthropic.Haiku3_5]: 200_000,
57
59
 
58
60
  // ─── Google ───────────────────────────────────────────────────────────────
59
61
  [Models.google.Gemini_31_Pro_Preview]: 1_000_000,
@@ -82,6 +84,9 @@ export const ContextLimits: Record<string, number> = {
82
84
  // ─── xAI ──────────────────────────────────────────────────────────────────
83
85
  [Models.xai.Grok4_1_Fast_Reasoning]: 2_000_000,
84
86
  [Models.xai.Grok4_1_Fast_NonReasoning]: 2_000_000,
87
+ [Models.xai.Grok_4_20_Reasoning]: 131_072,
88
+ [Models.xai.Grok_4_20_NonReasoning]: 131_072,
89
+ [Models.xai.Grok_4_20_MultiAgent]: 2_000_000,
85
90
  [Models.xai.GrokCodeFast]: 2_000_000,
86
91
  [Models.xai.Grok4]: 131_072,
87
92
  [Models.xai.Grok3Beta]: 131_072,
@@ -0,0 +1,23 @@
1
+ import { HttpClient } from "./http";
2
+ import { CopilotTextPricing } from "./pricing/copilot";
3
+
4
+ /**
5
+ * GitHub Copilot client — OpenAI-compatible API
6
+ * https://docs.github.com/en/copilot/reference/ai-models/supported-models
7
+ *
8
+ * GitHub Copilot exposes an OpenAI-compatible endpoint at https://api.githubcopilot.com
9
+ * that allows subscribers to use premium models (Claude Opus, GPT-5.x, Gemini, Grok etc.)
10
+ * via their Copilot subscription's premium request allowance — no per-token charges.
11
+ *
12
+ * Authentication: uses a GitHub token (same as GITHUB_TOKEN / a personal access token
13
+ * or OAuth token with copilot scope).
14
+ *
15
+ * Set env var GITHUB_COPILOT_TOKEN (preferred) or GITHUB_TOKEN to enable.
16
+ */
17
+ export class GitHubCopilotClient extends HttpClient {
18
+ constructor(apiKey = process.env.GITHUB_COPILOT_TOKEN ?? process.env.GITHUB_TOKEN) {
19
+ super("https://api.githubcopilot.com");
20
+ if (apiKey) this.setJwt(apiKey);
21
+ this.setPrices(CopilotTextPricing);
22
+ }
23
+ }
@@ -0,0 +1,16 @@
1
+ import { HttpClient } from "./http";
2
+ import { DeepSeekTextPricing } from "./pricing/deepseek";
3
+
4
+ /**
5
+ * DeepSeek client — OpenAI-compatible API
6
+ * https://platform.deepseek.com/api-docs/
7
+ * Industry-leading reasoning (R1) and coding (V3) models at very low cost.
8
+ * Set env var DEEPSEEK_API_KEY to enable.
9
+ */
10
+ export class GenericDeepSeekClient extends HttpClient {
11
+ constructor(apiKey = process.env.DEEPSEEK_API_KEY) {
12
+ super("https://api.deepseek.com");
13
+ if (apiKey) this.setJwt(apiKey);
14
+ this.setPrices(DeepSeekTextPricing);
15
+ }
16
+ }
@@ -0,0 +1,15 @@
1
+ import { HttpClient } from "./http";
2
+ import { FireworksTextPricing } from "./pricing/fireworks";
3
+
4
+ /**
5
+ * Fireworks AI client — OpenAI-compatible API (fast serverless inference)
6
+ * https://docs.fireworks.ai/api-reference/introduction
7
+ * Set env var FIREWORKS_API_KEY to enable.
8
+ */
9
+ export class GenericFireworksClient extends HttpClient {
10
+ constructor(apiKey = process.env.FIREWORKS_API_KEY) {
11
+ super("https://api.fireworks.ai/inference");
12
+ if (apiKey) this.setJwt(apiKey);
13
+ this.setPrices(FireworksTextPricing);
14
+ }
15
+ }
@@ -16,10 +16,12 @@ import { wait } from "../utils";
16
16
  import {
17
17
  EmbeddingModels,
18
18
  Models,
19
+ GoogleThinkingLevelModels,
20
+ GoogleThinkingBudgetModels,
19
21
  GoogleImageModels,
20
22
  GoogleVideoModels,
21
23
  GoogleTTSModels,
22
- GoogleEmbeddingModels,
24
+ GoogleEmbeddingModelsList,
23
25
  GoogleReasoningModels,
24
26
  } from "../types";
25
27
  import { GeminiTextPricing } from "./pricing";
@@ -389,9 +391,49 @@ export class GenericGeminiClient implements GenericClient {
389
391
  return [{ functionDeclarations }];
390
392
  }
391
393
 
394
+ /**
395
+ * Builds the thinkingConfig for Gemini models that support it.
396
+ * - Gemini 3.x models use thinkingLevel: "minimal" | "low" | "medium" | "high"
397
+ * - Gemini 2.5 models use thinkingBudget: number (0 = off, -1 = dynamic)
398
+ *
399
+ * Maps CompletionOptions.reasoning_effort to provider-specific values.
400
+ */
401
+ buildThinkingConfig(options: CompletionOptions): Record<string, unknown> | undefined {
402
+ const model = options.model;
403
+ const effort = options.reasoning_effort ?? "low";
404
+
405
+ // Gemini 3.x — use thinkingLevel
406
+ if (GoogleThinkingLevelModels.includes(model)) {
407
+ const levelMap: Record<string, string> = {
408
+ low: "low",
409
+ medium: "medium",
410
+ high: "high",
411
+ };
412
+ return {
413
+ thinkingLevel: levelMap[effort] ?? "low",
414
+ };
415
+ }
416
+
417
+ // Gemini 2.5 — use thinkingBudget
418
+ if (GoogleThinkingBudgetModels.includes(model)) {
419
+ // Map effort to token budget
420
+ const budgetMap: Record<string, number> = {
421
+ low: 1024,
422
+ medium: 8192,
423
+ high: -1, // dynamic
424
+ };
425
+ return {
426
+ thinkingBudget: budgetMap[effort] ?? 1024,
427
+ };
428
+ }
429
+
430
+ return undefined;
431
+ }
432
+
392
433
  async createChatCompletion(
393
434
  options: CompletionOptions
394
435
  ): Promise<CompletionResponse> {
436
+ const thinkingConfig = this.buildThinkingConfig(options);
395
437
  const { systemInstruction, contents } = this.transformMessages(
396
438
  options.messages
397
439
  );
@@ -403,6 +445,7 @@ export class GenericGeminiClient implements GenericClient {
403
445
  contents,
404
446
  config: {
405
447
  systemInstruction,
448
+ thinkingConfig,
406
449
  tools: this.transformTools(options.tools),
407
450
  maxOutputTokens: options.max_tokens,
408
451
  },
@@ -600,7 +643,7 @@ export class GenericGeminiClient implements GenericClient {
600
643
  if (modality) {
601
644
  const map: Partial<Record<ModelModality, string[]>> = {
602
645
  completion: GoogleReasoningModels,
603
- embedding: GoogleEmbeddingModels,
646
+ embedding: GoogleEmbeddingModelsList,
604
647
  image: GoogleImageModels,
605
648
  audio: GoogleTTSModels,
606
649
  video: GoogleVideoModels,
@@ -0,0 +1,16 @@
1
+ import { HttpClient } from "./http";
2
+ import { GitHubModelsTextPricing } from "./pricing/github";
3
+
4
+ /**
5
+ * GitHub Models client — OpenAI-compatible API
6
+ * https://docs.github.com/en/github-models
7
+ * Free access to premium models (GPT-4o, DeepSeek R1, Llama, Phi etc.) with a GitHub token.
8
+ * Set env var GITHUB_TOKEN to enable.
9
+ */
10
+ export class GenericGitHubModelsClient extends HttpClient {
11
+ constructor(apiKey = process.env.GITHUB_TOKEN) {
12
+ super("https://models.github.ai/inference");
13
+ if (apiKey) this.setJwt(apiKey);
14
+ this.setPrices(GitHubModelsTextPricing);
15
+ }
16
+ }
@@ -0,0 +1,15 @@
1
+ import { HttpClient } from "./http";
2
+ import { GroqTextPricing } from "./pricing/groq";
3
+
4
+ /**
5
+ * Groq client — OpenAI-compatible API (ultra-fast inference)
6
+ * https://console.groq.com/docs/openai
7
+ * Set env var GROQ_API_KEY to enable.
8
+ */
9
+ export class GenericGroqClient extends HttpClient {
10
+ constructor(apiKey = process.env.GROQ_API_KEY) {
11
+ super("https://api.groq.com/openai");
12
+ if (apiKey) this.setJwt(apiKey);
13
+ this.setPrices(GroqTextPricing);
14
+ }
15
+ }