kernl 0.12.0 → 0.12.2

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 (51) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +21 -0
  3. package/dist/api/resources/agents/agents.d.ts +2 -2
  4. package/dist/api/resources/agents/agents.d.ts.map +1 -1
  5. package/dist/api/resources/agents/agents.js +1 -1
  6. package/dist/index.d.ts +3 -2
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/kernl/index.d.ts +2 -1
  10. package/dist/kernl/index.d.ts.map +1 -1
  11. package/dist/kernl/index.js +1 -0
  12. package/dist/kernl/kernl.d.ts +4 -4
  13. package/dist/kernl/kernl.d.ts.map +1 -1
  14. package/dist/kernl/kernl.js +17 -13
  15. package/dist/kernl/registry.d.ts +46 -0
  16. package/dist/kernl/registry.d.ts.map +1 -0
  17. package/dist/kernl/registry.js +57 -0
  18. package/dist/kernl/types.d.ts +2 -2
  19. package/dist/kernl/types.d.ts.map +1 -1
  20. package/dist/storage/base.d.ts +3 -3
  21. package/dist/storage/base.d.ts.map +1 -1
  22. package/dist/storage/in-memory.d.ts +5 -5
  23. package/dist/storage/in-memory.d.ts.map +1 -1
  24. package/dist/thread/__tests__/mock.d.ts +2 -3
  25. package/dist/thread/__tests__/mock.d.ts.map +1 -1
  26. package/dist/thread/thread.d.ts +4 -0
  27. package/dist/thread/thread.d.ts.map +1 -1
  28. package/dist/thread/thread.js +43 -75
  29. package/dist/thread/types.d.ts +18 -3
  30. package/dist/thread/types.d.ts.map +1 -1
  31. package/dist/thread/utils.d.ts +7 -7
  32. package/dist/thread/utils.d.ts.map +1 -1
  33. package/dist/thread/utils.js +1 -1
  34. package/package.json +4 -6
  35. package/src/api/__tests__/threads.test.ts +3 -3
  36. package/src/api/resources/agents/agents.ts +3 -3
  37. package/src/index.ts +4 -2
  38. package/src/kernl/index.ts +3 -2
  39. package/src/kernl/kernl.ts +18 -15
  40. package/src/kernl/registry.ts +69 -0
  41. package/src/kernl/types.ts +2 -2
  42. package/src/storage/base.ts +2 -2
  43. package/src/storage/in-memory.ts +4 -4
  44. package/src/thread/__tests__/mock.ts +2 -2
  45. package/src/thread/thread.ts +47 -78
  46. package/src/thread/types.ts +49 -3
  47. package/src/thread/utils.ts +14 -7
  48. package/dist/thread/__tests__/integration.test.d.ts +0 -2
  49. package/dist/thread/__tests__/integration.test.d.ts.map +0 -1
  50. package/dist/thread/__tests__/integration.test.js +0 -320
  51. package/src/thread/__tests__/integration.test.ts +0 -434
@@ -25,7 +25,7 @@ import type {
25
25
  SortOrder,
26
26
  } from "@/storage";
27
27
  import type { ThreadEvent, ThreadState } from "@/thread/types";
28
- import type { AgentRegistry, ModelRegistry } from "@/kernl/types";
28
+ import type { IAgentRegistry, IModelRegistry } from "@/kernl/types";
29
29
  import type {
30
30
  MemoryStore,
31
31
  MemoryRecord,
@@ -47,7 +47,7 @@ export class InMemoryStorage implements KernlStorage {
47
47
  this.memories = new InMemoryMemoryStore();
48
48
  }
49
49
 
50
- bind(registries: { agents: AgentRegistry; models: ModelRegistry }): void {
50
+ bind(registries: { agents: IAgentRegistry; models: IModelRegistry }): void {
51
51
  this.threads.bind(registries);
52
52
  }
53
53
 
@@ -93,10 +93,10 @@ interface ThreadData {
93
93
  export class InMemoryThreadStore implements ThreadStore {
94
94
  private threads = new Map<string, ThreadData>();
95
95
  private events = new Map<string, ThreadEvent[]>(); // tid -> events
96
- private registries: { agents: AgentRegistry; models: ModelRegistry } | null =
96
+ private registries: { agents: IAgentRegistry; models: IModelRegistry } | null =
97
97
  null;
98
98
 
99
- bind(registries: { agents: AgentRegistry; models: ModelRegistry }): void {
99
+ bind(registries: { agents: IAgentRegistry; models: IModelRegistry }): void {
100
100
  this.registries = registries;
101
101
  }
102
102
 
@@ -4,8 +4,8 @@ import {
4
4
  LanguageModelResponse,
5
5
  LanguageModelResponseItem,
6
6
  LanguageModelItem,
7
+ LanguageModelStreamEvent,
7
8
  } from "@kernl-sdk/protocol";
8
- import type { ThreadStreamEvent } from "@/thread/types";
9
9
 
10
10
  /**
11
11
  * A mock language model that echoes the user input back as an assistant message.
@@ -54,7 +54,7 @@ export class MockLanguageModel implements LanguageModel {
54
54
 
55
55
  async *stream(
56
56
  request: LanguageModelRequest,
57
- ): AsyncIterable<ThreadStreamEvent> {
57
+ ): AsyncIterable<LanguageModelStreamEvent> {
58
58
  // TODO: Implement streaming (not needed for hello world)
59
59
  throw new Error("MockLanguageModel.stream() not implemented yet");
60
60
  }
@@ -19,6 +19,7 @@ import {
19
19
  LanguageModel,
20
20
  LanguageModelItem,
21
21
  LanguageModelRequest,
22
+ LanguageModelStreamEvent,
22
23
  type LanguageModelUsage,
23
24
  type LanguageModelFinishReason,
24
25
  } from "@kernl-sdk/protocol";
@@ -33,6 +34,7 @@ import type {
33
34
  ThreadStreamEvent,
34
35
  ThreadExecuteResult,
35
36
  PerformActionsResult,
37
+ PublicThreadEvent,
36
38
  } from "./types";
37
39
  import type { AgentOutputType } from "@/agent/types";
38
40
  import type { LanguageModelResponseType } from "@kernl-sdk/protocol";
@@ -176,35 +178,15 @@ export class Thread<
176
178
 
177
179
  await this.checkpoint(); /* c1: persist RUNNING state + initial input */
178
180
 
179
- this.agent.emit("thread.start", {
180
- kind: "thread.start",
181
- threadId: this.tid,
182
- agentId: this.agent.id,
183
- namespace: this.namespace,
184
- context: this.context,
185
- });
181
+ this.emit("thread.start");
186
182
 
187
183
  yield { kind: "stream.start" }; // always yield start immediately
188
184
 
189
185
  try {
190
186
  yield* this._execute();
191
-
192
- this.agent.emit("thread.stop", {
193
- kind: "thread.stop",
194
- threadId: this.tid,
195
- agentId: this.agent.id,
196
- namespace: this.namespace,
197
- context: this.context,
198
- state: STOPPED,
199
- result: this.tickres,
200
- });
187
+ this.emit("thread.stop", { state: STOPPED, result: this.tickres });
201
188
  } catch (err) {
202
- this.agent.emit("thread.stop", {
203
- kind: "thread.stop",
204
- threadId: this.tid,
205
- agentId: this.agent.id,
206
- namespace: this.namespace,
207
- context: this.context,
189
+ this.emit("thread.stop", {
208
190
  state: STOPPED,
209
191
  error: err instanceof Error ? err.message : String(err),
210
192
  });
@@ -236,12 +218,14 @@ export class Thread<
236
218
  err = e.error;
237
219
  logger.error(e.error); // (TODO): onError callback in options
238
220
  }
239
- // we don't want deltas in the history
221
+ // complete items get persisted with seq, deltas are ephemeral
240
222
  if (notDelta(e)) {
241
- events.push(e);
242
- this.append(e);
223
+ const [seqd] = this.append(e);
224
+ events.push(seqd);
225
+ yield seqd;
226
+ } else {
227
+ yield e;
243
228
  }
244
- yield e;
245
229
  }
246
230
 
247
231
  // if an error event occurred → throw it
@@ -267,10 +251,10 @@ export class Thread<
267
251
  const { actions, pendingApprovals } =
268
252
  await this.performActions(intentions);
269
253
 
270
- // append + yield action events
254
+ // append + yield action events (sequenced)
271
255
  for (const a of actions) {
272
- this.append(a);
273
- yield a;
256
+ const [seqd] = this.append(a);
257
+ yield seqd;
274
258
  }
275
259
 
276
260
  await this.checkpoint(); /* c3: tick complete */
@@ -293,23 +277,16 @@ export class Thread<
293
277
  * NOTE: Streaming structured outputs deferred until concrete use cases emerge.
294
278
  * For now, we stream text-delta and tool events, final validation happens in _execute().
295
279
  */
296
- private async *tick(): AsyncGenerator<ThreadStreamEvent> {
280
+ private async *tick(): AsyncGenerator<LanguageModelStreamEvent> {
297
281
  this._tick++;
298
282
 
299
283
  // (TODO): check limits (if this._tick > this.limits.maxTicks)
300
284
  // (TODO): run input guardrails on first tick (if this._tick === 1)
285
+ // (TODO): compaction if necessary
301
286
 
302
287
  const req = await this.prepareModelRequest(this.history);
303
288
 
304
- this.agent.emit("model.call.start", {
305
- kind: "model.call.start",
306
- provider: this.model.provider,
307
- modelId: this.model.modelId,
308
- settings: req.settings ?? {},
309
- threadId: this.tid,
310
- agentId: this.agent.id,
311
- context: this.context,
312
- });
289
+ this.emit("model.call.start", { settings: req.settings ?? {} });
313
290
 
314
291
  let usage: LanguageModelUsage | undefined;
315
292
  let finishReason: LanguageModelFinishReason = "unknown";
@@ -333,27 +310,9 @@ export class Thread<
333
310
  }
334
311
  }
335
312
 
336
- this.agent.emit("model.call.end", {
337
- kind: "model.call.end",
338
- provider: this.model.provider,
339
- modelId: this.model.modelId,
340
- finishReason,
341
- usage,
342
- threadId: this.tid,
343
- agentId: this.agent.id,
344
- context: this.context,
345
- });
313
+ this.emit("model.call.end", { finishReason, usage });
346
314
  } catch (error) {
347
- this.agent.emit("model.call.end", {
348
- kind: "model.call.end",
349
- provider: this.model.provider,
350
- modelId: this.model.modelId,
351
- finishReason: "error",
352
- threadId: this.tid,
353
- agentId: this.agent.id,
354
- context: this.context,
355
- });
356
-
315
+ this.emit("model.call.end", { finishReason: "error" });
357
316
  yield {
358
317
  kind: "error",
359
318
  error: error instanceof Error ? error : new Error(String(error)),
@@ -439,9 +398,31 @@ export class Thread<
439
398
  this.abort?.abort();
440
399
  }
441
400
 
442
- // ----------------------------
443
- // utils
444
- // ----------------------------
401
+ /**
402
+ * Emit an agent event with common fields auto-filled.
403
+ */
404
+ private emit(kind: string, payload?: Record<string, unknown>): void {
405
+ const base = {
406
+ kind,
407
+ threadId: this.tid,
408
+ agentId: this.agent.id,
409
+ context: this.context,
410
+ };
411
+
412
+ let auto = {};
413
+ switch (kind) {
414
+ case "thread.start":
415
+ case "thread.stop":
416
+ auto = { namespace: this.namespace };
417
+ break;
418
+ case "model.call.start":
419
+ case "model.call.end":
420
+ auto = { provider: this.model.provider, modelId: this.model.modelId };
421
+ break;
422
+ }
423
+
424
+ this.agent.emit(kind as any, { ...base, ...auto, ...payload } as any);
425
+ }
445
426
 
446
427
  /**
447
428
  * Perform the actions returned by the model
@@ -497,11 +478,7 @@ export class Thread<
497
478
  calls.map(async (call: ToolCall) => {
498
479
  const parsedArgs = JSON.parse(call.arguments || "{}");
499
480
 
500
- this.agent.emit("tool.call.start", {
501
- kind: "tool.call.start",
502
- threadId: this.tid,
503
- agentId: this.agent.id,
504
- context: this.context,
481
+ this.emit("tool.call.start", {
505
482
  toolId: call.toolId,
506
483
  callId: call.callId,
507
484
  args: parsedArgs,
@@ -526,11 +503,7 @@ export class Thread<
526
503
  ctx.approve(call.callId); // mark this call as approved
527
504
  const res = await tool.invoke(ctx, call.arguments, call.callId);
528
505
 
529
- this.agent.emit("tool.call.end", {
530
- kind: "tool.call.end",
531
- threadId: this.tid,
532
- agentId: this.agent.id,
533
- context: this.context,
506
+ this.emit("tool.call.end", {
534
507
  toolId: call.toolId,
535
508
  callId: call.callId,
536
509
  state: res.state,
@@ -550,11 +523,7 @@ export class Thread<
550
523
  error: res.error,
551
524
  };
552
525
  } catch (error) {
553
- this.agent.emit("tool.call.end", {
554
- kind: "tool.call.end",
555
- threadId: this.tid,
556
- agentId: this.agent.id,
557
- context: this.context,
526
+ this.emit("tool.call.end", {
558
527
  toolId: call.toolId,
559
528
  callId: call.callId,
560
529
  state: FAILED,
@@ -2,13 +2,27 @@ import {
2
2
  ToolCall,
3
3
  LanguageModel,
4
4
  LanguageModelItem,
5
- LanguageModelStreamEvent,
6
5
  RUNNING,
7
6
  STOPPED,
8
7
  INTERRUPTIBLE,
9
8
  UNINTERRUPTIBLE,
10
9
  ZOMBIE,
11
10
  DEAD,
11
+ // Stream event types
12
+ TextStartEvent,
13
+ TextEndEvent,
14
+ TextDeltaEvent,
15
+ ReasoningStartEvent,
16
+ ReasoningEndEvent,
17
+ ReasoningDeltaEvent,
18
+ ToolInputStartEvent,
19
+ ToolInputEndEvent,
20
+ ToolInputDeltaEvent,
21
+ StartEvent,
22
+ FinishEvent,
23
+ AbortEvent,
24
+ ErrorEvent,
25
+ RawEvent,
12
26
  } from "@kernl-sdk/protocol";
13
27
 
14
28
  import { Task } from "@/task";
@@ -128,9 +142,41 @@ export type ThreadEvent =
128
142
  | ThreadSystemEvent;
129
143
 
130
144
  /**
131
- * Stream events - use protocol definition directly.
145
+ * Incremental content chunks (ephemeral, not persisted).
132
146
  */
133
- export type ThreadStreamEvent = LanguageModelStreamEvent;
147
+ export type StreamDeltaEvent =
148
+ | TextDeltaEvent
149
+ | ReasoningDeltaEvent
150
+ | ToolInputDeltaEvent;
151
+
152
+ /**
153
+ * Boundary markers + control flow (ephemeral, not persisted).
154
+ */
155
+ export type StreamControlEvent =
156
+ | TextStartEvent
157
+ | TextEndEvent
158
+ | ReasoningStartEvent
159
+ | ReasoningEndEvent
160
+ | ToolInputStartEvent
161
+ | ToolInputEndEvent
162
+ | StartEvent
163
+ | FinishEvent
164
+ | AbortEvent
165
+ | ErrorEvent
166
+ | RawEvent;
167
+
168
+ /**
169
+ * All ephemeral stream types (not persisted to history).
170
+ */
171
+ export type StreamEvent = StreamDeltaEvent | StreamControlEvent;
172
+
173
+ /**
174
+ * Thread stream events = sequenced ThreadEvents + ephemeral StreamEvents.
175
+ *
176
+ * Complete items (Message, ToolCall, etc.) are yielded as ThreadEvents with seq.
177
+ * Deltas and control events are yielded as StreamEvents without seq.
178
+ */
179
+ export type ThreadStreamEvent = ThreadEvent | StreamEvent;
134
180
 
135
181
  /**
136
182
  * Result of thread execution
@@ -4,7 +4,11 @@ import type { ResolvedAgentResponse } from "@/guardrail";
4
4
 
5
5
  /* lib */
6
6
  import { json, randomID } from "@kernl-sdk/shared/lib";
7
- import { ToolCall, LanguageModelItem } from "@kernl-sdk/protocol";
7
+ import {
8
+ ToolCall,
9
+ LanguageModelItem,
10
+ LanguageModelStreamEvent,
11
+ } from "@kernl-sdk/protocol";
8
12
  import { ModelBehaviorError } from "@/lib/error";
9
13
 
10
14
  /* types */
@@ -12,7 +16,6 @@ import type { AgentOutputType } from "@/agent/types";
12
16
  import type {
13
17
  ThreadEvent,
14
18
  ThreadEventBase,
15
- ThreadStreamEvent,
16
19
  ActionSet,
17
20
  PublicThreadEvent,
18
21
  } from "./types";
@@ -21,7 +24,7 @@ import type {
21
24
  * Create a ThreadEvent from a LanguageModelItem with thread metadata.
22
25
  *
23
26
  * @example
24
- * ```typescript
27
+ * ```ts
25
28
  * tevent({
26
29
  * kind: "message",
27
30
  * seq: 0,
@@ -57,7 +60,9 @@ export function tevent(event: {
57
60
  /**
58
61
  * Check if an event is a tool call
59
62
  */
60
- export function isActionIntention(event: LanguageModelItem): event is ToolCall {
63
+ export function isActionIntention(
64
+ event: ThreadEvent,
65
+ ): event is ToolCall & ThreadEventBase {
61
66
  return event.kind === "tool.call";
62
67
  }
63
68
 
@@ -65,7 +70,7 @@ export function isActionIntention(event: LanguageModelItem): event is ToolCall {
65
70
  * Extract action intentions from a list of events.
66
71
  * Returns ActionSet if there are any tool calls, null otherwise.
67
72
  */
68
- export function getIntentions(events: LanguageModelItem[]): ActionSet | null {
73
+ export function getIntentions(events: ThreadEvent[]): ActionSet | null {
69
74
  const toolCalls = events.filter(isActionIntention);
70
75
  return toolCalls.length > 0 ? { toolCalls } : null;
71
76
  }
@@ -74,7 +79,9 @@ export function getIntentions(events: LanguageModelItem[]): ActionSet | null {
74
79
  * Check if an event is NOT a delta/start/end event (i.e., a complete item).
75
80
  * Returns true for complete items: Message, Reasoning, ToolCall, ToolResult
76
81
  */
77
- export function notDelta(event: ThreadStreamEvent): event is LanguageModelItem {
82
+ export function notDelta(
83
+ event: LanguageModelStreamEvent,
84
+ ): event is LanguageModelItem {
78
85
  switch (event.kind) {
79
86
  case "message":
80
87
  case "reasoning":
@@ -112,7 +119,7 @@ export function isPublicEvent(event: ThreadEvent): event is PublicThreadEvent {
112
119
  * Extract the final text response from a list of items.
113
120
  * Returns null if no assistant message with text content is found.
114
121
  */
115
- export function getFinalResponse(items: LanguageModelItem[]): string | null {
122
+ export function getFinalResponse(items: ThreadEvent[]): string | null {
116
123
  // scan backwards for the last assistant message
117
124
  for (let i = items.length - 1; i >= 0; i--) {
118
125
  const item = items[i];
@@ -1,2 +0,0 @@
1
- import "@kernl-sdk/ai/openai";
2
- //# sourceMappingURL=integration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../../../src/thread/__tests__/integration.test.ts"],"names":[],"mappings":"AAIA,OAAO,sBAAsB,CAAC"}