assistant-cloud 0.1.43 → 0.2.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/README.md +66 -0
- package/dist/AssistantCloud.d.ts +6 -1
- package/dist/AssistantCloud.d.ts.map +1 -1
- package/dist/AssistantCloud.js +13 -5
- package/dist/AssistantCloud.js.map +1 -1
- package/dist/AssistantCloudAPI.d.ts +28 -3
- package/dist/AssistantCloudAPI.d.ts.map +1 -1
- package/dist/AssistantCloudAPI.js +37 -10
- package/dist/AssistantCloudAPI.js.map +1 -1
- package/dist/AssistantCloudAuthStrategy.d.ts +4 -1
- package/dist/AssistantCloudAuthStrategy.d.ts.map +1 -1
- package/dist/AssistantCloudAuthStrategy.js +15 -3
- package/dist/AssistantCloudAuthStrategy.js.map +1 -1
- package/dist/AssistantCloudEvents.d.ts +32 -0
- package/dist/AssistantCloudEvents.d.ts.map +1 -0
- package/dist/AssistantCloudEvents.js +119 -0
- package/dist/AssistantCloudEvents.js.map +1 -0
- package/dist/AssistantCloudFiles.d.ts +12 -1
- package/dist/AssistantCloudFiles.d.ts.map +1 -1
- package/dist/AssistantCloudFiles.js +13 -1
- package/dist/AssistantCloudFiles.js.map +1 -1
- package/dist/AssistantCloudRuns.d.ts +12 -0
- package/dist/AssistantCloudRuns.d.ts.map +1 -1
- package/dist/AssistantCloudRuns.js +2 -1
- package/dist/AssistantCloudRuns.js.map +1 -1
- package/dist/AssistantCloudScores.d.ts +27 -0
- package/dist/AssistantCloudScores.d.ts.map +1 -0
- package/dist/AssistantCloudScores.js +30 -0
- package/dist/AssistantCloudScores.js.map +1 -0
- package/dist/AssistantCloudThreadMessages.d.ts +11 -1
- package/dist/AssistantCloudThreadMessages.d.ts.map +1 -1
- package/dist/AssistantCloudThreadMessages.js +12 -1
- package/dist/AssistantCloudThreadMessages.js.map +1 -1
- package/dist/AssistantCloudThreads.d.ts +8 -0
- package/dist/AssistantCloudThreads.d.ts.map +1 -1
- package/dist/AssistantCloudThreads.js +9 -1
- package/dist/AssistantCloudThreads.js.map +1 -1
- package/dist/CloudEngagementReporter.d.ts +74 -0
- package/dist/CloudEngagementReporter.d.ts.map +1 -0
- package/dist/CloudEngagementReporter.js +132 -0
- package/dist/CloudEngagementReporter.js.map +1 -0
- package/dist/CloudMessagePersistence.d.ts +12 -9
- package/dist/CloudMessagePersistence.d.ts.map +1 -1
- package/dist/CloudMessagePersistence.js +54 -20
- package/dist/CloudMessagePersistence.js.map +1 -1
- package/dist/CloudRunReporter.d.ts +20 -0
- package/dist/CloudRunReporter.d.ts.map +1 -0
- package/dist/CloudRunReporter.js +38 -0
- package/dist/CloudRunReporter.js.map +1 -0
- package/dist/FormattedCloudPersistence.d.ts +3 -8
- package/dist/FormattedCloudPersistence.d.ts.map +1 -1
- package/dist/FormattedCloudPersistence.js +3 -8
- package/dist/FormattedCloudPersistence.js.map +1 -1
- package/dist/ai-sdk/index.d.ts +33 -0
- package/dist/ai-sdk/index.d.ts.map +1 -0
- package/dist/ai-sdk/index.js +147 -0
- package/dist/ai-sdk/index.js.map +1 -0
- package/dist/cloudResponse.d.ts +4 -1
- package/dist/cloudResponse.d.ts.map +1 -1
- package/dist/cloudResponse.js +14 -1
- package/dist/cloudResponse.js.map +1 -1
- package/dist/generateThreadTitle.js +1 -0
- package/dist/generateThreadTitle.js.map +1 -1
- package/dist/index.d.ts +10 -4
- package/dist/index.js +7 -2
- package/dist/instrumentMcpSampling.d.ts.map +1 -1
- package/dist/instrumentMcpSampling.js +12 -2
- package/dist/instrumentMcpSampling.js.map +1 -1
- package/dist/runTelemetry.d.ts +86 -7
- package/dist/runTelemetry.d.ts.map +1 -1
- package/dist/runTelemetry.js +144 -1
- package/dist/runTelemetry.js.map +1 -1
- package/dist/telemetry/index.d.ts +32 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +62 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- package/package.json +43 -6
- package/src/AssistantCloud.ts +23 -8
- package/src/AssistantCloudAPI.ts +72 -10
- package/src/AssistantCloudAuthStrategy.ts +17 -2
- package/src/AssistantCloudEvents.test.ts +149 -0
- package/src/AssistantCloudEvents.ts +190 -0
- package/src/AssistantCloudFiles.test.ts +59 -0
- package/src/AssistantCloudFiles.ts +39 -0
- package/src/AssistantCloudRuns.ts +13 -1
- package/src/AssistantCloudScores.test.ts +113 -0
- package/src/AssistantCloudScores.ts +61 -0
- package/src/AssistantCloudThreadMessages.test.ts +51 -0
- package/src/AssistantCloudThreadMessages.ts +33 -0
- package/src/AssistantCloudThreads.test.ts +21 -0
- package/src/AssistantCloudThreads.ts +21 -0
- package/src/CloudEngagementReporter.ts +231 -0
- package/src/CloudMessagePersistence.ts +70 -27
- package/src/CloudRunReporter.ts +38 -0
- package/src/FormattedCloudPersistence.ts +3 -8
- package/src/ai-sdk/index.test.ts +258 -0
- package/src/ai-sdk/index.ts +222 -0
- package/src/cloudResponse.test.ts +34 -1
- package/src/cloudResponse.ts +30 -0
- package/src/generateThreadTitle.test.ts +32 -0
- package/src/generateThreadTitle.ts +1 -0
- package/src/index.ts +35 -2
- package/src/instrumentMcpSampling.test.ts +102 -0
- package/src/instrumentMcpSampling.ts +16 -2
- package/src/runTelemetry.test.ts +325 -0
- package/src/runTelemetry.ts +274 -6
- package/src/telemetry/index.test.ts +252 -0
- package/src/telemetry/index.ts +98 -0
- package/src/tests/AssistantCloud.test.ts +55 -1
- package/src/tests/AssistantCloudAPI.test.ts +95 -4
- package/src/tests/AssistantCloudAuthStrategy.test.ts +93 -2
- package/src/tests/AssistantCloudProjects.test.ts +1 -5
- package/src/tests/CloudEngagementReporter.test.ts +184 -0
- package/src/tests/CloudMessagePersistence.test.ts +350 -0
- package/src/tests/CloudRunReporter.test.ts +100 -0
- package/src/version.ts +4 -0
|
@@ -14,6 +14,18 @@ function createMockCloud() {
|
|
|
14
14
|
} as unknown as AssistantCloud;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function createCloudMessages(count: number) {
|
|
18
|
+
return Array.from({ length: count }, (_, index) => ({
|
|
19
|
+
id: `message-${index + 1}`,
|
|
20
|
+
parent_id: null,
|
|
21
|
+
height: index,
|
|
22
|
+
created_at: "2025-01-01T00:00:00Z" as unknown as Date,
|
|
23
|
+
updated_at: "2025-01-01T00:00:00Z" as unknown as Date,
|
|
24
|
+
format: "aui/v0",
|
|
25
|
+
content: {},
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
describe("CloudMessagePersistence", () => {
|
|
18
30
|
let cloud: AssistantCloud;
|
|
19
31
|
let persistence: CloudMessagePersistence;
|
|
@@ -35,8 +47,29 @@ describe("CloudMessagePersistence", () => {
|
|
|
35
47
|
|
|
36
48
|
expect(persistence.isPersisted("local-1")).toBe(true);
|
|
37
49
|
expect(await persistence.getRemoteId("local-1")).toBe("remote-1");
|
|
50
|
+
expect(persistence.getResolvedRemoteId("local-1")).toBe("remote-1");
|
|
38
51
|
});
|
|
39
52
|
|
|
53
|
+
it.each(["__proto__", "constructor", "toString"])(
|
|
54
|
+
"supports prototype-named local ID %s",
|
|
55
|
+
async (messageId) => {
|
|
56
|
+
vi.mocked(cloud.threads.messages.create).mockResolvedValue({
|
|
57
|
+
message_id: `remote-${messageId}`,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(persistence.isPersisted(messageId)).toBe(false);
|
|
61
|
+
|
|
62
|
+
await persistence.append("thread-1", messageId, null, "aui/v0", {
|
|
63
|
+
text: "hello",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(persistence.isPersisted(messageId)).toBe(true);
|
|
67
|
+
expect(await persistence.getRemoteId(messageId)).toBe(
|
|
68
|
+
`remote-${messageId}`,
|
|
69
|
+
);
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
40
73
|
it("uses the current client without losing ID mappings", async () => {
|
|
41
74
|
const firstCloud = createMockCloud();
|
|
42
75
|
const secondCloud = createMockCloud();
|
|
@@ -219,6 +252,299 @@ describe("CloudMessagePersistence", () => {
|
|
|
219
252
|
await persistence.load("thread-1");
|
|
220
253
|
|
|
221
254
|
expect(persistence.isPersisted("msg-1")).toBe(true);
|
|
255
|
+
expect(await persistence.getRemoteId("msg-1")).toBe("msg-1");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("preserves a pending append mapping when load returns the same ID", async () => {
|
|
259
|
+
const messages = createCloudMessages(1);
|
|
260
|
+
let resolveLoad!: (value: { messages: typeof messages }) => void;
|
|
261
|
+
let resolveAppend!: (value: { message_id: string }) => void;
|
|
262
|
+
vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
|
|
263
|
+
() =>
|
|
264
|
+
new Promise((resolve) => {
|
|
265
|
+
resolveLoad = resolve;
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
|
|
269
|
+
() =>
|
|
270
|
+
new Promise((resolve) => {
|
|
271
|
+
resolveAppend = resolve;
|
|
272
|
+
}),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const load = persistence.load("thread-1");
|
|
276
|
+
const append = persistence.append(
|
|
277
|
+
"thread-1",
|
|
278
|
+
"message-1",
|
|
279
|
+
null,
|
|
280
|
+
"aui/v0",
|
|
281
|
+
{},
|
|
282
|
+
);
|
|
283
|
+
resolveLoad({ messages });
|
|
284
|
+
await load;
|
|
285
|
+
resolveAppend({ message_id: "remote-2" });
|
|
286
|
+
await append;
|
|
287
|
+
|
|
288
|
+
expect(await persistence.getRemoteId("message-1")).toBe("remote-2");
|
|
289
|
+
await persistence.update("thread-1", "message-1", "aui/v0", {
|
|
290
|
+
text: "updated",
|
|
291
|
+
});
|
|
292
|
+
expect(cloud.threads.messages.update).toHaveBeenCalledWith(
|
|
293
|
+
"thread-1",
|
|
294
|
+
"remote-2",
|
|
295
|
+
{ content: { text: "updated" } },
|
|
296
|
+
);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("preserves a resolved append mapping when load returns the same ID", async () => {
|
|
300
|
+
const messages = createCloudMessages(1);
|
|
301
|
+
vi.mocked(cloud.threads.messages.create).mockResolvedValue({
|
|
302
|
+
message_id: "remote-1",
|
|
303
|
+
});
|
|
304
|
+
vi.mocked(cloud.threads.messages.list).mockResolvedValue({
|
|
305
|
+
messages,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
await persistence.append("thread-1", "message-1", null, "aui/v0", {});
|
|
309
|
+
await persistence.load("thread-1");
|
|
310
|
+
|
|
311
|
+
expect(await persistence.getRemoteId("message-1")).toBe("remote-1");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("keeps the loaded ID when a pending append for it fails", async () => {
|
|
315
|
+
const messages = createCloudMessages(1);
|
|
316
|
+
const failure = new Error("create failed");
|
|
317
|
+
let resolveLoad!: (value: { messages: typeof messages }) => void;
|
|
318
|
+
let rejectAppend!: (error: Error) => void;
|
|
319
|
+
vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
|
|
320
|
+
() =>
|
|
321
|
+
new Promise((resolve) => {
|
|
322
|
+
resolveLoad = resolve;
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
|
|
326
|
+
() =>
|
|
327
|
+
new Promise((_resolve, reject) => {
|
|
328
|
+
rejectAppend = reject;
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const load = persistence.load("thread-1");
|
|
333
|
+
const append = persistence.append(
|
|
334
|
+
"thread-1",
|
|
335
|
+
"message-1",
|
|
336
|
+
null,
|
|
337
|
+
"aui/v0",
|
|
338
|
+
{},
|
|
339
|
+
);
|
|
340
|
+
resolveLoad({ messages });
|
|
341
|
+
await load;
|
|
342
|
+
rejectAppend(failure);
|
|
343
|
+
|
|
344
|
+
await expect(append).rejects.toBe(failure);
|
|
345
|
+
expect(persistence.isPersisted("message-1")).toBe(true);
|
|
346
|
+
expect(await persistence.getRemoteId("message-1")).toBe("message-1");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("does not restore a loaded ID after reset when its pending append fails", async () => {
|
|
350
|
+
const messages = createCloudMessages(1);
|
|
351
|
+
const failure = new Error("create failed");
|
|
352
|
+
let rejectAppend!: (error: Error) => void;
|
|
353
|
+
vi.mocked(cloud.threads.messages.list).mockResolvedValue({ messages });
|
|
354
|
+
vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
|
|
355
|
+
() =>
|
|
356
|
+
new Promise((_resolve, reject) => {
|
|
357
|
+
rejectAppend = reject;
|
|
358
|
+
}),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const append = persistence.append(
|
|
362
|
+
"thread-1",
|
|
363
|
+
"message-1",
|
|
364
|
+
null,
|
|
365
|
+
"aui/v0",
|
|
366
|
+
{},
|
|
367
|
+
);
|
|
368
|
+
await persistence.load("thread-1");
|
|
369
|
+
persistence.reset();
|
|
370
|
+
rejectAppend(failure);
|
|
371
|
+
|
|
372
|
+
await expect(append).rejects.toBe(failure);
|
|
373
|
+
expect(persistence.isPersisted("message-1")).toBe(false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("does not restore IDs from a load that finishes after reset", async () => {
|
|
377
|
+
const oldMessages = createCloudMessages(1);
|
|
378
|
+
const newMessages = [{ ...oldMessages[0]!, id: "new-message" }];
|
|
379
|
+
let resolveOld!: (value: { messages: typeof oldMessages }) => void;
|
|
380
|
+
vi.mocked(cloud.threads.messages.list)
|
|
381
|
+
.mockImplementationOnce(
|
|
382
|
+
() =>
|
|
383
|
+
new Promise((resolve) => {
|
|
384
|
+
resolveOld = resolve;
|
|
385
|
+
}),
|
|
386
|
+
)
|
|
387
|
+
.mockResolvedValueOnce({ messages: newMessages });
|
|
388
|
+
|
|
389
|
+
const oldLoad = persistence.load("old-thread");
|
|
390
|
+
persistence.reset();
|
|
391
|
+
await persistence.load("new-thread");
|
|
392
|
+
resolveOld({ messages: oldMessages });
|
|
393
|
+
|
|
394
|
+
expect(await oldLoad).toEqual(oldMessages);
|
|
395
|
+
expect(persistence.isPersisted("message-1")).toBe(false);
|
|
396
|
+
expect(persistence.isPersisted("new-message")).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("keeps a new append mapping when an old load returns the same ID", async () => {
|
|
400
|
+
const messages = createCloudMessages(1);
|
|
401
|
+
let resolveOld!: (value: { messages: typeof messages }) => void;
|
|
402
|
+
vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
|
|
403
|
+
() =>
|
|
404
|
+
new Promise((resolve) => {
|
|
405
|
+
resolveOld = resolve;
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
408
|
+
vi.mocked(cloud.threads.messages.create).mockResolvedValue({
|
|
409
|
+
message_id: "new-remote-id",
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
const oldLoad = persistence.load("old-thread");
|
|
413
|
+
persistence.reset();
|
|
414
|
+
await persistence.append("new-thread", "message-1", null, "aui/v0", {});
|
|
415
|
+
resolveOld({ messages });
|
|
416
|
+
await oldLoad;
|
|
417
|
+
|
|
418
|
+
expect(await persistence.getRemoteId("message-1")).toBe("new-remote-id");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("does not populate any old IDs when reset happens between history pages", async () => {
|
|
422
|
+
const messages = createCloudMessages(201);
|
|
423
|
+
let resolveLast!: (value: { messages: typeof messages }) => void;
|
|
424
|
+
vi.mocked(cloud.threads.messages.list)
|
|
425
|
+
.mockResolvedValueOnce({ messages: messages.slice(0, 200) })
|
|
426
|
+
.mockImplementationOnce(
|
|
427
|
+
() =>
|
|
428
|
+
new Promise((resolve) => {
|
|
429
|
+
resolveLast = resolve;
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
const load = persistence.load("old-thread");
|
|
434
|
+
await vi.waitFor(() =>
|
|
435
|
+
expect(cloud.threads.messages.list).toHaveBeenCalledTimes(2),
|
|
436
|
+
);
|
|
437
|
+
persistence.reset();
|
|
438
|
+
resolveLast({ messages: messages.slice(200) });
|
|
439
|
+
|
|
440
|
+
expect(await load).toEqual(messages);
|
|
441
|
+
expect(
|
|
442
|
+
messages.some((message) => persistence.isPersisted(message.id)),
|
|
443
|
+
).toBe(false);
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it("propagates an old load failure without disturbing post-reset state", async () => {
|
|
447
|
+
let rejectOld!: (error: Error) => void;
|
|
448
|
+
vi.mocked(cloud.threads.messages.list).mockImplementationOnce(
|
|
449
|
+
() =>
|
|
450
|
+
new Promise((_resolve, reject) => {
|
|
451
|
+
rejectOld = reject;
|
|
452
|
+
}),
|
|
453
|
+
);
|
|
454
|
+
vi.mocked(cloud.threads.messages.create).mockResolvedValue({
|
|
455
|
+
message_id: "new-remote-id",
|
|
456
|
+
});
|
|
457
|
+
const failure = new Error("history failed");
|
|
458
|
+
const oldLoad = persistence.load("old-thread");
|
|
459
|
+
const rejected = expect(oldLoad).rejects.toBe(failure);
|
|
460
|
+
persistence.reset();
|
|
461
|
+
await persistence.append("new-thread", "new-message", null, "aui/v0", {});
|
|
462
|
+
rejectOld(failure);
|
|
463
|
+
|
|
464
|
+
await rejected;
|
|
465
|
+
expect(await persistence.getRemoteId("new-message")).toBe("new-remote-id");
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("maps prototype-named loaded messages", async () => {
|
|
469
|
+
vi.mocked(cloud.threads.messages.list).mockResolvedValue({
|
|
470
|
+
messages: [
|
|
471
|
+
{
|
|
472
|
+
id: "__proto__",
|
|
473
|
+
parent_id: null,
|
|
474
|
+
height: 0,
|
|
475
|
+
created_at: "2025-01-01T00:00:00Z" as unknown as Date,
|
|
476
|
+
updated_at: "2025-01-01T00:00:00Z" as unknown as Date,
|
|
477
|
+
format: "aui/v0",
|
|
478
|
+
content: { text: "loaded" },
|
|
479
|
+
},
|
|
480
|
+
],
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
await persistence.load("thread-1");
|
|
484
|
+
|
|
485
|
+
expect(persistence.isPersisted("__proto__")).toBe(true);
|
|
486
|
+
expect(await persistence.getRemoteId("__proto__")).toBe("__proto__");
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it("follows the message cursor across pages in server order", async () => {
|
|
490
|
+
const rows = createCloudMessages(450);
|
|
491
|
+
vi.mocked(cloud.threads.messages.list).mockImplementation(
|
|
492
|
+
async (_threadId, query) => {
|
|
493
|
+
const start = query?.after
|
|
494
|
+
? rows.findIndex((row) => row.id === query.after) + 1
|
|
495
|
+
: 0;
|
|
496
|
+
return { messages: rows.slice(start, start + 200) };
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
const messages = await persistence.load("thread-1", "aui/v0");
|
|
501
|
+
|
|
502
|
+
expect(messages.map((message) => message.id)).toEqual(
|
|
503
|
+
rows.map((row) => row.id),
|
|
504
|
+
);
|
|
505
|
+
expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
|
|
506
|
+
expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(1, "thread-1", {
|
|
507
|
+
format: "aui/v0",
|
|
508
|
+
limit: 200,
|
|
509
|
+
});
|
|
510
|
+
expect(cloud.threads.messages.list).toHaveBeenNthCalledWith(3, "thread-1", {
|
|
511
|
+
format: "aui/v0",
|
|
512
|
+
limit: 200,
|
|
513
|
+
after: "message-400",
|
|
514
|
+
});
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("stops without duplicates when a later cursor stops resolving", async () => {
|
|
518
|
+
const rows = createCloudMessages(450);
|
|
519
|
+
vi.mocked(cloud.threads.messages.list).mockImplementation(
|
|
520
|
+
async (_threadId, query) => {
|
|
521
|
+
// The third request replays page one, the way the endpoint answers a
|
|
522
|
+
// cursor whose message no longer resolves.
|
|
523
|
+
const start =
|
|
524
|
+
query?.after && query.after !== "message-400"
|
|
525
|
+
? rows.findIndex((row) => row.id === query.after) + 1
|
|
526
|
+
: 0;
|
|
527
|
+
return { messages: rows.slice(start, start + 200) };
|
|
528
|
+
},
|
|
529
|
+
);
|
|
530
|
+
|
|
531
|
+
const messages = await persistence.load("thread-1", "aui/v0");
|
|
532
|
+
|
|
533
|
+
expect(messages.map((message) => message.id)).toEqual(
|
|
534
|
+
rows.slice(0, 400).map((row) => row.id),
|
|
535
|
+
);
|
|
536
|
+
expect(cloud.threads.messages.list).toHaveBeenCalledTimes(3);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it("stops when a page does not advance the cursor", async () => {
|
|
540
|
+
vi.mocked(cloud.threads.messages.list).mockResolvedValue({
|
|
541
|
+
messages: createCloudMessages(200),
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const messages = await persistence.load("thread-1", "aui/v0");
|
|
545
|
+
|
|
546
|
+
expect(messages).toHaveLength(200);
|
|
547
|
+
expect(cloud.threads.messages.list).toHaveBeenCalledTimes(2);
|
|
222
548
|
});
|
|
223
549
|
|
|
224
550
|
it("updates an already-persisted message", async () => {
|
|
@@ -268,6 +594,30 @@ describe("CloudMessagePersistence", () => {
|
|
|
268
594
|
expect(persistence.isPersisted("local-1")).toBe(false);
|
|
269
595
|
});
|
|
270
596
|
|
|
597
|
+
it("does not restore an append mapping after reset", async () => {
|
|
598
|
+
let resolveAppend!: (value: { message_id: string }) => void;
|
|
599
|
+
vi.mocked(cloud.threads.messages.create).mockImplementationOnce(
|
|
600
|
+
() =>
|
|
601
|
+
new Promise((resolve) => {
|
|
602
|
+
resolveAppend = resolve;
|
|
603
|
+
}),
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
const append = persistence.append(
|
|
607
|
+
"thread-1",
|
|
608
|
+
"local-1",
|
|
609
|
+
null,
|
|
610
|
+
"aui/v0",
|
|
611
|
+
{},
|
|
612
|
+
);
|
|
613
|
+
persistence.reset();
|
|
614
|
+
resolveAppend({ message_id: "remote-1" });
|
|
615
|
+
|
|
616
|
+
await expect(append).resolves.toBeUndefined();
|
|
617
|
+
expect(persistence.isPersisted("local-1")).toBe(false);
|
|
618
|
+
expect(await persistence.getRemoteId("local-1")).toBeUndefined();
|
|
619
|
+
});
|
|
620
|
+
|
|
271
621
|
it("reset clears all ID mappings", async () => {
|
|
272
622
|
vi.mocked(cloud.threads.messages.create).mockResolvedValue({
|
|
273
623
|
message_id: "remote-1",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { AssistantCloud } from "../AssistantCloud";
|
|
3
|
+
import { CloudRunReporter } from "../CloudRunReporter";
|
|
4
|
+
|
|
5
|
+
const createCloud = (telemetry: AssistantCloud["telemetry"]) => {
|
|
6
|
+
const report = vi.fn().mockResolvedValue({ run_id: "run_1" });
|
|
7
|
+
const cloud = { telemetry, runs: { report } } as unknown as AssistantCloud;
|
|
8
|
+
return { cloud, report };
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe("CloudRunReporter", () => {
|
|
12
|
+
it("sends nothing while telemetry is disabled", async () => {
|
|
13
|
+
const { cloud, report } = createCloud({ enabled: false });
|
|
14
|
+
await new CloudRunReporter(cloud).report({
|
|
15
|
+
threadId: "thread_1",
|
|
16
|
+
status: "completed",
|
|
17
|
+
});
|
|
18
|
+
expect(report).not.toHaveBeenCalled();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("stamps the cloud's environment, release and tags and applies beforeReport last", async () => {
|
|
22
|
+
const beforeReport = vi.fn((report) => ({ ...report, model_id: "gpt" }));
|
|
23
|
+
const { cloud, report } = createCloud({
|
|
24
|
+
enabled: true,
|
|
25
|
+
environment: "production",
|
|
26
|
+
release: "1.2.3",
|
|
27
|
+
tags: ["web"],
|
|
28
|
+
beforeReport,
|
|
29
|
+
});
|
|
30
|
+
await new CloudRunReporter(cloud).report({
|
|
31
|
+
threadId: "thread_1",
|
|
32
|
+
status: "completed",
|
|
33
|
+
outputText: "hi",
|
|
34
|
+
});
|
|
35
|
+
expect(beforeReport).toHaveBeenCalledWith(
|
|
36
|
+
expect.objectContaining({
|
|
37
|
+
thread_id: "thread_1",
|
|
38
|
+
environment: "production",
|
|
39
|
+
release: "1.2.3",
|
|
40
|
+
tags: ["web"],
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
expect(report).toHaveBeenCalledWith(
|
|
44
|
+
expect.objectContaining({ thread_id: "thread_1", model_id: "gpt" }),
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("skips a report the hook vetoes without consuming its key", async () => {
|
|
49
|
+
const { cloud, report } = createCloud({
|
|
50
|
+
enabled: true,
|
|
51
|
+
beforeReport: vi
|
|
52
|
+
.fn()
|
|
53
|
+
.mockReturnValueOnce(null)
|
|
54
|
+
.mockImplementation((r) => r),
|
|
55
|
+
});
|
|
56
|
+
const reporter = new CloudRunReporter(cloud);
|
|
57
|
+
await reporter.report({ threadId: "t", status: "completed" }, "t:m");
|
|
58
|
+
await reporter.report({ threadId: "t", status: "completed" }, "t:m");
|
|
59
|
+
expect(report).toHaveBeenCalledOnce();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("reports a keyed run once and an unkeyed run every time", async () => {
|
|
63
|
+
const { cloud, report } = createCloud({ enabled: true });
|
|
64
|
+
const reporter = new CloudRunReporter(cloud);
|
|
65
|
+
await reporter.report({ threadId: "t", status: "completed" }, "t:m");
|
|
66
|
+
await reporter.report({ threadId: "t", status: "completed" }, "t:m");
|
|
67
|
+
await reporter.report({ threadId: "t", status: "completed" });
|
|
68
|
+
await reporter.report({ threadId: "t", status: "completed" });
|
|
69
|
+
expect(report).toHaveBeenCalledTimes(3);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("swallows a failed send and reads the cloud through a getter", async () => {
|
|
73
|
+
const { cloud } = createCloud({ enabled: true });
|
|
74
|
+
(cloud.runs.report as ReturnType<typeof vi.fn>).mockRejectedValue(
|
|
75
|
+
new Error("offline"),
|
|
76
|
+
);
|
|
77
|
+
await expect(
|
|
78
|
+
new CloudRunReporter(() => cloud).report({
|
|
79
|
+
threadId: "t",
|
|
80
|
+
status: "error",
|
|
81
|
+
}),
|
|
82
|
+
).resolves.toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("resolves when the hook throws and reports nothing", async () => {
|
|
86
|
+
const { cloud, report } = createCloud({
|
|
87
|
+
enabled: true,
|
|
88
|
+
beforeReport: () => {
|
|
89
|
+
throw new Error("hook");
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
await expect(
|
|
93
|
+
new CloudRunReporter(cloud).report({
|
|
94
|
+
threadId: "t",
|
|
95
|
+
status: "completed",
|
|
96
|
+
}),
|
|
97
|
+
).resolves.toBeUndefined();
|
|
98
|
+
expect(report).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
package/src/version.ts
ADDED