@mingxy/cerebro 1.20.4 → 1.20.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/package.json +7 -3
- package/src/client.test.ts +373 -0
- package/src/config.test.ts +405 -0
- package/src/hooks-tier1.test.ts +220 -0
- package/src/hooks-tier2.test.ts +275 -0
- package/src/hooks-tier3.test.ts +461 -0
- package/src/hooks.ts +48 -12
- package/src/index.test.ts +190 -0
- package/src/index.ts +12 -2
- package/src/keywords.test.ts +283 -0
- package/src/logger.test.ts +640 -0
- package/src/privacy.test.ts +128 -0
- package/src/tags.test.ts +86 -0
- package/src/tools.test.ts +508 -0
- package/src/tools.ts +34 -0
- package/src/updater.test.ts +380 -0
- package/src/web-server.test.ts +740 -0
- package/src/web-server.ts +8 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mingxy/cerebro",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.6",
|
|
4
4
|
"description": "Cerebro persistent memory plugin for OpenCode — auto-recall, auto-capture, 9 memory tools with clustering, project-scoped memory isolation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
],
|
|
21
21
|
"scripts": {
|
|
22
22
|
"build:web": "bash ../../scripts/build-plugin-web.sh",
|
|
23
|
-
"
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"prepublishOnly": "npm run build:web && npx tsc --noEmit -p tsconfig.publish.json"
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
26
29
|
"opencode",
|
|
@@ -51,6 +54,7 @@
|
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
56
|
"@types/node": "^25.5.0",
|
|
54
|
-
"typescript": "^5.7.0"
|
|
57
|
+
"typescript": "^5.7.0",
|
|
58
|
+
"vitest": "^4.1.7"
|
|
55
59
|
}
|
|
56
60
|
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { CerebroClient } from "./client.js";
|
|
3
|
+
|
|
4
|
+
// sanitizeContent and truncateQuery are not exported, so we test them indirectly
|
|
5
|
+
// through CerebroClient methods. For direct testing we re-implement the logic check
|
|
6
|
+
// via the client's behavior, or we can access the module's internals.
|
|
7
|
+
|
|
8
|
+
// Actually, sanitizeContent and truncateQuery are module-level (not exported).
|
|
9
|
+
// We test them via CerebroClient methods that use them.
|
|
10
|
+
|
|
11
|
+
function makeFetchMock(
|
|
12
|
+
status = 200,
|
|
13
|
+
body: unknown = {},
|
|
14
|
+
opts?: { statusText?: string; headers?: Record<string, string> },
|
|
15
|
+
) {
|
|
16
|
+
return vi.fn().mockResolvedValue(
|
|
17
|
+
new Response(
|
|
18
|
+
typeof body === "string" ? body : JSON.stringify(body),
|
|
19
|
+
{
|
|
20
|
+
status,
|
|
21
|
+
statusText: opts?.statusText ?? "OK",
|
|
22
|
+
headers: opts?.headers,
|
|
23
|
+
},
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
describe("sanitizeContent (via createMemory)", () => {
|
|
29
|
+
let client: CerebroClient;
|
|
30
|
+
let fetchMock: ReturnType<typeof makeFetchMock>;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
fetchMock = makeFetchMock(200, { id: "1" });
|
|
34
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
35
|
+
client = new CerebroClient("http://localhost:8080", "test-key");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.restoreAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("strips XML tags from content", async () => {
|
|
43
|
+
await client.createMemory('Hello <thinking>bad</thinking> world');
|
|
44
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
45
|
+
expect(body.content).toBe("Hello world");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("strips self-closing XML tags", async () => {
|
|
49
|
+
await client.createMemory('Text <br/> more text');
|
|
50
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
51
|
+
expect(body.content).toBe("Text more text");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("collapses whitespace", async () => {
|
|
55
|
+
await client.createMemory("hello world\n\nfoo\tbar");
|
|
56
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
57
|
+
expect(body.content).toBe("hello world foo bar");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("truncates content exceeding maxLen with ellipsis marker", async () => {
|
|
61
|
+
const longContent = "a".repeat(3001);
|
|
62
|
+
await client.createMemory(longContent);
|
|
63
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
64
|
+
expect(body.content).toBe("a".repeat(3000) + "…[truncated]");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("passes through content under maxLen unchanged", async () => {
|
|
68
|
+
const shortContent = "hello world";
|
|
69
|
+
await client.createMemory(shortContent);
|
|
70
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
71
|
+
expect(body.content).toBe(shortContent);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("trims leading/trailing whitespace", async () => {
|
|
75
|
+
await client.createMemory(" hello ");
|
|
76
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
77
|
+
expect(body.content).toBe("hello");
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("truncateQuery (via searchMemories)", () => {
|
|
82
|
+
let client: CerebroClient;
|
|
83
|
+
let fetchMock: ReturnType<typeof makeFetchMock>;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
fetchMock = makeFetchMock(200, { results: [] });
|
|
87
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
88
|
+
client = new CerebroClient("http://localhost:8080", "test-key");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
afterEach(() => {
|
|
92
|
+
vi.restoreAllMocks();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("truncates query exceeding maxLen", async () => {
|
|
96
|
+
const longQuery = "q".repeat(201);
|
|
97
|
+
await client.searchMemories(longQuery);
|
|
98
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
99
|
+
const params = new URL(url).searchParams;
|
|
100
|
+
expect(params.get("q")).toBe("q".repeat(200));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("passes through query under maxLen unchanged", async () => {
|
|
104
|
+
const shortQuery = "hello";
|
|
105
|
+
await client.searchMemories(shortQuery);
|
|
106
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
107
|
+
const params = new URL(url).searchParams;
|
|
108
|
+
expect(params.get("q")).toBe(shortQuery);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("CerebroClient constructor", () => {
|
|
113
|
+
it("removes trailing slashes from baseUrl", () => {
|
|
114
|
+
const client = new CerebroClient("http://localhost:8080///", "key");
|
|
115
|
+
// Access internal baseUrl via a fetch call
|
|
116
|
+
const fetchMock = makeFetchMock(200, {});
|
|
117
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
118
|
+
client.getMemory("id");
|
|
119
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
120
|
+
expect(url).toBe("http://localhost:8080/v1/memories/id");
|
|
121
|
+
vi.restoreAllMocks();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("CerebroClient HTTP methods", () => {
|
|
126
|
+
let client: CerebroClient;
|
|
127
|
+
let fetchMock: ReturnType<typeof makeFetchMock>;
|
|
128
|
+
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
fetchMock = makeFetchMock(200, { id: "m1", content: "test" });
|
|
131
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
132
|
+
client = new CerebroClient("http://localhost:8080", "test-key");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
afterEach(() => {
|
|
136
|
+
vi.restoreAllMocks();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("createMemory sends POST /v1/memories", async () => {
|
|
140
|
+
await client.createMemory("test content", ["tag1"], "source", "project", "agent1", "sess1");
|
|
141
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
142
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
143
|
+
expect(url).toBe("http://localhost:8080/v1/memories");
|
|
144
|
+
expect(init.method).toBe("POST");
|
|
145
|
+
expect(init.headers["X-API-Key"]).toBe("test-key");
|
|
146
|
+
expect(init.headers["Content-Type"]).toBe("application/json");
|
|
147
|
+
const body = JSON.parse(init.body as string);
|
|
148
|
+
expect(body.content).toBe("test content");
|
|
149
|
+
expect(body.tags).toEqual(["tag1"]);
|
|
150
|
+
expect(body.source).toBe("source");
|
|
151
|
+
expect(body.scope).toBe("project");
|
|
152
|
+
expect(body.agent_id).toBe("agent1");
|
|
153
|
+
expect(body.session_id).toBe("sess1");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("searchMemories sends GET /v1/memories/search with params", async () => {
|
|
157
|
+
const results = await client.searchMemories("hello", 5, "project", ["t1"], "/path");
|
|
158
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
159
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
160
|
+
expect(url).toContain("/v1/memories/search?");
|
|
161
|
+
expect(url).toContain("q=hello");
|
|
162
|
+
expect(url).toContain("limit=5");
|
|
163
|
+
expect(url).toContain("scope=project");
|
|
164
|
+
expect(url).toContain("tags=t1");
|
|
165
|
+
expect(url).toContain("project_path=%2Fpath");
|
|
166
|
+
expect(results).toEqual([]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("getMemory sends GET /v1/memories/:id", async () => {
|
|
170
|
+
const mem = await client.getMemory("abc-123");
|
|
171
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
172
|
+
expect(url).toBe("http://localhost:8080/v1/memories/abc-123");
|
|
173
|
+
expect(init.method).toBeUndefined(); // default GET
|
|
174
|
+
expect(mem).toEqual({ id: "m1", content: "test" });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("getMemory encodes id with special characters", async () => {
|
|
178
|
+
await client.getMemory("id with spaces");
|
|
179
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
180
|
+
expect(url).toContain("/v1/memories/id%20with%20spaces");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("updateMemory sends PUT /v1/memories/:id", async () => {
|
|
184
|
+
await client.updateMemory("m1", "new content", ["t1", "t2"]);
|
|
185
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
186
|
+
expect(url).toBe("http://localhost:8080/v1/memories/m1");
|
|
187
|
+
expect(init.method).toBe("PUT");
|
|
188
|
+
const body = JSON.parse(init.body as string);
|
|
189
|
+
expect(body.content).toBe("new content");
|
|
190
|
+
expect(body.tags).toEqual(["t1", "t2"]);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("deleteMemory sends DELETE /v1/memories/:id", async () => {
|
|
194
|
+
fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204, statusText: "No Content" }));
|
|
195
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
196
|
+
await client.deleteMemory("m1");
|
|
197
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
198
|
+
expect(url).toBe("http://localhost:8080/v1/memories/m1");
|
|
199
|
+
expect(init.method).toBe("DELETE");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("ingestMessages sends POST /v1/memories with sanitized messages", async () => {
|
|
203
|
+
await client.ingestMessages(
|
|
204
|
+
[
|
|
205
|
+
{ role: "user", content: "hello <thinking>hidden</thinking>" },
|
|
206
|
+
{ role: "assistant", content: "world" },
|
|
207
|
+
],
|
|
208
|
+
{ mode: "raw", agentId: "a1", sessionId: "s1", tags: ["t1"] },
|
|
209
|
+
);
|
|
210
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
211
|
+
expect(url).toBe("http://localhost:8080/v1/memories");
|
|
212
|
+
expect(init.method).toBe("POST");
|
|
213
|
+
const body = JSON.parse(init.body as string);
|
|
214
|
+
expect(body.messages).toEqual([
|
|
215
|
+
{ role: "user", content: "hello" },
|
|
216
|
+
{ role: "assistant", content: "world" },
|
|
217
|
+
]);
|
|
218
|
+
expect(body.mode).toBe("raw");
|
|
219
|
+
expect(body.agent_id).toBe("a1");
|
|
220
|
+
expect(body.session_id).toBe("s1");
|
|
221
|
+
expect(body.tags).toEqual(["t1"]);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe("CerebroClient error handling", () => {
|
|
226
|
+
let client: CerebroClient;
|
|
227
|
+
|
|
228
|
+
beforeEach(() => {
|
|
229
|
+
client = new CerebroClient("http://localhost:8080", "test-key");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
afterEach(() => {
|
|
233
|
+
vi.restoreAllMocks();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("throws on HTTP error response", async () => {
|
|
237
|
+
const fetchMock = makeFetchMock(500, "internal error", { statusText: "Internal Server Error" });
|
|
238
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
239
|
+
await expect(client.getMemory("id")).rejects.toThrow("[cerebro] 500 Internal Server Error: internal error");
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("throws on HTTP 4xx response", async () => {
|
|
243
|
+
const fetchMock = makeFetchMock(403, "forbidden body", { statusText: "Forbidden" });
|
|
244
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
245
|
+
await expect(client.getMemory("id")).rejects.toThrow("[cerebro] 403 Forbidden: forbidden body");
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("throws on timeout (AbortError)", async () => {
|
|
249
|
+
const fetchMock = vi.fn().mockImplementation(() => {
|
|
250
|
+
const err = new DOMException("The operation was aborted", "AbortError");
|
|
251
|
+
throw err;
|
|
252
|
+
});
|
|
253
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
254
|
+
// Use a client with very short timeout
|
|
255
|
+
const fastClient = new CerebroClient("http://localhost:8080", "key", {
|
|
256
|
+
connection: { requestTimeoutMs: 1 },
|
|
257
|
+
});
|
|
258
|
+
await expect(fastClient.getMemory("id")).rejects.toThrow("[cerebro] Request timed out (1ms)");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("returns null for 204 No Content", async () => {
|
|
262
|
+
const fetchMock = vi.fn().mockResolvedValue(new Response(null, { status: 204, statusText: "No Content" }));
|
|
263
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
264
|
+
const result = await client.getMemory("id");
|
|
265
|
+
expect(result).toBeNull();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("throws on malformed JSON response", async () => {
|
|
269
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
270
|
+
new Response("not valid json{{{", { status: 200, statusText: "OK" }),
|
|
271
|
+
);
|
|
272
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
273
|
+
await expect(client.getMemory("id")).rejects.toThrow();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("strips BOM from response", async () => {
|
|
277
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
278
|
+
new Response('\uFEFF{"id":"m1","content":"ok"}', { status: 200 }),
|
|
279
|
+
);
|
|
280
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
281
|
+
const result = await client.getMemory("id");
|
|
282
|
+
expect(result).toEqual({ id: "m1", content: "ok" });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("returns null for empty response body", async () => {
|
|
286
|
+
const fetchMock = vi.fn().mockResolvedValue(
|
|
287
|
+
new Response("", { status: 200 }),
|
|
288
|
+
);
|
|
289
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
290
|
+
const result = await client.getMemory("id");
|
|
291
|
+
expect(result).toBeNull();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe("CerebroClient additional methods", () => {
|
|
296
|
+
let client: CerebroClient;
|
|
297
|
+
let fetchMock: ReturnType<typeof makeFetchMock>;
|
|
298
|
+
|
|
299
|
+
beforeEach(() => {
|
|
300
|
+
fetchMock = makeFetchMock(200, { preferences: [] });
|
|
301
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
302
|
+
client = new CerebroClient("http://localhost:8080", "test-key");
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
afterEach(() => {
|
|
306
|
+
vi.restoreAllMocks();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it("getProfile returns empty array when null", async () => {
|
|
310
|
+
fetchMock = vi.fn().mockResolvedValue(new Response("null", { status: 200 }));
|
|
311
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
312
|
+
const result = await client.getProfile();
|
|
313
|
+
expect(result).toEqual([]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("getProfile passes project_path", async () => {
|
|
317
|
+
fetchMock = makeFetchMock(200, []);
|
|
318
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
319
|
+
await client.getProfile("/my/project");
|
|
320
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
321
|
+
expect(url).toContain("project_path=%2Fmy%2Fproject");
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("getInjection returns data", async () => {
|
|
325
|
+
fetchMock = makeFetchMock(200, { content: "injected", preference_count: 3, estimated_tokens: 100 });
|
|
326
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
327
|
+
const result = await client.getInjection();
|
|
328
|
+
expect(result).toEqual({ content: "injected", preference_count: 3, estimated_tokens: 100 });
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("listRecent returns memories array", async () => {
|
|
332
|
+
fetchMock = makeFetchMock(200, { memories: [{ id: "1" }], limit: 20, offset: 0 });
|
|
333
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
334
|
+
const result = await client.listRecent(5);
|
|
335
|
+
const url = fetchMock.mock.calls[0][0] as string;
|
|
336
|
+
expect(url).toContain("limit=5");
|
|
337
|
+
expect(result).toEqual([{ id: "1" }]);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("listSpaces returns empty array when null", async () => {
|
|
341
|
+
fetchMock = vi.fn().mockResolvedValue(new Response("null", { status: 200 }));
|
|
342
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
343
|
+
const result = await client.listSpaces();
|
|
344
|
+
expect(result).toEqual([]);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it("createSpace sends POST /v1/spaces", async () => {
|
|
348
|
+
fetchMock = makeFetchMock(200, { id: "sp1" });
|
|
349
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
350
|
+
await client.createSpace("team1", "team", [{ user_id: "u1", role: "admin" }]);
|
|
351
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
352
|
+
expect(url).toBe("http://localhost:8080/v1/spaces");
|
|
353
|
+
const body = JSON.parse(init.body as string);
|
|
354
|
+
expect(body.name).toBe("team1");
|
|
355
|
+
expect(body.space_type).toBe("team");
|
|
356
|
+
expect(body.members).toEqual([{ user_id: "u1", role: "admin" }]);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it("searchMemories returns empty array when response is null", async () => {
|
|
360
|
+
fetchMock = vi.fn().mockResolvedValue(new Response("null", { status: 200 }));
|
|
361
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
362
|
+
const result = await client.searchMemories("test");
|
|
363
|
+
expect(result).toEqual([]);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it("ingestMessages defaults mode to 'smart'", async () => {
|
|
367
|
+
fetchMock = makeFetchMock(200, {});
|
|
368
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
369
|
+
await client.ingestMessages([{ role: "user", content: "hi" }]);
|
|
370
|
+
const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
|
|
371
|
+
expect(body.mode).toBe("smart");
|
|
372
|
+
});
|
|
373
|
+
});
|