kernl 0.1.1
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +53 -0
- package/LICENSE +201 -0
- package/dist/agent.d.ts +43 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +130 -0
- package/dist/context.d.ts +70 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +111 -0
- package/dist/env.d.ts +45 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +31 -0
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +1 -0
- package/dist/guardrail.d.ts +178 -0
- package/dist/guardrail.d.ts.map +1 -0
- package/dist/guardrail.js +34 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/kernel.d.ts +7 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +7 -0
- package/dist/kernl.d.ts +18 -0
- package/dist/kernl.d.ts.map +1 -0
- package/dist/kernl.js +16 -0
- package/dist/lib/env.d.ts +43 -0
- package/dist/lib/env.d.ts.map +1 -0
- package/dist/lib/env.js +29 -0
- package/dist/lib/error.d.ts +88 -0
- package/dist/lib/error.d.ts.map +1 -0
- package/dist/lib/error.js +117 -0
- package/dist/lib/logger.d.ts +36 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +43 -0
- package/dist/lib/serde/__tests__/codec.test.d.ts +2 -0
- package/dist/lib/serde/__tests__/codec.test.d.ts.map +1 -0
- package/dist/lib/serde/__tests__/codec.test.js +75 -0
- package/dist/lib/serde/codec.d.ts +12 -0
- package/dist/lib/serde/codec.d.ts.map +1 -0
- package/dist/lib/serde/codec.js +54 -0
- package/dist/lib/serde/json.d.ts +8 -0
- package/dist/lib/serde/json.d.ts.map +1 -0
- package/dist/lib/serde/json.js +13 -0
- package/dist/lib/serde/thread.d.ts +1 -0
- package/dist/lib/serde/thread.d.ts.map +1 -0
- package/dist/lib/serde/thread.js +172 -0
- package/dist/lib/serde/tool.d.ts +36 -0
- package/dist/lib/serde/tool.d.ts.map +1 -0
- package/dist/lib/serde/tool.js +1 -0
- package/dist/lib/utils.d.ts +19 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +41 -0
- package/dist/lifecycle.d.ts +133 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +29 -0
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +43 -0
- package/dist/mcp/__tests__/base.test.d.ts +2 -0
- package/dist/mcp/__tests__/base.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/base.test.js +268 -0
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts +3 -0
- package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +1 -0
- package/dist/mcp/__tests__/fixtures/echo-server.js +92 -0
- package/dist/mcp/__tests__/fixtures/math-server.d.ts +3 -0
- package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +1 -0
- package/dist/mcp/__tests__/fixtures/math-server.js +98 -0
- package/dist/mcp/__tests__/fixtures/server.d.ts +3 -0
- package/dist/mcp/__tests__/fixtures/server.d.ts.map +1 -0
- package/dist/mcp/__tests__/fixtures/server.js +162 -0
- package/dist/mcp/__tests__/fixtures/test-server.d.ts +3 -0
- package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +1 -0
- package/dist/mcp/__tests__/fixtures/test-server.js +163 -0
- package/dist/mcp/__tests__/fixtures/utils.d.ts +17 -0
- package/dist/mcp/__tests__/fixtures/utils.d.ts.map +1 -0
- package/dist/mcp/__tests__/fixtures/utils.js +42 -0
- package/dist/mcp/__tests__/integration.test.d.ts +2 -0
- package/dist/mcp/__tests__/integration.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/integration.test.js +360 -0
- package/dist/mcp/__tests__/stdio.test.d.ts +2 -0
- package/dist/mcp/__tests__/stdio.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/stdio.test.js +180 -0
- package/dist/mcp/__tests__/test-utils.d.ts +17 -0
- package/dist/mcp/__tests__/test-utils.d.ts.map +1 -0
- package/dist/mcp/__tests__/test-utils.js +42 -0
- package/dist/mcp/__tests__/utils.test.d.ts +2 -0
- package/dist/mcp/__tests__/utils.test.d.ts.map +1 -0
- package/dist/mcp/__tests__/utils.test.js +300 -0
- package/dist/mcp/base.d.ts +88 -0
- package/dist/mcp/base.d.ts.map +1 -0
- package/dist/mcp/base.js +68 -0
- package/dist/mcp/http.d.ts +34 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/http.js +100 -0
- package/dist/mcp/node.d.ts +60 -0
- package/dist/mcp/node.d.ts.map +1 -0
- package/dist/mcp/node.js +297 -0
- package/dist/mcp/sse.d.ts +34 -0
- package/dist/mcp/sse.d.ts.map +1 -0
- package/dist/mcp/sse.js +97 -0
- package/dist/mcp/stdio.d.ts +32 -0
- package/dist/mcp/stdio.d.ts.map +1 -0
- package/dist/mcp/stdio.js +96 -0
- package/dist/mcp/types.d.ts +172 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +16 -0
- package/dist/mcp/utils.d.ts +23 -0
- package/dist/mcp/utils.d.ts.map +1 -0
- package/dist/mcp/utils.js +44 -0
- package/dist/model.d.ts +175 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +1 -0
- package/dist/providers/ai.d.ts +1 -0
- package/dist/providers/ai.d.ts.map +1 -0
- package/dist/providers/ai.js +1 -0
- package/dist/providers/default.d.ts +16 -0
- package/dist/providers/default.d.ts.map +1 -0
- package/dist/providers/default.js +17 -0
- package/dist/providers/registry.d.ts +1 -0
- package/dist/providers/registry.d.ts.map +1 -0
- package/dist/providers/registry.js +1 -0
- package/dist/sched/scheduler.d.ts +20 -0
- package/dist/sched/scheduler.d.ts.map +1 -0
- package/dist/sched/scheduler.js +1 -0
- package/dist/sched/task.d.ts +92 -0
- package/dist/sched/task.d.ts.map +1 -0
- package/dist/sched/task.js +102 -0
- package/dist/serde/__tests__/codec.test.d.ts +2 -0
- package/dist/serde/__tests__/codec.test.d.ts.map +1 -0
- package/dist/serde/__tests__/codec.test.js +75 -0
- package/dist/serde/codec.d.ts +12 -0
- package/dist/serde/codec.d.ts.map +1 -0
- package/dist/serde/codec.js +54 -0
- package/dist/serde/json.d.ts +8 -0
- package/dist/serde/json.d.ts.map +1 -0
- package/dist/serde/json.js +13 -0
- package/dist/serde/thread.d.ts +687 -0
- package/dist/serde/thread.d.ts.map +1 -0
- package/dist/serde/thread.js +158 -0
- package/dist/serde/tool.d.ts +36 -0
- package/dist/serde/tool.d.ts.map +1 -0
- package/dist/serde/tool.js +1 -0
- package/dist/session.d.ts +1 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +1 -0
- package/dist/task.d.ts +87 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +97 -0
- package/dist/thread/__tests__/mock.d.ts +28 -0
- package/dist/thread/__tests__/mock.d.ts.map +1 -0
- package/dist/thread/__tests__/mock.js +74 -0
- package/dist/thread/__tests__/thread.test.d.ts +2 -0
- package/dist/thread/__tests__/thread.test.d.ts.map +1 -0
- package/dist/thread/__tests__/thread.test.js +1412 -0
- package/dist/thread/index.d.ts +2 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/thread/index.js +1 -0
- package/dist/thread/thread.d.ts +66 -0
- package/dist/thread/thread.d.ts.map +1 -0
- package/dist/thread/thread.js +472 -0
- package/dist/thread/utils.d.ts +19 -0
- package/dist/thread/utils.d.ts.map +1 -0
- package/dist/thread/utils.js +50 -0
- package/dist/tool/__tests__/fixtures.d.ts +45 -0
- package/dist/tool/__tests__/fixtures.d.ts.map +1 -0
- package/dist/tool/__tests__/fixtures.js +97 -0
- package/dist/tool/__tests__/tool.test.d.ts +2 -0
- package/dist/tool/__tests__/tool.test.d.ts.map +1 -0
- package/dist/tool/__tests__/tool.test.js +172 -0
- package/dist/tool/__tests__/toolkit.test.d.ts +2 -0
- package/dist/tool/__tests__/toolkit.test.d.ts.map +1 -0
- package/dist/tool/__tests__/toolkit.test.js +134 -0
- package/dist/tool/index.d.ts +4 -0
- package/dist/tool/index.d.ts.map +1 -0
- package/dist/tool/index.js +2 -0
- package/dist/tool/mcp.d.ts +75 -0
- package/dist/tool/mcp.d.ts.map +1 -0
- package/dist/tool/mcp.js +111 -0
- package/dist/tool/tool.d.ts +95 -0
- package/dist/tool/tool.d.ts.map +1 -0
- package/dist/tool/tool.js +176 -0
- package/dist/tool/toolkit.d.ts +121 -0
- package/dist/tool/toolkit.d.ts.map +1 -0
- package/dist/tool/toolkit.js +180 -0
- package/dist/tool/types.d.ts +187 -0
- package/dist/tool/types.d.ts.map +1 -0
- package/dist/tool/types.js +1 -0
- package/dist/tools.d.ts +362 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +220 -0
- package/dist/trace/processor.d.ts +1 -0
- package/dist/trace/processor.d.ts.map +1 -0
- package/dist/trace/processor.js +1 -0
- package/dist/trace/traces.d.ts +1 -0
- package/dist/trace/traces.d.ts.map +1 -0
- package/dist/trace/traces.js +73 -0
- package/dist/trace/utils.d.ts +22 -0
- package/dist/trace/utils.d.ts.map +1 -0
- package/dist/trace/utils.js +30 -0
- package/dist/types/agent.d.ts +91 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +1 -0
- package/dist/types/proto.d.ts +1551 -0
- package/dist/types/proto.d.ts.map +1 -0
- package/dist/types/proto.js +531 -0
- package/dist/types/thread.d.ts +71 -0
- package/dist/types/thread.d.ts.map +1 -0
- package/dist/types/thread.js +5 -0
- package/dist/usage.d.ts +43 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +61 -0
- package/package.json +52 -0
- package/src/agent.ts +203 -0
- package/src/context.ts +265 -0
- package/src/guardrail.ts +277 -0
- package/src/index.ts +3 -0
- package/src/kernl.ts +22 -0
- package/src/lib/env.ts +36 -0
- package/src/lib/error.ts +158 -0
- package/src/lib/logger.ts +78 -0
- package/src/lib/serde/json.ts +18 -0
- package/src/lib/serde/thread.ts +188 -0
- package/src/lifecycle.ts +181 -0
- package/src/mcp/__tests__/base.test.ts +344 -0
- package/src/mcp/__tests__/fixtures/server.ts +179 -0
- package/src/mcp/__tests__/fixtures/utils.ts +58 -0
- package/src/mcp/__tests__/integration.test.ts +447 -0
- package/src/mcp/__tests__/stdio.test.ts +236 -0
- package/src/mcp/__tests__/utils.test.ts +360 -0
- package/src/mcp/base.ts +162 -0
- package/src/mcp/http.ts +147 -0
- package/src/mcp/sse.ts +137 -0
- package/src/mcp/stdio.ts +136 -0
- package/src/mcp/types.ts +202 -0
- package/src/mcp/utils.ts +62 -0
- package/src/task.ts +119 -0
- package/src/thread/__tests__/mock.ts +95 -0
- package/src/thread/__tests__/thread.test.ts +1574 -0
- package/src/thread/index.ts +1 -0
- package/src/thread/thread.ts +611 -0
- package/src/thread/utils.ts +67 -0
- package/src/tool/__tests__/fixtures.ts +106 -0
- package/src/tool/__tests__/tool.test.ts +235 -0
- package/src/tool/__tests__/toolkit.test.ts +174 -0
- package/src/tool/index.ts +10 -0
- package/src/tool/tool.ts +264 -0
- package/src/tool/toolkit.ts +234 -0
- package/src/tool/types.ts +243 -0
- package/src/trace/processor.ts +0 -0
- package/src/trace/traces.ts +86 -0
- package/src/trace/utils.ts +38 -0
- package/src/types/agent.ts +145 -0
- package/src/types/thread.ts +86 -0
- package/tsconfig.json +13 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Context } from "@/context";
|
|
3
|
+
import { tool, HostedTool } from "../tool";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Create a minimal mock context for testing
|
|
7
|
+
*/
|
|
8
|
+
export const mockContext = <T = any>(data?: T): Context<T> => {
|
|
9
|
+
return new Context<T>(data ?? ({} as T));
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Simple string tool with no parameters
|
|
14
|
+
*/
|
|
15
|
+
export const simpleStringTool = tool({
|
|
16
|
+
id: "simple",
|
|
17
|
+
description: "A simple tool that echoes input",
|
|
18
|
+
parameters: undefined,
|
|
19
|
+
execute: async (ctx, input: string) => `Echo: ${input}`,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Tool with Zod schema validation
|
|
24
|
+
*/
|
|
25
|
+
export const zodTool = tool({
|
|
26
|
+
id: "zod-tool",
|
|
27
|
+
description: "Tool with Zod schema",
|
|
28
|
+
parameters: z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
age: z.number(),
|
|
31
|
+
}),
|
|
32
|
+
execute: async (ctx, { name, age }) => `${name} is ${age} years old`,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool that always throws an error
|
|
37
|
+
*/
|
|
38
|
+
export const errorTool = tool({
|
|
39
|
+
id: "error-tool",
|
|
40
|
+
description: "Always throws an error",
|
|
41
|
+
parameters: undefined,
|
|
42
|
+
execute: async () => {
|
|
43
|
+
throw new Error("Tool execution failed");
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tool with custom error handler
|
|
49
|
+
*/
|
|
50
|
+
export const customErrorTool = tool({
|
|
51
|
+
id: "custom-error-tool",
|
|
52
|
+
description: "Tool with custom error handler",
|
|
53
|
+
parameters: undefined,
|
|
54
|
+
execute: async () => {
|
|
55
|
+
throw new Error("Original error");
|
|
56
|
+
},
|
|
57
|
+
errorfn: (ctx, error) => {
|
|
58
|
+
return "Custom error message";
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tool requiring approval (boolean)
|
|
64
|
+
*/
|
|
65
|
+
export const approvalRequiredTool = tool({
|
|
66
|
+
id: "approval-tool",
|
|
67
|
+
description: "Requires approval",
|
|
68
|
+
parameters: undefined,
|
|
69
|
+
requiresApproval: true,
|
|
70
|
+
execute: async () => "executed",
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Tool with conditional approval (function)
|
|
75
|
+
*/
|
|
76
|
+
export const conditionalApprovalTool = tool({
|
|
77
|
+
id: "conditional-approval",
|
|
78
|
+
description: "Conditionally requires approval",
|
|
79
|
+
parameters: z.object({
|
|
80
|
+
dangerous: z.boolean(),
|
|
81
|
+
}),
|
|
82
|
+
requiresApproval: async (ctx, input) => {
|
|
83
|
+
return input.dangerous === true;
|
|
84
|
+
},
|
|
85
|
+
execute: async (ctx, { dangerous }) => `Executed with dangerous=${dangerous}`,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Mock hosted tool (e.g., web_search)
|
|
90
|
+
*/
|
|
91
|
+
export const mockHostedTool = new HostedTool({
|
|
92
|
+
id: "web-search",
|
|
93
|
+
name: "Web Search",
|
|
94
|
+
providerData: {
|
|
95
|
+
provider: "anthropic",
|
|
96
|
+
capabilities: ["search", "fetch"],
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Another mock hosted tool
|
|
102
|
+
*/
|
|
103
|
+
export const anotherHostedTool = new HostedTool({
|
|
104
|
+
id: "file-search",
|
|
105
|
+
name: "File Search",
|
|
106
|
+
});
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { COMPLETED, FAILED } from "@kernl/protocol";
|
|
3
|
+
import { tool } from "../tool";
|
|
4
|
+
import {
|
|
5
|
+
mockContext,
|
|
6
|
+
simpleStringTool,
|
|
7
|
+
zodTool,
|
|
8
|
+
errorTool,
|
|
9
|
+
customErrorTool,
|
|
10
|
+
approvalRequiredTool,
|
|
11
|
+
conditionalApprovalTool,
|
|
12
|
+
} from "./fixtures";
|
|
13
|
+
|
|
14
|
+
describe("FunctionTool", () => {
|
|
15
|
+
describe("creation", () => {
|
|
16
|
+
it("should create tool with required config", () => {
|
|
17
|
+
const t = tool({
|
|
18
|
+
id: "test",
|
|
19
|
+
description: "Test tool",
|
|
20
|
+
parameters: undefined,
|
|
21
|
+
execute: async () => "result",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(t.id).toBe("test");
|
|
25
|
+
expect(t.description).toBe("Test tool");
|
|
26
|
+
expect(t.type).toBe("function");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should default mode to blocking", () => {
|
|
30
|
+
const t = tool({
|
|
31
|
+
id: "test",
|
|
32
|
+
description: "Test tool",
|
|
33
|
+
parameters: undefined,
|
|
34
|
+
execute: async () => "result",
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
expect(t.mode).toBe("blocking");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should accept custom mode", () => {
|
|
41
|
+
const t = tool({
|
|
42
|
+
id: "test",
|
|
43
|
+
description: "Test tool",
|
|
44
|
+
parameters: undefined,
|
|
45
|
+
mode: "async",
|
|
46
|
+
execute: async () => "result",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(t.mode).toBe("async");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("parameter parsing", () => {
|
|
54
|
+
it("should handle string input when no parameters schema", async () => {
|
|
55
|
+
const ctx = mockContext();
|
|
56
|
+
const result = await simpleStringTool.invoke(ctx, "hello");
|
|
57
|
+
|
|
58
|
+
expect(result.state).toBe(COMPLETED);
|
|
59
|
+
expect(result.result).toBe("Echo: hello");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should parse and validate with Zod schema", async () => {
|
|
63
|
+
const ctx = mockContext();
|
|
64
|
+
const input = JSON.stringify({ name: "Alice", age: 30 });
|
|
65
|
+
const result = await zodTool.invoke(ctx, input);
|
|
66
|
+
|
|
67
|
+
expect(result.state).toBe(COMPLETED);
|
|
68
|
+
expect(result.result).toBe("Alice is 30 years old");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should throw ModelBehaviorError on invalid JSON", async () => {
|
|
72
|
+
const ctx = mockContext();
|
|
73
|
+
const result = await zodTool.invoke(ctx, "not valid json");
|
|
74
|
+
|
|
75
|
+
expect(result.state).toBe(FAILED);
|
|
76
|
+
expect(result.error).toContain("Invalid JSON input for tool");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should throw ModelBehaviorError on schema validation failure", async () => {
|
|
80
|
+
const ctx = mockContext();
|
|
81
|
+
const input = JSON.stringify({ name: "Alice", age: "not a number" });
|
|
82
|
+
const result = await zodTool.invoke(ctx, input);
|
|
83
|
+
|
|
84
|
+
expect(result.state).toBe(FAILED);
|
|
85
|
+
expect(result.error).toContain("Invalid JSON input for tool");
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("execution", () => {
|
|
90
|
+
it("should execute and return ToolResult with completed status", async () => {
|
|
91
|
+
const ctx = mockContext();
|
|
92
|
+
const result = await simpleStringTool.invoke(ctx, "test");
|
|
93
|
+
|
|
94
|
+
expect(result.state).toBe(COMPLETED);
|
|
95
|
+
expect(result.result).toBe("Echo: test");
|
|
96
|
+
expect(result.error).toBe(null);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should handle sync execute functions", async () => {
|
|
100
|
+
const syncTool = tool({
|
|
101
|
+
id: "sync",
|
|
102
|
+
description: "Sync tool",
|
|
103
|
+
parameters: undefined,
|
|
104
|
+
execute: (ctx, input: string) => `Sync: ${input}`,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const ctx = mockContext();
|
|
108
|
+
const result = await syncTool.invoke(ctx, "test");
|
|
109
|
+
|
|
110
|
+
expect(result.state).toBe(COMPLETED);
|
|
111
|
+
expect(result.result).toBe("Sync: test");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should handle async execute functions", async () => {
|
|
115
|
+
const asyncTool = tool({
|
|
116
|
+
id: "async",
|
|
117
|
+
description: "Async tool",
|
|
118
|
+
parameters: undefined,
|
|
119
|
+
execute: async (ctx, input: string) =>
|
|
120
|
+
Promise.resolve(`Async: ${input}`),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const ctx = mockContext();
|
|
124
|
+
const result = await asyncTool.invoke(ctx, "test");
|
|
125
|
+
|
|
126
|
+
expect(result.state).toBe(COMPLETED);
|
|
127
|
+
expect(result.result).toBe("Async: test");
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("error handling", () => {
|
|
132
|
+
it("should return error ToolResult when execute throws", async () => {
|
|
133
|
+
const ctx = mockContext();
|
|
134
|
+
const result = await errorTool.invoke(ctx, "test");
|
|
135
|
+
|
|
136
|
+
expect(result.state).toBe(FAILED);
|
|
137
|
+
expect(result.result).toBe(undefined);
|
|
138
|
+
expect(result.error).toContain("Tool execution failed");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should use custom error function if provided", async () => {
|
|
142
|
+
const ctx = mockContext();
|
|
143
|
+
const result = await customErrorTool.invoke(ctx, "test");
|
|
144
|
+
|
|
145
|
+
expect(result.state).toBe(FAILED);
|
|
146
|
+
expect(result.error).toBe("Custom error message");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("should use default error function if none provided", async () => {
|
|
150
|
+
const ctx = mockContext();
|
|
151
|
+
const result = await errorTool.invoke(ctx, "test");
|
|
152
|
+
|
|
153
|
+
expect(result.state).toBe(FAILED);
|
|
154
|
+
expect(result.error).toContain(
|
|
155
|
+
"An error occurred while running the tool",
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe("approval", () => {
|
|
161
|
+
it("should evaluate boolean requiresApproval", async () => {
|
|
162
|
+
const ctx = mockContext();
|
|
163
|
+
const requiresApproval = await approvalRequiredTool.requiresApproval(
|
|
164
|
+
ctx,
|
|
165
|
+
"test",
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(requiresApproval).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should evaluate function requiresApproval", async () => {
|
|
172
|
+
const ctx = mockContext();
|
|
173
|
+
|
|
174
|
+
const dangerous = await conditionalApprovalTool.requiresApproval(ctx, {
|
|
175
|
+
dangerous: true,
|
|
176
|
+
});
|
|
177
|
+
const safe = await conditionalApprovalTool.requiresApproval(ctx, {
|
|
178
|
+
dangerous: false,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
expect(dangerous).toBe(true);
|
|
182
|
+
expect(safe).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("isEnabled", () => {
|
|
187
|
+
it("should default to enabled", async () => {
|
|
188
|
+
const ctx = mockContext();
|
|
189
|
+
const enabled = await simpleStringTool.isEnabled(ctx, null as any);
|
|
190
|
+
|
|
191
|
+
expect(enabled).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should evaluate boolean isEnabled", async () => {
|
|
195
|
+
const disabledTool = tool({
|
|
196
|
+
id: "disabled",
|
|
197
|
+
description: "Disabled tool",
|
|
198
|
+
parameters: undefined,
|
|
199
|
+
isEnabled: false,
|
|
200
|
+
execute: async () => "result",
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const ctx = mockContext();
|
|
204
|
+
const enabled = await disabledTool.isEnabled(ctx, null as any);
|
|
205
|
+
|
|
206
|
+
expect(enabled).toBe(false);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should evaluate function isEnabled with typed context", async () => {
|
|
210
|
+
interface MyContext {
|
|
211
|
+
enabled: boolean;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const conditionalTool = tool<MyContext>({
|
|
215
|
+
id: "conditional",
|
|
216
|
+
description: "Conditional tool",
|
|
217
|
+
parameters: undefined,
|
|
218
|
+
isEnabled: ({ context }) => {
|
|
219
|
+
return context.context.enabled === true;
|
|
220
|
+
},
|
|
221
|
+
execute: async () => "result",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const enabledCtx = mockContext<MyContext>({ enabled: true });
|
|
225
|
+
const disabledCtx = mockContext<MyContext>({ enabled: false });
|
|
226
|
+
|
|
227
|
+
expect(await conditionalTool.isEnabled(enabledCtx, null as any)).toBe(
|
|
228
|
+
true,
|
|
229
|
+
);
|
|
230
|
+
expect(await conditionalTool.isEnabled(disabledCtx, null as any)).toBe(
|
|
231
|
+
false,
|
|
232
|
+
);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { FunctionToolkit } from "../toolkit";
|
|
3
|
+
import {
|
|
4
|
+
simpleStringTool,
|
|
5
|
+
zodTool,
|
|
6
|
+
errorTool,
|
|
7
|
+
mockHostedTool,
|
|
8
|
+
anotherHostedTool,
|
|
9
|
+
} from "./fixtures";
|
|
10
|
+
|
|
11
|
+
describe("FunctionToolkit", () => {
|
|
12
|
+
describe("constructor", () => {
|
|
13
|
+
it("should create empty toolkit with no tools", async () => {
|
|
14
|
+
const toolkit = new FunctionToolkit({ id: "empty", tools: [] });
|
|
15
|
+
|
|
16
|
+
expect(await toolkit.list()).toEqual([]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should initialize with systools", async () => {
|
|
20
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool] });
|
|
21
|
+
|
|
22
|
+
expect(await toolkit.list()).toHaveLength(1);
|
|
23
|
+
expect(toolkit.get("web-search")).toBe(mockHostedTool);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should initialize with function tools", async () => {
|
|
27
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [simpleStringTool, zodTool] });
|
|
28
|
+
|
|
29
|
+
expect(await toolkit.list()).toHaveLength(2);
|
|
30
|
+
expect(toolkit.get("simple")).toBe(simpleStringTool);
|
|
31
|
+
expect(toolkit.get("zod-tool")).toBe(zodTool);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should initialize with both systools and functions", async () => {
|
|
35
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool, simpleStringTool] });
|
|
36
|
+
|
|
37
|
+
expect(await toolkit.list()).toHaveLength(2);
|
|
38
|
+
expect(toolkit.get("web-search")).toBe(mockHostedTool);
|
|
39
|
+
expect(toolkit.get("simple")).toBe(simpleStringTool);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("get", () => {
|
|
44
|
+
it("should return undefined for non-existent tool", () => {
|
|
45
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [] });
|
|
46
|
+
|
|
47
|
+
expect(toolkit.get("non-existent")).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should get systool by id", () => {
|
|
51
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool] });
|
|
52
|
+
|
|
53
|
+
expect(toolkit.get("web-search")).toBe(mockHostedTool);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should get function tool by id", () => {
|
|
57
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [simpleStringTool] });
|
|
58
|
+
|
|
59
|
+
expect(toolkit.get("simple")).toBe(simpleStringTool);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("list", () => {
|
|
64
|
+
it("should return empty array for empty toolkit", async () => {
|
|
65
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [] });
|
|
66
|
+
|
|
67
|
+
expect(await toolkit.list()).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should list all systools", async () => {
|
|
71
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool, anotherHostedTool] });
|
|
72
|
+
|
|
73
|
+
const tools = await toolkit.list();
|
|
74
|
+
expect(tools).toHaveLength(2);
|
|
75
|
+
expect(tools).toContain(mockHostedTool);
|
|
76
|
+
expect(tools).toContain(anotherHostedTool);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should list all function tools", async () => {
|
|
80
|
+
const toolkit = new FunctionToolkit({
|
|
81
|
+
id: "test",
|
|
82
|
+
tools: [simpleStringTool, zodTool, errorTool],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const tools = await toolkit.list();
|
|
86
|
+
expect(tools).toHaveLength(3);
|
|
87
|
+
expect(tools).toContain(simpleStringTool);
|
|
88
|
+
expect(tools).toContain(zodTool);
|
|
89
|
+
expect(tools).toContain(errorTool);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should list both systools and function tools", async () => {
|
|
93
|
+
const toolkit = new FunctionToolkit({
|
|
94
|
+
id: "test",
|
|
95
|
+
tools: [mockHostedTool, simpleStringTool, zodTool],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const tools = await toolkit.list();
|
|
99
|
+
expect(tools).toHaveLength(3);
|
|
100
|
+
expect(tools).toContain(mockHostedTool);
|
|
101
|
+
expect(tools).toContain(simpleStringTool);
|
|
102
|
+
expect(tools).toContain(zodTool);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("serialize", () => {
|
|
107
|
+
it("should return empty array for empty toolkit", async () => {
|
|
108
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [] });
|
|
109
|
+
|
|
110
|
+
expect((await toolkit.list()).map((tool: any) => tool.serialize())).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should serialize function tools correctly", async () => {
|
|
114
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [simpleStringTool] });
|
|
115
|
+
|
|
116
|
+
const serialized = (await toolkit.list()).map((tool: any) => tool.serialize());
|
|
117
|
+
expect(serialized).toHaveLength(1);
|
|
118
|
+
expect(serialized[0]).toEqual({
|
|
119
|
+
type: "function",
|
|
120
|
+
name: simpleStringTool.name,
|
|
121
|
+
description: simpleStringTool.description,
|
|
122
|
+
parameters: simpleStringTool.parameters,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should serialize hosted tools correctly", async () => {
|
|
127
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool] });
|
|
128
|
+
|
|
129
|
+
const serialized = (await toolkit.list()).map((tool: any) => tool.serialize());
|
|
130
|
+
expect(serialized).toHaveLength(1);
|
|
131
|
+
expect(serialized[0]).toEqual({
|
|
132
|
+
type: "hosted-tool",
|
|
133
|
+
id: mockHostedTool.id,
|
|
134
|
+
name: mockHostedTool.name,
|
|
135
|
+
providerData: mockHostedTool.providerData,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should serialize both function and hosted tools", async () => {
|
|
140
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [mockHostedTool, simpleStringTool] });
|
|
141
|
+
|
|
142
|
+
const serialized = (await toolkit.list()).map((tool: any) => tool.serialize());
|
|
143
|
+
expect(serialized).toHaveLength(2);
|
|
144
|
+
|
|
145
|
+
// Check both tools are present (order not guaranteed with Map)
|
|
146
|
+
expect(serialized).toContainEqual({
|
|
147
|
+
type: "hosted-tool",
|
|
148
|
+
id: mockHostedTool.id,
|
|
149
|
+
name: mockHostedTool.name,
|
|
150
|
+
providerData: mockHostedTool.providerData,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(serialized).toContainEqual({
|
|
154
|
+
type: "function",
|
|
155
|
+
name: simpleStringTool.name,
|
|
156
|
+
description: simpleStringTool.description,
|
|
157
|
+
parameters: simpleStringTool.parameters,
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should handle hosted tools without providerData", async () => {
|
|
162
|
+
const toolkit = new FunctionToolkit({ id: "test", tools: [anotherHostedTool] });
|
|
163
|
+
|
|
164
|
+
const serialized = (await toolkit.list()).map((tool: any) => tool.serialize());
|
|
165
|
+
expect(serialized).toHaveLength(1);
|
|
166
|
+
expect(serialized[0]).toEqual({
|
|
167
|
+
type: "hosted-tool",
|
|
168
|
+
id: anotherHostedTool.id,
|
|
169
|
+
name: anotherHostedTool.name,
|
|
170
|
+
providerData: undefined,
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { BaseTool, FunctionTool, HostedTool, tool } from "./tool";
|
|
2
|
+
export { Toolkit, FunctionToolkit, MCPToolkit } from "./toolkit";
|
|
3
|
+
export type {
|
|
4
|
+
Tool,
|
|
5
|
+
ToolResult,
|
|
6
|
+
FunctionToolkitConfig,
|
|
7
|
+
MCPToolkitConfig,
|
|
8
|
+
ToolkitFilter,
|
|
9
|
+
ToolkitFilterContext,
|
|
10
|
+
} from "./types";
|