kernl 0.7.4 → 0.8.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 +1 -1
- package/CHANGELOG.md +27 -1
- package/dist/agent/types.d.ts +20 -12
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent.d.ts +7 -7
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +3 -14
- package/dist/api/resources/agents/agents.d.ts +5 -5
- package/dist/api/resources/agents/agents.d.ts.map +1 -1
- package/dist/api/resources/agents/agents.js +1 -1
- package/dist/guardrail.d.ts +19 -19
- package/dist/guardrail.d.ts.map +1 -1
- package/dist/kernl/kernl.d.ts +6 -6
- package/dist/kernl/kernl.d.ts.map +1 -1
- package/dist/lib/error.d.ts +3 -3
- package/dist/lib/error.d.ts.map +1 -1
- package/dist/lifecycle.d.ts +6 -6
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/memory/__tests__/encoder.test.d.ts +2 -0
- package/dist/memory/__tests__/encoder.test.d.ts.map +1 -0
- package/dist/memory/__tests__/encoder.test.js +121 -0
- package/dist/memory/codecs/domain.d.ts +6 -0
- package/dist/memory/codecs/domain.d.ts.map +1 -1
- package/dist/memory/codecs/domain.js +6 -0
- package/dist/memory/codecs/tpuf.d.ts.map +1 -1
- package/dist/memory/codecs/tpuf.js +2 -5
- package/dist/memory/encoder.d.ts +25 -2
- package/dist/memory/encoder.d.ts.map +1 -1
- package/dist/memory/encoder.js +46 -5
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/schema.js +5 -4
- package/dist/memory/types.d.ts +2 -1
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/thread/__tests__/integration.test.d.ts +1 -1
- package/dist/thread/__tests__/integration.test.d.ts.map +1 -1
- package/dist/thread/__tests__/integration.test.js +10 -5
- package/dist/thread/__tests__/thread.test.js +8 -8
- package/dist/thread/thread.d.ts +5 -5
- package/dist/thread/thread.d.ts.map +1 -1
- package/dist/thread/thread.js +13 -2
- package/dist/thread/types.d.ts +9 -6
- package/dist/thread/types.d.ts.map +1 -1
- package/dist/thread/utils.d.ts +7 -6
- package/dist/thread/utils.d.ts.map +1 -1
- package/dist/thread/utils.js +9 -8
- package/package.json +5 -4
- package/src/agent/types.ts +25 -29
- package/src/agent.ts +15 -28
- package/src/api/resources/agents/agents.ts +8 -8
- package/src/guardrail.ts +28 -28
- package/src/kernl/kernl.ts +12 -12
- package/src/lib/error.ts +3 -3
- package/src/lifecycle.ts +6 -6
- package/src/memory/__tests__/encoder.test.ts +154 -0
- package/src/memory/codecs/domain.ts +6 -0
- package/src/memory/codecs/tpuf.ts +2 -5
- package/src/memory/encoder.ts +51 -6
- package/src/memory/index.ts +1 -1
- package/src/memory/schema.ts +5 -5
- package/src/memory/types.ts +2 -1
- package/src/thread/__tests__/integration.test.ts +130 -146
- package/src/thread/__tests__/thread.test.ts +8 -8
- package/src/thread/thread.ts +21 -7
- package/src/thread/types.ts +9 -6
- package/src/thread/utils.ts +15 -14
package/src/lifecycle.ts
CHANGED
|
@@ -5,8 +5,8 @@ import { Context, UnknownContext } from "./context";
|
|
|
5
5
|
import { Tool } from "./tool";
|
|
6
6
|
import type { ToolCall } from "@kernl-sdk/protocol";
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { AgentOutputType } from "@/agent/types";
|
|
9
|
+
import { TextOutput } from "@/thread/types";
|
|
10
10
|
|
|
11
11
|
export type EventEmitterEvents = Record<string, any[]>;
|
|
12
12
|
|
|
@@ -60,7 +60,7 @@ class TypedEventEmitter<
|
|
|
60
60
|
|
|
61
61
|
export type AgentHookEvents<
|
|
62
62
|
TContext = UnknownContext,
|
|
63
|
-
TOutput extends
|
|
63
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
64
64
|
> = {
|
|
65
65
|
/**
|
|
66
66
|
* @param context - The context of the run
|
|
@@ -107,7 +107,7 @@ export type AgentHookEvents<
|
|
|
107
107
|
*/
|
|
108
108
|
export class AgentHooks<
|
|
109
109
|
TContext = UnknownContext,
|
|
110
|
-
TOutput extends
|
|
110
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
111
111
|
> extends TypedEventEmitter<AgentHookEvents<TContext, TOutput>> {}
|
|
112
112
|
|
|
113
113
|
/**
|
|
@@ -119,7 +119,7 @@ export class AgentHooks<
|
|
|
119
119
|
*/
|
|
120
120
|
export type KernlHookEvents<
|
|
121
121
|
TContext = UnknownContext,
|
|
122
|
-
TOutput extends
|
|
122
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
123
123
|
> = {
|
|
124
124
|
/**
|
|
125
125
|
* @param context - The context of the run
|
|
@@ -177,5 +177,5 @@ export type KernlHookEvents<
|
|
|
177
177
|
*/
|
|
178
178
|
export class KernlHooks<
|
|
179
179
|
TContext = UnknownContext,
|
|
180
|
-
TOutput extends
|
|
180
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
181
181
|
> extends TypedEventEmitter<KernlHookEvents<TContext, TOutput>> {}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import type { EmbeddingModel } from "@kernl-sdk/protocol";
|
|
3
|
+
|
|
4
|
+
import { MemoryByteEncoder, ObjectTextCodec } from "../encoder";
|
|
5
|
+
import type { MemoryByte } from "../types";
|
|
6
|
+
|
|
7
|
+
// Mock embedder that returns predictable vectors
|
|
8
|
+
function createMockEmbedder(): EmbeddingModel<string> {
|
|
9
|
+
return {
|
|
10
|
+
provider: "test",
|
|
11
|
+
modelId: "test-embedder",
|
|
12
|
+
embed: vi.fn(async ({ values }: { values: string[] }) => ({
|
|
13
|
+
embeddings: values.map((v) => [v.length, 0, 0]), // simple: [length, 0, 0]
|
|
14
|
+
})),
|
|
15
|
+
} as unknown as EmbeddingModel<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("ObjectTextCodec", () => {
|
|
19
|
+
it("encodes simple object to YAML", () => {
|
|
20
|
+
const obj = { name: "Tony", preference: "coffee" };
|
|
21
|
+
const result = ObjectTextCodec.encode(obj);
|
|
22
|
+
|
|
23
|
+
expect(result).toContain("name: Tony");
|
|
24
|
+
expect(result).toContain("preference: coffee");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("sorts keys for determinism", () => {
|
|
28
|
+
const obj1 = { z: 1, a: 2, m: 3 };
|
|
29
|
+
const obj2 = { a: 2, m: 3, z: 1 };
|
|
30
|
+
|
|
31
|
+
expect(ObjectTextCodec.encode(obj1)).toBe(ObjectTextCodec.encode(obj2));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("truncates long objects with ellipsis", () => {
|
|
35
|
+
const longValue = "x".repeat(4000);
|
|
36
|
+
const obj = { data: longValue };
|
|
37
|
+
const result = ObjectTextCodec.encode(obj);
|
|
38
|
+
|
|
39
|
+
expect(result.length).toBeLessThanOrEqual(3005); // 3000 + "\n..."
|
|
40
|
+
expect(result.endsWith("\n...")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("handles nested objects", () => {
|
|
44
|
+
const obj = {
|
|
45
|
+
user: {
|
|
46
|
+
name: "Tony",
|
|
47
|
+
prefs: { coffee: { shots: 2 } },
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
const result = ObjectTextCodec.encode(obj);
|
|
51
|
+
|
|
52
|
+
expect(result).toContain("user:");
|
|
53
|
+
expect(result).toContain("name: Tony");
|
|
54
|
+
expect(result).toContain("shots: 2");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("handles arrays", () => {
|
|
58
|
+
const obj = { items: ["a", "b", "c"] };
|
|
59
|
+
const result = ObjectTextCodec.encode(obj);
|
|
60
|
+
|
|
61
|
+
expect(result).toContain("items:");
|
|
62
|
+
expect(result).toContain("- a");
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("MemoryByteEncoder", () => {
|
|
67
|
+
describe("encode", () => {
|
|
68
|
+
it("encodes text-only content", async () => {
|
|
69
|
+
const embedder = createMockEmbedder();
|
|
70
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
71
|
+
|
|
72
|
+
const byte: MemoryByte = { text: "Hello world" };
|
|
73
|
+
const result = await encoder.encode(byte);
|
|
74
|
+
|
|
75
|
+
expect(result.text).toBe("Hello world");
|
|
76
|
+
expect(result.objtext).toBeUndefined();
|
|
77
|
+
expect(result.tvec).toBeDefined();
|
|
78
|
+
expect(embedder.embed).toHaveBeenCalledWith({ values: ["Hello world"] });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("encodes object-only content with projection", async () => {
|
|
82
|
+
const embedder = createMockEmbedder();
|
|
83
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
84
|
+
|
|
85
|
+
const byte: MemoryByte = {
|
|
86
|
+
object: { preference: "coffee", shots: 2 },
|
|
87
|
+
};
|
|
88
|
+
const result = await encoder.encode(byte);
|
|
89
|
+
|
|
90
|
+
// text falls back to objtext projection
|
|
91
|
+
expect(result.text).toContain("preference: coffee");
|
|
92
|
+
expect(result.objtext).toContain("preference: coffee");
|
|
93
|
+
expect(result.tvec).toBeDefined();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("combines text and object for embedding", async () => {
|
|
97
|
+
const embedder = createMockEmbedder();
|
|
98
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
99
|
+
|
|
100
|
+
const byte: MemoryByte = {
|
|
101
|
+
text: "Tony likes coffee",
|
|
102
|
+
object: { shots: 2, sugar: false },
|
|
103
|
+
};
|
|
104
|
+
const result = await encoder.encode(byte);
|
|
105
|
+
|
|
106
|
+
expect(result.text).toBe("Tony likes coffee");
|
|
107
|
+
expect(result.objtext).toContain("shots: 2");
|
|
108
|
+
|
|
109
|
+
// embedding should be called with combined text
|
|
110
|
+
const embedCall = (embedder.embed as ReturnType<typeof vi.fn>).mock
|
|
111
|
+
.calls[0][0];
|
|
112
|
+
expect(embedCall.values[0]).toContain("Tony likes coffee");
|
|
113
|
+
expect(embedCall.values[0]).toContain("shots: 2");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("does not include metadata (lives in primary DB only)", async () => {
|
|
117
|
+
const embedder = createMockEmbedder();
|
|
118
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
119
|
+
|
|
120
|
+
const byte: MemoryByte = {
|
|
121
|
+
text: "test",
|
|
122
|
+
object: { key: "value" },
|
|
123
|
+
};
|
|
124
|
+
const result = await encoder.encode(byte);
|
|
125
|
+
|
|
126
|
+
// metadata is not part of IndexableByte - it stays in the primary DB
|
|
127
|
+
expect("metadata" in result).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("returns undefined tvec when no content", async () => {
|
|
131
|
+
const embedder = createMockEmbedder();
|
|
132
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
133
|
+
|
|
134
|
+
const byte: MemoryByte = {};
|
|
135
|
+
const result = await encoder.encode(byte);
|
|
136
|
+
|
|
137
|
+
expect(result.text).toBeUndefined();
|
|
138
|
+
expect(result.objtext).toBeUndefined();
|
|
139
|
+
expect(result.tvec).toBeUndefined();
|
|
140
|
+
expect(embedder.embed).not.toHaveBeenCalled();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("embed", () => {
|
|
145
|
+
it("exposes embed method for query embedding", async () => {
|
|
146
|
+
const embedder = createMockEmbedder();
|
|
147
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
148
|
+
|
|
149
|
+
const vec = await encoder.embed("search query");
|
|
150
|
+
|
|
151
|
+
expect(vec).toEqual([12, 0, 0]); // "search query".length = 12
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -58,6 +58,12 @@ export const MEMORY_FILTER: Codec<MemoryFilter, SearchFilter> = {
|
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
60
|
* Create a codec for MemoryRecord -> IndexMemoryRecord.
|
|
61
|
+
*
|
|
62
|
+
* Combines:
|
|
63
|
+
* - Record scope/timestamps from MemoryRecord
|
|
64
|
+
* - Indexed content (text, object projection, embeddings) from byte codec
|
|
65
|
+
*
|
|
66
|
+
* Note: metadata is NOT included - it lives in the primary DB only.
|
|
61
67
|
*/
|
|
62
68
|
export function recordCodec(
|
|
63
69
|
bytecodec: MemoryByteCodec,
|
|
@@ -29,11 +29,8 @@ import type { IndexMemoryRecord } from "../types";
|
|
|
29
29
|
*/
|
|
30
30
|
export const TPUF_DOC: Codec<IndexMemoryRecord, UnknownDocument> = {
|
|
31
31
|
encode(doc: IndexMemoryRecord): UnknownDocument {
|
|
32
|
-
const { tvec, ivec, avec, vvec,
|
|
33
|
-
const row: UnknownDocument = {
|
|
34
|
-
...rest,
|
|
35
|
-
metadata: metadata as UnknownDocument["metadata"], // metadata is JSONObject | null, cast to FieldValue for UnknownDocument
|
|
36
|
-
};
|
|
32
|
+
const { tvec, ivec, avec, vvec, ...rest } = doc;
|
|
33
|
+
const row: UnknownDocument = { ...rest };
|
|
37
34
|
if (tvec) row.vector = tvec;
|
|
38
35
|
return row;
|
|
39
36
|
},
|
package/src/memory/encoder.ts
CHANGED
|
@@ -2,10 +2,41 @@
|
|
|
2
2
|
* MemoryByte encoder - converts MemoryByte to IndexableByte with embeddings.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { EmbeddingModel } from "@kernl-sdk/protocol";
|
|
5
|
+
import type { EmbeddingModel, JSONObject } from "@kernl-sdk/protocol";
|
|
6
|
+
import { stringify as yamlStringify } from "yaml";
|
|
6
7
|
|
|
7
8
|
import type { MemoryByte, IndexableByte, MemoryByteCodec } from "./types";
|
|
8
9
|
|
|
10
|
+
// ---------------------
|
|
11
|
+
// ObjectTextCodec
|
|
12
|
+
// ---------------------
|
|
13
|
+
|
|
14
|
+
const MAX_OBJECT_TEXT_LENGTH = 3000;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Codec for converting JSONObject to a canonical text representation.
|
|
18
|
+
*
|
|
19
|
+
* Uses YAML for human-readable, deterministic output suitable for:
|
|
20
|
+
* - Full-text search indexing
|
|
21
|
+
* - Embedding input (combined with text field)
|
|
22
|
+
*
|
|
23
|
+
* TODO: Allow users to pass custom codec via MemoryOptions.
|
|
24
|
+
*/
|
|
25
|
+
export const ObjectTextCodec = {
|
|
26
|
+
/**
|
|
27
|
+
* Encode a JSONObject to canonical text.
|
|
28
|
+
* Uses YAML with sorted keys for determinism.
|
|
29
|
+
* Truncates at MAX_OBJECT_TEXT_LENGTH chars.
|
|
30
|
+
*/
|
|
31
|
+
encode(obj: JSONObject): string {
|
|
32
|
+
const yaml = yamlStringify(obj, { sortMapEntries: true });
|
|
33
|
+
if (yaml.length <= MAX_OBJECT_TEXT_LENGTH) {
|
|
34
|
+
return yaml;
|
|
35
|
+
}
|
|
36
|
+
return yaml.slice(0, MAX_OBJECT_TEXT_LENGTH) + "\n...";
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
9
40
|
/**
|
|
10
41
|
* Encoder that converts MemoryByte to IndexableByte.
|
|
11
42
|
*
|
|
@@ -20,21 +51,35 @@ export class MemoryByteEncoder implements MemoryByteCodec {
|
|
|
20
51
|
|
|
21
52
|
/**
|
|
22
53
|
* Encode a MemoryByte to IndexableByte.
|
|
23
|
-
*
|
|
54
|
+
*
|
|
55
|
+
* - Produces `objtext` string projection for FTS indexing
|
|
56
|
+
* - Combines text + objtext for embedding input
|
|
57
|
+
* - Returns text (fallback to objtext if no text provided)
|
|
58
|
+
*
|
|
59
|
+
* Note: metadata is NOT set here - it comes from record.metadata
|
|
60
|
+
* via the domain codec, not from MemoryByte.object.
|
|
24
61
|
*/
|
|
25
62
|
async encode(byte: MemoryByte): Promise<IndexableByte> {
|
|
26
|
-
const
|
|
27
|
-
|
|
63
|
+
const objtext = byte.object
|
|
64
|
+
? ObjectTextCodec.encode(byte.object) // encode object as embeddable string
|
|
65
|
+
: undefined;
|
|
66
|
+
|
|
67
|
+
// (TODO): this behavior deserves consideration - do we always want to merge text + object?
|
|
68
|
+
//
|
|
69
|
+
// combine text + object for richer embedding
|
|
70
|
+
const combined = [byte.text, objtext].filter(Boolean).join("\n");
|
|
71
|
+
const tvec = combined ? await this.embed(combined) : undefined;
|
|
28
72
|
|
|
29
73
|
// TODO: embed other modalities (image, audio, video)
|
|
74
|
+
//
|
|
30
75
|
// const ivec = byte.image ? await this.embedImage(byte.image) : undefined;
|
|
31
76
|
// const avec = byte.audio ? await this.embedAudio(byte.audio) : undefined;
|
|
32
77
|
// const vvec = byte.video ? await this.embedVideo(byte.video) : undefined;
|
|
33
78
|
|
|
34
79
|
return {
|
|
35
|
-
text,
|
|
80
|
+
text: byte.text ?? objtext, // fallback to projection if no text
|
|
81
|
+
objtext,
|
|
36
82
|
tvec,
|
|
37
|
-
metadata: byte.object ?? null,
|
|
38
83
|
};
|
|
39
84
|
}
|
|
40
85
|
|
package/src/memory/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { Memory } from "./memory";
|
|
6
|
-
export { MemoryByteEncoder } from "./encoder";
|
|
6
|
+
export { MemoryByteEncoder, ObjectTextCodec } from "./encoder";
|
|
7
7
|
export { buildMemoryIndexSchema } from "./schema";
|
|
8
8
|
export { MemoryIndexHandle } from "./handle";
|
|
9
9
|
export type { MemoryIndexHandleConfig } from "./handle";
|
package/src/memory/schema.ts
CHANGED
|
@@ -105,6 +105,11 @@ export function buildMemoryIndexSchema(
|
|
|
105
105
|
fts: true,
|
|
106
106
|
optional: true,
|
|
107
107
|
},
|
|
108
|
+
objtext: {
|
|
109
|
+
type: "string",
|
|
110
|
+
fts: true,
|
|
111
|
+
optional: true,
|
|
112
|
+
},
|
|
108
113
|
|
|
109
114
|
// vector fields for different modalities
|
|
110
115
|
tvec: {
|
|
@@ -131,11 +136,6 @@ export function buildMemoryIndexSchema(
|
|
|
131
136
|
similarity,
|
|
132
137
|
optional: true,
|
|
133
138
|
},
|
|
134
|
-
|
|
135
|
-
metadata: {
|
|
136
|
-
type: "object",
|
|
137
|
-
optional: true,
|
|
138
|
-
},
|
|
139
139
|
};
|
|
140
140
|
|
|
141
141
|
return schema;
|
package/src/memory/types.ts
CHANGED
|
@@ -60,14 +60,15 @@ export interface MemoryByte {
|
|
|
60
60
|
* Search-ready projection of a MemoryByte.
|
|
61
61
|
*
|
|
62
62
|
* Contains canonical text plus embeddings for each modality.
|
|
63
|
+
* Note: metadata is NOT included - it lives in the primary DB only.
|
|
63
64
|
*/
|
|
64
65
|
export interface IndexableByte {
|
|
65
66
|
text?: string; // canonical semantic text
|
|
67
|
+
objtext?: string; // string projection of object for indexing (not full JSON)
|
|
66
68
|
tvec?: number[]; // text embedding
|
|
67
69
|
ivec?: number[]; // image embedding
|
|
68
70
|
avec?: number[]; // audio embedding
|
|
69
71
|
vvec?: number[]; // video embedding
|
|
70
|
-
metadata?: JSONObject | null; // structured payload
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
/**
|