@tyvm/knowhow 0.0.128 → 0.0.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/scripts/cache-miss-debug.ts +365 -0
- package/src/agents/base/base.ts +159 -59
- package/src/agents/tools/execCommand.ts +26 -6
- package/src/agents/tools/index.ts +1 -0
- package/src/agents/tools/list.ts +2 -0
- package/src/agents/tools/minimal/callTool.ts +45 -0
- package/src/agents/tools/minimal/definitions.ts +50 -0
- package/src/agents/tools/minimal/index.ts +3 -0
- package/src/agents/tools/minimal/inspectTools.ts +27 -0
- package/src/agents/tools/skills.ts +79 -0
- package/src/chat/modules/AgentModule.ts +111 -9
- package/src/cli.ts +4 -0
- package/src/clients/anthropic.ts +59 -1
- package/src/clients/contextLimits.ts +9 -0
- package/src/clients/index.ts +6 -0
- package/src/clients/meta.ts +18 -0
- package/src/clients/openai.ts +28 -5
- package/src/clients/pricing/anthropic.ts +13 -0
- package/src/clients/pricing/fireworks.ts +18 -12
- package/src/clients/pricing/index.ts +1 -0
- package/src/clients/pricing/meta.ts +12 -0
- package/src/clients/pricing/openai.ts +16 -0
- package/src/clients/pricing/xai.ts +3 -0
- package/src/clients/types.ts +27 -4
- package/src/clients/xai.ts +2 -2
- package/src/commands/agent.ts +19 -0
- package/src/commands/behaviors.ts +47 -0
- package/src/commands/services.ts +7 -3
- package/src/commands/skills.ts +71 -0
- package/src/index.ts +2 -0
- package/src/plugins/PluginBase.ts +0 -2
- package/src/plugins/SkillsPlugin.ts +11 -124
- package/src/plugins/language.ts +0 -1
- package/src/plugins/plugins.ts +24 -13
- package/src/plugins/types.ts +8 -2
- package/src/processors/MinimalToolsMessageProcessor.ts +113 -0
- package/src/processors/TokenCompressor.ts +21 -4
- package/src/processors/index.ts +7 -1
- package/src/services/AgentSyncFs.ts +49 -0
- package/src/services/AgentSyncKnowhowWeb.ts +1 -1
- package/src/services/BehaviorsService.ts +432 -0
- package/src/services/KnowhowClient.ts +62 -1
- package/src/services/LazyToolsService.ts +12 -0
- package/src/services/MinimalToolsService.ts +167 -0
- package/src/services/SkillsService.ts +622 -0
- package/src/services/Tools.ts +11 -0
- package/src/services/index.ts +9 -2
- package/src/services/modules/types.ts +2 -0
- package/src/worker.ts +7 -0
- package/tests/processors/TokenCompressor.test.ts +45 -0
- package/tests/unit/services/MinimalToolsService.test.ts +424 -0
- package/ts_build/package.json +5 -4
- package/ts_build/src/agents/base/base.d.ts +13 -2
- package/ts_build/src/agents/base/base.js +91 -35
- package/ts_build/src/agents/base/base.js.map +1 -1
- package/ts_build/src/agents/tools/execCommand.d.ts +1 -1
- package/ts_build/src/agents/tools/execCommand.js +18 -5
- package/ts_build/src/agents/tools/execCommand.js.map +1 -1
- package/ts_build/src/agents/tools/index.d.ts +1 -0
- package/ts_build/src/agents/tools/index.js +1 -0
- package/ts_build/src/agents/tools/index.js.map +1 -1
- package/ts_build/src/agents/tools/list.js +2 -0
- package/ts_build/src/agents/tools/list.js.map +1 -1
- package/ts_build/src/agents/tools/minimal/callTool.d.ts +5 -0
- package/ts_build/src/agents/tools/minimal/callTool.js +28 -0
- package/ts_build/src/agents/tools/minimal/callTool.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/definitions.d.ts +4 -0
- package/ts_build/src/agents/tools/minimal/definitions.js +46 -0
- package/ts_build/src/agents/tools/minimal/definitions.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/index.d.ts +3 -0
- package/ts_build/src/agents/tools/minimal/index.js +20 -0
- package/ts_build/src/agents/tools/minimal/index.js.map +1 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.d.ts +2 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.js +15 -0
- package/ts_build/src/agents/tools/minimal/inspectTools.js.map +1 -0
- package/ts_build/src/agents/tools/skills.d.ts +33 -0
- package/ts_build/src/agents/tools/skills.js +62 -0
- package/ts_build/src/agents/tools/skills.js.map +1 -0
- package/ts_build/src/chat/modules/AgentModule.d.ts +2 -0
- package/ts_build/src/chat/modules/AgentModule.js +77 -2
- package/ts_build/src/chat/modules/AgentModule.js.map +1 -1
- package/ts_build/src/chat/modules/CustomCommandsModule.d.ts +1 -1
- package/ts_build/src/cli.js +4 -0
- package/ts_build/src/cli.js.map +1 -1
- package/ts_build/src/clients/anthropic.d.ts +2 -0
- package/ts_build/src/clients/anthropic.js +35 -0
- package/ts_build/src/clients/anthropic.js.map +1 -1
- package/ts_build/src/clients/contextLimits.js +8 -0
- package/ts_build/src/clients/contextLimits.js.map +1 -1
- package/ts_build/src/clients/index.d.ts +1 -0
- package/ts_build/src/clients/index.js +6 -0
- package/ts_build/src/clients/index.js.map +1 -1
- package/ts_build/src/clients/meta.d.ts +4 -0
- package/ts_build/src/clients/meta.js +15 -0
- package/ts_build/src/clients/meta.js.map +1 -0
- package/ts_build/src/clients/openai.d.ts +1 -0
- package/ts_build/src/clients/openai.js +25 -5
- package/ts_build/src/clients/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/anthropic.d.ts +4 -0
- package/ts_build/src/clients/pricing/anthropic.js +12 -0
- package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
- package/ts_build/src/clients/pricing/fireworks.d.ts +2 -5
- package/ts_build/src/clients/pricing/fireworks.js +13 -11
- package/ts_build/src/clients/pricing/fireworks.js.map +1 -1
- package/ts_build/src/clients/pricing/index.d.ts +1 -0
- package/ts_build/src/clients/pricing/index.js +5 -2
- package/ts_build/src/clients/pricing/index.js.map +1 -1
- package/ts_build/src/clients/pricing/meta.d.ts +6 -0
- package/ts_build/src/clients/pricing/meta.js +8 -0
- package/ts_build/src/clients/pricing/meta.js.map +1 -0
- package/ts_build/src/clients/pricing/openai.d.ts +3 -0
- package/ts_build/src/clients/pricing/openai.js +15 -0
- package/ts_build/src/clients/pricing/openai.js.map +1 -1
- package/ts_build/src/clients/pricing/xai.d.ts +1 -0
- package/ts_build/src/clients/pricing/xai.js +3 -0
- package/ts_build/src/clients/pricing/xai.js.map +1 -1
- package/ts_build/src/clients/types.d.ts +6 -1
- package/ts_build/src/clients/xai.js +1 -1
- package/ts_build/src/clients/xai.js.map +1 -1
- package/ts_build/src/commands/agent.js +16 -0
- package/ts_build/src/commands/agent.js.map +1 -1
- package/ts_build/src/commands/behaviors.d.ts +2 -0
- package/ts_build/src/commands/behaviors.js +43 -0
- package/ts_build/src/commands/behaviors.js.map +1 -0
- package/ts_build/src/commands/services.js +4 -2
- package/ts_build/src/commands/services.js.map +1 -1
- package/ts_build/src/commands/skills.d.ts +2 -0
- package/ts_build/src/commands/skills.js +61 -0
- package/ts_build/src/commands/skills.js.map +1 -0
- package/ts_build/src/index.d.ts +2 -0
- package/ts_build/src/index.js +5 -1
- package/ts_build/src/index.js.map +1 -1
- package/ts_build/src/plugins/PluginBase.d.ts +0 -2
- package/ts_build/src/plugins/PluginBase.js.map +1 -1
- package/ts_build/src/plugins/SkillsPlugin.d.ts +1 -6
- package/ts_build/src/plugins/SkillsPlugin.js +6 -135
- package/ts_build/src/plugins/SkillsPlugin.js.map +1 -1
- package/ts_build/src/plugins/language.js.map +1 -1
- package/ts_build/src/plugins/plugins.d.ts +4 -1
- package/ts_build/src/plugins/plugins.js +17 -11
- package/ts_build/src/plugins/plugins.js.map +1 -1
- package/ts_build/src/plugins/types.d.ts +3 -2
- package/ts_build/src/processors/MinimalToolsMessageProcessor.d.ts +7 -0
- package/ts_build/src/processors/MinimalToolsMessageProcessor.js +61 -0
- package/ts_build/src/processors/MinimalToolsMessageProcessor.js.map +1 -0
- package/ts_build/src/processors/TokenCompressor.js +5 -2
- package/ts_build/src/processors/TokenCompressor.js.map +1 -1
- package/ts_build/src/processors/index.d.ts +2 -1
- package/ts_build/src/processors/index.js +3 -1
- package/ts_build/src/processors/index.js.map +1 -1
- package/ts_build/src/services/AgentSyncFs.d.ts +2 -0
- package/ts_build/src/services/AgentSyncFs.js +30 -0
- package/ts_build/src/services/AgentSyncFs.js.map +1 -1
- package/ts_build/src/services/AgentSyncKnowhowWeb.js +1 -1
- package/ts_build/src/services/BehaviorsService.d.ts +46 -0
- package/ts_build/src/services/BehaviorsService.js +358 -0
- package/ts_build/src/services/BehaviorsService.js.map +1 -0
- package/ts_build/src/services/KnowhowClient.d.ts +9 -1
- package/ts_build/src/services/KnowhowClient.js +20 -0
- package/ts_build/src/services/KnowhowClient.js.map +1 -1
- package/ts_build/src/services/LazyToolsService.js.map +1 -1
- package/ts_build/src/services/MinimalToolsService.d.ts +14 -0
- package/ts_build/src/services/MinimalToolsService.js +80 -0
- package/ts_build/src/services/MinimalToolsService.js.map +1 -0
- package/ts_build/src/services/SkillsService.d.ts +53 -0
- package/ts_build/src/services/SkillsService.js +478 -0
- package/ts_build/src/services/SkillsService.js.map +1 -0
- package/ts_build/src/services/Tools.d.ts +2 -0
- package/ts_build/src/services/Tools.js.map +1 -1
- package/ts_build/src/services/index.d.ts +4 -0
- package/ts_build/src/services/index.js +9 -2
- package/ts_build/src/services/index.js.map +1 -1
- package/ts_build/src/services/modules/index.d.ts +4 -2
- package/ts_build/src/services/modules/types.d.ts +2 -0
- package/ts_build/src/types.d.ts +8 -0
- package/ts_build/src/worker.js +5 -0
- package/ts_build/src/worker.js.map +1 -1
- package/ts_build/tests/processors/TokenCompressor.test.js +27 -0
- package/ts_build/tests/processors/TokenCompressor.test.js.map +1 -1
- package/ts_build/tests/unit/services/MinimalToolsService.test.d.ts +1 -0
- package/ts_build/tests/unit/services/MinimalToolsService.test.js +320 -0
- package/ts_build/tests/unit/services/MinimalToolsService.test.js.map +1 -0
|
@@ -13,6 +13,7 @@ import { MediaProcessorService } from "../MediaProcessorService";
|
|
|
13
13
|
import { TunnelHandler } from "@tyvm/knowhow-tunnel";
|
|
14
14
|
import { EventService } from "../EventService";
|
|
15
15
|
import { ConversionService } from "../conversion/ConversionService";
|
|
16
|
+
import { BehaviorsService } from "../BehaviorsService";
|
|
16
17
|
|
|
17
18
|
/*
|
|
18
19
|
*
|
|
@@ -61,6 +62,7 @@ export interface ModuleContext {
|
|
|
61
62
|
Conversion?: ConversionService;
|
|
62
63
|
Tunnel?: TunnelHandler;
|
|
63
64
|
Program?: Command;
|
|
65
|
+
Behaviors?: BehaviorsService
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
export interface KnowhowModule {
|
package/src/worker.ts
CHANGED
|
@@ -225,9 +225,16 @@ export async function worker(options?: {
|
|
|
225
225
|
console.log(
|
|
226
226
|
"Worker tools configured! Update knowhow.json to adjust which tools are allowed by the worker."
|
|
227
227
|
);
|
|
228
|
+
console.log(
|
|
229
|
+
"Tunnel is disabled by default. Set worker.tunnel.enabled = true in knowhow.json to enable it."
|
|
230
|
+
);
|
|
228
231
|
config.worker = {
|
|
229
232
|
...config.worker,
|
|
230
233
|
allowedTools: Tools.getToolNames(),
|
|
234
|
+
tunnel: {
|
|
235
|
+
enabled: false,
|
|
236
|
+
...config.worker?.tunnel,
|
|
237
|
+
},
|
|
231
238
|
};
|
|
232
239
|
await updateConfig(config);
|
|
233
240
|
return;
|
|
@@ -83,6 +83,51 @@ describe("TokenCompressor", () => {
|
|
|
83
83
|
expect(parsed.smallProp).toBe("small"); // Should remain unchanged
|
|
84
84
|
expect(parsed.largeProp).toContain("[COMPRESSED_JSON_PROPERTY"); // Should be compressed
|
|
85
85
|
});
|
|
86
|
+
|
|
87
|
+
it("should compress large content that merely CONTAINS a compression marker deep inside (regression)", () => {
|
|
88
|
+
// Regression for the false-positive in isAlreadyCompressed(): a large
|
|
89
|
+
// payload (e.g. a usage.json / message-history dump) whose body contains
|
|
90
|
+
// the substring "[COMPRESSED_STRING" deep inside its nested data was
|
|
91
|
+
// incorrectly treated as already-compressed and returned uncompressed,
|
|
92
|
+
// letting 1MB+ raw tool responses flow into createCompletion untouched.
|
|
93
|
+
const nestedMarker =
|
|
94
|
+
'[COMPRESSED_STRING - 10155 tokens in 3 chunks]\\nKey: compressed_abc123';
|
|
95
|
+
// Build a big JSON array whose entries embed the marker far from the start.
|
|
96
|
+
const entry = JSON.stringify({
|
|
97
|
+
role: "tool",
|
|
98
|
+
name: "readFile",
|
|
99
|
+
content: nestedMarker,
|
|
100
|
+
filler: "z".repeat(500),
|
|
101
|
+
});
|
|
102
|
+
const bigContent = "[" + new Array(200).fill(entry).join(",") + "]";
|
|
103
|
+
|
|
104
|
+
// Sanity: the raw content contains the marker but is NOT at the start.
|
|
105
|
+
expect(bigContent.includes("[COMPRESSED_STRING")).toBe(true);
|
|
106
|
+
expect(bigContent.trimStart().startsWith("[COMPRESSED_STRING")).toBe(false);
|
|
107
|
+
|
|
108
|
+
const result = tokenCompressor.compressContent(bigContent);
|
|
109
|
+
|
|
110
|
+
// It MUST actually get compressed, not returned untouched.
|
|
111
|
+
expect(result.length).toBeLessThan(bigContent.length);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should NOT recompress content that genuinely starts with a compression marker", () => {
|
|
115
|
+
// Idempotency: real compressor output begins with "[COMPRESSED_STRING".
|
|
116
|
+
const alreadyCompressed =
|
|
117
|
+
"[COMPRESSED_STRING - 999 tokens in 2 chunks]\nKey: compressed_xyz\nPath: \nPreview: ...";
|
|
118
|
+
const result = tokenCompressor.compressContent(alreadyCompressed);
|
|
119
|
+
expect(result).toBe(alreadyCompressed);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should NOT recompress JSON whose top-level _schema_key marks it compressed", () => {
|
|
123
|
+
// Idempotency: JSON-compressed output carries a top-level _schema_key.
|
|
124
|
+
const compressedJson = JSON.stringify({
|
|
125
|
+
_schema_key: "compressed_schema_1",
|
|
126
|
+
data: "x".repeat(40000),
|
|
127
|
+
});
|
|
128
|
+
const result = tokenCompressor.compressContent(compressedJson);
|
|
129
|
+
expect(result).toBe(compressedJson);
|
|
130
|
+
});
|
|
86
131
|
});
|
|
87
132
|
|
|
88
133
|
describe("compressStringInChunks", () => {
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for MinimalToolsService and MinimalToolsMessageProcessor.
|
|
3
|
+
*
|
|
4
|
+
* Key scenarios:
|
|
5
|
+
* 1. Base tools are always in the visible tools array (cache-stable).
|
|
6
|
+
* 2. Extended tools added via addTools() are NOT in the visible array.
|
|
7
|
+
* 3. callTool dispatches to any tool in the catalog by name.
|
|
8
|
+
* 4. inspectTools returns schemas from the full catalog.
|
|
9
|
+
* 5. MinimalToolsMessageProcessor unwraps callTool(name, args) in the last
|
|
10
|
+
* assistant message's tool_calls.
|
|
11
|
+
* 6. Unwrapping callTool("finalAnswer", ...) → finalAnswer so the agent
|
|
12
|
+
* terminates correctly.
|
|
13
|
+
* 7. Unwrapping callTool("testTool123", ...) → testTool123 so a custom
|
|
14
|
+
* required tool is satisfied.
|
|
15
|
+
* 8. Multiple callTool entries in one message are all unwrapped.
|
|
16
|
+
* 9. Only the last assistant message is rewritten (older messages untouched).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── Mocks (must come before any imports that touch config/clients) ──────────
|
|
20
|
+
jest.mock("../../../src/config", () => ({
|
|
21
|
+
getConfig: jest.fn(),
|
|
22
|
+
getGlobalConfig: jest.fn(),
|
|
23
|
+
getConfigSync: jest.fn().mockReturnValue({}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock("../../../src/clients", () => ({
|
|
27
|
+
AIClient: jest.fn(),
|
|
28
|
+
Clients: jest.fn(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
jest.mock("../../../src/services", () => ({
|
|
32
|
+
services: jest.fn().mockReturnValue({
|
|
33
|
+
Clients: {},
|
|
34
|
+
Plugins: { listPlugins: jest.fn().mockReturnValue([]) },
|
|
35
|
+
Agents: {},
|
|
36
|
+
Tools: {},
|
|
37
|
+
}),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
// ── Imports ──────────────────────────────────────────────────────────────────
|
|
41
|
+
import { MinimalToolsService } from "../../../src/services/MinimalToolsService";
|
|
42
|
+
import { MinimalToolsMessageProcessor } from "../../../src/processors/MinimalToolsMessageProcessor";
|
|
43
|
+
import { Tool } from "../../../src/clients/types";
|
|
44
|
+
import { Message } from "../../../src/clients/types";
|
|
45
|
+
|
|
46
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
function makeTool(name: string): Tool {
|
|
49
|
+
return {
|
|
50
|
+
type: "function",
|
|
51
|
+
function: {
|
|
52
|
+
name,
|
|
53
|
+
description: `${name} tool`,
|
|
54
|
+
parameters: {
|
|
55
|
+
type: "object",
|
|
56
|
+
positional: false,
|
|
57
|
+
properties: {
|
|
58
|
+
input: { type: "string", description: "input" },
|
|
59
|
+
},
|
|
60
|
+
required: [],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function makeCallToolMessage(
|
|
67
|
+
toolName: string,
|
|
68
|
+
args: Record<string, any>,
|
|
69
|
+
id = "tc_1"
|
|
70
|
+
): Message {
|
|
71
|
+
return {
|
|
72
|
+
role: "assistant",
|
|
73
|
+
content: null,
|
|
74
|
+
tool_calls: [
|
|
75
|
+
{
|
|
76
|
+
id,
|
|
77
|
+
type: "function",
|
|
78
|
+
function: {
|
|
79
|
+
name: "callTool",
|
|
80
|
+
arguments: JSON.stringify({ name: toolName, args }),
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
} as unknown as Message;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── MinimalToolsService ───────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
describe("MinimalToolsService", () => {
|
|
90
|
+
let service: MinimalToolsService;
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
service = new MinimalToolsService();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("base tools are always visible", () => {
|
|
97
|
+
const names = service.getTools().map((t) => t.function.name);
|
|
98
|
+
expect(names).toContain("finalAnswer");
|
|
99
|
+
expect(names).toContain("callTool");
|
|
100
|
+
expect(names).toContain("inspectTools");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("extended tools added via addTools() are NOT in visible tools", () => {
|
|
104
|
+
const extended = makeTool("readFile");
|
|
105
|
+
service.addTools([extended]);
|
|
106
|
+
|
|
107
|
+
const visibleNames = service.getTools().map((t) => t.function.name);
|
|
108
|
+
expect(visibleNames).not.toContain("readFile");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("extended tools appear in getAllTools()", () => {
|
|
112
|
+
const extended = makeTool("readFile");
|
|
113
|
+
service.addTools([extended]);
|
|
114
|
+
|
|
115
|
+
const allNames = service.getAllTools().map((t) => t.function.name);
|
|
116
|
+
expect(allNames).toContain("readFile");
|
|
117
|
+
expect(allNames).toContain("finalAnswer");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("visible tools array is stable after adding extended tools", () => {
|
|
121
|
+
const before = service.getTools().map((t) => t.function.name);
|
|
122
|
+
service.addTools([makeTool("writeFile"), makeTool("patchFile")]);
|
|
123
|
+
const after = service.getTools().map((t) => t.function.name);
|
|
124
|
+
expect(before).toEqual(after);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("duplicate extended tools are not added twice", () => {
|
|
128
|
+
service.addTools([makeTool("readFile")]);
|
|
129
|
+
service.addTools([makeTool("readFile")]);
|
|
130
|
+
const allNames = service.getAllTools().map((t) => t.function.name);
|
|
131
|
+
const count = allNames.filter((n) => n === "readFile").length;
|
|
132
|
+
expect(count).toBe(1);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("inspectTools returns schema for all tools when no patterns given", () => {
|
|
136
|
+
service.addTools([makeTool("readFile"), makeTool("writeFile")]);
|
|
137
|
+
const result = service.callTool(
|
|
138
|
+
{
|
|
139
|
+
id: "tc_inspect",
|
|
140
|
+
type: "function",
|
|
141
|
+
function: { name: "inspectTools", arguments: JSON.stringify({}) },
|
|
142
|
+
},
|
|
143
|
+
service.getToolNames()
|
|
144
|
+
);
|
|
145
|
+
// inspectTools is synchronous inside callTool, check via direct function call
|
|
146
|
+
const fn = service.getFunction("inspectTools");
|
|
147
|
+
expect(fn).toBeDefined();
|
|
148
|
+
const schemas = fn.call(service) as any[];
|
|
149
|
+
const names = schemas.map((s: any) => s.name);
|
|
150
|
+
expect(names).toContain("readFile");
|
|
151
|
+
expect(names).toContain("writeFile");
|
|
152
|
+
expect(names).toContain("finalAnswer");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("inspectTools filters by glob pattern", () => {
|
|
156
|
+
service.addTools([makeTool("readFile"), makeTool("writeFile"), makeTool("patchFile")]);
|
|
157
|
+
const fn = service.getFunction("inspectTools");
|
|
158
|
+
const schemas = fn.call(service, ["*File"]) as any[];
|
|
159
|
+
const names = schemas.map((s: any) => s.name);
|
|
160
|
+
expect(names).toContain("readFile");
|
|
161
|
+
expect(names).toContain("writeFile");
|
|
162
|
+
expect(names).toContain("patchFile");
|
|
163
|
+
expect(names).not.toContain("finalAnswer");
|
|
164
|
+
expect(names).not.toContain("callTool");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("callToolByName dispatches to an extended tool and returns response", async () => {
|
|
168
|
+
const mockFn = jest.fn().mockResolvedValue("file contents");
|
|
169
|
+
service.addTools([makeTool("readFile")]);
|
|
170
|
+
service.addFunctions({ readFile: mockFn });
|
|
171
|
+
|
|
172
|
+
const result = await service.callToolByName(
|
|
173
|
+
{
|
|
174
|
+
id: "tc_read",
|
|
175
|
+
type: "function",
|
|
176
|
+
function: { name: "readFile", arguments: JSON.stringify({ input: "path/to/file" }) },
|
|
177
|
+
},
|
|
178
|
+
"readFile"
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
expect(mockFn).toHaveBeenCalled();
|
|
182
|
+
expect(result).toBe("file contents");
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("callTool() dispatches an extended tool called directly by its real name (not via the callTool meta-tool wrapper)", async () => {
|
|
186
|
+
// This covers the case where an agent calls a registered tool directly
|
|
187
|
+
// (toolCall.function.name === "execCommand") instead of routing through
|
|
188
|
+
// the callTool({name, args}) meta-tool. Previously this threw
|
|
189
|
+
// "Tool execCommand not found" because extended tools only live in
|
|
190
|
+
// allToolsCatalog, not in the frozen visible `tools` array that the base
|
|
191
|
+
// ToolsService.getTool() searches.
|
|
192
|
+
const mockFn = jest.fn().mockResolvedValue("command output");
|
|
193
|
+
service.addTools([makeTool("execCommand")]);
|
|
194
|
+
service.addFunctions({ execCommand: mockFn });
|
|
195
|
+
|
|
196
|
+
const result = await service.callTool({
|
|
197
|
+
id: "tc_direct_exec",
|
|
198
|
+
type: "function",
|
|
199
|
+
function: {
|
|
200
|
+
name: "execCommand",
|
|
201
|
+
arguments: JSON.stringify({ input: "ls" }),
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(mockFn).toHaveBeenCalled();
|
|
206
|
+
expect(result.functionResp).toBe("command output");
|
|
207
|
+
expect(result.toolMessages[0].content).toBe("command output");
|
|
208
|
+
|
|
209
|
+
// Visible tools array must remain stable (cache-stable guarantee) after
|
|
210
|
+
// dispatching a direct call to an extended tool.
|
|
211
|
+
const visibleNames = service.getTools().map((t) => t.function.name);
|
|
212
|
+
expect(visibleNames).not.toContain("execCommand");
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("callTool() returns a standard error for an unknown tool name", async () => {
|
|
216
|
+
const result = await service.callTool({
|
|
217
|
+
id: "tc_unknown",
|
|
218
|
+
type: "function",
|
|
219
|
+
function: { name: "totallyUnknownTool", arguments: JSON.stringify({}) },
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(result.toolMessages[0].name).toBe("error");
|
|
223
|
+
expect(result.toolMessages[0].content).toMatch(/not found|not enabled/);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ── MinimalToolsMessageProcessor ─────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
describe("MinimalToolsMessageProcessor", () => {
|
|
230
|
+
let processor: MinimalToolsMessageProcessor;
|
|
231
|
+
let processorFn: ReturnType<MinimalToolsMessageProcessor["createProcessor"]>;
|
|
232
|
+
|
|
233
|
+
beforeEach(() => {
|
|
234
|
+
processor = new MinimalToolsMessageProcessor();
|
|
235
|
+
processorFn = processor.createProcessor();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
function applyProcessor(messages: Message[]): Message[] {
|
|
239
|
+
const original = JSON.parse(JSON.stringify(messages)) as Message[];
|
|
240
|
+
const modified = JSON.parse(JSON.stringify(messages)) as Message[];
|
|
241
|
+
processorFn(original, modified);
|
|
242
|
+
return modified;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
test("unwraps callTool(name, args) to the real tool call", () => {
|
|
246
|
+
const messages: Message[] = [
|
|
247
|
+
makeCallToolMessage("readFile", { filePath: "foo.ts" }, "tc_1"),
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const result = applyProcessor(messages);
|
|
251
|
+
const lastMsg = result[result.length - 1];
|
|
252
|
+
|
|
253
|
+
expect(lastMsg.tool_calls).toHaveLength(1);
|
|
254
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("readFile");
|
|
255
|
+
expect(JSON.parse(lastMsg.tool_calls[0].function.arguments)).toEqual({
|
|
256
|
+
filePath: "foo.ts",
|
|
257
|
+
});
|
|
258
|
+
// ID preserved
|
|
259
|
+
expect(lastMsg.tool_calls[0].id).toBe("tc_1");
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("unwraps callTool('finalAnswer', ...) so agent terminates", () => {
|
|
263
|
+
const messages: Message[] = [
|
|
264
|
+
makeCallToolMessage("finalAnswer", { answer: "All done!" }, "tc_fa"),
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
const result = applyProcessor(messages);
|
|
268
|
+
const lastMsg = result[result.length - 1];
|
|
269
|
+
|
|
270
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("finalAnswer");
|
|
271
|
+
expect(JSON.parse(lastMsg.tool_calls[0].function.arguments)).toEqual({
|
|
272
|
+
answer: "All done!",
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("unwraps callTool('testTool123', ...) so custom required tool is satisfied", () => {
|
|
277
|
+
const messages: Message[] = [
|
|
278
|
+
makeCallToolMessage("testTool123", { data: "value" }, "tc_custom"),
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
const result = applyProcessor(messages);
|
|
282
|
+
const lastMsg = result[result.length - 1];
|
|
283
|
+
|
|
284
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("testTool123");
|
|
285
|
+
expect(JSON.parse(lastMsg.tool_calls[0].function.arguments)).toEqual({
|
|
286
|
+
data: "value",
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("unwraps multiple callTool entries in a single assistant message", () => {
|
|
291
|
+
const messages: Message[] = [
|
|
292
|
+
{
|
|
293
|
+
role: "assistant",
|
|
294
|
+
content: null,
|
|
295
|
+
tool_calls: [
|
|
296
|
+
{
|
|
297
|
+
id: "tc_a",
|
|
298
|
+
type: "function",
|
|
299
|
+
function: {
|
|
300
|
+
name: "callTool",
|
|
301
|
+
arguments: JSON.stringify({ name: "readFile", args: { filePath: "a.ts" } }),
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "tc_b",
|
|
306
|
+
type: "function",
|
|
307
|
+
function: {
|
|
308
|
+
name: "callTool",
|
|
309
|
+
arguments: JSON.stringify({ name: "writeFile", args: { filePath: "b.ts", content: "hi" } }),
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
} as unknown as Message,
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
const result = applyProcessor(messages);
|
|
317
|
+
const lastMsg = result[result.length - 1];
|
|
318
|
+
|
|
319
|
+
expect(lastMsg.tool_calls).toHaveLength(2);
|
|
320
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("readFile");
|
|
321
|
+
expect(lastMsg.tool_calls[1].function.name).toBe("writeFile");
|
|
322
|
+
expect(JSON.parse(lastMsg.tool_calls[0].function.arguments)).toEqual({ filePath: "a.ts" });
|
|
323
|
+
expect(JSON.parse(lastMsg.tool_calls[1].function.arguments)).toEqual({
|
|
324
|
+
filePath: "b.ts",
|
|
325
|
+
content: "hi",
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("does NOT rewrite older assistant messages (only the last one)", () => {
|
|
330
|
+
const messages: Message[] = [
|
|
331
|
+
// older assistant message with callTool — should NOT be rewritten
|
|
332
|
+
makeCallToolMessage("oldTool", { x: 1 }, "tc_old"),
|
|
333
|
+
// tool result for that call
|
|
334
|
+
{
|
|
335
|
+
role: "tool",
|
|
336
|
+
tool_call_id: "tc_old",
|
|
337
|
+
name: "callTool",
|
|
338
|
+
content: "some result",
|
|
339
|
+
} as unknown as Message,
|
|
340
|
+
// newest assistant message — should be rewritten
|
|
341
|
+
makeCallToolMessage("finalAnswer", { answer: "done" }, "tc_new"),
|
|
342
|
+
];
|
|
343
|
+
|
|
344
|
+
const result = applyProcessor(messages);
|
|
345
|
+
|
|
346
|
+
// First assistant message should be unchanged (still "callTool")
|
|
347
|
+
const firstAssistant = result[0];
|
|
348
|
+
expect(firstAssistant.tool_calls[0].function.name).toBe("callTool");
|
|
349
|
+
|
|
350
|
+
// Last assistant message should be rewritten
|
|
351
|
+
const lastAssistant = result[result.length - 1];
|
|
352
|
+
expect(lastAssistant.tool_calls[0].function.name).toBe("finalAnswer");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
test("leaves non-callTool tool_calls unchanged", () => {
|
|
356
|
+
const messages: Message[] = [
|
|
357
|
+
{
|
|
358
|
+
role: "assistant",
|
|
359
|
+
content: null,
|
|
360
|
+
tool_calls: [
|
|
361
|
+
{
|
|
362
|
+
id: "tc_direct",
|
|
363
|
+
type: "function",
|
|
364
|
+
function: {
|
|
365
|
+
name: "inspectTools",
|
|
366
|
+
arguments: JSON.stringify({ patterns: ["*"] }),
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
} as unknown as Message,
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
const result = applyProcessor(messages);
|
|
374
|
+
const lastMsg = result[result.length - 1];
|
|
375
|
+
|
|
376
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("inspectTools");
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test("skips messages with no tool_calls", () => {
|
|
380
|
+
const messages: Message[] = [
|
|
381
|
+
{ role: "user", content: "hello" } as Message,
|
|
382
|
+
{ role: "assistant", content: "I will help" } as Message,
|
|
383
|
+
];
|
|
384
|
+
|
|
385
|
+
const result = applyProcessor(messages);
|
|
386
|
+
expect(result[1].tool_calls).toBeUndefined();
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test("mixed: callTool entries alongside direct tool calls are selectively unwrapped", () => {
|
|
390
|
+
const messages: Message[] = [
|
|
391
|
+
{
|
|
392
|
+
role: "assistant",
|
|
393
|
+
content: null,
|
|
394
|
+
tool_calls: [
|
|
395
|
+
{
|
|
396
|
+
id: "tc_direct",
|
|
397
|
+
type: "function",
|
|
398
|
+
function: {
|
|
399
|
+
name: "inspectTools",
|
|
400
|
+
arguments: JSON.stringify({}),
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
id: "tc_wrapped",
|
|
405
|
+
type: "function",
|
|
406
|
+
function: {
|
|
407
|
+
name: "callTool",
|
|
408
|
+
arguments: JSON.stringify({ name: "readFile", args: { filePath: "x.ts" } }),
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
} as unknown as Message,
|
|
413
|
+
];
|
|
414
|
+
|
|
415
|
+
const result = applyProcessor(messages);
|
|
416
|
+
const lastMsg = result[result.length - 1];
|
|
417
|
+
|
|
418
|
+
expect(lastMsg.tool_calls).toHaveLength(2);
|
|
419
|
+
// Direct call preserved
|
|
420
|
+
expect(lastMsg.tool_calls[0].function.name).toBe("inspectTools");
|
|
421
|
+
// Wrapped call unwrapped
|
|
422
|
+
expect(lastMsg.tool_calls[1].function.name).toBe("readFile");
|
|
423
|
+
});
|
|
424
|
+
});
|
package/ts_build/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tyvm/knowhow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.130",
|
|
4
4
|
"description": "ai cli with plugins and agents",
|
|
5
5
|
"main": "ts_build/src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"lint:deps": "depcheck --config=.depcheckrc",
|
|
23
23
|
"lint:all": "npm run lint && npm run lint:deps",
|
|
24
24
|
"check:model-pricing": "ts-node scripts/check-model-pricing.ts",
|
|
25
|
+
"debug:cache-miss": "ts-node scripts/cache-miss-debug.ts",
|
|
25
26
|
"version:nightly": "bash scripts/publish.sh version:nightly",
|
|
26
27
|
"version:dev": "bash scripts/publish.sh version:dev",
|
|
27
28
|
"publish:nightly": "bash scripts/publish.sh nightly",
|
|
@@ -34,7 +35,6 @@
|
|
|
34
35
|
"license": "MIT",
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@babel/preset-typescript": "^7.23.3",
|
|
37
|
-
"typescript": "^5.0.0",
|
|
38
38
|
"@types/diff": "^5.2.1",
|
|
39
39
|
"@types/express": "^4.17.13",
|
|
40
40
|
"@types/jest": "^29.5.13",
|
|
@@ -46,10 +46,11 @@
|
|
|
46
46
|
"ts-jest": "^29.1.1",
|
|
47
47
|
"ts-node": "^10.7.0",
|
|
48
48
|
"tslint": "^6.1.3",
|
|
49
|
-
"tslint-config-prettier": "^1.18.0"
|
|
49
|
+
"tslint-config-prettier": "^1.18.0",
|
|
50
|
+
"typescript": "^5.0.0"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"@anthropic-ai/sdk": "^0.
|
|
53
|
+
"@anthropic-ai/sdk": "^0.111.0",
|
|
53
54
|
"@google/genai": "^0.14.1",
|
|
54
55
|
"@inquirer/editor": "^4.2.18",
|
|
55
56
|
"@modelcontextprotocol/sdk": "^1.13.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CompletionResponse, GenericClient, Message, MessageContent, Tool, ToolCall } from "../../clients/types";
|
|
1
|
+
import { CompletionResponse, CompletionOptions, GenericClient, Message, MessageContent, Tool, ToolCall } from "../../clients/types";
|
|
2
2
|
import { IAgent } from "../interface";
|
|
3
3
|
import { ToolsService } from "../../services/Tools";
|
|
4
4
|
import { EventService } from "../../services/EventService";
|
|
@@ -8,6 +8,8 @@ export { Message, Tool, ToolCall };
|
|
|
8
8
|
export interface ModelPreference {
|
|
9
9
|
model: string;
|
|
10
10
|
provider: keyof typeof Clients.clients;
|
|
11
|
+
reasoning_effort?: CompletionOptions["reasoning_effort"];
|
|
12
|
+
reasoning_summary?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export interface AgentContext {
|
|
13
15
|
Tools?: ToolsService;
|
|
@@ -38,6 +40,8 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
38
40
|
protected turnCount: number;
|
|
39
41
|
protected totalCostUsd: number;
|
|
40
42
|
protected currentThread: number;
|
|
43
|
+
protected reasoningEffort: CompletionOptions["reasoning_effort"] | undefined;
|
|
44
|
+
protected summarizeReasoning: boolean | undefined;
|
|
41
45
|
protected totalInputTokens: number;
|
|
42
46
|
protected totalOutputTokens: number;
|
|
43
47
|
protected totalCacheReadTokens: number;
|
|
@@ -94,6 +98,10 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
94
98
|
setModel(value: string): void;
|
|
95
99
|
getProvider(): string;
|
|
96
100
|
setProvider(value: keyof typeof Clients.clients): void;
|
|
101
|
+
setReasoningEffort(effort: CompletionOptions["reasoning_effort"]): void;
|
|
102
|
+
getReasoningEffort(): CompletionOptions["reasoning_effort"];
|
|
103
|
+
setSummarizeReasoning(value: boolean): void;
|
|
104
|
+
getSummarizeReasoning(): boolean | undefined;
|
|
97
105
|
getClient(): GenericClient;
|
|
98
106
|
setClient(client: GenericClient): void;
|
|
99
107
|
setEasyFinalAnswer(value: boolean): void;
|
|
@@ -109,13 +117,16 @@ export declare abstract class BaseAgent implements IAgent {
|
|
|
109
117
|
getTurnCount(): number;
|
|
110
118
|
adjustTotalCostUsd(cost: number): void;
|
|
111
119
|
getTotalCostUsd(): number;
|
|
112
|
-
adjustTokenUsage(usage: any): void;
|
|
120
|
+
adjustTokenUsage(usage: any, messages?: Message[]): void;
|
|
113
121
|
getTokenUsage(): {
|
|
114
122
|
totalInputTokens: number;
|
|
115
123
|
totalOutputTokens: number;
|
|
116
124
|
totalCacheReadTokens: number;
|
|
117
125
|
totalCacheWriteTokens: number;
|
|
118
126
|
};
|
|
127
|
+
protected createAgentCompletion(params: Parameters<GenericClient["createChatCompletion"]>[0], options?: {
|
|
128
|
+
interruptValue?: CompletionResponse;
|
|
129
|
+
}): Promise<CompletionResponse>;
|
|
119
130
|
startNewThread(messages: Message[]): void;
|
|
120
131
|
updateCurrentThread(messages: Message[]): void;
|
|
121
132
|
getThreads(): Message[][];
|