kernl 0.7.3 → 0.8.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +24 -0
- package/dist/agent/__tests__/systools.test.js +13 -9
- package/dist/agent/types.d.ts +20 -12
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent.d.ts +12 -8
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +14 -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 +120 -0
- package/dist/memory/codecs/domain.d.ts +5 -0
- package/dist/memory/codecs/domain.d.ts.map +1 -1
- package/dist/memory/codecs/domain.js +6 -0
- 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 +2 -2
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -1
- package/dist/memory/schema.d.ts.map +1 -1
- package/dist/memory/schema.js +5 -0
- package/dist/memory/types.d.ts +21 -4
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/storage/in-memory.d.ts.map +1 -1
- package/dist/storage/in-memory.js +4 -2
- package/dist/thread/__tests__/integration.test.js +1 -1
- 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/dist/tool/sys/memory.d.ts +1 -1
- package/dist/tool/sys/memory.d.ts.map +1 -1
- package/dist/tool/sys/memory.js +29 -8
- package/package.json +4 -3
- package/src/agent/__tests__/systools.test.ts +13 -9
- package/src/agent/types.ts +25 -29
- package/src/agent.ts +29 -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 +153 -0
- package/src/memory/codecs/domain.ts +6 -0
- package/src/memory/encoder.ts +51 -6
- package/src/memory/index.ts +2 -1
- package/src/memory/schema.ts +5 -0
- package/src/memory/types.ts +20 -4
- package/src/storage/in-memory.ts +6 -2
- 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/tool/sys/memory.ts +33 -9
package/src/guardrail.ts
CHANGED
|
@@ -4,19 +4,19 @@ import { LanguageModelResponse } from "@kernl-sdk/protocol";
|
|
|
4
4
|
|
|
5
5
|
import { Agent } from "./agent";
|
|
6
6
|
import { Context, UnknownContext } from "./context";
|
|
7
|
-
import type {
|
|
8
|
-
import type {
|
|
7
|
+
import type { AgentOutputType } from "@/agent/types";
|
|
8
|
+
import type { TextOutput, ThreadEvent } from "@/thread/types";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Resolves the agent output type based on the response type.
|
|
12
|
-
* - If
|
|
13
|
-
* - If
|
|
12
|
+
* - If TOutput is "text" → output is string
|
|
13
|
+
* - If TOutput is a ZodType → output is the inferred type from that schema
|
|
14
14
|
*/
|
|
15
|
-
export type ResolvedAgentResponse<
|
|
16
|
-
|
|
15
|
+
export type ResolvedAgentResponse<TOutput extends AgentOutputType> =
|
|
16
|
+
TOutput extends TextOutput
|
|
17
17
|
? string
|
|
18
|
-
:
|
|
19
|
-
? z.infer<
|
|
18
|
+
: TOutput extends ZodType
|
|
19
|
+
? z.infer<TOutput>
|
|
20
20
|
: never;
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -149,10 +149,10 @@ export function defineInputGuardrail({
|
|
|
149
149
|
*/
|
|
150
150
|
export interface OutputGuardrailFunctionArgs<
|
|
151
151
|
TContext = UnknownContext,
|
|
152
|
-
|
|
152
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
153
153
|
> {
|
|
154
154
|
agent: Agent<any, any>;
|
|
155
|
-
agentOutput: ResolvedAgentResponse<
|
|
155
|
+
agentOutput: ResolvedAgentResponse<TOutput>; // ??
|
|
156
156
|
context: Context<TContext>;
|
|
157
157
|
/**
|
|
158
158
|
* Additional details about the agent output.
|
|
@@ -166,16 +166,16 @@ export interface OutputGuardrailFunctionArgs<
|
|
|
166
166
|
* A function that takes an output guardrail function arguments and returns a `GuardrailFunctionOutput`.
|
|
167
167
|
*/
|
|
168
168
|
export type OutputGuardrailFunction<
|
|
169
|
-
|
|
169
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
170
170
|
> = (
|
|
171
|
-
args: OutputGuardrailFunctionArgs<UnknownContext,
|
|
171
|
+
args: OutputGuardrailFunctionArgs<UnknownContext, TOutput>,
|
|
172
172
|
) => Promise<GuardrailFunctionOutput>;
|
|
173
173
|
|
|
174
174
|
/**
|
|
175
175
|
* A guardrail that checks the output of the agent.
|
|
176
176
|
*/
|
|
177
177
|
export interface OutputGuardrail<
|
|
178
|
-
|
|
178
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
179
179
|
> {
|
|
180
180
|
/**
|
|
181
181
|
* The name of the guardrail.
|
|
@@ -185,7 +185,7 @@ export interface OutputGuardrail<
|
|
|
185
185
|
/**
|
|
186
186
|
* The function that performs the guardrail check.
|
|
187
187
|
*/
|
|
188
|
-
execute: OutputGuardrailFunction<
|
|
188
|
+
execute: OutputGuardrailFunction<TOutput>;
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
@@ -201,7 +201,7 @@ export interface OutputGuardrailMetadata {
|
|
|
201
201
|
*/
|
|
202
202
|
export interface OutputGuardrailResult<
|
|
203
203
|
TMeta = OutputGuardrailMetadata,
|
|
204
|
-
|
|
204
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
205
205
|
> {
|
|
206
206
|
/**
|
|
207
207
|
* The metadata of the guardrail.
|
|
@@ -211,12 +211,12 @@ export interface OutputGuardrailResult<
|
|
|
211
211
|
/**
|
|
212
212
|
* The output of the agent that ran.
|
|
213
213
|
*/
|
|
214
|
-
agentOutput: ResolvedAgentResponse<
|
|
214
|
+
agentOutput: ResolvedAgentResponse<TOutput>; // ??
|
|
215
215
|
|
|
216
216
|
/**
|
|
217
217
|
* The agent that ran.
|
|
218
218
|
*/
|
|
219
|
-
agent: Agent<UnknownContext,
|
|
219
|
+
agent: Agent<UnknownContext, TOutput>;
|
|
220
220
|
|
|
221
221
|
/**
|
|
222
222
|
* The output of the guardrail.
|
|
@@ -229,43 +229,43 @@ export interface OutputGuardrailResult<
|
|
|
229
229
|
*/
|
|
230
230
|
export interface OutputGuardrailDefinition<
|
|
231
231
|
TMeta = OutputGuardrailMetadata,
|
|
232
|
-
|
|
232
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
233
233
|
> extends OutputGuardrailMetadata {
|
|
234
|
-
guardrailFunction: OutputGuardrailFunction<
|
|
234
|
+
guardrailFunction: OutputGuardrailFunction<TOutput>;
|
|
235
235
|
run(
|
|
236
|
-
args: OutputGuardrailFunctionArgs<UnknownContext,
|
|
237
|
-
): Promise<OutputGuardrailResult<TMeta,
|
|
236
|
+
args: OutputGuardrailFunctionArgs<UnknownContext, TOutput>,
|
|
237
|
+
): Promise<OutputGuardrailResult<TMeta, TOutput>>;
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
/**
|
|
241
241
|
* Arguments for defining an output guardrail definition.
|
|
242
242
|
*/
|
|
243
243
|
export interface DefineOutputGuardrailArgs<
|
|
244
|
-
|
|
244
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
245
245
|
> {
|
|
246
246
|
name: string;
|
|
247
|
-
execute: OutputGuardrailFunction<
|
|
247
|
+
execute: OutputGuardrailFunction<TOutput>;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
/**
|
|
251
251
|
* Creates an output guardrail definition.
|
|
252
252
|
*/
|
|
253
253
|
export function defineOutputGuardrail<
|
|
254
|
-
|
|
254
|
+
TOutput extends AgentOutputType = TextOutput,
|
|
255
255
|
>({
|
|
256
256
|
name,
|
|
257
257
|
execute,
|
|
258
|
-
}: DefineOutputGuardrailArgs<
|
|
258
|
+
}: DefineOutputGuardrailArgs<TOutput>): OutputGuardrailDefinition<
|
|
259
259
|
OutputGuardrailMetadata,
|
|
260
|
-
|
|
260
|
+
TOutput
|
|
261
261
|
> {
|
|
262
262
|
return {
|
|
263
263
|
type: "output",
|
|
264
264
|
name,
|
|
265
265
|
guardrailFunction: execute,
|
|
266
266
|
async run(
|
|
267
|
-
args: OutputGuardrailFunctionArgs<UnknownContext,
|
|
268
|
-
): Promise<OutputGuardrailResult<OutputGuardrailMetadata,
|
|
267
|
+
args: OutputGuardrailFunctionArgs<UnknownContext, TOutput>,
|
|
268
|
+
): Promise<OutputGuardrailResult<OutputGuardrailMetadata, TOutput>> {
|
|
269
269
|
return {
|
|
270
270
|
guardrail: { type: "output", name },
|
|
271
271
|
agent: args.agent,
|
package/src/kernl/kernl.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "@/memory";
|
|
18
18
|
|
|
19
19
|
import type { ThreadExecuteResult, ThreadStreamEvent } from "@/thread/types";
|
|
20
|
-
import type {
|
|
20
|
+
import type { AgentOutputType } from "@/agent/types";
|
|
21
21
|
|
|
22
22
|
import type { KernlOptions } from "./types";
|
|
23
23
|
|
|
@@ -27,7 +27,7 @@ import type { KernlOptions } from "./types";
|
|
|
27
27
|
* Orchestrates agent execution, including guardrails, tool calls, session persistence, and
|
|
28
28
|
* tracing.
|
|
29
29
|
*/
|
|
30
|
-
export class Kernl extends KernlHooks<UnknownContext,
|
|
30
|
+
export class Kernl extends KernlHooks<UnknownContext, AgentOutputType> {
|
|
31
31
|
private readonly _agents: Map<string, Agent> = new Map();
|
|
32
32
|
private readonly _models: Map<string, LanguageModel> = new Map();
|
|
33
33
|
|
|
@@ -94,9 +94,9 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
94
94
|
/**
|
|
95
95
|
* Spawn a new thread - blocking execution
|
|
96
96
|
*/
|
|
97
|
-
async spawn<TContext,
|
|
98
|
-
thread: Thread<TContext,
|
|
99
|
-
): Promise<ThreadExecuteResult<ResolvedAgentResponse<
|
|
97
|
+
async spawn<TContext, TOutput extends AgentOutputType>(
|
|
98
|
+
thread: Thread<TContext, TOutput>,
|
|
99
|
+
): Promise<ThreadExecuteResult<ResolvedAgentResponse<TOutput>>> {
|
|
100
100
|
this.athreads.set(thread.tid, thread);
|
|
101
101
|
try {
|
|
102
102
|
return await thread.execute();
|
|
@@ -110,9 +110,9 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
110
110
|
*
|
|
111
111
|
* NOTE: just blocks for now
|
|
112
112
|
*/
|
|
113
|
-
async schedule<TContext,
|
|
114
|
-
thread: Thread<TContext,
|
|
115
|
-
): Promise<ThreadExecuteResult<ResolvedAgentResponse<
|
|
113
|
+
async schedule<TContext, TOutput extends AgentOutputType>(
|
|
114
|
+
thread: Thread<TContext, TOutput>,
|
|
115
|
+
): Promise<ThreadExecuteResult<ResolvedAgentResponse<TOutput>>> {
|
|
116
116
|
this.athreads.set(thread.tid, thread);
|
|
117
117
|
try {
|
|
118
118
|
return await thread.execute();
|
|
@@ -126,8 +126,8 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
126
126
|
*
|
|
127
127
|
* Spawn a new thread - streaming execution
|
|
128
128
|
*/
|
|
129
|
-
async *spawnStream<TContext,
|
|
130
|
-
thread: Thread<TContext,
|
|
129
|
+
async *spawnStream<TContext, TOutput extends AgentOutputType>(
|
|
130
|
+
thread: Thread<TContext, TOutput>,
|
|
131
131
|
): AsyncIterable<ThreadStreamEvent> {
|
|
132
132
|
this.athreads.set(thread.tid, thread);
|
|
133
133
|
try {
|
|
@@ -142,8 +142,8 @@ export class Kernl extends KernlHooks<UnknownContext, AgentResponseType> {
|
|
|
142
142
|
*
|
|
143
143
|
* Schedule an existing thread - streaming execution
|
|
144
144
|
*/
|
|
145
|
-
async *scheduleStream<TContext,
|
|
146
|
-
thread: Thread<TContext,
|
|
145
|
+
async *scheduleStream<TContext, TOutput extends AgentOutputType>(
|
|
146
|
+
thread: Thread<TContext, TOutput>,
|
|
147
147
|
): AsyncIterable<ThreadStreamEvent> {
|
|
148
148
|
this.athreads.set(thread.tid, thread);
|
|
149
149
|
try {
|
package/src/lib/error.ts
CHANGED
|
@@ -9,8 +9,8 @@ import { randomID } from "@kernl-sdk/shared/lib";
|
|
|
9
9
|
// import { SerializedThread } from "@/lib/serde/thread";
|
|
10
10
|
type SerializedThread = any;
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { AgentOutputType } from "@/agent/types";
|
|
13
|
+
import { TextOutput } from "@/thread/types";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Abstract base class for all `kernl` errors
|
|
@@ -144,7 +144,7 @@ export class InputGuardrailTripwireTriggered extends AgentError {
|
|
|
144
144
|
*/
|
|
145
145
|
export class OutputGuardrailTripwireTriggered<
|
|
146
146
|
TMeta extends OutputGuardrailMetadata,
|
|
147
|
-
TOutputType extends
|
|
147
|
+
TOutputType extends AgentOutputType = TextOutput,
|
|
148
148
|
> extends AgentError {
|
|
149
149
|
result: OutputGuardrailResult<TMeta, TOutputType>;
|
|
150
150
|
constructor(
|
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,153 @@
|
|
|
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 set metadata (handled by domain codec)", 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
|
+
expect(result.metadata).toBeUndefined();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns undefined tvec when no content", async () => {
|
|
130
|
+
const embedder = createMockEmbedder();
|
|
131
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
132
|
+
|
|
133
|
+
const byte: MemoryByte = {};
|
|
134
|
+
const result = await encoder.encode(byte);
|
|
135
|
+
|
|
136
|
+
expect(result.text).toBeUndefined();
|
|
137
|
+
expect(result.objtext).toBeUndefined();
|
|
138
|
+
expect(result.tvec).toBeUndefined();
|
|
139
|
+
expect(embedder.embed).not.toHaveBeenCalled();
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("embed", () => {
|
|
144
|
+
it("exposes embed method for query embedding", async () => {
|
|
145
|
+
const embedder = createMockEmbedder();
|
|
146
|
+
const encoder = new MemoryByteEncoder(embedder);
|
|
147
|
+
|
|
148
|
+
const vec = await encoder.embed("search query");
|
|
149
|
+
|
|
150
|
+
expect(vec).toEqual([12, 0, 0]); // "search query".length = 12
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -58,6 +58,11 @@ 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
|
+
* - User metadata from record.metadata (not from content.object)
|
|
61
66
|
*/
|
|
62
67
|
export function recordCodec(
|
|
63
68
|
bytecodec: MemoryByteCodec,
|
|
@@ -76,6 +81,7 @@ export function recordCodec(
|
|
|
76
81
|
createdAt: record.createdAt,
|
|
77
82
|
updatedAt: record.updatedAt,
|
|
78
83
|
...indexable,
|
|
84
|
+
metadata: record.metadata ?? null, // user metadata, not content.object
|
|
79
85
|
};
|
|
80
86
|
},
|
|
81
87
|
async decode(): Promise<MemoryRecord> {
|
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";
|
|
@@ -22,6 +22,7 @@ export type {
|
|
|
22
22
|
MemoryKind,
|
|
23
23
|
NewMemory,
|
|
24
24
|
AgentMemoryCreate,
|
|
25
|
+
AgentMemoryUpdate,
|
|
25
26
|
MemoryConfig,
|
|
26
27
|
MemoryReindexParams,
|
|
27
28
|
MemoryRecord,
|
package/src/memory/schema.ts
CHANGED
package/src/memory/types.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface MemoryByte {
|
|
|
63
63
|
*/
|
|
64
64
|
export interface IndexableByte {
|
|
65
65
|
text?: string; // canonical semantic text
|
|
66
|
+
objtext?: string; // string projection of object for indexing (not full JSON)
|
|
66
67
|
tvec?: number[]; // text embedding
|
|
67
68
|
ivec?: number[]; // image embedding
|
|
68
69
|
avec?: number[]; // audio embedding
|
|
@@ -128,7 +129,7 @@ export interface NewMemory {
|
|
|
128
129
|
id: string;
|
|
129
130
|
scope: MemoryScope;
|
|
130
131
|
kind: MemoryKind;
|
|
131
|
-
collection
|
|
132
|
+
collection?: string;
|
|
132
133
|
content: MemoryByte;
|
|
133
134
|
wmem?: boolean;
|
|
134
135
|
smem?: { expiresAt: number | null };
|
|
@@ -148,7 +149,7 @@ export interface AgentMemoryCreate {
|
|
|
148
149
|
id?: string;
|
|
149
150
|
namespace?: string;
|
|
150
151
|
entityId?: string;
|
|
151
|
-
collection
|
|
152
|
+
collection?: string;
|
|
152
153
|
content: MemoryByte;
|
|
153
154
|
wmem?: boolean;
|
|
154
155
|
smem?: { expiresAt: number | null };
|
|
@@ -156,6 +157,21 @@ export interface AgentMemoryCreate {
|
|
|
156
157
|
metadata?: JSONObject | null;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Simplified input for agent-scoped memory updates.
|
|
162
|
+
*
|
|
163
|
+
* Allows updating content, collection, and memory layer flags.
|
|
164
|
+
* Scope (namespace, entityId, agentId) cannot be changed after creation.
|
|
165
|
+
*/
|
|
166
|
+
export interface AgentMemoryUpdate {
|
|
167
|
+
id: string;
|
|
168
|
+
content?: MemoryByte;
|
|
169
|
+
collection?: string;
|
|
170
|
+
wmem?: boolean;
|
|
171
|
+
smem?: { expiresAt: number | null };
|
|
172
|
+
metadata?: JSONObject | null;
|
|
173
|
+
}
|
|
174
|
+
|
|
159
175
|
/**
|
|
160
176
|
* Base memory record fields.
|
|
161
177
|
*/
|
|
@@ -163,7 +179,7 @@ interface BaseMemoryRecord {
|
|
|
163
179
|
id: string;
|
|
164
180
|
scope: MemoryScope;
|
|
165
181
|
kind: MemoryKind;
|
|
166
|
-
collection: string;
|
|
182
|
+
collection: string | null;
|
|
167
183
|
content: MemoryByte;
|
|
168
184
|
wmem: boolean;
|
|
169
185
|
smem: { expiresAt: number | null };
|
|
@@ -260,7 +276,7 @@ export interface IndexMemoryRecord extends IndexableByte {
|
|
|
260
276
|
entityId: string | null;
|
|
261
277
|
agentId: string | null;
|
|
262
278
|
kind: MemoryKind;
|
|
263
|
-
collection: string;
|
|
279
|
+
collection: string | null;
|
|
264
280
|
timestamp: number;
|
|
265
281
|
createdAt: number;
|
|
266
282
|
updatedAt: number;
|
package/src/storage/in-memory.ts
CHANGED
|
@@ -415,7 +415,7 @@ export class InMemoryMemoryStore implements MemoryStore {
|
|
|
415
415
|
id: memory.id,
|
|
416
416
|
scope: memory.scope,
|
|
417
417
|
kind: memory.kind,
|
|
418
|
-
collection: memory.collection,
|
|
418
|
+
collection: memory.collection ?? null,
|
|
419
419
|
content: memory.content,
|
|
420
420
|
wmem: memory.wmem ?? false,
|
|
421
421
|
smem: memory.smem ?? { expiresAt: null },
|
|
@@ -490,7 +490,11 @@ export class InMemoryMemoryStore implements MemoryStore {
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
// Filter by collections
|
|
493
|
-
if (
|
|
493
|
+
if (
|
|
494
|
+
filter.collections &&
|
|
495
|
+
(record.collection === null ||
|
|
496
|
+
!filter.collections.includes(record.collection))
|
|
497
|
+
) {
|
|
494
498
|
return false;
|
|
495
499
|
}
|
|
496
500
|
|