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.
Files changed (257) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +53 -0
  3. package/LICENSE +201 -0
  4. package/dist/agent.d.ts +43 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +130 -0
  7. package/dist/context.d.ts +70 -0
  8. package/dist/context.d.ts.map +1 -0
  9. package/dist/context.js +111 -0
  10. package/dist/env.d.ts +45 -0
  11. package/dist/env.d.ts.map +1 -0
  12. package/dist/env.js +31 -0
  13. package/dist/error.d.ts +1 -0
  14. package/dist/error.d.ts.map +1 -0
  15. package/dist/error.js +1 -0
  16. package/dist/guardrail.d.ts +178 -0
  17. package/dist/guardrail.d.ts.map +1 -0
  18. package/dist/guardrail.js +34 -0
  19. package/dist/index.d.ts +4 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +2 -0
  22. package/dist/kernel.d.ts +7 -0
  23. package/dist/kernel.d.ts.map +1 -0
  24. package/dist/kernel.js +7 -0
  25. package/dist/kernl.d.ts +18 -0
  26. package/dist/kernl.d.ts.map +1 -0
  27. package/dist/kernl.js +16 -0
  28. package/dist/lib/env.d.ts +43 -0
  29. package/dist/lib/env.d.ts.map +1 -0
  30. package/dist/lib/env.js +29 -0
  31. package/dist/lib/error.d.ts +88 -0
  32. package/dist/lib/error.d.ts.map +1 -0
  33. package/dist/lib/error.js +117 -0
  34. package/dist/lib/logger.d.ts +36 -0
  35. package/dist/lib/logger.d.ts.map +1 -0
  36. package/dist/lib/logger.js +43 -0
  37. package/dist/lib/serde/__tests__/codec.test.d.ts +2 -0
  38. package/dist/lib/serde/__tests__/codec.test.d.ts.map +1 -0
  39. package/dist/lib/serde/__tests__/codec.test.js +75 -0
  40. package/dist/lib/serde/codec.d.ts +12 -0
  41. package/dist/lib/serde/codec.d.ts.map +1 -0
  42. package/dist/lib/serde/codec.js +54 -0
  43. package/dist/lib/serde/json.d.ts +8 -0
  44. package/dist/lib/serde/json.d.ts.map +1 -0
  45. package/dist/lib/serde/json.js +13 -0
  46. package/dist/lib/serde/thread.d.ts +1 -0
  47. package/dist/lib/serde/thread.d.ts.map +1 -0
  48. package/dist/lib/serde/thread.js +172 -0
  49. package/dist/lib/serde/tool.d.ts +36 -0
  50. package/dist/lib/serde/tool.d.ts.map +1 -0
  51. package/dist/lib/serde/tool.js +1 -0
  52. package/dist/lib/utils.d.ts +19 -0
  53. package/dist/lib/utils.d.ts.map +1 -0
  54. package/dist/lib/utils.js +41 -0
  55. package/dist/lifecycle.d.ts +133 -0
  56. package/dist/lifecycle.d.ts.map +1 -0
  57. package/dist/lifecycle.js +29 -0
  58. package/dist/logger.d.ts +36 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +43 -0
  61. package/dist/mcp/__tests__/base.test.d.ts +2 -0
  62. package/dist/mcp/__tests__/base.test.d.ts.map +1 -0
  63. package/dist/mcp/__tests__/base.test.js +268 -0
  64. package/dist/mcp/__tests__/fixtures/echo-server.d.ts +3 -0
  65. package/dist/mcp/__tests__/fixtures/echo-server.d.ts.map +1 -0
  66. package/dist/mcp/__tests__/fixtures/echo-server.js +92 -0
  67. package/dist/mcp/__tests__/fixtures/math-server.d.ts +3 -0
  68. package/dist/mcp/__tests__/fixtures/math-server.d.ts.map +1 -0
  69. package/dist/mcp/__tests__/fixtures/math-server.js +98 -0
  70. package/dist/mcp/__tests__/fixtures/server.d.ts +3 -0
  71. package/dist/mcp/__tests__/fixtures/server.d.ts.map +1 -0
  72. package/dist/mcp/__tests__/fixtures/server.js +162 -0
  73. package/dist/mcp/__tests__/fixtures/test-server.d.ts +3 -0
  74. package/dist/mcp/__tests__/fixtures/test-server.d.ts.map +1 -0
  75. package/dist/mcp/__tests__/fixtures/test-server.js +163 -0
  76. package/dist/mcp/__tests__/fixtures/utils.d.ts +17 -0
  77. package/dist/mcp/__tests__/fixtures/utils.d.ts.map +1 -0
  78. package/dist/mcp/__tests__/fixtures/utils.js +42 -0
  79. package/dist/mcp/__tests__/integration.test.d.ts +2 -0
  80. package/dist/mcp/__tests__/integration.test.d.ts.map +1 -0
  81. package/dist/mcp/__tests__/integration.test.js +360 -0
  82. package/dist/mcp/__tests__/stdio.test.d.ts +2 -0
  83. package/dist/mcp/__tests__/stdio.test.d.ts.map +1 -0
  84. package/dist/mcp/__tests__/stdio.test.js +180 -0
  85. package/dist/mcp/__tests__/test-utils.d.ts +17 -0
  86. package/dist/mcp/__tests__/test-utils.d.ts.map +1 -0
  87. package/dist/mcp/__tests__/test-utils.js +42 -0
  88. package/dist/mcp/__tests__/utils.test.d.ts +2 -0
  89. package/dist/mcp/__tests__/utils.test.d.ts.map +1 -0
  90. package/dist/mcp/__tests__/utils.test.js +300 -0
  91. package/dist/mcp/base.d.ts +88 -0
  92. package/dist/mcp/base.d.ts.map +1 -0
  93. package/dist/mcp/base.js +68 -0
  94. package/dist/mcp/http.d.ts +34 -0
  95. package/dist/mcp/http.d.ts.map +1 -0
  96. package/dist/mcp/http.js +100 -0
  97. package/dist/mcp/node.d.ts +60 -0
  98. package/dist/mcp/node.d.ts.map +1 -0
  99. package/dist/mcp/node.js +297 -0
  100. package/dist/mcp/sse.d.ts +34 -0
  101. package/dist/mcp/sse.d.ts.map +1 -0
  102. package/dist/mcp/sse.js +97 -0
  103. package/dist/mcp/stdio.d.ts +32 -0
  104. package/dist/mcp/stdio.d.ts.map +1 -0
  105. package/dist/mcp/stdio.js +96 -0
  106. package/dist/mcp/types.d.ts +172 -0
  107. package/dist/mcp/types.d.ts.map +1 -0
  108. package/dist/mcp/types.js +16 -0
  109. package/dist/mcp/utils.d.ts +23 -0
  110. package/dist/mcp/utils.d.ts.map +1 -0
  111. package/dist/mcp/utils.js +44 -0
  112. package/dist/model.d.ts +175 -0
  113. package/dist/model.d.ts.map +1 -0
  114. package/dist/model.js +1 -0
  115. package/dist/providers/ai.d.ts +1 -0
  116. package/dist/providers/ai.d.ts.map +1 -0
  117. package/dist/providers/ai.js +1 -0
  118. package/dist/providers/default.d.ts +16 -0
  119. package/dist/providers/default.d.ts.map +1 -0
  120. package/dist/providers/default.js +17 -0
  121. package/dist/providers/registry.d.ts +1 -0
  122. package/dist/providers/registry.d.ts.map +1 -0
  123. package/dist/providers/registry.js +1 -0
  124. package/dist/sched/scheduler.d.ts +20 -0
  125. package/dist/sched/scheduler.d.ts.map +1 -0
  126. package/dist/sched/scheduler.js +1 -0
  127. package/dist/sched/task.d.ts +92 -0
  128. package/dist/sched/task.d.ts.map +1 -0
  129. package/dist/sched/task.js +102 -0
  130. package/dist/serde/__tests__/codec.test.d.ts +2 -0
  131. package/dist/serde/__tests__/codec.test.d.ts.map +1 -0
  132. package/dist/serde/__tests__/codec.test.js +75 -0
  133. package/dist/serde/codec.d.ts +12 -0
  134. package/dist/serde/codec.d.ts.map +1 -0
  135. package/dist/serde/codec.js +54 -0
  136. package/dist/serde/json.d.ts +8 -0
  137. package/dist/serde/json.d.ts.map +1 -0
  138. package/dist/serde/json.js +13 -0
  139. package/dist/serde/thread.d.ts +687 -0
  140. package/dist/serde/thread.d.ts.map +1 -0
  141. package/dist/serde/thread.js +158 -0
  142. package/dist/serde/tool.d.ts +36 -0
  143. package/dist/serde/tool.d.ts.map +1 -0
  144. package/dist/serde/tool.js +1 -0
  145. package/dist/session.d.ts +1 -0
  146. package/dist/session.d.ts.map +1 -0
  147. package/dist/session.js +1 -0
  148. package/dist/task.d.ts +87 -0
  149. package/dist/task.d.ts.map +1 -0
  150. package/dist/task.js +97 -0
  151. package/dist/thread/__tests__/mock.d.ts +28 -0
  152. package/dist/thread/__tests__/mock.d.ts.map +1 -0
  153. package/dist/thread/__tests__/mock.js +74 -0
  154. package/dist/thread/__tests__/thread.test.d.ts +2 -0
  155. package/dist/thread/__tests__/thread.test.d.ts.map +1 -0
  156. package/dist/thread/__tests__/thread.test.js +1412 -0
  157. package/dist/thread/index.d.ts +2 -0
  158. package/dist/thread/index.d.ts.map +1 -0
  159. package/dist/thread/index.js +1 -0
  160. package/dist/thread/thread.d.ts +66 -0
  161. package/dist/thread/thread.d.ts.map +1 -0
  162. package/dist/thread/thread.js +472 -0
  163. package/dist/thread/utils.d.ts +19 -0
  164. package/dist/thread/utils.d.ts.map +1 -0
  165. package/dist/thread/utils.js +50 -0
  166. package/dist/tool/__tests__/fixtures.d.ts +45 -0
  167. package/dist/tool/__tests__/fixtures.d.ts.map +1 -0
  168. package/dist/tool/__tests__/fixtures.js +97 -0
  169. package/dist/tool/__tests__/tool.test.d.ts +2 -0
  170. package/dist/tool/__tests__/tool.test.d.ts.map +1 -0
  171. package/dist/tool/__tests__/tool.test.js +172 -0
  172. package/dist/tool/__tests__/toolkit.test.d.ts +2 -0
  173. package/dist/tool/__tests__/toolkit.test.d.ts.map +1 -0
  174. package/dist/tool/__tests__/toolkit.test.js +134 -0
  175. package/dist/tool/index.d.ts +4 -0
  176. package/dist/tool/index.d.ts.map +1 -0
  177. package/dist/tool/index.js +2 -0
  178. package/dist/tool/mcp.d.ts +75 -0
  179. package/dist/tool/mcp.d.ts.map +1 -0
  180. package/dist/tool/mcp.js +111 -0
  181. package/dist/tool/tool.d.ts +95 -0
  182. package/dist/tool/tool.d.ts.map +1 -0
  183. package/dist/tool/tool.js +176 -0
  184. package/dist/tool/toolkit.d.ts +121 -0
  185. package/dist/tool/toolkit.d.ts.map +1 -0
  186. package/dist/tool/toolkit.js +180 -0
  187. package/dist/tool/types.d.ts +187 -0
  188. package/dist/tool/types.d.ts.map +1 -0
  189. package/dist/tool/types.js +1 -0
  190. package/dist/tools.d.ts +362 -0
  191. package/dist/tools.d.ts.map +1 -0
  192. package/dist/tools.js +220 -0
  193. package/dist/trace/processor.d.ts +1 -0
  194. package/dist/trace/processor.d.ts.map +1 -0
  195. package/dist/trace/processor.js +1 -0
  196. package/dist/trace/traces.d.ts +1 -0
  197. package/dist/trace/traces.d.ts.map +1 -0
  198. package/dist/trace/traces.js +73 -0
  199. package/dist/trace/utils.d.ts +22 -0
  200. package/dist/trace/utils.d.ts.map +1 -0
  201. package/dist/trace/utils.js +30 -0
  202. package/dist/types/agent.d.ts +91 -0
  203. package/dist/types/agent.d.ts.map +1 -0
  204. package/dist/types/agent.js +1 -0
  205. package/dist/types/proto.d.ts +1551 -0
  206. package/dist/types/proto.d.ts.map +1 -0
  207. package/dist/types/proto.js +531 -0
  208. package/dist/types/thread.d.ts +71 -0
  209. package/dist/types/thread.d.ts.map +1 -0
  210. package/dist/types/thread.js +5 -0
  211. package/dist/usage.d.ts +43 -0
  212. package/dist/usage.d.ts.map +1 -0
  213. package/dist/usage.js +61 -0
  214. package/package.json +52 -0
  215. package/src/agent.ts +203 -0
  216. package/src/context.ts +265 -0
  217. package/src/guardrail.ts +277 -0
  218. package/src/index.ts +3 -0
  219. package/src/kernl.ts +22 -0
  220. package/src/lib/env.ts +36 -0
  221. package/src/lib/error.ts +158 -0
  222. package/src/lib/logger.ts +78 -0
  223. package/src/lib/serde/json.ts +18 -0
  224. package/src/lib/serde/thread.ts +188 -0
  225. package/src/lifecycle.ts +181 -0
  226. package/src/mcp/__tests__/base.test.ts +344 -0
  227. package/src/mcp/__tests__/fixtures/server.ts +179 -0
  228. package/src/mcp/__tests__/fixtures/utils.ts +58 -0
  229. package/src/mcp/__tests__/integration.test.ts +447 -0
  230. package/src/mcp/__tests__/stdio.test.ts +236 -0
  231. package/src/mcp/__tests__/utils.test.ts +360 -0
  232. package/src/mcp/base.ts +162 -0
  233. package/src/mcp/http.ts +147 -0
  234. package/src/mcp/sse.ts +137 -0
  235. package/src/mcp/stdio.ts +136 -0
  236. package/src/mcp/types.ts +202 -0
  237. package/src/mcp/utils.ts +62 -0
  238. package/src/task.ts +119 -0
  239. package/src/thread/__tests__/mock.ts +95 -0
  240. package/src/thread/__tests__/thread.test.ts +1574 -0
  241. package/src/thread/index.ts +1 -0
  242. package/src/thread/thread.ts +611 -0
  243. package/src/thread/utils.ts +67 -0
  244. package/src/tool/__tests__/fixtures.ts +106 -0
  245. package/src/tool/__tests__/tool.test.ts +235 -0
  246. package/src/tool/__tests__/toolkit.test.ts +174 -0
  247. package/src/tool/index.ts +10 -0
  248. package/src/tool/tool.ts +264 -0
  249. package/src/tool/toolkit.ts +234 -0
  250. package/src/tool/types.ts +243 -0
  251. package/src/trace/processor.ts +0 -0
  252. package/src/trace/traces.ts +86 -0
  253. package/src/trace/utils.ts +38 -0
  254. package/src/types/agent.ts +145 -0
  255. package/src/types/thread.ts +86 -0
  256. package/tsconfig.json +13 -0
  257. package/vitest.config.ts +14 -0
@@ -0,0 +1,188 @@
1
+ // import { z } from "zod";
2
+
3
+ // import * as events from "@/types/thread";
4
+
5
+ // /**
6
+ // * Utilities and schemas for serializing the run-state so that it can be paused / resumed later.
7
+ // *
8
+ // * (TODO): should be able to define this as a codec for the ThreadState schema
9
+ // */
10
+
11
+ // /**
12
+ // * The schema version of the serialized run state. This is used to ensure that the serialized
13
+ // * run state is compatible with the current version of the SDK.
14
+ // * If anything in this schema changes, the version will have to be incremented.
15
+ // */
16
+ // export const CURRENT_SCHEMA_VERSION = "1.0" as const;
17
+ // const $schemaVersion = z.literal(CURRENT_SCHEMA_VERSION);
18
+
19
+ // const serializedAgentSchema = z.object({
20
+ // id: z.string(),
21
+ // name: z.string().optional(),
22
+ // });
23
+
24
+ // const serializedSpanBase = z.object({
25
+ // object: z.literal("trace.span"),
26
+ // id: z.string(),
27
+ // trace_id: z.string(),
28
+ // parent_id: z.string().nullable(),
29
+ // started_at: z.string().nullable(),
30
+ // ended_at: z.string().nullable(),
31
+ // error: z
32
+ // .object({
33
+ // message: z.string(),
34
+ // data: z.record(z.string(), z.any()).optional(),
35
+ // })
36
+ // .nullable(),
37
+ // span_data: z.record(z.string(), z.any()),
38
+ // });
39
+
40
+ // type SerializedSpanType = z.infer<typeof serializedSpanBase> & {
41
+ // previous_span?: SerializedSpanType;
42
+ // };
43
+
44
+ // const SerializedSpan: z.ZodType<SerializedSpanType> = serializedSpanBase.extend(
45
+ // {
46
+ // previous_span: z.lazy(() => SerializedSpan).optional(),
47
+ // },
48
+ // );
49
+
50
+ // const usageSchema = z.object({
51
+ // requests: z.number(),
52
+ // inputTokens: z.number(),
53
+ // outputTokens: z.number(),
54
+ // totalTokens: z.number(),
55
+ // });
56
+
57
+ // const modelResponseSchema = z.object({
58
+ // usage: usageSchema,
59
+ // events: z.array(events.ThreadEvent),
60
+ // responseId: z.string().optional(),
61
+ // providerData: z.record(z.string(), z.any()).optional(),
62
+ // });
63
+
64
+ // const itemSchema = z.discriminatedUnion("kind", [
65
+ // z.object({
66
+ // kind: z.literal("message"),
67
+ // rawItem: events.AssistantMessage,
68
+ // agent: serializedAgentSchema,
69
+ // }),
70
+ // z.object({
71
+ // kind: z.literal("tool-call"),
72
+ // // rawItem: events.ToolCall.or(events.HostedToolCall),
73
+ // rawItem: events.ToolCall,
74
+ // agent: serializedAgentSchema,
75
+ // }),
76
+ // z.object({
77
+ // kind: z.literal("tool-result"),
78
+ // rawItem: events.ToolResultEvent,
79
+ // agent: serializedAgentSchema,
80
+ // output: z.string(),
81
+ // }),
82
+ // z.object({
83
+ // kind: z.literal("reasoning"),
84
+ // rawItem: events.Reasoning,
85
+ // agent: serializedAgentSchema,
86
+ // }),
87
+ // ]);
88
+
89
+ // const serializedTraceSchema = z.object({
90
+ // object: z.literal("trace"),
91
+ // id: z.string(),
92
+ // workflow_name: z.string(),
93
+ // group_id: z.string().nullable(),
94
+ // metadata: z.record(z.string(), z.any()),
95
+ // });
96
+
97
+ // const serializedProcessedResponseSchema = z.object({
98
+ // newItems: z.array(itemSchema),
99
+ // toolsUsed: z.array(z.string()),
100
+ // functions: z.array(
101
+ // z.object({
102
+ // toolCall: z.any(),
103
+ // tool: z.any(),
104
+ // }),
105
+ // ),
106
+ // mcpApprovalRequests: z
107
+ // .array(
108
+ // z.object({
109
+ // requestItem: z.object({
110
+ // // protocol.HostedToolCallItem
111
+ // rawItem: z.object({
112
+ // type: z.literal("hosted_tool_call"),
113
+ // name: z.string(),
114
+ // arguments: z.string().optional(),
115
+ // status: z.string().optional(),
116
+ // output: z.string().optional(),
117
+ // // this always exists but marked as optional for early version compatibility; when releasing 1.0, we can remove the nullable and optional
118
+ // providerData: z.record(z.string(), z.any()).nullable().optional(),
119
+ // }),
120
+ // }),
121
+ // // HostedMCPTool
122
+ // mcpTool: z.object({
123
+ // type: z.literal("hosted_tool"),
124
+ // name: z.literal("hosted_mcp"),
125
+ // providerData: z.record(z.string(), z.any()),
126
+ // }),
127
+ // }),
128
+ // )
129
+ // .optional(),
130
+ // });
131
+
132
+ // const guardrailFunctionOutputSchema = z.object({
133
+ // tripwireTriggered: z.boolean(),
134
+ // outputInfo: z.any(),
135
+ // });
136
+
137
+ // const inputGuardrailResultSchema = z.object({
138
+ // guardrail: z.object({
139
+ // type: z.literal("input"),
140
+ // name: z.string(),
141
+ // }),
142
+ // output: guardrailFunctionOutputSchema,
143
+ // });
144
+
145
+ // const outputGuardrailResultSchema = z.object({
146
+ // guardrail: z.object({
147
+ // type: z.literal("output"),
148
+ // name: z.string(),
149
+ // }),
150
+ // agentOutput: z.any(),
151
+ // agent: serializedAgentSchema,
152
+ // output: guardrailFunctionOutputSchema,
153
+ // });
154
+
155
+ // // (TODO): define z.codec
156
+ // export const SerializedThread = z.object({
157
+ // $schemaVersion,
158
+ // currentTurn: z.number(),
159
+ // currentAgent: serializedAgentSchema, // (TODO): in our case we probably don't need to serialize the whole agent - an ID would suffice
160
+ // originalInput: z.string().or(z.array(events.ThreadEvent)),
161
+ // modelResponses: z.array(modelResponseSchema),
162
+ // context: z.object({
163
+ // usage: usageSchema, // (TODO): move to stats
164
+ // // (TODO): belongs elsewhere
165
+ // approvals: z.record(
166
+ // z.string(),
167
+ // z.object({
168
+ // approved: z.array(z.string()).or(z.boolean()),
169
+ // rejected: z.array(z.string()).or(z.boolean()),
170
+ // }),
171
+ // ),
172
+ // context: z.record(z.string(), z.any()),
173
+ // }),
174
+ // toolUseTracker: z.record(z.string(), z.array(z.string())),
175
+ // maxTurns: z.number(),
176
+ // currentAgentSpan: SerializedSpan.nullable().optional(),
177
+ // noActiveAgentRun: z.boolean(),
178
+ // inputGuardrailResults: z.array(inputGuardrailResultSchema),
179
+ // outputGuardrailResults: z.array(outputGuardrailResultSchema),
180
+ // // (TODO): currentStep: nextStepSchema.optional(),
181
+ // lastModelResponse: modelResponseSchema.optional(),
182
+ // generatedItems: z.array(itemSchema),
183
+ // lastProcessedResponse: serializedProcessedResponseSchema.optional(),
184
+ // currentTurnPersistedItemCount: z.number().int().min(0).optional(),
185
+ // trace: serializedTraceSchema.nullable(),
186
+ // });
187
+
188
+ // export type SerializedThread = z.infer<typeof SerializedThread>;
@@ -0,0 +1,181 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ import { Agent } from "./agent";
4
+ import { Context, UnknownContext } from "./context";
5
+ import { Tool } from "./tool";
6
+ import type { ToolCall } from "@kernl/protocol";
7
+
8
+ import { AgentResponseType } from "./types/agent";
9
+ import { TextResponse } from "./types/thread";
10
+
11
+ export type EventEmitterEvents = Record<string, any[]>;
12
+
13
+ /**
14
+ * Generic typed event emitter that wraps Node's EventEmitter with type safety
15
+ */
16
+ class TypedEventEmitter<
17
+ EventTypes extends EventEmitterEvents = Record<string, any[]>,
18
+ > extends EventEmitter {
19
+ // Overload for typed events
20
+ on<K extends keyof EventTypes>(
21
+ event: K,
22
+ listener: (...args: EventTypes[K]) => void,
23
+ ): this;
24
+ // Fallback for compatibility with parent
25
+ on(event: string | symbol, listener: (...args: any[]) => void): this;
26
+ on(event: any, listener: any): this {
27
+ return super.on(event, listener);
28
+ }
29
+
30
+ // Overload for typed events
31
+ off<K extends keyof EventTypes>(
32
+ event: K,
33
+ listener: (...args: EventTypes[K]) => void,
34
+ ): this;
35
+ // Fallback for compatibility with parent
36
+ off(event: string | symbol, listener: (...args: any[]) => void): this;
37
+ off(event: any, listener: any): this {
38
+ return super.off(event, listener);
39
+ }
40
+
41
+ // Overload for typed events
42
+ emit<K extends keyof EventTypes>(event: K, ...args: EventTypes[K]): boolean;
43
+ // Fallback for compatibility with parent
44
+ emit(event: string | symbol, ...args: any[]): boolean;
45
+ emit(event: any, ...args: any[]): boolean {
46
+ return super.emit(event, ...args);
47
+ }
48
+
49
+ // Overload for typed events
50
+ once<K extends keyof EventTypes>(
51
+ event: K,
52
+ listener: (...args: EventTypes[K]) => void,
53
+ ): this;
54
+ // Fallback for compatibility with parent
55
+ once(event: string | symbol, listener: (...args: any[]) => void): this;
56
+ once(event: any, listener: any): this {
57
+ return super.once(event, listener);
58
+ }
59
+ }
60
+
61
+ export type AgentHookEvents<
62
+ TContext = UnknownContext,
63
+ TOutput extends AgentResponseType = TextResponse,
64
+ > = {
65
+ /**
66
+ * @param context - The context of the run
67
+ */
68
+ agent_start: [context: Context<TContext>, agent: Agent<TContext, TOutput>];
69
+ /**
70
+ * @param context - The context of the run
71
+ * @param output - The output of the agent
72
+ */
73
+ agent_end: [context: Context<TContext>, output: string];
74
+ // /**
75
+ // * @param context - The context of the run
76
+ // * @param agent - The agent that is handing off
77
+ // * @param nextAgent - The next agent to run
78
+ // */
79
+ // agent_handoff: [context: Context<TContext>, nextAgent: Agent<any, any>];
80
+ /**
81
+ * @param context - The context of the run
82
+ * @param agent - The agent that is starting a tool
83
+ * @param tool - The tool that is starting
84
+ */
85
+ agent_tool_start: [
86
+ context: Context<TContext>,
87
+ tool: Tool<any>,
88
+ details: { toolCall: ToolCall },
89
+ ];
90
+ /**
91
+ * @param context - The context of the run
92
+ * @param agent - The agent that is ending a tool
93
+ * @param tool - The tool that is ending
94
+ * @param result - The result of the tool
95
+ */
96
+ agent_tool_end: [
97
+ context: Context<TContext>,
98
+ tool: Tool<any>,
99
+ result: string,
100
+ details: { toolCall: ToolCall },
101
+ ];
102
+ };
103
+
104
+ /**
105
+ * Event emitter that every Agent instance inherits from and that emits events for the lifecycle
106
+ * of the agent.
107
+ */
108
+ export class AgentHooks<
109
+ TContext = UnknownContext,
110
+ TOutput extends AgentResponseType = TextResponse,
111
+ > extends TypedEventEmitter<AgentHookEvents<TContext, TOutput>> {}
112
+
113
+ /**
114
+ * Events emitted by the kernl during execution.
115
+ *
116
+ * Unlike AgentHookEvents (which are emitted by individual agents with implicit context),
117
+ * KernlHookEvents explicitly include the agent reference in all events since it needs to
118
+ * coordinate multiple agents and listeners need to know which agent triggered each event.
119
+ */
120
+ export type KernlHookEvents<
121
+ TContext = UnknownContext,
122
+ TOutput extends AgentResponseType = TextResponse,
123
+ > = {
124
+ /**
125
+ * @param context - The context of the run
126
+ * @param agent - The agent that is starting
127
+ */
128
+ agent_start: [context: Context<TContext>, agent: Agent<TContext, TOutput>];
129
+ /**
130
+ * @param context - The context of the run
131
+ * @param agent - The agent that is ending
132
+ * @param output - The output of the agent
133
+ */
134
+ agent_end: [
135
+ context: Context<TContext>,
136
+ agent: Agent<TContext, TOutput>,
137
+ output: string,
138
+ ];
139
+ /**
140
+ * @param context - The context of the run
141
+ * @param fromAgent - The agent that is handing off
142
+ * @param toAgent - The next agent to run
143
+ */
144
+ agent_handoff: [
145
+ context: Context<TContext>,
146
+ fromAgent: Agent<any, any>,
147
+ toAgent: Agent<any, any>,
148
+ ];
149
+ /**
150
+ * @param context - The context of the run
151
+ * @param agent - The agent that is starting a tool
152
+ * @param tool - The tool that is starting
153
+ */
154
+ agent_tool_start: [
155
+ context: Context<TContext>,
156
+ agent: Agent<TContext, TOutput>,
157
+ tool: Tool,
158
+ details: { toolCall: ToolCall },
159
+ ];
160
+ /**
161
+ * @param context - The context of the run
162
+ * @param agent - The agent that is ending a tool
163
+ * @param tool - The tool that is ending
164
+ * @param result - The result of the tool
165
+ */
166
+ agent_tool_end: [
167
+ context: Context<TContext>,
168
+ agent: Agent<TContext, TOutput>,
169
+ tool: Tool,
170
+ result: string,
171
+ details: { toolCall: ToolCall },
172
+ ];
173
+ };
174
+
175
+ /**
176
+ * Event emitter that the kernl uses to emit events for the lifecycle of every agent run.
177
+ */
178
+ export class KernlHooks<
179
+ TContext = UnknownContext,
180
+ TOutput extends AgentResponseType = TextResponse,
181
+ > extends TypedEventEmitter<KernlHookEvents<TContext, TOutput>> {}
@@ -0,0 +1,344 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { BaseMCPServer } from "../base";
3
+ import type { MCPTool, CallToolResultContent } from "../types";
4
+ import { logger } from "@/lib/logger";
5
+
6
+ // Create a minimal concrete implementation for testing
7
+ class TestMCPServer extends BaseMCPServer {
8
+ readonly id: string;
9
+ private mockTools: MCPTool[] = [];
10
+ private isConnected = false;
11
+
12
+ constructor(
13
+ id: string,
14
+ options?: {
15
+ cacheToolsList?: boolean;
16
+ toolFilter?: any;
17
+ },
18
+ ) {
19
+ super({
20
+ cacheToolsList: options?.cacheToolsList,
21
+ toolFilter: options?.toolFilter,
22
+ logger: logger,
23
+ });
24
+ this.id = id;
25
+ }
26
+
27
+ // Method to set mock tools for testing
28
+ setMockTools(tools: MCPTool[]): void {
29
+ this.mockTools = tools;
30
+ }
31
+
32
+ async connect(): Promise<void> {
33
+ this.isConnected = true;
34
+ }
35
+
36
+ async close(): Promise<void> {
37
+ this.isConnected = false;
38
+ }
39
+
40
+ protected async _listTools(): Promise<MCPTool[]> {
41
+ if (!this.isConnected) {
42
+ throw new Error("Not connected");
43
+ }
44
+ return this.mockTools;
45
+ }
46
+
47
+ async callTool(
48
+ toolName: string,
49
+ args: Record<string, unknown> | null,
50
+ ): Promise<CallToolResultContent> {
51
+ if (!this.isConnected) {
52
+ throw new Error("Not connected");
53
+ }
54
+ return [{ type: "text", text: `Called ${toolName}` }];
55
+ }
56
+ }
57
+
58
+ describe("BaseMCPServer", () => {
59
+ describe("Caching mechanisms", () => {
60
+ it("should have cache disabled by default", () => {
61
+ const server = new TestMCPServer("test-server");
62
+ expect(server.cacheToolsList).toBe(false);
63
+ });
64
+
65
+ it("should store tools correctly when cache enabled", async () => {
66
+ const server = new TestMCPServer("test-server", {
67
+ cacheToolsList: true,
68
+ });
69
+
70
+ const mockTools: MCPTool[] = [
71
+ {
72
+ name: "tool1",
73
+ description: "First tool",
74
+ inputSchema: { type: "object", properties: {} },
75
+ },
76
+ {
77
+ name: "tool2",
78
+ description: "Second tool",
79
+ inputSchema: { type: "object", properties: {} },
80
+ },
81
+ ];
82
+
83
+ server.setMockTools(mockTools);
84
+ await server.connect();
85
+
86
+ const tools = await server.listTools();
87
+ expect(tools).toEqual(mockTools);
88
+ expect(tools).toHaveLength(2);
89
+ });
90
+
91
+ it("should return same reference on subsequent calls when cached", async () => {
92
+ const server = new TestMCPServer("test-server", {
93
+ cacheToolsList: true,
94
+ });
95
+
96
+ const mockTools: MCPTool[] = [
97
+ {
98
+ name: "tool1",
99
+ inputSchema: { type: "object", properties: {} },
100
+ },
101
+ ];
102
+
103
+ server.setMockTools(mockTools);
104
+ await server.connect();
105
+
106
+ const tools1 = await server.listTools();
107
+ const tools2 = await server.listTools();
108
+
109
+ // Should return the exact same cached reference
110
+ expect(tools1).toBe(tools2);
111
+ });
112
+
113
+ it("should fetch fresh tools when cache disabled", async () => {
114
+ const server = new TestMCPServer("test-server", {
115
+ cacheToolsList: false,
116
+ });
117
+
118
+ const mockTools: MCPTool[] = [
119
+ {
120
+ name: "tool1",
121
+ inputSchema: { type: "object", properties: {} },
122
+ },
123
+ ];
124
+
125
+ server.setMockTools(mockTools);
126
+ await server.connect();
127
+
128
+ const tools1 = await server.listTools();
129
+ const tools2 = await server.listTools();
130
+
131
+ // Both should succeed and have correct data
132
+ expect(tools1).toEqual(mockTools);
133
+ expect(tools2).toEqual(mockTools);
134
+ });
135
+
136
+ it("should have cache isolated per server instance", async () => {
137
+ const server1 = new TestMCPServer("server1", {
138
+ cacheToolsList: true,
139
+ });
140
+ const server2 = new TestMCPServer("server2", {
141
+ cacheToolsList: true,
142
+ });
143
+
144
+ const tools1: MCPTool[] = [
145
+ {
146
+ name: "tool1",
147
+ inputSchema: { type: "object", properties: {} },
148
+ },
149
+ ];
150
+ const tools2: MCPTool[] = [
151
+ {
152
+ name: "tool2",
153
+ inputSchema: { type: "object", properties: {} },
154
+ },
155
+ ];
156
+
157
+ server1.setMockTools(tools1);
158
+ server2.setMockTools(tools2);
159
+
160
+ await server1.connect();
161
+ await server2.connect();
162
+
163
+ const result1 = await server1.listTools();
164
+ const result2 = await server2.listTools();
165
+
166
+ expect(result1).toEqual(tools1);
167
+ expect(result2).toEqual(tools2);
168
+ expect(result1).not.toEqual(result2);
169
+ });
170
+
171
+ it("should clear cache when invalidateCache() called", async () => {
172
+ const server = new TestMCPServer("test-server", {
173
+ cacheToolsList: true,
174
+ });
175
+
176
+ const initialTools: MCPTool[] = [
177
+ {
178
+ name: "tool1",
179
+ inputSchema: { type: "object", properties: {} },
180
+ },
181
+ ];
182
+
183
+ server.setMockTools(initialTools);
184
+ await server.connect();
185
+
186
+ // First fetch - populates cache
187
+ await server.listTools();
188
+
189
+ // Invalidate cache
190
+ await server.invalidateCache();
191
+
192
+ // Update mock tools
193
+ const newTools: MCPTool[] = [
194
+ {
195
+ name: "tool2",
196
+ inputSchema: { type: "object", properties: {} },
197
+ },
198
+ ];
199
+ server.setMockTools(newTools);
200
+
201
+ // Next fetch should get new tools, not cached ones
202
+ const result = await server.listTools();
203
+ expect(result).toEqual(newTools);
204
+ });
205
+
206
+ it("should set _cacheDirty flag when invalidateCache() called", async () => {
207
+ const server = new TestMCPServer("test-server", {
208
+ cacheToolsList: true,
209
+ });
210
+
211
+ await server.connect();
212
+ server.setMockTools([
213
+ { name: "tool1", inputSchema: { type: "object", properties: {} } },
214
+ ]);
215
+
216
+ // Populate cache
217
+ await server.listTools();
218
+
219
+ // Invalidate
220
+ await server.invalidateCache();
221
+
222
+ // The next listTools() call should fetch fresh data
223
+ // We can verify this by changing the mock tools and seeing the change
224
+ server.setMockTools([
225
+ { name: "tool2", inputSchema: { type: "object", properties: {} } },
226
+ ]);
227
+
228
+ const tools = await server.listTools();
229
+ expect(tools[0].name).toBe("tool2");
230
+ });
231
+
232
+ it("should skip cache when _cacheDirty is true", async () => {
233
+ const server = new TestMCPServer("test-server", {
234
+ cacheToolsList: true,
235
+ });
236
+
237
+ await server.connect();
238
+
239
+ // First call
240
+ server.setMockTools([
241
+ { name: "tool1", inputSchema: { type: "object", properties: {} } },
242
+ ]);
243
+ await server.listTools();
244
+
245
+ // Invalidate cache (sets _cacheDirty = true)
246
+ await server.invalidateCache();
247
+
248
+ // Change mock tools
249
+ server.setMockTools([
250
+ { name: "tool2", inputSchema: { type: "object", properties: {} } },
251
+ ]);
252
+
253
+ // Should fetch fresh tools, not use cache
254
+ const tools = await server.listTools();
255
+ expect(tools[0].name).toBe("tool2");
256
+ });
257
+ });
258
+
259
+ describe("Abstract class contracts", () => {
260
+ it("should allow subclass to implement connect()", async () => {
261
+ const server = new TestMCPServer("test-server");
262
+
263
+ await expect(server.connect()).resolves.not.toThrow();
264
+ });
265
+
266
+ it("should allow subclass to implement close()", async () => {
267
+ const server = new TestMCPServer("test-server");
268
+
269
+ await server.connect();
270
+ await expect(server.close()).resolves.not.toThrow();
271
+ });
272
+
273
+ it("should allow subclass to implement _listTools()", async () => {
274
+ const server = new TestMCPServer("test-server");
275
+
276
+ server.setMockTools([
277
+ { name: "tool1", inputSchema: { type: "object", properties: {} } },
278
+ ]);
279
+ await server.connect();
280
+
281
+ const tools = await server.listTools();
282
+ expect(tools).toHaveLength(1);
283
+ expect(tools[0].name).toBe("tool1");
284
+ });
285
+
286
+ it("should allow subclass to implement callTool()", async () => {
287
+ const server = new TestMCPServer("test-server");
288
+
289
+ await server.connect();
290
+ const result = await server.callTool("test_tool", { arg: "value" });
291
+
292
+ expect(result).toHaveLength(1);
293
+ expect(result[0].text).toContain("test_tool");
294
+ });
295
+ });
296
+
297
+ describe("toolFilter integration", () => {
298
+ it("should default to allow-all filter", async () => {
299
+ const server = new TestMCPServer("test-server");
300
+
301
+ // The default filter should allow all tools
302
+ expect(server.toolFilter).toBeDefined();
303
+
304
+ // Test that it returns true for any tool
305
+ const mockContext: any = {
306
+ context: {},
307
+ agent: {},
308
+ serverId: "test",
309
+ };
310
+ const mockTool: MCPTool = {
311
+ name: "any_tool",
312
+ inputSchema: { type: "object", properties: {} },
313
+ };
314
+
315
+ const result = await server.toolFilter(mockContext, mockTool);
316
+ expect(result).toBe(true);
317
+ });
318
+
319
+ it("should store custom filter correctly in constructor", async () => {
320
+ const customFilter = vi.fn().mockResolvedValue(false);
321
+
322
+ const server = new TestMCPServer("test-server", {
323
+ toolFilter: customFilter,
324
+ });
325
+
326
+ expect(server.toolFilter).toBe(customFilter);
327
+
328
+ // Verify the filter is actually used
329
+ const mockContext: any = {
330
+ context: {},
331
+ agent: {},
332
+ serverId: "test",
333
+ };
334
+ const mockTool: MCPTool = {
335
+ name: "tool1",
336
+ inputSchema: { type: "object", properties: {} },
337
+ };
338
+
339
+ const result = await server.toolFilter(mockContext, mockTool);
340
+ expect(result).toBe(false);
341
+ expect(customFilter).toHaveBeenCalledWith(mockContext, mockTool);
342
+ });
343
+ });
344
+ });