@forwardimpact/svcmemory 0.1.87 → 0.1.88
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/package.json +7 -1
- package/proto/memory.proto +32 -0
- package/server.js +0 -23
- package/test/memory.test.js +0 -275
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forwardimpact/svcmemory",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.88",
|
|
4
4
|
"description": "Memory service for managing transient resources and memory windows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,5 +22,11 @@
|
|
|
22
22
|
"engines": {
|
|
23
23
|
"bun": ">=1.2.0",
|
|
24
24
|
"node": ">=18.0.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"proto/"
|
|
28
|
+
],
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package memory;
|
|
4
|
+
|
|
5
|
+
import "common.proto";
|
|
6
|
+
import "resource.proto";
|
|
7
|
+
import "tool.proto";
|
|
8
|
+
|
|
9
|
+
service Memory {
|
|
10
|
+
rpc AppendMemory(AppendRequest) returns (AppendResponse);
|
|
11
|
+
rpc GetWindow(WindowRequest) returns (WindowResponse);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
message AppendRequest {
|
|
15
|
+
string resource_id = 1;
|
|
16
|
+
repeated resource.Identifier identifiers = 2;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
message AppendResponse {
|
|
20
|
+
string accepted = 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
message WindowRequest {
|
|
24
|
+
string resource_id = 1;
|
|
25
|
+
string model = 2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
message WindowResponse {
|
|
29
|
+
repeated common.Message messages = 1;
|
|
30
|
+
repeated tool.ToolCall tools = 2;
|
|
31
|
+
int32 max_tokens = 3;
|
|
32
|
+
}
|
package/server.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
import { Server } from "@forwardimpact/librpc";
|
|
3
|
-
import { createServiceConfig } from "@forwardimpact/libconfig";
|
|
4
|
-
import { createStorage } from "@forwardimpact/libstorage";
|
|
5
|
-
import { createTracer } from "@forwardimpact/librpc";
|
|
6
|
-
import { createLogger } from "@forwardimpact/libtelemetry";
|
|
7
|
-
import { createResourceIndex } from "@forwardimpact/libresource";
|
|
8
|
-
|
|
9
|
-
import { MemoryService } from "./index.js";
|
|
10
|
-
|
|
11
|
-
const config = await createServiceConfig("memory");
|
|
12
|
-
|
|
13
|
-
// Initialize observability
|
|
14
|
-
const logger = createLogger("memory");
|
|
15
|
-
const tracer = await createTracer("memory");
|
|
16
|
-
|
|
17
|
-
const memoryStorage = createStorage("memories");
|
|
18
|
-
const resourceIndex = createResourceIndex("resources");
|
|
19
|
-
|
|
20
|
-
const service = new MemoryService(config, memoryStorage, resourceIndex);
|
|
21
|
-
const server = new Server(service, config, logger, tracer);
|
|
22
|
-
|
|
23
|
-
await server.start();
|
package/test/memory.test.js
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { test, describe, beforeEach } from "node:test";
|
|
2
|
-
import assert from "node:assert";
|
|
3
|
-
import { resource, common } from "@forwardimpact/libtype";
|
|
4
|
-
|
|
5
|
-
// Module under test
|
|
6
|
-
import { MemoryService } from "../index.js";
|
|
7
|
-
import {
|
|
8
|
-
createMockConfig,
|
|
9
|
-
createMockStorage,
|
|
10
|
-
createMockResourceIndex,
|
|
11
|
-
} from "@forwardimpact/libharness";
|
|
12
|
-
|
|
13
|
-
describe("memory service", () => {
|
|
14
|
-
describe("MemoryService", () => {
|
|
15
|
-
test("exports MemoryService class", () => {
|
|
16
|
-
assert.strictEqual(typeof MemoryService, "function");
|
|
17
|
-
assert.ok(MemoryService.prototype);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("MemoryService has AppendMemory method", () => {
|
|
21
|
-
assert.strictEqual(
|
|
22
|
-
typeof MemoryService.prototype.AppendMemory,
|
|
23
|
-
"function",
|
|
24
|
-
);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("MemoryService has GetWindow method", () => {
|
|
28
|
-
assert.strictEqual(typeof MemoryService.prototype.GetWindow, "function");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("MemoryService constructor accepts expected parameters", () => {
|
|
32
|
-
// Test constructor signature by checking parameter count
|
|
33
|
-
assert.strictEqual(MemoryService.length, 3); // config, storage, resourceIndex
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("MemoryService has proper method signatures", () => {
|
|
37
|
-
const methods = Object.getOwnPropertyNames(MemoryService.prototype);
|
|
38
|
-
assert(methods.includes("AppendMemory"));
|
|
39
|
-
assert(methods.includes("GetWindow"));
|
|
40
|
-
assert(methods.includes("constructor"));
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe("MemoryService business logic", () => {
|
|
45
|
-
let mockConfig;
|
|
46
|
-
let mockStorage;
|
|
47
|
-
let mockResourceIndex;
|
|
48
|
-
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
mockConfig = createMockConfig("memory");
|
|
51
|
-
mockStorage = createMockStorage();
|
|
52
|
-
mockResourceIndex = createMockResourceIndex({ tools: ["search"] });
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test("constructor validates required dependencies", () => {
|
|
56
|
-
assert.throws(
|
|
57
|
-
() => new MemoryService(mockConfig, null),
|
|
58
|
-
/storage is required/,
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
assert.throws(
|
|
62
|
-
() => new MemoryService(mockConfig, mockStorage, null),
|
|
63
|
-
/resourceIndex is required/,
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
test("creates service instance with valid parameters", () => {
|
|
68
|
-
const service = new MemoryService(
|
|
69
|
-
mockConfig,
|
|
70
|
-
mockStorage,
|
|
71
|
-
mockResourceIndex,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
assert.ok(service);
|
|
75
|
-
assert.strictEqual(service.config, mockConfig);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
test("AppendMemory validates required resource_id parameter", async () => {
|
|
79
|
-
const service = new MemoryService(
|
|
80
|
-
mockConfig,
|
|
81
|
-
mockStorage,
|
|
82
|
-
mockResourceIndex,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
await assert.rejects(
|
|
86
|
-
() => service.AppendMemory({ identifiers: [] }),
|
|
87
|
-
/resource_id is required/,
|
|
88
|
-
);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("AppendMemory processes identifiers correctly", async () => {
|
|
92
|
-
const service = new MemoryService(
|
|
93
|
-
mockConfig,
|
|
94
|
-
mockStorage,
|
|
95
|
-
mockResourceIndex,
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const result = await service.AppendMemory({
|
|
99
|
-
resource_id: "test-conversation",
|
|
100
|
-
identifiers: [
|
|
101
|
-
resource.Identifier.fromObject({
|
|
102
|
-
type: "common.Message",
|
|
103
|
-
name: "message1",
|
|
104
|
-
tokens: 10,
|
|
105
|
-
}),
|
|
106
|
-
],
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
assert.ok(result);
|
|
110
|
-
assert.strictEqual(result.accepted, "test-conversation");
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test("GetWindow validates required resource_id parameter", async () => {
|
|
114
|
-
const service = new MemoryService(
|
|
115
|
-
mockConfig,
|
|
116
|
-
mockStorage,
|
|
117
|
-
mockResourceIndex,
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
await assert.rejects(
|
|
121
|
-
() => service.GetWindow({}),
|
|
122
|
-
/resource_id is required/,
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("GetWindow validates required model parameter", async () => {
|
|
127
|
-
const service = new MemoryService(
|
|
128
|
-
mockConfig,
|
|
129
|
-
mockStorage,
|
|
130
|
-
mockResourceIndex,
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
await assert.rejects(
|
|
134
|
-
() => service.GetWindow({ resource_id: "test-conversation" }),
|
|
135
|
-
/model is required/,
|
|
136
|
-
);
|
|
137
|
-
|
|
138
|
-
await assert.rejects(
|
|
139
|
-
() =>
|
|
140
|
-
service.GetWindow({ resource_id: "test-conversation", model: "" }),
|
|
141
|
-
/model is required/,
|
|
142
|
-
);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("GetWindow returns messages and tools structure with max_tokens", async () => {
|
|
146
|
-
const service = new MemoryService(
|
|
147
|
-
mockConfig,
|
|
148
|
-
mockStorage,
|
|
149
|
-
mockResourceIndex,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
const result = await service.GetWindow({
|
|
153
|
-
resource_id: "test-conversation",
|
|
154
|
-
model: "test-model-1000",
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
assert.ok(result);
|
|
158
|
-
assert.ok(Array.isArray(result.messages), "Should have messages array");
|
|
159
|
-
assert.ok(Array.isArray(result.tools), "Should have tools array");
|
|
160
|
-
assert.strictEqual(
|
|
161
|
-
result.max_tokens,
|
|
162
|
-
4096,
|
|
163
|
-
"Should include max_tokens from config",
|
|
164
|
-
);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("GetWindow returns assistant as first message", async () => {
|
|
168
|
-
const service = new MemoryService(
|
|
169
|
-
mockConfig,
|
|
170
|
-
mockStorage,
|
|
171
|
-
mockResourceIndex,
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const result = await service.GetWindow({
|
|
175
|
-
resource_id: "test-conversation",
|
|
176
|
-
model: "test-model-1000",
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
assert.ok(
|
|
180
|
-
result.messages.length >= 1,
|
|
181
|
-
"Should have at least one message",
|
|
182
|
-
);
|
|
183
|
-
assert.strictEqual(
|
|
184
|
-
result.messages[0].id?.name,
|
|
185
|
-
"test-agent",
|
|
186
|
-
"First message should be assistant",
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("GetWindow returns tools from assistant configuration", async () => {
|
|
191
|
-
const service = new MemoryService(
|
|
192
|
-
mockConfig,
|
|
193
|
-
mockStorage,
|
|
194
|
-
mockResourceIndex,
|
|
195
|
-
);
|
|
196
|
-
|
|
197
|
-
const result = await service.GetWindow({
|
|
198
|
-
resource_id: "test-conversation",
|
|
199
|
-
model: "test-model-1000",
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
assert.strictEqual(result.tools.length, 1, "Should have 1 tool");
|
|
203
|
-
assert.strictEqual(
|
|
204
|
-
result.tools[0].function?.name,
|
|
205
|
-
"search",
|
|
206
|
-
"Tool should be search",
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
test("GetWindow returns conversation messages within budget", async () => {
|
|
211
|
-
// Override max_tokens to fit within test model context (1000 tokens)
|
|
212
|
-
const customConfig = {
|
|
213
|
-
...mockConfig,
|
|
214
|
-
max_tokens: 100,
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Add some test messages to memory
|
|
218
|
-
await mockStorage.append(
|
|
219
|
-
"test-conversation.jsonl",
|
|
220
|
-
JSON.stringify({
|
|
221
|
-
id: "common.Message.message1",
|
|
222
|
-
identifier: resource.Identifier.fromObject({
|
|
223
|
-
type: "common.Message",
|
|
224
|
-
name: "message1",
|
|
225
|
-
tokens: 15,
|
|
226
|
-
}),
|
|
227
|
-
}),
|
|
228
|
-
);
|
|
229
|
-
await mockStorage.append(
|
|
230
|
-
"test-conversation.jsonl",
|
|
231
|
-
JSON.stringify({
|
|
232
|
-
id: "common.Message.message2",
|
|
233
|
-
identifier: resource.Identifier.fromObject({
|
|
234
|
-
type: "common.Message",
|
|
235
|
-
name: "message2",
|
|
236
|
-
tokens: 20,
|
|
237
|
-
}),
|
|
238
|
-
}),
|
|
239
|
-
);
|
|
240
|
-
|
|
241
|
-
// Add messages to resource index so they can be loaded
|
|
242
|
-
mockResourceIndex.addMessage(
|
|
243
|
-
common.Message.fromObject({
|
|
244
|
-
id: { name: "message1", tokens: 15 },
|
|
245
|
-
role: "user",
|
|
246
|
-
content: "Hello",
|
|
247
|
-
}),
|
|
248
|
-
);
|
|
249
|
-
mockResourceIndex.addMessage(
|
|
250
|
-
common.Message.fromObject({
|
|
251
|
-
id: { name: "message2", tokens: 20 },
|
|
252
|
-
role: "assistant",
|
|
253
|
-
content: "Hi there",
|
|
254
|
-
}),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
const service = new MemoryService(
|
|
258
|
-
customConfig,
|
|
259
|
-
mockStorage,
|
|
260
|
-
mockResourceIndex,
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
const result = await service.GetWindow({
|
|
264
|
-
resource_id: "test-conversation",
|
|
265
|
-
model: "test-model-1000",
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// Should return assistant + 2 conversation messages
|
|
269
|
-
assert.strictEqual(result.messages.length, 3);
|
|
270
|
-
assert.strictEqual(result.messages[0].id?.name, "test-agent");
|
|
271
|
-
assert.strictEqual(result.messages[1].id?.name, "message1");
|
|
272
|
-
assert.strictEqual(result.messages[2].id?.name, "message2");
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
});
|