@debriefer/core 2.0.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/README.md +86 -0
- package/dist/__tests__/base-source.test.d.ts +2 -0
- package/dist/__tests__/base-source.test.d.ts.map +1 -0
- package/dist/__tests__/base-source.test.js +333 -0
- package/dist/__tests__/base-source.test.js.map +1 -0
- package/dist/__tests__/batch-runner.test.d.ts +2 -0
- package/dist/__tests__/batch-runner.test.d.ts.map +1 -0
- package/dist/__tests__/batch-runner.test.js +217 -0
- package/dist/__tests__/batch-runner.test.js.map +1 -0
- package/dist/__tests__/cache.test.d.ts +2 -0
- package/dist/__tests__/cache.test.d.ts.map +1 -0
- package/dist/__tests__/cache.test.js +127 -0
- package/dist/__tests__/cache.test.js.map +1 -0
- package/dist/__tests__/confidence.test.d.ts +2 -0
- package/dist/__tests__/confidence.test.d.ts.map +1 -0
- package/dist/__tests__/confidence.test.js +81 -0
- package/dist/__tests__/confidence.test.js.map +1 -0
- package/dist/__tests__/cost-tracker.test.d.ts +2 -0
- package/dist/__tests__/cost-tracker.test.d.ts.map +1 -0
- package/dist/__tests__/cost-tracker.test.js +149 -0
- package/dist/__tests__/cost-tracker.test.js.map +1 -0
- package/dist/__tests__/orchestrator.test.d.ts +2 -0
- package/dist/__tests__/orchestrator.test.d.ts.map +1 -0
- package/dist/__tests__/orchestrator.test.js +751 -0
- package/dist/__tests__/orchestrator.test.js.map +1 -0
- package/dist/__tests__/rate-limiter.test.d.ts +2 -0
- package/dist/__tests__/rate-limiter.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limiter.test.js +83 -0
- package/dist/__tests__/rate-limiter.test.js.map +1 -0
- package/dist/__tests__/reliability.test.d.ts +2 -0
- package/dist/__tests__/reliability.test.d.ts.map +1 -0
- package/dist/__tests__/reliability.test.js +207 -0
- package/dist/__tests__/reliability.test.js.map +1 -0
- package/dist/__tests__/synthesizer.test.d.ts +2 -0
- package/dist/__tests__/synthesizer.test.d.ts.map +1 -0
- package/dist/__tests__/synthesizer.test.js +50 -0
- package/dist/__tests__/synthesizer.test.js.map +1 -0
- package/dist/__tests__/telemetry.test.d.ts +2 -0
- package/dist/__tests__/telemetry.test.d.ts.map +1 -0
- package/dist/__tests__/telemetry.test.js +81 -0
- package/dist/__tests__/telemetry.test.js.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.js +708 -0
- package/dist/__tests__/types.test.js.map +1 -0
- package/dist/base-source.d.ts +91 -0
- package/dist/base-source.d.ts.map +1 -0
- package/dist/base-source.js +144 -0
- package/dist/base-source.js.map +1 -0
- package/dist/batch-runner.d.ts +40 -0
- package/dist/batch-runner.d.ts.map +1 -0
- package/dist/batch-runner.js +65 -0
- package/dist/batch-runner.js.map +1 -0
- package/dist/cache/in-memory.d.ts +26 -0
- package/dist/cache/in-memory.d.ts.map +1 -0
- package/dist/cache/in-memory.js +51 -0
- package/dist/cache/in-memory.js.map +1 -0
- package/dist/confidence.d.ts +13 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/confidence.js +29 -0
- package/dist/confidence.js.map +1 -0
- package/dist/cost-tracker.d.ts +37 -0
- package/dist/cost-tracker.d.ts.map +1 -0
- package/dist/cost-tracker.js +62 -0
- package/dist/cost-tracker.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +92 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +373 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/rate-limiter.d.ts +31 -0
- package/dist/rate-limiter.d.ts.map +1 -0
- package/dist/rate-limiter.js +79 -0
- package/dist/rate-limiter.js.map +1 -0
- package/dist/reliability.d.ts +49 -0
- package/dist/reliability.d.ts.map +1 -0
- package/dist/reliability.js +67 -0
- package/dist/reliability.js.map +1 -0
- package/dist/synthesizer.d.ts +31 -0
- package/dist/synthesizer.d.ts.map +1 -0
- package/dist/synthesizer.js +47 -0
- package/dist/synthesizer.js.map +1 -0
- package/dist/telemetry/console.d.ts +7 -0
- package/dist/telemetry/console.d.ts.map +1 -0
- package/dist/telemetry/console.js +21 -0
- package/dist/telemetry/console.js.map +1 -0
- package/dist/telemetry/noop.d.ts +7 -0
- package/dist/telemetry/noop.d.ts.map +1 -0
- package/dist/telemetry/noop.js +12 -0
- package/dist/telemetry/noop.js.map +1 -0
- package/dist/types.d.ts +417 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +102 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { ParallelBatchRunner } from "../batch-runner.js";
|
|
3
|
+
function delay(ms) {
|
|
4
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
}
|
|
6
|
+
describe("ParallelBatchRunner", () => {
|
|
7
|
+
it("processes all items and returns results in order", async () => {
|
|
8
|
+
const runner = new ParallelBatchRunner({
|
|
9
|
+
concurrency: 3,
|
|
10
|
+
});
|
|
11
|
+
const results = await runner.run([1, 2, 3, 4, 5], async (item) => {
|
|
12
|
+
await delay(10);
|
|
13
|
+
return `result-${item}`;
|
|
14
|
+
});
|
|
15
|
+
expect(results).toHaveLength(5);
|
|
16
|
+
expect(results[0]).toEqual({ item: 1, result: "result-1" });
|
|
17
|
+
expect(results[1]).toEqual({ item: 2, result: "result-2" });
|
|
18
|
+
expect(results[2]).toEqual({ item: 3, result: "result-3" });
|
|
19
|
+
expect(results[3]).toEqual({ item: 4, result: "result-4" });
|
|
20
|
+
expect(results[4]).toEqual({ item: 5, result: "result-5" });
|
|
21
|
+
});
|
|
22
|
+
it("respects concurrency limit", async () => {
|
|
23
|
+
// With concurrency 1, 3 items at ~50ms each should take ~150ms
|
|
24
|
+
const runnerSerial = new ParallelBatchRunner({
|
|
25
|
+
concurrency: 1,
|
|
26
|
+
});
|
|
27
|
+
const items = [1, 2, 3];
|
|
28
|
+
const serialStart = Date.now();
|
|
29
|
+
await runnerSerial.run(items, async (item) => {
|
|
30
|
+
await delay(50);
|
|
31
|
+
return item;
|
|
32
|
+
});
|
|
33
|
+
const serialElapsed = Date.now() - serialStart;
|
|
34
|
+
// With concurrency 3, all 3 run in parallel, should take ~50ms
|
|
35
|
+
const runnerParallel = new ParallelBatchRunner({
|
|
36
|
+
concurrency: 3,
|
|
37
|
+
});
|
|
38
|
+
const parallelStart = Date.now();
|
|
39
|
+
await runnerParallel.run(items, async (item) => {
|
|
40
|
+
await delay(50);
|
|
41
|
+
return item;
|
|
42
|
+
});
|
|
43
|
+
const parallelElapsed = Date.now() - parallelStart;
|
|
44
|
+
// Serial should take at least 130ms (3 * 50ms with some tolerance)
|
|
45
|
+
expect(serialElapsed).toBeGreaterThanOrEqual(130);
|
|
46
|
+
// Parallel should take less than 120ms (roughly 1 * 50ms with tolerance)
|
|
47
|
+
expect(parallelElapsed).toBeLessThan(120);
|
|
48
|
+
// Serial should be meaningfully slower than parallel
|
|
49
|
+
expect(serialElapsed).toBeGreaterThan(parallelElapsed * 1.5);
|
|
50
|
+
});
|
|
51
|
+
it("handles per-item errors without failing the batch", async () => {
|
|
52
|
+
const runner = new ParallelBatchRunner({
|
|
53
|
+
concurrency: 5,
|
|
54
|
+
});
|
|
55
|
+
const results = await runner.run([1, 2, 3, 4], async (item) => {
|
|
56
|
+
if (item === 2 || item === 4) {
|
|
57
|
+
throw new Error(`failed on ${item}`);
|
|
58
|
+
}
|
|
59
|
+
return `ok-${item}`;
|
|
60
|
+
});
|
|
61
|
+
expect(results).toHaveLength(4);
|
|
62
|
+
expect(results[0]).toEqual({ item: 1, result: "ok-1" });
|
|
63
|
+
expect(results[1]).toEqual({
|
|
64
|
+
item: 2,
|
|
65
|
+
result: null,
|
|
66
|
+
error: expect.objectContaining({ message: "failed on 2" }),
|
|
67
|
+
});
|
|
68
|
+
expect(results[2]).toEqual({ item: 3, result: "ok-3" });
|
|
69
|
+
expect(results[3]).toEqual({
|
|
70
|
+
item: 4,
|
|
71
|
+
result: null,
|
|
72
|
+
error: expect.objectContaining({ message: "failed on 4" }),
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
it("calls onItemComplete for each successful item", async () => {
|
|
76
|
+
const onItemComplete = vi.fn();
|
|
77
|
+
const runner = new ParallelBatchRunner({
|
|
78
|
+
concurrency: 1,
|
|
79
|
+
onItemComplete,
|
|
80
|
+
});
|
|
81
|
+
await runner.run([10, 20, 30], async (item) => `done-${item}`);
|
|
82
|
+
expect(onItemComplete).toHaveBeenCalledTimes(3);
|
|
83
|
+
expect(onItemComplete).toHaveBeenCalledWith(10, "done-10", {
|
|
84
|
+
completed: 1,
|
|
85
|
+
total: 3,
|
|
86
|
+
inFlight: 0,
|
|
87
|
+
});
|
|
88
|
+
expect(onItemComplete).toHaveBeenCalledWith(20, "done-20", {
|
|
89
|
+
completed: 2,
|
|
90
|
+
total: 3,
|
|
91
|
+
inFlight: 0,
|
|
92
|
+
});
|
|
93
|
+
expect(onItemComplete).toHaveBeenCalledWith(30, "done-30", {
|
|
94
|
+
completed: 3,
|
|
95
|
+
total: 3,
|
|
96
|
+
inFlight: 0,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
it("calls onItemError for failed items", async () => {
|
|
100
|
+
const onItemError = vi.fn();
|
|
101
|
+
const runner = new ParallelBatchRunner({
|
|
102
|
+
concurrency: 5,
|
|
103
|
+
onItemError,
|
|
104
|
+
});
|
|
105
|
+
await runner.run(["a", "b"], async (item) => {
|
|
106
|
+
if (item === "b")
|
|
107
|
+
throw new Error("boom");
|
|
108
|
+
return item;
|
|
109
|
+
});
|
|
110
|
+
expect(onItemError).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(onItemError).toHaveBeenCalledWith("b", expect.objectContaining({ message: "boom" }), expect.objectContaining({ total: 2 }));
|
|
112
|
+
});
|
|
113
|
+
it("does not call onItemComplete for failed items", async () => {
|
|
114
|
+
const onItemComplete = vi.fn();
|
|
115
|
+
const runner = new ParallelBatchRunner({
|
|
116
|
+
concurrency: 1,
|
|
117
|
+
onItemComplete,
|
|
118
|
+
});
|
|
119
|
+
await runner.run([1, 2], async (item) => {
|
|
120
|
+
if (item === 2)
|
|
121
|
+
throw new Error("fail");
|
|
122
|
+
return `ok-${item}`;
|
|
123
|
+
});
|
|
124
|
+
expect(onItemComplete).toHaveBeenCalledTimes(1);
|
|
125
|
+
expect(onItemComplete).toHaveBeenCalledWith(1, "ok-1", expect.any(Object));
|
|
126
|
+
});
|
|
127
|
+
it("respects abort signal and stops processing new items", async () => {
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const processedItems = [];
|
|
130
|
+
const runner = new ParallelBatchRunner({
|
|
131
|
+
concurrency: 1,
|
|
132
|
+
signal: controller.signal,
|
|
133
|
+
});
|
|
134
|
+
const results = await runner.run([1, 2, 3, 4, 5], async (item) => {
|
|
135
|
+
processedItems.push(item);
|
|
136
|
+
await delay(30);
|
|
137
|
+
// Abort after item 2 completes
|
|
138
|
+
if (item === 2) {
|
|
139
|
+
controller.abort();
|
|
140
|
+
}
|
|
141
|
+
return item;
|
|
142
|
+
});
|
|
143
|
+
expect(results).toHaveLength(5);
|
|
144
|
+
// Items 1 and 2 should have been processed
|
|
145
|
+
expect(processedItems).toContain(1);
|
|
146
|
+
expect(processedItems).toContain(2);
|
|
147
|
+
// Some later items should have been skipped (result: null, no error)
|
|
148
|
+
const skipped = results.filter((r) => r.result === null && r.error === undefined);
|
|
149
|
+
expect(skipped.length).toBeGreaterThan(0);
|
|
150
|
+
});
|
|
151
|
+
it("reports correct progress counts", async () => {
|
|
152
|
+
const progressSnapshots = [];
|
|
153
|
+
const runner = new ParallelBatchRunner({
|
|
154
|
+
concurrency: 1,
|
|
155
|
+
onItemComplete: (_item, _result, progress) => {
|
|
156
|
+
progressSnapshots.push({ ...progress });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
await runner.run([1, 2, 3], async (item) => item * 10);
|
|
160
|
+
expect(progressSnapshots).toEqual([
|
|
161
|
+
{ completed: 1, total: 3, inFlight: 0 },
|
|
162
|
+
{ completed: 2, total: 3, inFlight: 0 },
|
|
163
|
+
{ completed: 3, total: 3, inFlight: 0 },
|
|
164
|
+
]);
|
|
165
|
+
});
|
|
166
|
+
it("reports inFlight > 0 during parallel execution", async () => {
|
|
167
|
+
const maxInFlightSeen = [];
|
|
168
|
+
const runner = new ParallelBatchRunner({
|
|
169
|
+
concurrency: 3,
|
|
170
|
+
onItemComplete: (_item, _result, progress) => {
|
|
171
|
+
maxInFlightSeen.push(progress.inFlight);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
await runner.run([1, 2, 3, 4, 5, 6], async (item) => {
|
|
175
|
+
await delay(50);
|
|
176
|
+
return item;
|
|
177
|
+
});
|
|
178
|
+
// With concurrency 3 and 6 items, some completions should see
|
|
179
|
+
// other items still in flight
|
|
180
|
+
const hadInFlight = maxInFlightSeen.some((n) => n > 0);
|
|
181
|
+
expect(hadInFlight).toBe(true);
|
|
182
|
+
});
|
|
183
|
+
it("returns empty results for empty input", async () => {
|
|
184
|
+
const runner = new ParallelBatchRunner({
|
|
185
|
+
concurrency: 5,
|
|
186
|
+
});
|
|
187
|
+
const results = await runner.run([], async (item) => `${item}`);
|
|
188
|
+
expect(results).toEqual([]);
|
|
189
|
+
});
|
|
190
|
+
it("returns null result for failed items", async () => {
|
|
191
|
+
const runner = new ParallelBatchRunner({
|
|
192
|
+
concurrency: 5,
|
|
193
|
+
});
|
|
194
|
+
const results = await runner.run([1, 2, 3], async (item) => {
|
|
195
|
+
if (item === 2)
|
|
196
|
+
throw new Error("broken");
|
|
197
|
+
return item * 2;
|
|
198
|
+
});
|
|
199
|
+
expect(results[0].result).toBe(2);
|
|
200
|
+
expect(results[1].result).toBeNull();
|
|
201
|
+
expect(results[1].error).toBeDefined();
|
|
202
|
+
expect(results[1].error.message).toBe("broken");
|
|
203
|
+
expect(results[2].result).toBe(6);
|
|
204
|
+
});
|
|
205
|
+
it("wraps non-Error throws into Error objects", async () => {
|
|
206
|
+
const runner = new ParallelBatchRunner({
|
|
207
|
+
concurrency: 1,
|
|
208
|
+
});
|
|
209
|
+
const results = await runner.run([1], async () => {
|
|
210
|
+
throw "string error";
|
|
211
|
+
});
|
|
212
|
+
expect(results[0].result).toBeNull();
|
|
213
|
+
expect(results[0].error).toBeInstanceOf(Error);
|
|
214
|
+
expect(results[0].error.message).toBe("string error");
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
//# sourceMappingURL=batch-runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-runner.test.js","sourceRoot":"","sources":["../../src/__tests__/batch-runner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cache.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { InMemoryCache } from "../cache/in-memory.js";
|
|
3
|
+
describe("InMemoryCache", () => {
|
|
4
|
+
let cache;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
cache = new InMemoryCache();
|
|
7
|
+
});
|
|
8
|
+
it("returns null for missing key", async () => {
|
|
9
|
+
const result = await cache.get("nonexistent");
|
|
10
|
+
expect(result).toBeNull();
|
|
11
|
+
});
|
|
12
|
+
it("set/get roundtrip works", async () => {
|
|
13
|
+
await cache.set("key1", "value1");
|
|
14
|
+
const result = await cache.get("key1");
|
|
15
|
+
expect(result).toBe("value1");
|
|
16
|
+
});
|
|
17
|
+
it("delete removes entry", async () => {
|
|
18
|
+
await cache.set("key1", "value1");
|
|
19
|
+
await cache.delete("key1");
|
|
20
|
+
const result = await cache.get("key1");
|
|
21
|
+
expect(result).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
describe("TTL expiration", () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
vi.useFakeTimers();
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
vi.useRealTimers();
|
|
29
|
+
});
|
|
30
|
+
it("returns value before TTL expires", async () => {
|
|
31
|
+
await cache.set("key1", "value1", 60);
|
|
32
|
+
vi.advanceTimersByTime(59_000);
|
|
33
|
+
const result = await cache.get("key1");
|
|
34
|
+
expect(result).toBe("value1");
|
|
35
|
+
});
|
|
36
|
+
it("returns null after TTL expires", async () => {
|
|
37
|
+
await cache.set("key1", "value1", 60);
|
|
38
|
+
vi.advanceTimersByTime(61_000);
|
|
39
|
+
const result = await cache.get("key1");
|
|
40
|
+
expect(result).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
it("evicts expired entry on get", async () => {
|
|
43
|
+
await cache.set("key1", "value1", 1);
|
|
44
|
+
expect(cache.size).toBe(1);
|
|
45
|
+
vi.advanceTimersByTime(2_000);
|
|
46
|
+
await cache.get("key1");
|
|
47
|
+
expect(cache.size).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
it("no TTL means no expiration", async () => {
|
|
51
|
+
vi.useFakeTimers();
|
|
52
|
+
await cache.set("key1", "value1");
|
|
53
|
+
vi.advanceTimersByTime(1_000_000_000);
|
|
54
|
+
const result = await cache.get("key1");
|
|
55
|
+
expect(result).toBe("value1");
|
|
56
|
+
vi.useRealTimers();
|
|
57
|
+
});
|
|
58
|
+
it("overwrites existing key", async () => {
|
|
59
|
+
await cache.set("key1", "original");
|
|
60
|
+
await cache.set("key1", "updated");
|
|
61
|
+
const result = await cache.get("key1");
|
|
62
|
+
expect(result).toBe("updated");
|
|
63
|
+
});
|
|
64
|
+
it("size property reflects entry count", async () => {
|
|
65
|
+
expect(cache.size).toBe(0);
|
|
66
|
+
await cache.set("a", "1");
|
|
67
|
+
expect(cache.size).toBe(1);
|
|
68
|
+
await cache.set("b", "2");
|
|
69
|
+
expect(cache.size).toBe(2);
|
|
70
|
+
await cache.delete("a");
|
|
71
|
+
expect(cache.size).toBe(1);
|
|
72
|
+
});
|
|
73
|
+
it("clear removes all entries", async () => {
|
|
74
|
+
await cache.set("a", "1");
|
|
75
|
+
await cache.set("b", "2");
|
|
76
|
+
await cache.set("c", "3");
|
|
77
|
+
expect(cache.size).toBe(3);
|
|
78
|
+
cache.clear();
|
|
79
|
+
expect(cache.size).toBe(0);
|
|
80
|
+
expect(await cache.get("a")).toBeNull();
|
|
81
|
+
expect(await cache.get("b")).toBeNull();
|
|
82
|
+
expect(await cache.get("c")).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
describe("maxSize eviction", () => {
|
|
85
|
+
it("evicts oldest entry when maxSize exceeded", async () => {
|
|
86
|
+
const bounded = new InMemoryCache({ maxSize: 2 });
|
|
87
|
+
await bounded.set("a", "1");
|
|
88
|
+
await bounded.set("b", "2");
|
|
89
|
+
await bounded.set("c", "3"); // should evict "a"
|
|
90
|
+
expect(bounded.size).toBe(2);
|
|
91
|
+
expect(await bounded.get("a")).toBeNull();
|
|
92
|
+
expect(await bounded.get("b")).toBe("2");
|
|
93
|
+
expect(await bounded.get("c")).toBe("3");
|
|
94
|
+
});
|
|
95
|
+
it("overwriting existing key does not trigger eviction", async () => {
|
|
96
|
+
const bounded = new InMemoryCache({ maxSize: 2 });
|
|
97
|
+
await bounded.set("a", "1");
|
|
98
|
+
await bounded.set("b", "2");
|
|
99
|
+
await bounded.set("a", "updated"); // overwrite, not new entry
|
|
100
|
+
expect(bounded.size).toBe(2);
|
|
101
|
+
expect(await bounded.get("a")).toBe("updated");
|
|
102
|
+
expect(await bounded.get("b")).toBe("2");
|
|
103
|
+
});
|
|
104
|
+
it("evicts in FIFO order", async () => {
|
|
105
|
+
const bounded = new InMemoryCache({ maxSize: 3 });
|
|
106
|
+
await bounded.set("a", "1");
|
|
107
|
+
await bounded.set("b", "2");
|
|
108
|
+
await bounded.set("c", "3");
|
|
109
|
+
await bounded.set("d", "4"); // evicts "a"
|
|
110
|
+
await bounded.set("e", "5"); // evicts "b"
|
|
111
|
+
expect(await bounded.get("a")).toBeNull();
|
|
112
|
+
expect(await bounded.get("b")).toBeNull();
|
|
113
|
+
expect(await bounded.get("c")).toBe("3");
|
|
114
|
+
expect(await bounded.get("d")).toBe("4");
|
|
115
|
+
expect(await bounded.get("e")).toBe("5");
|
|
116
|
+
});
|
|
117
|
+
it("unlimited by default", async () => {
|
|
118
|
+
const unlimited = new InMemoryCache();
|
|
119
|
+
for (let i = 0; i < 100; i++) {
|
|
120
|
+
await unlimited.set(`key-${i}`, `value-${i}`);
|
|
121
|
+
}
|
|
122
|
+
expect(unlimited.size).toBe(100);
|
|
123
|
+
expect(await unlimited.get("key-0")).toBe("value-0");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=cache.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.test.js","sourceRoot":"","sources":["../../src/__tests__/cache.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/confidence.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { calculateConfidence } from "../confidence.js";
|
|
3
|
+
describe("calculateConfidence", () => {
|
|
4
|
+
const requiredKeywords = ["died", "death", "passed away"];
|
|
5
|
+
const bonusKeywords = ["cancer", "heart attack", "accident", "overdose", "suicide"];
|
|
6
|
+
it("returns 0 for empty text", () => {
|
|
7
|
+
expect(calculateConfidence("", requiredKeywords)).toBe(0);
|
|
8
|
+
});
|
|
9
|
+
it("returns 0 for null-ish text", () => {
|
|
10
|
+
expect(calculateConfidence("", requiredKeywords)).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
it("returns 0 when no required keywords found", () => {
|
|
13
|
+
const text = "John Wayne starred in many Western films throughout his career.";
|
|
14
|
+
expect(calculateConfidence(text, requiredKeywords)).toBe(0);
|
|
15
|
+
});
|
|
16
|
+
it("returns 0.5 when required keyword found but no bonus keywords defined", () => {
|
|
17
|
+
const text = "The actor died in 1979 at his home.";
|
|
18
|
+
expect(calculateConfidence(text, requiredKeywords)).toBe(0.5);
|
|
19
|
+
});
|
|
20
|
+
it("returns 0.5 when required keyword found but no bonus keywords match", () => {
|
|
21
|
+
const text = "The actor died in 1979 at his home.";
|
|
22
|
+
expect(calculateConfidence(text, requiredKeywords, bonusKeywords)).toBe(0.5);
|
|
23
|
+
});
|
|
24
|
+
it("adds bonus proportional to matching bonus keywords", () => {
|
|
25
|
+
const text = "The actor died of cancer after a heart attack.";
|
|
26
|
+
// 2 out of 5 bonus keywords match
|
|
27
|
+
const result = calculateConfidence(text, requiredKeywords, bonusKeywords);
|
|
28
|
+
expect(result).toBeCloseTo(0.5 + 0.5 * (2 / 5));
|
|
29
|
+
});
|
|
30
|
+
it("returns 1.0 when all bonus keywords match", () => {
|
|
31
|
+
const text = "The actor died of cancer after a heart attack in an accident involving an overdose, ruled a suicide.";
|
|
32
|
+
const result = calculateConfidence(text, requiredKeywords, bonusKeywords);
|
|
33
|
+
expect(result).toBe(1.0);
|
|
34
|
+
});
|
|
35
|
+
it("caps at 1.0", () => {
|
|
36
|
+
// Even with all matches, should never exceed 1.0
|
|
37
|
+
const text = "died death passed away cancer heart attack accident overdose suicide";
|
|
38
|
+
const result = calculateConfidence(text, requiredKeywords, bonusKeywords);
|
|
39
|
+
expect(result).toBeLessThanOrEqual(1.0);
|
|
40
|
+
});
|
|
41
|
+
it("is case insensitive for required keywords", () => {
|
|
42
|
+
const text = "The actor DIED in 1979.";
|
|
43
|
+
expect(calculateConfidence(text, requiredKeywords)).toBe(0.5);
|
|
44
|
+
});
|
|
45
|
+
it("is case insensitive for bonus keywords", () => {
|
|
46
|
+
const text = "The actor died of CANCER.";
|
|
47
|
+
const result = calculateConfidence(text, requiredKeywords, bonusKeywords);
|
|
48
|
+
expect(result).toBeGreaterThan(0.5);
|
|
49
|
+
});
|
|
50
|
+
it("multiple required keywords - any one is sufficient", () => {
|
|
51
|
+
const textDied = "He died in 1979.";
|
|
52
|
+
const textDeath = "His death was unexpected.";
|
|
53
|
+
const textPassedAway = "She passed away peacefully.";
|
|
54
|
+
expect(calculateConfidence(textDied, requiredKeywords)).toBe(0.5);
|
|
55
|
+
expect(calculateConfidence(textDeath, requiredKeywords)).toBe(0.5);
|
|
56
|
+
expect(calculateConfidence(textPassedAway, requiredKeywords)).toBe(0.5);
|
|
57
|
+
});
|
|
58
|
+
it("works with multi-word keywords", () => {
|
|
59
|
+
const text = "The celebrity passed away after a long illness.";
|
|
60
|
+
const required = ["passed away", "died"];
|
|
61
|
+
expect(calculateConfidence(text, required)).toBe(0.5);
|
|
62
|
+
});
|
|
63
|
+
it("works with single required keyword", () => {
|
|
64
|
+
const text = "The obituary mentioned many details.";
|
|
65
|
+
const required = ["obituary"];
|
|
66
|
+
expect(calculateConfidence(text, required)).toBe(0.5);
|
|
67
|
+
});
|
|
68
|
+
it("works with biography-style keywords", () => {
|
|
69
|
+
const required = ["childhood", "family", "early life", "grew up"];
|
|
70
|
+
const bonus = ["parents", "siblings", "school", "education"];
|
|
71
|
+
const text = "During her childhood, her parents sent her to a boarding school.";
|
|
72
|
+
const result = calculateConfidence(text, required, bonus);
|
|
73
|
+
// 1 required match ("childhood") + 2 bonus ("parents", "school") out of 4
|
|
74
|
+
expect(result).toBeCloseTo(0.5 + 0.5 * (2 / 4));
|
|
75
|
+
});
|
|
76
|
+
it("returns 0 with empty required keywords list", () => {
|
|
77
|
+
const text = "Some text with words.";
|
|
78
|
+
expect(calculateConfidence(text, [])).toBe(0);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=confidence.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confidence.test.js","sourceRoot":"","sources":["../../src/__tests__/confidence.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAEtD,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,aAAa,CAAC,CAAA;IACzD,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,CAAA;IAEnF,EAAE,CAAC,0BAA0B,EAAE,GAAG,EAAE;QAClC,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,mBAAmB,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GAAG,iEAAiE,CAAA;QAC9E,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC7D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uEAAuE,EAAE,GAAG,EAAE;QAC/E,MAAM,IAAI,GAAG,qCAAqC,CAAA;QAClD,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,IAAI,GAAG,qCAAqC,CAAA;QAClD,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC9E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,IAAI,GAAG,gDAAgD,CAAA;QAC7D,kCAAkC;QAClC,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;QACzE,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GACR,sGAAsG,CAAA;QACxG,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;QACzE,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACrB,iDAAiD;QACjD,MAAM,IAAI,GAAG,sEAAsE,CAAA;QACnF,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;QACzE,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACnD,MAAM,IAAI,GAAG,yBAAyB,CAAA;QACtC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/D,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,IAAI,GAAG,2BAA2B,CAAA;QACxC,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,EAAE,aAAa,CAAC,CAAA;QACzE,MAAM,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC5D,MAAM,QAAQ,GAAG,kBAAkB,CAAA;QACnC,MAAM,SAAS,GAAG,2BAA2B,CAAA;QAC7C,MAAM,cAAc,GAAG,6BAA6B,CAAA;QAEpD,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACjE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAClE,MAAM,CAAC,mBAAmB,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACzE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG,iDAAiD,CAAA;QAC9D,MAAM,QAAQ,GAAG,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QACxC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,IAAI,GAAG,sCAAsC,CAAA;QACnD,MAAM,QAAQ,GAAG,CAAC,UAAU,CAAC,CAAA;QAC7B,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qCAAqC,EAAE,GAAG,EAAE;QAC7C,MAAM,QAAQ,GAAG,CAAC,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,CAAC,CAAA;QACjE,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAA;QAE5D,MAAM,IAAI,GAAG,kEAAkE,CAAA;QAC/E,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;QACzD,0EAA0E;QAC1E,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IACjD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,IAAI,GAAG,uBAAuB,CAAA;QACpC,MAAM,CAAC,mBAAmB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cost-tracker.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cost-tracker.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { BatchCostTracker } from "../cost-tracker.js";
|
|
3
|
+
describe("BatchCostTracker", () => {
|
|
4
|
+
describe("with no limits", () => {
|
|
5
|
+
let tracker;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
tracker = new BatchCostTracker();
|
|
8
|
+
});
|
|
9
|
+
it("starts with zero total cost", () => {
|
|
10
|
+
expect(tracker.getTotalCost()).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
it("addSubjectCost accumulates correctly", () => {
|
|
13
|
+
tracker.addSubjectCost("actor-1", 0.05);
|
|
14
|
+
tracker.addSubjectCost("actor-1", 0.1);
|
|
15
|
+
expect(tracker.getTotalCost()).toBeCloseTo(0.15);
|
|
16
|
+
});
|
|
17
|
+
it("tracks per-subject costs independently", () => {
|
|
18
|
+
tracker.addSubjectCost("actor-1", 0.05);
|
|
19
|
+
tracker.addSubjectCost("actor-2", 0.2);
|
|
20
|
+
tracker.addSubjectCost("actor-1", 0.1);
|
|
21
|
+
expect(tracker.getSubjectCost("actor-1")).toBeCloseTo(0.15);
|
|
22
|
+
expect(tracker.getSubjectCost("actor-2")).toBeCloseTo(0.2);
|
|
23
|
+
expect(tracker.getTotalCost()).toBeCloseTo(0.35);
|
|
24
|
+
});
|
|
25
|
+
it("never exceeds when no limits are set", () => {
|
|
26
|
+
const exceeded = tracker.addSubjectCost("actor-1", 1000);
|
|
27
|
+
expect(exceeded).toBe(false);
|
|
28
|
+
expect(tracker.isTotalLimitExceeded()).toBe(false);
|
|
29
|
+
expect(tracker.isSubjectLimitExceeded("actor-1")).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
it("returns zero for unknown subject", () => {
|
|
32
|
+
expect(tracker.getSubjectCost("unknown")).toBe(0);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
describe("with total limit", () => {
|
|
36
|
+
let tracker;
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
tracker = new BatchCostTracker({ maxTotalCost: 1.0 });
|
|
39
|
+
});
|
|
40
|
+
it("isTotalLimitExceeded returns false below limit", () => {
|
|
41
|
+
tracker.addSubjectCost("actor-1", 0.5);
|
|
42
|
+
expect(tracker.isTotalLimitExceeded()).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
it("isTotalLimitExceeded returns true when limit hit", () => {
|
|
45
|
+
tracker.addSubjectCost("actor-1", 0.6);
|
|
46
|
+
tracker.addSubjectCost("actor-2", 0.4);
|
|
47
|
+
expect(tracker.isTotalLimitExceeded()).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
it("isTotalLimitExceeded returns true when limit exceeded", () => {
|
|
50
|
+
tracker.addSubjectCost("actor-1", 1.5);
|
|
51
|
+
expect(tracker.isTotalLimitExceeded()).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
it("addSubjectCost returns true when total limit exceeded", () => {
|
|
54
|
+
tracker.addSubjectCost("actor-1", 0.6);
|
|
55
|
+
const exceeded = tracker.addSubjectCost("actor-2", 0.5);
|
|
56
|
+
expect(exceeded).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("with per-subject limit", () => {
|
|
60
|
+
let tracker;
|
|
61
|
+
beforeEach(() => {
|
|
62
|
+
tracker = new BatchCostTracker({ maxCostPerSubject: 0.5 });
|
|
63
|
+
});
|
|
64
|
+
it("isSubjectLimitExceeded returns false below limit", () => {
|
|
65
|
+
tracker.addSubjectCost("actor-1", 0.25);
|
|
66
|
+
expect(tracker.isSubjectLimitExceeded("actor-1")).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
it("isSubjectLimitExceeded returns true when per-subject limit hit", () => {
|
|
69
|
+
tracker.addSubjectCost("actor-1", 0.3);
|
|
70
|
+
tracker.addSubjectCost("actor-1", 0.2);
|
|
71
|
+
expect(tracker.isSubjectLimitExceeded("actor-1")).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
it("does not affect other subjects", () => {
|
|
74
|
+
tracker.addSubjectCost("actor-1", 0.6);
|
|
75
|
+
expect(tracker.isSubjectLimitExceeded("actor-1")).toBe(true);
|
|
76
|
+
expect(tracker.isSubjectLimitExceeded("actor-2")).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it("addSubjectCost returns true when per-subject limit exceeded", () => {
|
|
79
|
+
const first = tracker.addSubjectCost("actor-1", 0.25);
|
|
80
|
+
expect(first).toBe(false);
|
|
81
|
+
const second = tracker.addSubjectCost("actor-1", 0.3);
|
|
82
|
+
expect(second).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe("source cost tracking", () => {
|
|
86
|
+
let tracker;
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
tracker = new BatchCostTracker();
|
|
89
|
+
});
|
|
90
|
+
it("addSourceCost tracks by source type", () => {
|
|
91
|
+
tracker.addSourceCost("wikipedia", 0.01);
|
|
92
|
+
tracker.addSourceCost("claude", 0.05);
|
|
93
|
+
tracker.addSourceCost("wikipedia", 0.02);
|
|
94
|
+
const breakdown = tracker.getCostBySource();
|
|
95
|
+
expect(breakdown["wikipedia"]).toBeCloseTo(0.03);
|
|
96
|
+
expect(breakdown["claude"]).toBeCloseTo(0.05);
|
|
97
|
+
});
|
|
98
|
+
it("getCostBySource returns a copy", () => {
|
|
99
|
+
tracker.addSourceCost("wikipedia", 0.01);
|
|
100
|
+
const first = tracker.getCostBySource();
|
|
101
|
+
tracker.addSourceCost("wikipedia", 0.05);
|
|
102
|
+
const second = tracker.getCostBySource();
|
|
103
|
+
// first snapshot should not be mutated
|
|
104
|
+
expect(first["wikipedia"]).toBeCloseTo(0.01);
|
|
105
|
+
expect(second["wikipedia"]).toBeCloseTo(0.06);
|
|
106
|
+
});
|
|
107
|
+
it("getCostBySource returns empty object when no costs added", () => {
|
|
108
|
+
expect(tracker.getCostBySource()).toEqual({});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe("reset", () => {
|
|
112
|
+
it("clears all state", () => {
|
|
113
|
+
const tracker = new BatchCostTracker({
|
|
114
|
+
maxTotalCost: 10,
|
|
115
|
+
maxCostPerSubject: 5,
|
|
116
|
+
});
|
|
117
|
+
tracker.addSubjectCost("actor-1", 3.0);
|
|
118
|
+
tracker.addSubjectCost("actor-2", 2.0);
|
|
119
|
+
tracker.addSourceCost("wikipedia", 1.0);
|
|
120
|
+
tracker.addSourceCost("claude", 4.0);
|
|
121
|
+
tracker.reset();
|
|
122
|
+
expect(tracker.getTotalCost()).toBe(0);
|
|
123
|
+
expect(tracker.getSubjectCost("actor-1")).toBe(0);
|
|
124
|
+
expect(tracker.getSubjectCost("actor-2")).toBe(0);
|
|
125
|
+
expect(tracker.getCostBySource()).toEqual({});
|
|
126
|
+
expect(tracker.isTotalLimitExceeded()).toBe(false);
|
|
127
|
+
expect(tracker.isSubjectLimitExceeded("actor-1")).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it("allows tracking to resume after reset", () => {
|
|
130
|
+
const tracker = new BatchCostTracker({ maxTotalCost: 1.0 });
|
|
131
|
+
tracker.addSubjectCost("actor-1", 0.9);
|
|
132
|
+
expect(tracker.isTotalLimitExceeded()).toBe(false);
|
|
133
|
+
tracker.reset();
|
|
134
|
+
tracker.addSubjectCost("actor-1", 0.5);
|
|
135
|
+
expect(tracker.getTotalCost()).toBeCloseTo(0.5);
|
|
136
|
+
expect(tracker.isTotalLimitExceeded()).toBe(false);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe("numeric subject IDs", () => {
|
|
140
|
+
it("works with number subject IDs", () => {
|
|
141
|
+
const tracker = new BatchCostTracker({ maxCostPerSubject: 1.0 });
|
|
142
|
+
tracker.addSubjectCost(42, 0.3);
|
|
143
|
+
tracker.addSubjectCost(42, 0.4);
|
|
144
|
+
expect(tracker.getSubjectCost(42)).toBeCloseTo(0.7);
|
|
145
|
+
expect(tracker.isSubjectLimitExceeded(42)).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=cost-tracker.test.js.map
|