@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.
- package/CONFIG.md +8 -5
- package/package.json +3 -2
- package/scripts/check-model-pricing.ts +509 -0
- package/scripts/compare-openrouter-coverage.ts +576 -0
- package/src/agents/base/base.ts +127 -2
- package/src/agents/tools/execCommand.ts +4 -0
- package/src/agents/tools/executeScript/definition.ts +1 -1
- package/src/agents/tools/index.ts +0 -1
- package/src/agents/tools/list.ts +3 -43
- package/src/agents/tools/writeFile.ts +1 -1
- package/src/auth/browserLogin.ts +9 -4
- package/src/chat/modules/RemoteSyncModule.ts +3 -0
- package/src/cli.ts +31 -1
- package/src/clients/cerebras.ts +10 -0
- package/src/clients/contextLimits.ts +7 -2
- package/src/clients/copilot.ts +23 -0
- package/src/clients/deepseek.ts +16 -0
- package/src/clients/fireworks.ts +15 -0
- package/src/clients/gemini.ts +45 -2
- package/src/clients/github.ts +16 -0
- package/src/clients/groq.ts +15 -0
- package/src/clients/http.ts +190 -6
- package/src/clients/index.ts +116 -4
- package/src/clients/llama.ts +16 -0
- package/src/clients/mistral.ts +16 -0
- package/src/clients/nvidia.ts +16 -0
- package/src/clients/openai.ts +41 -11
- package/src/clients/openrouter.ts +17 -0
- package/src/clients/pricing/anthropic.ts +105 -78
- package/src/clients/pricing/cerebras.ts +11 -0
- package/src/clients/pricing/copilot.ts +60 -0
- package/src/clients/pricing/deepseek.ts +15 -0
- package/src/clients/pricing/fireworks.ts +32 -0
- package/src/clients/pricing/github.ts +69 -0
- package/src/clients/pricing/google.ts +245 -206
- package/src/clients/pricing/groq.ts +56 -0
- package/src/clients/pricing/index.ts +42 -5
- package/src/clients/pricing/llama.ts +18 -0
- package/src/clients/pricing/mistral.ts +34 -0
- package/src/clients/pricing/models.ts +7 -236
- package/src/clients/pricing/nvidia.ts +102 -0
- package/src/clients/pricing/openai.ts +347 -171
- package/src/clients/pricing/openrouter.ts +36 -0
- package/src/clients/pricing/types.ts +83 -2
- package/src/clients/pricing/xai.ts +121 -65
- package/src/clients/types.ts +4 -0
- package/src/clients/xai.ts +150 -0
- package/src/fileSync.ts +8 -2
- package/src/login.ts +11 -3
- package/src/services/AgentSyncFs.ts +36 -12
- package/src/services/KnowhowClient.ts +11 -0
- package/src/services/LazyToolsService.ts +6 -0
- package/src/services/S3.ts +0 -7
- package/src/services/modules/index.ts +11 -2
- package/src/types.ts +56 -279
- package/src/worker.ts +174 -0
- package/tests/clients/pricing.test.ts +37 -0
- package/tests/manual/clients/completions.json +838 -226
- package/tests/manual/clients/completions.test.ts +46 -31
- package/ts_build/package.json +3 -2
- package/ts_build/src/agents/base/base.d.ts +17 -1
- package/ts_build/src/agents/base/base.js +82 -1
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.js +3 -0
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
- package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +0 -1
- package/ts_build/src/agents/tools/index.js +0 -1
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +3 -38
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
- package/ts_build/src/agents/tools/writeFile.js +1 -1
- package/ts_build/src/agents/tools/writeFile.js.map +1 -1
- package/ts_build/src/ai.d.ts +1 -1
- package/ts_build/src/auth/browserLogin.d.ts +2 -1
- package/ts_build/src/auth/browserLogin.js +10 -3
- package/ts_build/src/auth/browserLogin.js.map +1 -1
- package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
- package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
- package/ts_build/src/cli.js +19 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +1 -82
- package/ts_build/src/clients/cerebras.d.ts +4 -0
- package/ts_build/src/clients/cerebras.js +14 -0
- package/ts_build/src/clients/cerebras.js.map +1 -0
- package/ts_build/src/clients/contextLimits.js +7 -2
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/copilot.d.ts +4 -0
- package/ts_build/src/clients/copilot.js +15 -0
- package/ts_build/src/clients/copilot.js.map +1 -0
- package/ts_build/src/clients/deepseek.d.ts +4 -0
- package/ts_build/src/clients/deepseek.js +15 -0
- package/ts_build/src/clients/deepseek.js.map +1 -0
- package/ts_build/src/clients/fireworks.d.ts +4 -0
- package/ts_build/src/clients/fireworks.js +15 -0
- package/ts_build/src/clients/fireworks.js.map +1 -0
- package/ts_build/src/clients/gemini.d.ts +1 -0
- package/ts_build/src/clients/gemini.js +28 -1
- package/ts_build/src/clients/gemini.js.map +1 -1
- package/ts_build/src/clients/github.d.ts +4 -0
- package/ts_build/src/clients/github.js +15 -0
- package/ts_build/src/clients/github.js.map +1 -0
- package/ts_build/src/clients/groq.d.ts +4 -0
- package/ts_build/src/clients/groq.js +15 -0
- package/ts_build/src/clients/groq.js.map +1 -0
- package/ts_build/src/clients/http.d.ts +22 -1
- package/ts_build/src/clients/http.js +132 -7
- package/ts_build/src/clients/http.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +14 -0
- package/ts_build/src/clients/index.js +94 -4
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/llama.d.ts +4 -0
- package/ts_build/src/clients/llama.js +15 -0
- package/ts_build/src/clients/llama.js.map +1 -0
- package/ts_build/src/clients/mistral.d.ts +4 -0
- package/ts_build/src/clients/mistral.js +15 -0
- package/ts_build/src/clients/mistral.js.map +1 -0
- package/ts_build/src/clients/nvidia.d.ts +4 -0
- package/ts_build/src/clients/nvidia.js +15 -0
- package/ts_build/src/clients/nvidia.js.map +1 -0
- package/ts_build/src/clients/openai.d.ts +4 -206
- package/ts_build/src/clients/openai.js +27 -9
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/openrouter.d.ts +4 -0
- package/ts_build/src/clients/openrouter.js +15 -0
- package/ts_build/src/clients/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
- package/ts_build/src/clients/pricing/anthropic.js +75 -78
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
- package/ts_build/src/clients/pricing/cerebras.js +11 -0
- package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
- package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
- package/ts_build/src/clients/pricing/copilot.js +35 -0
- package/ts_build/src/clients/pricing/copilot.js.map +1 -0
- package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
- package/ts_build/src/clients/pricing/deepseek.js +10 -0
- package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
- package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
- package/ts_build/src/clients/pricing/fireworks.js +21 -0
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
- package/ts_build/src/clients/pricing/github.d.ts +4 -0
- package/ts_build/src/clients/pricing/github.js +58 -0
- package/ts_build/src/clients/pricing/github.js.map +1 -0
- package/ts_build/src/clients/pricing/google.d.ts +59 -6
- package/ts_build/src/clients/pricing/google.js +214 -167
- package/ts_build/src/clients/pricing/google.js.map +1 -1
- package/ts_build/src/clients/pricing/groq.d.ts +5 -0
- package/ts_build/src/clients/pricing/groq.js +41 -0
- package/ts_build/src/clients/pricing/groq.js.map +1 -0
- package/ts_build/src/clients/pricing/index.d.ts +16 -5
- package/ts_build/src/clients/pricing/index.js +62 -7
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/llama.d.ts +4 -0
- package/ts_build/src/clients/pricing/llama.js +14 -0
- package/ts_build/src/clients/pricing/llama.js.map +1 -0
- package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
- package/ts_build/src/clients/pricing/mistral.js +23 -0
- package/ts_build/src/clients/pricing/mistral.js.map +1 -0
- package/ts_build/src/clients/pricing/models.d.ts +5 -4
- package/ts_build/src/clients/pricing/models.js +8 -162
- package/ts_build/src/clients/pricing/models.js.map +1 -1
- package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
- package/ts_build/src/clients/pricing/nvidia.js +96 -0
- package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +86 -197
- package/ts_build/src/clients/pricing/openai.js +294 -168
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
- package/ts_build/src/clients/pricing/openrouter.js +29 -0
- package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
- package/ts_build/src/clients/pricing/types.d.ts +27 -2
- package/ts_build/src/clients/pricing/types.js +46 -0
- package/ts_build/src/clients/pricing/types.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +37 -57
- package/ts_build/src/clients/pricing/xai.js +92 -59
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +1 -0
- package/ts_build/src/clients/xai.d.ts +2 -62
- package/ts_build/src/clients/xai.js +121 -0
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/fileSync.js +7 -2
- package/ts_build/src/fileSync.js.map +1 -1
- package/ts_build/src/login.js +8 -2
- package/ts_build/src/login.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.js +1 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/KnowhowClient.d.ts +1 -0
- package/ts_build/src/services/KnowhowClient.js +7 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.d.ts +1 -0
- package/ts_build/src/services/LazyToolsService.js +3 -0
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/S3.js +0 -7
- package/ts_build/src/services/S3.js.map +1 -1
- package/ts_build/src/services/modules/index.js +41 -1
- package/ts_build/src/services/modules/index.js.map +1 -1
- package/ts_build/src/types.d.ts +163 -124
- package/ts_build/src/types.js +33 -213
- package/ts_build/src/types.js.map +1 -1
- package/ts_build/src/worker.d.ts +4 -0
- package/ts_build/src/worker.js +140 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/clients/pricing.test.js +21 -0
- package/ts_build/tests/clients/pricing.test.js.map +1 -1
- package/ts_build/tests/manual/clients/completions.test.js +27 -24
- package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
package/src/agents/base/base.ts
CHANGED
|
@@ -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 (
|
|
548
|
-
this.
|
|
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";
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -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
|
|
package/src/auth/browserLogin.ts
CHANGED
|
@@ -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
|
-
|
|
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: ${
|
|
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.
|
|
23
|
-
[Models.openai.
|
|
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
|
+
}
|
package/src/clients/gemini.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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
|
+
}
|