@tyvm/knowhow 0.0.15 → 0.0.17
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/package.json +2 -1
- package/src/agents/base/base.ts +25 -6
- package/src/agents/index.ts +2 -2
- package/src/agents/tools/agentCall.ts +0 -1
- package/src/agents/tools/execCommand.ts +95 -4
- package/src/agents/tools/list.ts +23 -19
- package/src/agents/tools/writeFile.ts +1 -1
- package/src/chat.ts +11 -0
- package/src/config.ts +3 -1
- package/src/processors/Base64ImageDetector.ts +190 -0
- package/src/processors/TokenCompressor.ts +357 -0
- package/src/processors/ToolResponseCache.ts +235 -0
- package/src/services/Mcp.ts +4 -1
- package/src/services/MessageProcessor.ts +107 -0
- package/src/services/Tools.ts +100 -1
- package/src/services/types.ts +57 -0
- package/ts_build/src/agents/base/base.d.ts +3 -1
- package/ts_build/src/agents/base/base.js +20 -5
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/index.d.ts +2 -2
- package/ts_build/src/agents/index.js +5 -3
- package/ts_build/src/agents/index.js.map +1 -1
- package/ts_build/src/agents/tools/agentCall.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +6 -1
- package/ts_build/src/agents/tools/execCommand.js +70 -4
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/expandTokens.d.ts +3 -0
- package/ts_build/src/agents/tools/expandTokens.js +33 -0
- package/ts_build/src/agents/tools/expandTokens.js.map +1 -0
- package/ts_build/src/agents/tools/getBigString.d.ts +3 -0
- package/ts_build/src/agents/tools/getBigString.js +33 -0
- package/ts_build/src/agents/tools/getBigString.js.map +1 -0
- package/ts_build/src/agents/tools/list.js +19 -17
- package/ts_build/src/agents/tools/list.js.map +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/chat.js +6 -0
- package/ts_build/src/chat.js.map +1 -1
- package/ts_build/src/config.js +1 -1
- package/ts_build/src/config.js.map +1 -1
- package/ts_build/src/processors/Base64ImageDetector.d.ts +14 -0
- package/ts_build/src/processors/Base64ImageDetector.js +153 -0
- package/ts_build/src/processors/Base64ImageDetector.js.map +1 -0
- package/ts_build/src/processors/TokenCompressor.d.ts +28 -0
- package/ts_build/src/processors/TokenCompressor.js +226 -0
- package/ts_build/src/processors/TokenCompressor.js.map +1 -0
- package/ts_build/src/processors/ToolResponseCache.d.ts +22 -0
- package/ts_build/src/processors/ToolResponseCache.js +164 -0
- package/ts_build/src/processors/ToolResponseCache.js.map +1 -0
- package/ts_build/src/processors/ToolResponseManipulator.d.ts +22 -0
- package/ts_build/src/processors/ToolResponseManipulator.js +162 -0
- package/ts_build/src/processors/ToolResponseManipulator.js.map +1 -0
- package/ts_build/src/services/Mcp.js +3 -1
- package/ts_build/src/services/Mcp.js.map +1 -1
- package/ts_build/src/services/MessageProcessor.d.ts +17 -0
- package/ts_build/src/services/MessageProcessor.js +63 -0
- package/ts_build/src/services/MessageProcessor.js.map +1 -0
- package/ts_build/src/services/Tools.d.ts +12 -0
- package/ts_build/src/services/Tools.js +71 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/types.d.ts +32 -0
- package/ts_build/src/services/types.js +38 -0
- package/ts_build/src/services/types.js.map +1 -0
- package/ts_build/src/agents/configurable/OpenAIAgent.d.ts +0 -0
- package/ts_build/src/agents/configurable/OpenAIAgent.js +0 -1
- package/ts_build/src/agents/configurable/OpenAIAgent.js.map +0 -1
- package/ts_build/src/agents/tools/client.d.ts +0 -5
- package/ts_build/src/agents/tools/client.js +0 -21
- package/ts_build/src/agents/tools/client.js.map +0 -1
- package/ts_build/src/agents/tools/googleSearchTypes.d.ts +0 -74
- package/ts_build/src/agents/tools/googleSearchTypes.js +0 -3
- package/ts_build/src/agents/tools/googleSearchTypes.js.map +0 -1
- package/ts_build/src/commands/chat-ui.d.ts +0 -1
- package/ts_build/src/commands/chat-ui.js +0 -14
- package/ts_build/src/commands/chat-ui.js.map +0 -1
- package/ts_build/src/demo/chat-ui-demo.d.ts +0 -3
- package/ts_build/src/demo/chat-ui-demo.js +0 -20
- package/ts_build/src/demo/chat-ui-demo.js.map +0 -1
- package/ts_build/src/plugins/EmbeddingPluginV2.d.ts +0 -7
- package/ts_build/src/plugins/EmbeddingPluginV2.js +0 -41
- package/ts_build/src/plugins/EmbeddingPluginV2.js.map +0 -1
- package/ts_build/src/plugins/GitHubPluginV2.d.ts +0 -10
- package/ts_build/src/plugins/GitHubPluginV2.js +0 -57
- package/ts_build/src/plugins/GitHubPluginV2.js.map +0 -1
- package/ts_build/src/plugins/downloader/index.d.ts +0 -3
- package/ts_build/src/plugins/downloader/index.js +0 -41
- package/ts_build/src/plugins/downloader/index.js.map +0 -1
- package/ts_build/src/services/MessagePreprocessor.d.ts +0 -26
- package/ts_build/src/services/MessagePreprocessor.js +0 -190
- package/ts_build/src/services/MessagePreprocessor.js.map +0 -1
- package/ts_build/src/services/__tests__/MessagePreprocessor.test.d.ts +0 -1
- package/ts_build/src/services/__tests__/MessagePreprocessor.test.js +0 -117
- package/ts_build/src/services/__tests__/MessagePreprocessor.test.js.map +0 -1
- package/ts_build/src/terminal.d.ts +0 -1
- package/ts_build/src/terminal.js +0 -35
- package/ts_build/src/terminal.js.map +0 -1
- package/ts_build/src/ui/InkChatUI.d.ts +0 -1
- package/ts_build/src/ui/InkChatUI.js +0 -792
- package/ts_build/src/ui/InkChatUI.js.map +0 -1
- package/ts_build/src/ui/components/ChatInterface.d.ts +0 -15
- package/ts_build/src/ui/components/ChatInterface.js +0 -39
- package/ts_build/src/ui/components/ChatInterface.js.map +0 -1
- package/ts_build/src/ui/components/ChatMessage.d.ts +0 -8
- package/ts_build/src/ui/components/ChatMessage.js +0 -7
- package/ts_build/src/ui/components/ChatMessage.js.map +0 -1
- package/ts_build/src/ui/components/CommandPalette.d.ts +0 -8
- package/ts_build/src/ui/components/CommandPalette.js +0 -23
- package/ts_build/src/ui/components/CommandPalette.js.map +0 -1
- package/ts_build/src/ui/components/InputBar.d.ts +0 -8
- package/ts_build/src/ui/components/InputBar.js +0 -8
- package/ts_build/src/ui/components/InputBar.js.map +0 -1
- package/ts_build/src/ui/components/Sidebar.d.ts +0 -9
- package/ts_build/src/ui/components/Sidebar.js +0 -7
- package/ts_build/src/ui/components/Sidebar.js.map +0 -1
- package/ts_build/src/ui/components/StatusBar.d.ts +0 -10
- package/ts_build/src/ui/components/StatusBar.js +0 -8
- package/ts_build/src/ui/components/StatusBar.js.map +0 -1
- package/ts_build/src/ui/demo.d.ts +0 -3
- package/ts_build/src/ui/demo.js +0 -26
- package/ts_build/src/ui/demo.js.map +0 -1
- package/ts_build/src/ui/index.d.ts +0 -13
- package/ts_build/src/ui/index.js +0 -16
- package/ts_build/src/ui/index.js.map +0 -1
- package/ts_build/tests/integration/OpenAI-MessagePreprocessor.test.d.ts +0 -1
- package/ts_build/tests/integration/OpenAI-MessagePreprocessor.test.js +0 -148
- package/ts_build/tests/integration/OpenAI-MessagePreprocessor.test.js.map +0 -1
- package/ts_build/tests/services/MessagePreprocessor.test.d.ts +0 -1
- package/ts_build/tests/services/MessagePreprocessor.test.js +0 -117
- package/ts_build/tests/services/MessagePreprocessor.test.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.17",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
"marked-terminal": "^6.2.0",
|
|
61
61
|
"morgan": "^1.10.0",
|
|
62
62
|
"node-fetch": "^3.2.3",
|
|
63
|
+
"node-jq": "^6.0.1",
|
|
63
64
|
"node-pty": "^1.0.0",
|
|
64
65
|
"node-record-lpcm16": "^1.0.1",
|
|
65
66
|
"openai": "4.89.1",
|
package/src/agents/base/base.ts
CHANGED
|
@@ -13,10 +13,10 @@ import {
|
|
|
13
13
|
replaceEscapedNewLines,
|
|
14
14
|
restoreEscapedNewLines,
|
|
15
15
|
} from "../../utils";
|
|
16
|
-
import { Agents, AgentService } from "../../services/AgentService";
|
|
17
16
|
import { Events, EventService } from "../../services/EventService";
|
|
18
17
|
import { AIClient, Clients } from "../../clients";
|
|
19
18
|
import { Models } from "../../ai";
|
|
19
|
+
import { MessageProcessor } from "../../services/MessageProcessor";
|
|
20
20
|
|
|
21
21
|
export { Message, Tool, ToolCall };
|
|
22
22
|
export interface ModelPreference {
|
|
@@ -60,7 +60,8 @@ export abstract class BaseAgent implements IAgent {
|
|
|
60
60
|
|
|
61
61
|
constructor(
|
|
62
62
|
public tools: ToolsService = Tools,
|
|
63
|
-
public events: EventService = Events
|
|
63
|
+
public events: EventService = Events,
|
|
64
|
+
public messageProcessor: MessageProcessor = new MessageProcessor()
|
|
64
65
|
) {}
|
|
65
66
|
|
|
66
67
|
newTask() {
|
|
@@ -418,6 +419,11 @@ export abstract class BaseAgent implements IAgent {
|
|
|
418
419
|
const model = this.getModel();
|
|
419
420
|
let messages = _messages || (await this.getInitialMessages(userInput));
|
|
420
421
|
|
|
422
|
+
// Process initial messages if this is the first call
|
|
423
|
+
if (!_messages) {
|
|
424
|
+
messages = await this.messageProcessor.processMessages(messages, "initial_call");
|
|
425
|
+
}
|
|
426
|
+
|
|
421
427
|
if (this.pendingUserMessages.length) {
|
|
422
428
|
messages.push(...this.pendingUserMessages);
|
|
423
429
|
this.pendingUserMessages = [];
|
|
@@ -429,6 +435,9 @@ export abstract class BaseAgent implements IAgent {
|
|
|
429
435
|
|
|
430
436
|
const startIndex = 0;
|
|
431
437
|
const endIndex = messages.length;
|
|
438
|
+
|
|
439
|
+
// Process messages before each AI call
|
|
440
|
+
messages = await this.messageProcessor.processMessages(messages, "per_call");
|
|
432
441
|
const compressThreshold = 10000;
|
|
433
442
|
|
|
434
443
|
const response = await this.getClient().createChatCompletion({
|
|
@@ -479,6 +488,11 @@ export abstract class BaseAgent implements IAgent {
|
|
|
479
488
|
}
|
|
480
489
|
}
|
|
481
490
|
|
|
491
|
+
// Process messages after tool execution
|
|
492
|
+
if (newToolCalls && newToolCalls.length > 0) {
|
|
493
|
+
messages = await this.messageProcessor.processMessages(messages, "post_call");
|
|
494
|
+
}
|
|
495
|
+
|
|
482
496
|
// Early exit: not required to call tool
|
|
483
497
|
if (
|
|
484
498
|
response.choices.length === 1 &&
|
|
@@ -565,7 +579,9 @@ export abstract class BaseAgent implements IAgent {
|
|
|
565
579
|
1. Task List
|
|
566
580
|
2. Completion Criteria - when the agent should stop
|
|
567
581
|
|
|
568
|
-
|
|
582
|
+
This output will be used to guide the work of the agent, and determine when we've accomplished the goal
|
|
583
|
+
|
|
584
|
+
\n\n<ToAnalyze>${JSON.stringify(messages)}</ToAnalyze>`;
|
|
569
585
|
|
|
570
586
|
const model = this.getModel();
|
|
571
587
|
|
|
@@ -577,6 +593,7 @@ export abstract class BaseAgent implements IAgent {
|
|
|
577
593
|
content: taskPrompt,
|
|
578
594
|
},
|
|
579
595
|
],
|
|
596
|
+
max_tokens: 2000,
|
|
580
597
|
});
|
|
581
598
|
|
|
582
599
|
this.adjustTotalCostUsd(response.usd_cost);
|
|
@@ -602,10 +619,12 @@ export abstract class BaseAgent implements IAgent {
|
|
|
602
619
|
3. Next Steps - what we're about to do next to continue the user's original request.
|
|
603
620
|
4. Tasks remaining - what tasks are left from the initial task breakdown.
|
|
604
621
|
|
|
605
|
-
|
|
606
|
-
|
|
622
|
+
Our initial task breakdown: ${this.taskBreakdown}
|
|
623
|
+
|
|
624
|
+
This summary will become the agent's only memory of the past, all other messages will be dropped:
|
|
625
|
+
<ToSummarize>${JSON.stringify(toCompress)}</ToSummarize>
|
|
607
626
|
|
|
608
|
-
|
|
627
|
+
`;
|
|
609
628
|
|
|
610
629
|
const model = this.getModel();
|
|
611
630
|
|
package/src/agents/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
1
|
+
export { BaseAgent } from "./base/base";
|
|
2
|
+
export { ConfigAgent } from "./configurable/ConfigAgent";
|
|
3
3
|
export * from "./developer/developer";
|
|
4
4
|
export * from "./patcher/patcher";
|
|
5
5
|
export * from "./researcher/researcher";
|
|
@@ -1,15 +1,106 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
|
|
4
|
+
const execAsync = promisify(exec);
|
|
5
|
+
|
|
6
|
+
export interface ExecCommandOptions {
|
|
7
|
+
timeout?: number; // Timeout in milliseconds
|
|
8
|
+
killOnTimeout?: boolean; // Whether to kill the command on timeout (default: false)
|
|
9
|
+
waitForCompletion?: boolean; // Whether to wait for full completion (default: true)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Enhanced exec function with timeout support
|
|
13
|
+
const execWithTimeout = async (
|
|
14
|
+
command: string,
|
|
15
|
+
options: ExecCommandOptions = {}
|
|
16
|
+
): Promise<{ stdout: string; stderr: string; timedOut: boolean; killed: boolean }> => {
|
|
17
|
+
const { timeout, killOnTimeout = false, waitForCompletion = true } = options;
|
|
18
|
+
|
|
19
|
+
if (!timeout || waitForCompletion) {
|
|
20
|
+
// Default behavior - wait for completion
|
|
21
|
+
try {
|
|
22
|
+
const result = await execAsync(command);
|
|
23
|
+
return { ...result, timedOut: false, killed: false };
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return {
|
|
26
|
+
stdout: error.stdout || "",
|
|
27
|
+
stderr: error.stderr || error.message,
|
|
28
|
+
timedOut: false,
|
|
29
|
+
killed: false
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Timeout behavior
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const childProcess = exec(command, (error, stdout, stderr) => {
|
|
37
|
+
if (error && !error.killed) {
|
|
38
|
+
resolve({ stdout, stderr: stderr || error.message, timedOut: false, killed: false });
|
|
39
|
+
} else {
|
|
40
|
+
resolve({ stdout, stderr, timedOut: false, killed: error?.killed || false });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const timeoutId = setTimeout(() => {
|
|
45
|
+
if (killOnTimeout) {
|
|
46
|
+
childProcess.kill('SIGTERM');
|
|
47
|
+
// Force kill after additional 5 seconds if still running
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
if (!childProcess.killed) {
|
|
50
|
+
childProcess.kill('SIGKILL');
|
|
51
|
+
}
|
|
52
|
+
}, 5000);
|
|
53
|
+
resolve({
|
|
54
|
+
stdout: "",
|
|
55
|
+
stderr: `Command timed out after ${timeout}ms and was killed`,
|
|
56
|
+
timedOut: true,
|
|
57
|
+
killed: true
|
|
58
|
+
});
|
|
59
|
+
} else {
|
|
60
|
+
resolve({
|
|
61
|
+
stdout: "",
|
|
62
|
+
stderr: `Command timed out after ${timeout}ms but is still running in background`,
|
|
63
|
+
timedOut: true,
|
|
64
|
+
killed: false
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}, timeout);
|
|
68
|
+
|
|
69
|
+
// Clear timeout if command completes before timeout
|
|
70
|
+
childProcess.on('exit', () => {
|
|
71
|
+
clearTimeout(timeoutId);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
2
75
|
|
|
3
76
|
// Tool to execute a command in the system's command line interface
|
|
4
|
-
export const execCommand = async (
|
|
77
|
+
export const execCommand = async (
|
|
78
|
+
command: string,
|
|
79
|
+
timeout?: number,
|
|
80
|
+
killOnTimeout?: boolean,
|
|
81
|
+
waitForCompletion?: boolean
|
|
82
|
+
): Promise<string> => {
|
|
5
83
|
let output = "";
|
|
6
84
|
console.log("execCommand:", command);
|
|
7
|
-
|
|
85
|
+
|
|
86
|
+
const { stdout, stderr, timedOut, killed } = await execWithTimeout(command, {
|
|
87
|
+
timeout,
|
|
88
|
+
killOnTimeout,
|
|
89
|
+
waitForCompletion,
|
|
90
|
+
|
|
91
|
+
});
|
|
92
|
+
|
|
8
93
|
if (stderr) {
|
|
9
94
|
output += stderr + "\n";
|
|
10
95
|
}
|
|
11
96
|
output += stdout;
|
|
12
|
-
|
|
97
|
+
|
|
98
|
+
if (timedOut) {
|
|
99
|
+
const statusMsg = killed ? " (killed due to timeout)" : " (timed out, still running)";
|
|
100
|
+
console.log(`$ ${command}${statusMsg}:\n${output}`);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`$ ${command}:\n${output}`);
|
|
103
|
+
}
|
|
13
104
|
|
|
14
105
|
const fullOutput = output.split("\n");
|
|
15
106
|
|
package/src/agents/tools/list.ts
CHANGED
|
@@ -7,7 +7,6 @@ import * as github from "./github/definitions";
|
|
|
7
7
|
import * as asana from "./asana/definitions";
|
|
8
8
|
import * as language from "./language/definitions";
|
|
9
9
|
import { googleSearchDefinition } from "./googleSearch";
|
|
10
|
-
import { Agents } from "../../services/AgentService";
|
|
11
10
|
|
|
12
11
|
export const includedTools = [
|
|
13
12
|
{
|
|
@@ -39,7 +38,7 @@ export const includedTools = [
|
|
|
39
38
|
function: {
|
|
40
39
|
name: "execCommand",
|
|
41
40
|
description:
|
|
42
|
-
"Execute a command in the system's command line interface. Use this to run tests and things in the terminal",
|
|
41
|
+
"Execute a command in the system's command line interface. Use this to run tests and things in the terminal. Supports timeout functionality.",
|
|
43
42
|
parameters: {
|
|
44
43
|
type: "object",
|
|
45
44
|
positional: true,
|
|
@@ -48,23 +47,28 @@ export const includedTools = [
|
|
|
48
47
|
type: "string",
|
|
49
48
|
description: "The command to execute",
|
|
50
49
|
},
|
|
50
|
+
timeout: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description:
|
|
53
|
+
"Timeout in milliseconds (optional). If not provided, waits indefinitely.",
|
|
54
|
+
},
|
|
55
|
+
killOnTimeout: {
|
|
56
|
+
type: "boolean",
|
|
57
|
+
description:
|
|
58
|
+
"Whether to kill the command when timeout is reached (default: false). If false, command continues running in background.",
|
|
59
|
+
},
|
|
60
|
+
waitForCompletion: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description:
|
|
63
|
+
"Whether to wait for full completion regardless of timeout (default: true). Overrides timeout behavior.",
|
|
64
|
+
},
|
|
51
65
|
},
|
|
52
66
|
required: ["command"],
|
|
53
67
|
},
|
|
54
68
|
returns: {
|
|
55
|
-
type: "
|
|
56
|
-
properties: {
|
|
57
|
-
stdout: {
|
|
58
|
-
type: "string",
|
|
59
|
-
description: "The standard output of the executed command",
|
|
60
|
-
},
|
|
61
|
-
stderr: {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: "The standard error output of the executed command",
|
|
64
|
-
},
|
|
65
|
-
},
|
|
69
|
+
type: "string",
|
|
66
70
|
description:
|
|
67
|
-
"The result of the command execution, including any output and errors",
|
|
71
|
+
"The result of the command execution, including any output and errors. May include timeout status information.",
|
|
68
72
|
},
|
|
69
73
|
},
|
|
70
74
|
},
|
|
@@ -473,8 +477,6 @@ export const includedTools = [
|
|
|
473
477
|
},
|
|
474
478
|
},
|
|
475
479
|
},
|
|
476
|
-
|
|
477
|
-
googleSearchDefinition,
|
|
478
480
|
{
|
|
479
481
|
type: "function",
|
|
480
482
|
function: {
|
|
@@ -515,9 +517,6 @@ export const includedTools = [
|
|
|
515
517
|
},
|
|
516
518
|
},
|
|
517
519
|
},
|
|
518
|
-
...asana.definitions,
|
|
519
|
-
...github.definitions,
|
|
520
|
-
...language.definitions,
|
|
521
520
|
{
|
|
522
521
|
type: "function",
|
|
523
522
|
function: {
|
|
@@ -553,4 +552,9 @@ export const includedTools = [
|
|
|
553
552
|
},
|
|
554
553
|
},
|
|
555
554
|
},
|
|
555
|
+
|
|
556
|
+
googleSearchDefinition,
|
|
557
|
+
...asana.definitions,
|
|
558
|
+
...github.definitions,
|
|
559
|
+
...language.definitions,
|
|
556
560
|
] as Tool[];
|
package/src/chat.ts
CHANGED
|
@@ -21,6 +21,8 @@ import { recordAudio, voiceToText } from "./microphone";
|
|
|
21
21
|
import { Models } from "./ai";
|
|
22
22
|
import { BaseAgent } from "./agents";
|
|
23
23
|
import { getConfig } from "./config";
|
|
24
|
+
import { TokenCompressor } from "./processors/TokenCompressor";
|
|
25
|
+
import { ToolResponseCache } from "./processors/ToolResponseCache";
|
|
24
26
|
|
|
25
27
|
enum ChatFlags {
|
|
26
28
|
agent = "agent",
|
|
@@ -346,6 +348,15 @@ export async function startAgent(
|
|
|
346
348
|
);
|
|
347
349
|
activeAgent.call(formattedPrompt);
|
|
348
350
|
|
|
351
|
+
// Compress tokens of tool responses
|
|
352
|
+
activeAgent.messageProcessor.setProcessors("per_call", [
|
|
353
|
+
new ToolResponseCache(activeAgent.tools).createProcessor(),
|
|
354
|
+
|
|
355
|
+
new TokenCompressor(activeAgent.tools).createProcessor((msg) =>
|
|
356
|
+
Boolean(msg.role === "tool" && msg.tool_call_id)
|
|
357
|
+
),
|
|
358
|
+
]);
|
|
359
|
+
|
|
349
360
|
activeAgent.agentEvents.once(activeAgent.eventTypes.done, (doneMsg) => {
|
|
350
361
|
console.log("Agent has finished.");
|
|
351
362
|
done = true;
|
package/src/config.ts
CHANGED
|
@@ -187,7 +187,9 @@ export function getConfigSync() {
|
|
|
187
187
|
|
|
188
188
|
export async function getConfig() {
|
|
189
189
|
if (!fs.existsSync(".knowhow/knowhow.json")) {
|
|
190
|
-
|
|
190
|
+
throw new Error(
|
|
191
|
+
"KnowHow config file not found. Please run `knowhow init` to create it."
|
|
192
|
+
);
|
|
191
193
|
}
|
|
192
194
|
const config = JSON.parse(await readFile(".knowhow/knowhow.json", "utf8"));
|
|
193
195
|
return config as Config;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { Message } from "../clients/types";
|
|
2
|
+
import { MessageProcessorFunction } from "../services/MessageProcessor";
|
|
3
|
+
|
|
4
|
+
interface ImageContent {
|
|
5
|
+
type: "image_url";
|
|
6
|
+
image_url: {
|
|
7
|
+
url: string;
|
|
8
|
+
detail?: "auto" | "low" | "high";
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TextContent {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class Base64ImageDetector {
|
|
18
|
+
private imageDetail: "auto" | "low" | "high";
|
|
19
|
+
private supportedFormats: string[];
|
|
20
|
+
|
|
21
|
+
constructor(
|
|
22
|
+
imageDetail: "auto" | "low" | "high" = "auto",
|
|
23
|
+
supportedFormats: string[] = ["png", "jpeg", "jpg", "gif", "webp"]
|
|
24
|
+
) {
|
|
25
|
+
this.imageDetail = imageDetail;
|
|
26
|
+
this.supportedFormats = supportedFormats;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private isBase64Image(text: string): { isImage: boolean; mimeType?: string; data?: string } {
|
|
30
|
+
// Check for data URL format: data:image/type;base64,actualdata
|
|
31
|
+
const dataUrlPattern = /^data:image\/([a-zA-Z]+);base64,(.+)$/;
|
|
32
|
+
const match = text.match(dataUrlPattern);
|
|
33
|
+
|
|
34
|
+
if (match) {
|
|
35
|
+
const [, mimeType, data] = match;
|
|
36
|
+
if (this.supportedFormats.includes(mimeType.toLowerCase())) {
|
|
37
|
+
return { isImage: true, mimeType, data };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for plain base64 that might be an image
|
|
42
|
+
// This is a heuristic - look for long base64 strings that might be images
|
|
43
|
+
const base64Pattern = /^[A-Za-z0-9+/]+=*$/;
|
|
44
|
+
if (base64Pattern.test(text) && text.length > 100) {
|
|
45
|
+
// Try to detect image type from base64 header
|
|
46
|
+
const header = text.substring(0, 50);
|
|
47
|
+
try {
|
|
48
|
+
const decoded = atob(header);
|
|
49
|
+
// Check for common image file signatures
|
|
50
|
+
if (decoded.startsWith('\x89PNG')) {
|
|
51
|
+
return { isImage: true, mimeType: 'png', data: text };
|
|
52
|
+
} else if (decoded.startsWith('\xFF\xD8\xFF')) {
|
|
53
|
+
return { isImage: true, mimeType: 'jpeg', data: text };
|
|
54
|
+
} else if (decoded.startsWith('GIF87a') || decoded.startsWith('GIF89a')) {
|
|
55
|
+
return { isImage: true, mimeType: 'gif', data: text };
|
|
56
|
+
} else if (decoded.startsWith('RIFF') && decoded.includes('WEBP')) {
|
|
57
|
+
return { isImage: true, mimeType: 'webp', data: text };
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// Not valid base64 or not an image
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { isImage: false };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private convertBase64ToImageContent(text: string): ImageContent | null {
|
|
68
|
+
const detection = this.isBase64Image(text);
|
|
69
|
+
|
|
70
|
+
if (!detection.isImage) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const dataUrl = detection.data!.startsWith('data:')
|
|
75
|
+
? detection.data
|
|
76
|
+
: `data:image/${detection.mimeType};base64,${detection.data}`;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
type: "image_url",
|
|
80
|
+
image_url: {
|
|
81
|
+
url: dataUrl,
|
|
82
|
+
detail: this.imageDetail
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private processMessageContent(message: Message): void {
|
|
88
|
+
if (typeof message.content === 'string') {
|
|
89
|
+
const imageContent = this.convertBase64ToImageContent(message.content);
|
|
90
|
+
if (imageContent) {
|
|
91
|
+
// Convert string content to multimodal array
|
|
92
|
+
message.content = [imageContent];
|
|
93
|
+
}
|
|
94
|
+
} else if (Array.isArray(message.content)) {
|
|
95
|
+
// Process each content item
|
|
96
|
+
const newContent: (TextContent | ImageContent)[] = [];
|
|
97
|
+
|
|
98
|
+
for (const item of message.content) {
|
|
99
|
+
if (item.type === 'text' && item.text) {
|
|
100
|
+
const imageContent = this.convertBase64ToImageContent(item.text);
|
|
101
|
+
if (imageContent) {
|
|
102
|
+
newContent.push(imageContent);
|
|
103
|
+
} else {
|
|
104
|
+
newContent.push(item as TextContent);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
newContent.push(item as TextContent | ImageContent);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
message.content = newContent;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private processToolCallArguments(message: Message): void {
|
|
116
|
+
if (message.tool_calls) {
|
|
117
|
+
for (const toolCall of message.tool_calls) {
|
|
118
|
+
if (toolCall.function.arguments) {
|
|
119
|
+
try {
|
|
120
|
+
const args = JSON.parse(toolCall.function.arguments);
|
|
121
|
+
let modified = false;
|
|
122
|
+
|
|
123
|
+
// Recursively check all string values in arguments
|
|
124
|
+
const processValue = (obj: any): any => {
|
|
125
|
+
if (typeof obj === 'string') {
|
|
126
|
+
const detection = this.isBase64Image(obj);
|
|
127
|
+
if (detection.isImage) {
|
|
128
|
+
modified = true;
|
|
129
|
+
const dataUrl = detection.data!.startsWith('data:')
|
|
130
|
+
? detection.data
|
|
131
|
+
: `data:image/${detection.mimeType};base64,${detection.data}`;
|
|
132
|
+
return `[CONVERTED TO IMAGE: ${dataUrl.substring(0, 50)}...]`;
|
|
133
|
+
}
|
|
134
|
+
return obj;
|
|
135
|
+
} else if (Array.isArray(obj)) {
|
|
136
|
+
return obj.map(processValue);
|
|
137
|
+
} else if (obj && typeof obj === 'object') {
|
|
138
|
+
const result = {};
|
|
139
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
140
|
+
result[key] = processValue(value);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
return obj;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const processedArgs = processValue(args);
|
|
148
|
+
if (modified) {
|
|
149
|
+
toolCall.function.arguments = JSON.stringify(processedArgs);
|
|
150
|
+
}
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Arguments are not valid JSON, treat as string
|
|
153
|
+
const detection = this.isBase64Image(toolCall.function.arguments);
|
|
154
|
+
if (detection.isImage) {
|
|
155
|
+
const dataUrl = detection.data!.startsWith('data:')
|
|
156
|
+
? detection.data
|
|
157
|
+
: `data:image/${detection.mimeType};base64,${detection.data}`;
|
|
158
|
+
toolCall.function.arguments = `[CONVERTED TO IMAGE: ${dataUrl.substring(0, 50)}...]`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
createProcessor(): MessageProcessorFunction {
|
|
167
|
+
return (originalMessages: Message[], modifiedMessages: Message[]) => {
|
|
168
|
+
for (const message of modifiedMessages) {
|
|
169
|
+
// Only process user messages (images typically come from users)
|
|
170
|
+
if (message.role === 'user') {
|
|
171
|
+
this.processMessageContent(message);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Process tool calls in any message
|
|
175
|
+
this.processToolCallArguments(message);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
setImageDetail(detail: "auto" | "low" | "high"): void {
|
|
181
|
+
this.imageDetail = detail;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
setSupportedFormats(formats: string[]): void {
|
|
185
|
+
this.supportedFormats = formats;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Global instance
|
|
190
|
+
export const globalBase64ImageDetector = new Base64ImageDetector();
|