kernl 0.8.4 → 0.9.0

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 (70) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/dist/agent/base.d.ts +73 -0
  4. package/dist/agent/base.d.ts.map +1 -0
  5. package/dist/agent/base.js +137 -0
  6. package/dist/agent/index.d.ts +2 -0
  7. package/dist/agent/index.d.ts.map +1 -1
  8. package/dist/agent/index.js +2 -1
  9. package/dist/agent/types.d.ts +4 -0
  10. package/dist/agent/types.d.ts.map +1 -1
  11. package/dist/agent.d.ts +10 -90
  12. package/dist/agent.d.ts.map +1 -1
  13. package/dist/agent.js +5 -171
  14. package/dist/api/resources/agents/agents.d.ts +11 -7
  15. package/dist/api/resources/agents/agents.d.ts.map +1 -1
  16. package/dist/api/resources/agents/agents.js +14 -8
  17. package/dist/kernl/kernl.d.ts +2 -2
  18. package/dist/kernl/kernl.d.ts.map +1 -1
  19. package/dist/kernl/kernl.js +6 -6
  20. package/dist/kernl/types.d.ts +3 -3
  21. package/dist/kernl/types.d.ts.map +1 -1
  22. package/dist/lib/env.d.ts +2 -2
  23. package/dist/mcp/__tests__/utils.test.js +4 -2
  24. package/dist/mcp/utils.d.ts +1 -1
  25. package/dist/mcp/utils.js +1 -1
  26. package/dist/realtime/agent.d.ts +17 -0
  27. package/dist/realtime/agent.d.ts.map +1 -0
  28. package/dist/realtime/agent.js +17 -0
  29. package/dist/realtime/channel.d.ts +30 -0
  30. package/dist/realtime/channel.d.ts.map +1 -0
  31. package/dist/realtime/channel.js +1 -0
  32. package/dist/realtime/index.d.ts +5 -0
  33. package/dist/realtime/index.d.ts.map +1 -0
  34. package/dist/realtime/index.js +4 -0
  35. package/dist/realtime/session.d.ts +98 -0
  36. package/dist/realtime/session.d.ts.map +1 -0
  37. package/dist/realtime/session.js +203 -0
  38. package/dist/realtime/types.d.ts +58 -0
  39. package/dist/realtime/types.d.ts.map +1 -0
  40. package/dist/realtime/types.js +1 -0
  41. package/dist/storage/in-memory.d.ts.map +1 -1
  42. package/dist/storage/in-memory.js +5 -1
  43. package/dist/tool/__tests__/toolkit.test.js +2 -2
  44. package/dist/tool/tool.d.ts +2 -1
  45. package/dist/tool/tool.d.ts.map +1 -1
  46. package/dist/tool/toolkit.d.ts +4 -4
  47. package/dist/tool/toolkit.d.ts.map +1 -1
  48. package/dist/tool/toolkit.js +2 -1
  49. package/dist/tool/types.d.ts +4 -4
  50. package/dist/tool/types.d.ts.map +1 -1
  51. package/package.json +4 -4
  52. package/src/agent/base.ts +220 -0
  53. package/src/agent/index.ts +2 -0
  54. package/src/agent/types.ts +5 -0
  55. package/src/agent.ts +12 -231
  56. package/src/api/resources/agents/agents.ts +19 -13
  57. package/src/kernl/kernl.ts +9 -9
  58. package/src/kernl/types.ts +3 -3
  59. package/src/mcp/__tests__/utils.test.ts +4 -2
  60. package/src/mcp/utils.ts +1 -1
  61. package/src/realtime/agent.ts +24 -0
  62. package/src/realtime/channel.ts +32 -0
  63. package/src/realtime/index.ts +4 -0
  64. package/src/realtime/session.ts +259 -0
  65. package/src/realtime/types.ts +73 -0
  66. package/src/storage/in-memory.ts +9 -1
  67. package/src/tool/__tests__/toolkit.test.ts +2 -2
  68. package/src/tool/tool.ts +2 -1
  69. package/src/tool/toolkit.ts +6 -5
  70. package/src/tool/types.ts +4 -4
package/src/agent.ts CHANGED
@@ -6,7 +6,6 @@ import {
6
6
  } from "@kernl-sdk/protocol";
7
7
 
8
8
  import { Thread } from "./thread";
9
- import type { Kernl } from "./kernl";
10
9
  import type {
11
10
  RThreadsListParams,
12
11
  RThreadCreateParams,
@@ -15,30 +14,16 @@ import type {
15
14
  RThreadUpdateParams,
16
15
  } from "@/api/resources/threads/types";
17
16
  import type { Context, UnknownContext } from "./context";
18
- import { Tool, memory } from "./tool";
19
- import { BaseToolkit } from "./tool/toolkit";
20
17
  import {
21
18
  InputGuardrail,
22
19
  OutputGuardrail,
23
20
  type ResolvedAgentResponse,
24
21
  } from "./guardrail";
25
- import { AgentHooks } from "./lifecycle";
26
- import type {
27
- AgentMemoryCreate,
28
- AgentMemoryUpdate,
29
- MemoryListOptions,
30
- MemorySearchQuery,
31
- } from "./memory";
22
+ import { BaseAgent } from "./agent/base";
32
23
 
33
- import { randomID } from "@kernl-sdk/shared/lib";
34
24
  import { MisconfiguredError, RuntimeError } from "./lib/error";
35
25
 
36
- /* types */
37
- import type {
38
- AgentConfig,
39
- AgentMemoryConfig,
40
- AgentOutputType,
41
- } from "./agent/types";
26
+ import type { AgentConfig, AgentOutputType } from "./agent/types";
42
27
  import type {
43
28
  TextOutput,
44
29
  ThreadExecuteOptions,
@@ -50,74 +35,30 @@ export class Agent<
50
35
  TContext = UnknownContext,
51
36
  TOutput extends AgentOutputType = TextOutput,
52
37
  >
53
- extends AgentHooks<TContext, TOutput>
38
+ extends BaseAgent<TContext, TOutput>
54
39
  implements AgentConfig<TContext, TOutput>
55
40
  {
56
- private kernl?: Kernl;
57
-
58
- id: string;
59
- name: string;
60
- description?: string;
61
- instructions: (context: Context<TContext>) => Promise<string> | string;
41
+ readonly kind = "llm";
42
+ readonly model: LanguageModel;
43
+ readonly modelSettings: LanguageModelRequestSettings;
62
44
 
63
- model: LanguageModel;
64
- modelSettings: LanguageModelRequestSettings;
65
- // actions: ActionSet; /* TODO */
66
- toolkits: BaseToolkit<TContext>[];
67
- systools: BaseToolkit<TContext>[];
68
- memory: AgentMemoryConfig;
69
-
70
- guardrails: {
45
+ readonly guardrails: {
71
46
  input: InputGuardrail[];
72
47
  output: OutputGuardrail<AgentOutputType>[];
73
48
  };
74
- output: TOutput = "text" as TOutput;
75
- resetToolChoice: boolean;
49
+ readonly output: TOutput = "text" as TOutput;
50
+ readonly resetToolChoice: boolean;
76
51
 
77
52
  constructor(config: AgentConfig<TContext, TOutput>) {
78
- super();
79
- if (config.id.trim() === "") {
80
- throw new MisconfiguredError("Agent must have an id.");
81
- }
82
- this.id = config.id;
83
- this.name = config.name;
84
- this.description = config.description;
85
- this.instructions =
86
- typeof config.instructions === "function"
87
- ? config.instructions
88
- : () => config.instructions as string;
89
- this.model = config.model; // (TODO): include optional default setting for convenience like env.DEFAULT_LLM = "gpt-5"
90
- this.modelSettings = config.modelSettings ?? {};
91
-
92
- this.toolkits = config.toolkits ?? [];
93
- this.systools = [];
94
- this.memory = config.memory ?? { enabled: false };
95
-
96
- for (const toolkit of this.toolkits) {
97
- toolkit.bind(this);
98
- }
53
+ super(config);
99
54
 
55
+ this.model = config.model;
56
+ this.modelSettings = config.modelSettings ?? {};
100
57
  this.guardrails = config.guardrails ?? { input: [], output: [] };
101
58
  if (config.output) {
102
59
  this.output = config.output;
103
60
  }
104
61
  this.resetToolChoice = config.resetToolChoice ?? true;
105
- // this.toolUseBehavior = config.toolUseBehavior ?? "run_llm_again";
106
- }
107
-
108
- /**
109
- * Bind this agent to a kernl instance. Called by kernl.register().
110
- */
111
- bind(kernl: Kernl): void {
112
- this.kernl = kernl;
113
-
114
- // initialize system toolkits
115
- if (this.memory.enabled) {
116
- // safety: system tools only rely on ctx.agent, not ctx.context
117
- const toolkit = memory as unknown as BaseToolkit<TContext>;
118
- this.systools.push(toolkit);
119
- toolkit.bind(this);
120
- }
121
62
  }
122
63
 
123
64
  /**
@@ -241,60 +182,6 @@ export class Agent<
241
182
  yield* this.kernl.scheduleStream(thread);
242
183
  }
243
184
 
244
- /**
245
- * @internal
246
- *
247
- * Get a specific tool by ID from systools and toolkits.
248
- *
249
- * @param id The tool ID to look up
250
- * @returns The tool if found, undefined otherwise
251
- */
252
- tool(id: string): Tool<TContext> | undefined {
253
- // Check systools first
254
- for (const toolkit of this.systools) {
255
- const tool = toolkit.get(id);
256
- if (tool) return tool;
257
- }
258
- // Then user toolkits
259
- for (const toolkit of this.toolkits) {
260
- const tool = toolkit.get(id);
261
- if (tool) return tool;
262
- }
263
- return undefined;
264
- }
265
-
266
- /**
267
- * @internal
268
- *
269
- * Get all tools available from systools and toolkits for the given context.
270
- * Checks for duplicate tool IDs across toolkits and throws an error if found.
271
- *
272
- * (TODO): Consider returning toolkits alongside tools so we can serialize them
273
- * together and give agents more options for dealing with tool groups.
274
- *
275
- * @param context The context to use for filtering tools
276
- * @returns Array of all available tools
277
- * @throws {MisconfiguredError} If duplicate tool IDs are found across toolkits
278
- */
279
- async tools(context: Context<TContext>): Promise<Tool<TContext>[]> {
280
- const all: Tool<TContext>[] = [];
281
-
282
- for (const toolkit of [...this.systools, ...this.toolkits]) {
283
- all.push(...(await toolkit.list(context)));
284
- }
285
-
286
- const ids = all.map((t) => t.id);
287
- const duplicates = ids.filter((id, i) => ids.indexOf(id) !== i);
288
-
289
- if (duplicates.length > 0) {
290
- throw new MisconfiguredError(
291
- `Duplicate tool IDs found: ${[...new Set(duplicates)].join(", ")}`,
292
- );
293
- }
294
-
295
- return all;
296
- }
297
-
298
185
  /**
299
186
  * Thread management scoped to this agent.
300
187
  *
@@ -331,110 +218,4 @@ export class Agent<
331
218
  kthreads.update(tid, patch),
332
219
  };
333
220
  }
334
-
335
- /**
336
- * Memory management scoped to this agent.
337
- *
338
- * Provides a simplified API for creating memories with:
339
- * - Auto-generated IDs
340
- * - Flattened scope fields (namespace, entityId) - agentId is implicit
341
- * - Default kind of "semantic"
342
- *
343
- * @example
344
- * ```ts
345
- * await agent.memories.create({
346
- * namespace: "user-123",
347
- * collection: "preferences",
348
- * content: { text: "User prefers TypeScript" },
349
- * });
350
- * ```
351
- */
352
- get memories() {
353
- if (!this.kernl) {
354
- throw new MisconfiguredError(
355
- `Agent ${this.id} not bound to kernl. Call kernl.register(agent) first.`,
356
- );
357
- }
358
-
359
- const agentId = this.id;
360
- const kmem = this.kernl.memories;
361
-
362
- return {
363
- /**
364
- * List memories scoped to this agent.
365
- */
366
- list: (
367
- params?: Omit<MemoryListOptions, "filter"> & {
368
- collection?: string;
369
- limit?: number;
370
- // (TODO): we might want to add the filter back here
371
- },
372
- ) =>
373
- kmem.list({
374
- filter: {
375
- scope: { agentId },
376
- collections: params?.collection ? [params.collection] : undefined,
377
- },
378
- limit: params?.limit,
379
- }),
380
-
381
- /**
382
- * Create a new memory scoped to this agent.
383
- */
384
- create: (params: AgentMemoryCreate) =>
385
- kmem.create({
386
- id: params.id ?? `mem_${randomID()}`,
387
- scope: {
388
- namespace: params.namespace,
389
- entityId: params.entityId,
390
- agentId,
391
- },
392
- kind: "semantic",
393
- collection: params.collection,
394
- content: params.content,
395
- wmem: params.wmem,
396
- smem: params.smem,
397
- timestamp: params.timestamp,
398
- metadata: params.metadata,
399
- }),
400
-
401
- /**
402
- * Update an existing memory scoped to this agent.
403
- */
404
- update: (params: AgentMemoryUpdate) =>
405
- kmem.update({
406
- id: params.id,
407
- content: params.content,
408
- collection: params.collection,
409
- wmem: params.wmem,
410
- smem: params.smem,
411
- metadata: params.metadata,
412
- }),
413
-
414
- /**
415
- * Search memories scoped to this agent.
416
- */
417
- search: (
418
- params: Omit<MemorySearchQuery, "filter"> & {
419
- // (TODO): is this correct?
420
- filter?: Omit<NonNullable<MemorySearchQuery["filter"]>, "scope"> & {
421
- scope?: Omit<
422
- NonNullable<NonNullable<MemorySearchQuery["filter"]>["scope"]>,
423
- "agentId"
424
- >;
425
- };
426
- },
427
- ) =>
428
- kmem.search({
429
- ...params,
430
- filter: {
431
- ...params.filter,
432
- scope: {
433
- ...params.filter?.scope,
434
- agentId,
435
- },
436
- },
437
- }),
438
- };
439
- }
440
221
  }
@@ -1,4 +1,5 @@
1
- import type { Agent } from "@/agent";
1
+ import { Agent } from "@/agent";
2
+ import type { BaseAgent } from "@/agent/base";
2
3
  import type { AgentOutputType } from "@/agent/types";
3
4
  import type { UnknownContext } from "@/context";
4
5
  import type { TextOutput } from "@/thread/types";
@@ -9,13 +10,19 @@ import type { TextOutput } from "@/thread/types";
9
10
  * Thin facade over the in-process agent registry, returning live Agent instances.
10
11
  *
11
12
  * Note: agents are code, not persisted data; this is process-local.
13
+ *
14
+ * Currently only exposes LLM agents (kind: "llm"). RealtimeAgents are stored
15
+ * in the internal registry but not returned by these methods. If public access
16
+ * to realtime agents is needed, add a separate `kernl.realtimeAgents` resource.
12
17
  */
13
18
  export class RAgents {
14
- constructor(private readonly registry: Map<string, Agent>) {}
19
+ constructor(private readonly registry: Map<string, BaseAgent>) {}
15
20
 
16
21
  /**
17
22
  * Get a live Agent instance by id.
18
23
  *
24
+ * Only returns LLM agents. Returns undefined for realtime agents.
25
+ *
19
26
  * Callers are expected to know the concrete TContext/TOutput types
20
27
  * for their own agents and can specify them via generics.
21
28
  */
@@ -24,27 +31,26 @@ export class RAgents {
24
31
  TOutput extends AgentOutputType = TextOutput,
25
32
  >(id: string): Agent<TContext, TOutput> | undefined {
26
33
  const agent = this.registry.get(id);
27
- return agent as Agent<TContext, TOutput> | undefined;
34
+ if (agent?.kind === "llm") {
35
+ return agent as Agent<TContext, TOutput>;
36
+ }
37
+ return undefined;
28
38
  }
29
39
 
30
40
  /**
31
- * Check if an agent with the given id is registered.
41
+ * Check if an LLM agent with the given id is registered.
32
42
  */
33
43
  has(id: string): boolean {
34
- return this.registry.has(id);
44
+ return this.registry.get(id)?.kind === "llm";
35
45
  }
36
46
 
37
47
  /**
38
- * List all registered agents as live instances.
39
- *
40
- * Since this is a heterogeneous collection, we expose the widest safe
41
- * type parameters here.
48
+ * List all registered LLM agents as live instances.
42
49
  */
43
50
  list(): Agent<UnknownContext, AgentOutputType>[] {
44
- return Array.from(this.registry.values()) as Agent<
45
- UnknownContext,
46
- AgentOutputType
47
- >[];
51
+ return Array.from(this.registry.values()).filter(
52
+ (a): a is Agent<UnknownContext, AgentOutputType> => a.kind === "llm",
53
+ );
48
54
  }
49
55
 
50
56
  /**
@@ -1,7 +1,7 @@
1
1
  import type { LanguageModel } from "@kernl-sdk/protocol";
2
2
  import { resolveEmbeddingModel } from "@kernl-sdk/retrieval";
3
3
 
4
- import { Agent } from "@/agent";
4
+ import { BaseAgent } from "@/agent/base";
5
5
  import { UnknownContext } from "@/context";
6
6
  import { KernlHooks } from "@/lifecycle";
7
7
  import type { Thread } from "@/thread";
@@ -29,7 +29,7 @@ import type { KernlOptions, MemoryOptions, StorageOptions } from "./types";
29
29
  * tracing.
30
30
  */
31
31
  export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
32
- private readonly _agents: Map<string, Agent> = new Map();
32
+ private readonly _agents: Map<string, BaseAgent> = new Map();
33
33
  private readonly _models: Map<string, LanguageModel> = new Map();
34
34
 
35
35
  readonly storage: KernlStorage;
@@ -64,7 +64,7 @@ export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
64
64
  /**
65
65
  * Registers a new agent with the kernl instance.
66
66
  */
67
- register(agent: Agent): void {
67
+ register(agent: BaseAgent): void {
68
68
  this._agents.set(agent.id, agent);
69
69
  agent.bind(this);
70
70
 
@@ -87,12 +87,12 @@ export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
87
87
  }
88
88
  }
89
89
 
90
- // (TODO): implement exhaustive model registry in protocol/ package
91
- //
92
- // auto-populate model registry for storage hydration
93
- const key = `${agent.model.provider}/${agent.model.modelId}`;
94
- if (!this._models.has(key)) {
95
- this._models.set(key, agent.model);
90
+ // auto-populate model registry for storage hydration (llm agents only - for now)
91
+ if (agent.kind === "llm") {
92
+ const key = `${agent.model.provider}/${agent.model.modelId}`;
93
+ if (!this._models.has(key)) {
94
+ this._models.set(key, agent.model as LanguageModel);
95
+ }
96
96
  }
97
97
  }
98
98
 
@@ -1,7 +1,7 @@
1
1
  import { LanguageModel, EmbeddingModel } from "@kernl-sdk/protocol";
2
2
  import { SearchIndex } from "@kernl-sdk/retrieval";
3
3
 
4
- import { Agent } from "@/agent";
4
+ import { BaseAgent } from "@/agent/base";
5
5
  import { KernlStorage } from "@/storage";
6
6
 
7
7
  /**
@@ -87,10 +87,10 @@ export interface KernlOptions {
87
87
  /**
88
88
  * Agent registry interface.
89
89
  *
90
- * Satisfied by Map<string, Agent>.
90
+ * Satisfied by Map<string, BaseAgent>.
91
91
  */
92
92
  export interface AgentRegistry {
93
- get(id: string): Agent<any> | undefined;
93
+ get(id: string): BaseAgent<any> | undefined;
94
94
  }
95
95
 
96
96
  /**
@@ -63,7 +63,7 @@ describe("mcpToFunctionTool", () => {
63
63
  expect(functionTool.parameters).toBeDefined();
64
64
  });
65
65
 
66
- it("should handle tools without inputSchema (undefined parameters)", () => {
66
+ it("should handle tools without inputSchema (empty object parameters)", () => {
67
67
  const server = createMockServer();
68
68
  // In practice, MCP SDK tools require inputSchema, but our function handles
69
69
  // the case where it might not be present. We use 'as any' to test this edge case.
@@ -75,7 +75,9 @@ describe("mcpToFunctionTool", () => {
75
75
  const functionTool = mcpToFunctionTool(server, mcpTool);
76
76
 
77
77
  expect(functionTool.id).toBe("no_params");
78
- expect(functionTool.parameters).toBeUndefined();
78
+ // When no inputSchema, we use an empty z.object({}) to match AI SDK behavior
79
+ expect(functionTool.parameters).toBeDefined();
80
+ expect(functionTool.parameters?.def.type).toBe("object");
79
81
  });
80
82
 
81
83
  it("should invoke server.callTool with correct params", async () => {
package/src/mcp/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  import { UnknownContext } from "@/context";
4
- import { tool } from "@/tool";
4
+ import { tool } from "@/tool/tool";
5
5
 
6
6
  import { MCPServer } from "./base";
7
7
  import type { MCPTool, MCPToolFilter } from "./types";
@@ -0,0 +1,24 @@
1
+ import type { RealtimeModel } from "@kernl-sdk/protocol";
2
+
3
+ import type { UnknownContext } from "@/context";
4
+ import { BaseAgent } from "@/agent/base";
5
+
6
+ import type { RealtimeAgentConfig, RealtimeAgentVoiceConfig } from "./types";
7
+
8
+ /**
9
+ * A realtime agent definition.
10
+ *
11
+ * Stateless configuration that describes what a realtime voice agent does.
12
+ * Create sessions with `new RealtimeSession(agent, options)`.
13
+ */
14
+ export class RealtimeAgent<TContext = UnknownContext> extends BaseAgent<TContext> {
15
+ readonly kind = "realtime";
16
+ readonly model: RealtimeModel;
17
+ readonly voice?: RealtimeAgentVoiceConfig;
18
+
19
+ constructor(config: RealtimeAgentConfig<TContext>) {
20
+ super(config);
21
+ this.model = config.model;
22
+ this.voice = config.voice;
23
+ }
24
+ }
@@ -0,0 +1,32 @@
1
+ import { EventEmitter } from "node:events";
2
+
3
+ /**
4
+ * Base interface for audio I/O channels.
5
+ *
6
+ * Channels bridge between audio sources (browser mic, Twilio, Discord)
7
+ * and the realtime session. They handle audio capture/playback and emit
8
+ * events that the session listens to.
9
+ *
10
+ * Events emitted:
11
+ * - 'audio' (audio: string) - Raw audio chunk (base64)
12
+ * - 'commit' () - User finished speaking (VAD or manual)
13
+ * - 'interrupt' () - User started speaking mid-response
14
+ */
15
+ export interface RealtimeChannel extends EventEmitter {
16
+ /**
17
+ * Send audio to be played/transmitted by the channel.
18
+ * Called by session when audio is received from the model.
19
+ */
20
+ sendAudio(audio: string): void;
21
+
22
+ /**
23
+ * Interrupt current audio playback.
24
+ * Called by session when response is cancelled.
25
+ */
26
+ interrupt(): void;
27
+
28
+ /**
29
+ * Clean up resources and close the channel.
30
+ */
31
+ close(): void;
32
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export * from "./channel";
3
+ export * from "./agent";
4
+ export * from "./session";