kernl 0.12.3 → 0.12.6
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 +1 -1
- package/CHANGELOG.md +18 -0
- package/README.md +29 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +2 -0
- package/dist/kernl/kernl.d.ts +6 -0
- package/dist/kernl/kernl.d.ts.map +1 -1
- package/dist/kernl/kernl.js +19 -0
- package/dist/kernl/types.d.ts +6 -0
- package/dist/kernl/types.d.ts.map +1 -1
- package/dist/lib/env.d.ts +2 -2
- package/dist/mcp/http.d.ts.map +1 -1
- package/dist/mcp/http.js +1 -5
- package/dist/mcp/sse.d.ts.map +1 -1
- package/dist/mcp/sse.js +1 -5
- package/dist/mcp/stdio.d.ts.map +1 -1
- package/dist/mcp/stdio.js +1 -5
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +0 -1
- package/dist/thread/__tests__/thread.test.js +241 -0
- package/dist/thread/thread.d.ts +5 -4
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +91 -22
- package/dist/thread/types.d.ts +5 -0
- package/dist/thread/types.d.ts.map +1 -1
- package/dist/tool/tool.d.ts +2 -2
- package/dist/tool/tool.d.ts.map +1 -1
- package/dist/tracing/__tests__/composite.test.d.ts +2 -0
- package/dist/tracing/__tests__/composite.test.d.ts.map +1 -0
- package/dist/tracing/__tests__/composite.test.js +146 -0
- package/dist/tracing/__tests__/dispatch.test.d.ts +2 -0
- package/dist/tracing/__tests__/dispatch.test.d.ts.map +1 -0
- package/dist/tracing/__tests__/dispatch.test.js +160 -0
- package/dist/tracing/__tests__/helpers.d.ts +69 -0
- package/dist/tracing/__tests__/helpers.d.ts.map +1 -0
- package/dist/tracing/__tests__/helpers.js +109 -0
- package/dist/tracing/__tests__/integration.test.d.ts +2 -0
- package/dist/tracing/__tests__/integration.test.d.ts.map +1 -0
- package/dist/tracing/__tests__/integration.test.js +675 -0
- package/dist/tracing/__tests__/span.test.d.ts +2 -0
- package/dist/tracing/__tests__/span.test.d.ts.map +1 -0
- package/dist/tracing/__tests__/span.test.js +188 -0
- package/dist/tracing/dispatch.d.ts +43 -0
- package/dist/tracing/dispatch.d.ts.map +1 -0
- package/dist/tracing/dispatch.js +70 -0
- package/dist/tracing/index.d.ts +8 -0
- package/dist/tracing/index.d.ts.map +1 -0
- package/dist/tracing/index.js +6 -0
- package/dist/tracing/span.d.ts +69 -0
- package/dist/tracing/span.d.ts.map +1 -0
- package/dist/tracing/span.js +64 -0
- package/dist/tracing/subscriber.d.ts +53 -0
- package/dist/tracing/subscriber.d.ts.map +1 -0
- package/dist/tracing/subscriber.js +1 -0
- package/dist/tracing/subscribers/composite.d.ts +26 -0
- package/dist/tracing/subscribers/composite.d.ts.map +1 -0
- package/dist/tracing/subscribers/composite.js +96 -0
- package/dist/tracing/subscribers/console.d.ts +22 -0
- package/dist/tracing/subscribers/console.d.ts.map +1 -0
- package/dist/tracing/subscribers/console.js +82 -0
- package/dist/tracing/types.d.ts +77 -0
- package/dist/tracing/types.d.ts.map +1 -0
- package/dist/tracing/types.js +1 -0
- package/package.json +6 -2
- package/src/agent.ts +2 -0
- package/src/index.ts +1 -0
- package/src/kernl/kernl.ts +21 -0
- package/src/kernl/types.ts +7 -0
- package/src/mcp/http.ts +1 -9
- package/src/mcp/sse.ts +1 -10
- package/src/mcp/stdio.ts +1 -10
- package/src/task.ts +0 -1
- package/src/thread/__tests__/thread.test.ts +280 -0
- package/src/thread/thread.ts +111 -24
- package/src/thread/types.ts +5 -0
- package/src/tool/tool.ts +1 -1
- package/src/tracing/__tests__/composite.test.ts +218 -0
- package/src/tracing/__tests__/dispatch.test.ts +222 -0
- package/src/tracing/__tests__/helpers.ts +138 -0
- package/src/tracing/__tests__/integration.test.ts +808 -0
- package/src/tracing/__tests__/span.test.ts +250 -0
- package/src/tracing/dispatch.ts +114 -0
- package/src/tracing/index.ts +39 -0
- package/src/tracing/span.ts +115 -0
- package/src/tracing/subscriber.ts +62 -0
- package/src/tracing/subscribers/composite.ts +102 -0
- package/src/tracing/subscribers/console.ts +101 -0
- package/src/tracing/types.ts +115 -0
- package/dist/trace/processor.d.ts +0 -1
- package/dist/trace/processor.d.ts.map +0 -1
- package/dist/trace/processor.js +0 -1
- package/dist/trace/traces.d.ts +0 -1
- package/dist/trace/traces.d.ts.map +0 -1
- package/dist/trace/traces.js +0 -73
- package/dist/trace/utils.d.ts +0 -22
- package/dist/trace/utils.d.ts.map +0 -1
- package/dist/trace/utils.js +0 -30
- package/src/trace/processor.ts +0 -0
- package/src/trace/traces.ts +0 -86
- package/src/trace/utils.ts +0 -38
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
span,
|
|
5
|
+
event,
|
|
6
|
+
run,
|
|
7
|
+
current,
|
|
8
|
+
setSubscriber,
|
|
9
|
+
clearSubscriber,
|
|
10
|
+
getSubscriber,
|
|
11
|
+
} from "../dispatch";
|
|
12
|
+
import { NoopSpan, SpanImpl } from "../span";
|
|
13
|
+
import { TestSubscriber } from "./helpers";
|
|
14
|
+
|
|
15
|
+
describe("dispatch", () => {
|
|
16
|
+
let subscriber: TestSubscriber;
|
|
17
|
+
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
subscriber = new TestSubscriber();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
clearSubscriber();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("setSubscriber / clearSubscriber / getSubscriber", () => {
|
|
27
|
+
it("should set and get subscriber", () => {
|
|
28
|
+
expect(getSubscriber()).toBeNull();
|
|
29
|
+
setSubscriber(subscriber);
|
|
30
|
+
expect(getSubscriber()).toBe(subscriber);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should throw if subscriber already set", () => {
|
|
34
|
+
setSubscriber(subscriber);
|
|
35
|
+
expect(() => setSubscriber(new TestSubscriber())).toThrow(
|
|
36
|
+
"Global subscriber already set",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should allow re-setting after clear", () => {
|
|
41
|
+
setSubscriber(subscriber);
|
|
42
|
+
clearSubscriber();
|
|
43
|
+
expect(getSubscriber()).toBeNull();
|
|
44
|
+
|
|
45
|
+
const newSubscriber = new TestSubscriber();
|
|
46
|
+
setSubscriber(newSubscriber);
|
|
47
|
+
expect(getSubscriber()).toBe(newSubscriber);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("span", () => {
|
|
52
|
+
it("should return NoopSpan when no subscriber is set", () => {
|
|
53
|
+
const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
|
|
54
|
+
expect(s).toBeInstanceOf(NoopSpan);
|
|
55
|
+
expect(s.noop()).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should return NoopSpan when subscriber.enabled returns false", () => {
|
|
59
|
+
subscriber.enabledKinds = new Set(["model.call"]); // only model.call enabled
|
|
60
|
+
setSubscriber(subscriber);
|
|
61
|
+
|
|
62
|
+
const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
|
|
63
|
+
expect(s).toBeInstanceOf(NoopSpan);
|
|
64
|
+
expect(subscriber.spans.size).toBe(0);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should return SpanImpl when subscriber is set and enabled", () => {
|
|
68
|
+
setSubscriber(subscriber);
|
|
69
|
+
|
|
70
|
+
const s = span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
|
|
71
|
+
expect(s).toBeInstanceOf(SpanImpl);
|
|
72
|
+
expect(s.noop()).toBe(false);
|
|
73
|
+
expect(s.id).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should pass span data to subscriber", () => {
|
|
77
|
+
setSubscriber(subscriber);
|
|
78
|
+
|
|
79
|
+
const data = { kind: "thread" as const, threadId: "t1", agentId: "a1", namespace: "ns" };
|
|
80
|
+
span(data);
|
|
81
|
+
|
|
82
|
+
expect(subscriber.spans.size).toBe(1);
|
|
83
|
+
const [, captured] = [...subscriber.spans.entries()][0];
|
|
84
|
+
expect(captured.data).toEqual(data);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("should use null parent when parent=null", () => {
|
|
88
|
+
setSubscriber(subscriber);
|
|
89
|
+
|
|
90
|
+
span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" }, null);
|
|
91
|
+
|
|
92
|
+
const [, captured] = [...subscriber.spans.entries()][0];
|
|
93
|
+
expect(captured.parent).toBeNull();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should use explicit parent when provided", () => {
|
|
97
|
+
setSubscriber(subscriber);
|
|
98
|
+
|
|
99
|
+
span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" }, "parent_123");
|
|
100
|
+
|
|
101
|
+
const [, captured] = [...subscriber.spans.entries()][0];
|
|
102
|
+
expect(captured.parent).toBe("parent_123");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("should resolve parent from context when parent='current' (default)", () => {
|
|
106
|
+
setSubscriber(subscriber);
|
|
107
|
+
|
|
108
|
+
// Create parent span
|
|
109
|
+
const parentSpan = span(
|
|
110
|
+
{ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" },
|
|
111
|
+
null,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
// Create child span within run() context
|
|
115
|
+
run(parentSpan.id, () => {
|
|
116
|
+
span({ kind: "model.call", provider: "test", modelId: "m1" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const modelSpans = subscriber.spansOfKind("model.call");
|
|
120
|
+
expect(modelSpans).toHaveLength(1);
|
|
121
|
+
expect(modelSpans[0].parent).toBe(parentSpan.id);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should use null parent when parent='current' but no context", () => {
|
|
125
|
+
setSubscriber(subscriber);
|
|
126
|
+
|
|
127
|
+
// No run() context
|
|
128
|
+
span({ kind: "thread", threadId: "t1", agentId: "a1", namespace: "ns" });
|
|
129
|
+
|
|
130
|
+
const [, captured] = [...subscriber.spans.entries()][0];
|
|
131
|
+
expect(captured.parent).toBeNull();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("run / current", () => {
|
|
136
|
+
it("should return null when no context", () => {
|
|
137
|
+
expect(current()).toBeNull();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should return spanId within run context", () => {
|
|
141
|
+
const result = run("span_123", () => {
|
|
142
|
+
return current();
|
|
143
|
+
});
|
|
144
|
+
expect(result).toBe("span_123");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("should return to null after run completes", () => {
|
|
148
|
+
run("span_123", () => {
|
|
149
|
+
expect(current()).toBe("span_123");
|
|
150
|
+
});
|
|
151
|
+
expect(current()).toBeNull();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("should support nested run contexts", () => {
|
|
155
|
+
const results: (string | null)[] = [];
|
|
156
|
+
|
|
157
|
+
run("outer", () => {
|
|
158
|
+
results.push(current());
|
|
159
|
+
run("inner", () => {
|
|
160
|
+
results.push(current());
|
|
161
|
+
});
|
|
162
|
+
results.push(current());
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
expect(results).toEqual(["outer", "inner", "outer"]);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should handle null spanId in run", () => {
|
|
169
|
+
run("span_123", () => {
|
|
170
|
+
run(null, () => {
|
|
171
|
+
expect(current()).toBeNull();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("event", () => {
|
|
178
|
+
it("should do nothing when no subscriber is set", () => {
|
|
179
|
+
// Should not throw
|
|
180
|
+
event({ kind: "thread.error", message: "test" });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should emit event to subscriber", () => {
|
|
184
|
+
setSubscriber(subscriber);
|
|
185
|
+
|
|
186
|
+
event({ kind: "thread.error", message: "test error", stack: "stack" });
|
|
187
|
+
|
|
188
|
+
expect(subscriber.events).toHaveLength(1);
|
|
189
|
+
expect(subscriber.events[0].data).toEqual({
|
|
190
|
+
kind: "thread.error",
|
|
191
|
+
message: "test error",
|
|
192
|
+
stack: "stack",
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("should use null parent when parent=null", () => {
|
|
197
|
+
setSubscriber(subscriber);
|
|
198
|
+
|
|
199
|
+
event({ kind: "thread.error", message: "test" }, null);
|
|
200
|
+
|
|
201
|
+
expect(subscriber.events[0].parent).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should use explicit parent when provided", () => {
|
|
205
|
+
setSubscriber(subscriber);
|
|
206
|
+
|
|
207
|
+
event({ kind: "thread.error", message: "test" }, "parent_123");
|
|
208
|
+
|
|
209
|
+
expect(subscriber.events[0].parent).toBe("parent_123");
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should resolve parent from context when parent='current' (default)", () => {
|
|
213
|
+
setSubscriber(subscriber);
|
|
214
|
+
|
|
215
|
+
run("span_123", () => {
|
|
216
|
+
event({ kind: "thread.error", message: "test" });
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
expect(subscriber.events[0].parent).toBe("span_123");
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { SpanId, SpanData, EventData } from "../types";
|
|
2
|
+
import type { Subscriber } from "../subscriber";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A test subscriber that captures all tracing calls for assertions.
|
|
6
|
+
*/
|
|
7
|
+
export class TestSubscriber implements Subscriber {
|
|
8
|
+
private nextId = 0;
|
|
9
|
+
|
|
10
|
+
// Captured data
|
|
11
|
+
spans = new Map<SpanId, { data: SpanData; parent: SpanId | null }>();
|
|
12
|
+
recorded = new Map<SpanId, Partial<SpanData>[]>();
|
|
13
|
+
errors = new Map<SpanId, Error[]>();
|
|
14
|
+
events: { data: EventData; parent: SpanId | null }[] = [];
|
|
15
|
+
|
|
16
|
+
// Call log for verifying order
|
|
17
|
+
calls: { method: string; spanId?: SpanId; args?: unknown }[] = [];
|
|
18
|
+
|
|
19
|
+
// State tracking
|
|
20
|
+
entered = new Set<SpanId>();
|
|
21
|
+
exited = new Set<SpanId>();
|
|
22
|
+
closed = new Set<SpanId>();
|
|
23
|
+
|
|
24
|
+
// Configuration
|
|
25
|
+
enabledKinds: Set<SpanData["kind"]> | null = null; // null = all enabled
|
|
26
|
+
|
|
27
|
+
enabled(data: SpanData): boolean {
|
|
28
|
+
if (this.enabledKinds === null) return true;
|
|
29
|
+
return this.enabledKinds.has(data.kind);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
span(data: SpanData, parent: SpanId | null): SpanId {
|
|
33
|
+
const id = `test_span_${this.nextId++}`;
|
|
34
|
+
this.spans.set(id, { data, parent });
|
|
35
|
+
this.calls.push({ method: "span", spanId: id, args: { data, parent } });
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
enter(spanId: SpanId): void {
|
|
40
|
+
this.entered.add(spanId);
|
|
41
|
+
this.calls.push({ method: "enter", spanId });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exit(spanId: SpanId): void {
|
|
45
|
+
this.exited.add(spanId);
|
|
46
|
+
this.calls.push({ method: "exit", spanId });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
record(spanId: SpanId, delta: Partial<SpanData>): void {
|
|
50
|
+
const existing = this.recorded.get(spanId) ?? [];
|
|
51
|
+
existing.push(delta);
|
|
52
|
+
this.recorded.set(spanId, existing);
|
|
53
|
+
this.calls.push({ method: "record", spanId, args: delta });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
error(spanId: SpanId, err: Error): void {
|
|
57
|
+
const existing = this.errors.get(spanId) ?? [];
|
|
58
|
+
existing.push(err);
|
|
59
|
+
this.errors.set(spanId, existing);
|
|
60
|
+
this.calls.push({ method: "error", spanId, args: err });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
close(spanId: SpanId): void {
|
|
64
|
+
this.closed.add(spanId);
|
|
65
|
+
this.calls.push({ method: "close", spanId });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
event(data: EventData, parent: SpanId | null): void {
|
|
69
|
+
this.events.push({ data, parent });
|
|
70
|
+
this.calls.push({ method: "event", args: { data, parent } });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async flush(): Promise<void> {
|
|
74
|
+
this.calls.push({ method: "flush" });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async shutdown(_timeout?: number): Promise<void> {
|
|
78
|
+
this.calls.push({ method: "shutdown" });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// --- Test helpers ---
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get all spans of a specific kind.
|
|
85
|
+
*/
|
|
86
|
+
spansOfKind<K extends SpanData["kind"]>(
|
|
87
|
+
kind: K,
|
|
88
|
+
): Array<{ id: SpanId; data: Extract<SpanData, { kind: K }>; parent: SpanId | null }> {
|
|
89
|
+
const result: Array<{ id: SpanId; data: Extract<SpanData, { kind: K }>; parent: SpanId | null }> = [];
|
|
90
|
+
for (const [id, { data, parent }] of this.spans) {
|
|
91
|
+
if (data.kind === kind) {
|
|
92
|
+
result.push({ id, data: data as Extract<SpanData, { kind: K }>, parent });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get events of a specific kind.
|
|
100
|
+
*/
|
|
101
|
+
eventsOfKind<K extends EventData["kind"]>(
|
|
102
|
+
kind: K,
|
|
103
|
+
): Array<{ data: Extract<EventData, { kind: K }>; parent: SpanId | null }> {
|
|
104
|
+
return this.events.filter((e) => e.data.kind === kind) as Array<{
|
|
105
|
+
data: Extract<EventData, { kind: K }>;
|
|
106
|
+
parent: SpanId | null;
|
|
107
|
+
}>;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get recorded data for a span.
|
|
112
|
+
*/
|
|
113
|
+
getRecorded(spanId: SpanId): Partial<SpanData>[] {
|
|
114
|
+
return this.recorded.get(spanId) ?? [];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if a span was fully completed (entered, exited, closed).
|
|
119
|
+
*/
|
|
120
|
+
isComplete(spanId: SpanId): boolean {
|
|
121
|
+
return this.entered.has(spanId) && this.exited.has(spanId) && this.closed.has(spanId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Reset all captured data.
|
|
126
|
+
*/
|
|
127
|
+
reset(): void {
|
|
128
|
+
this.nextId = 0;
|
|
129
|
+
this.spans.clear();
|
|
130
|
+
this.recorded.clear();
|
|
131
|
+
this.errors.clear();
|
|
132
|
+
this.events = [];
|
|
133
|
+
this.calls = [];
|
|
134
|
+
this.entered.clear();
|
|
135
|
+
this.exited.clear();
|
|
136
|
+
this.closed.clear();
|
|
137
|
+
}
|
|
138
|
+
}
|