@rulvar/testing 1.12.0 → 1.13.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/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as FAKE_MODEL, c as FakeAdapterOptions, d as FakeToolCallsValue, f as FakeWireErrorValue, i as createTestEngine, l as FakeCall, m as fakeWireError, n as TestEngine, o as FAKE_MODEL_REF, p as fakeToolCalls, r as TestRunHandle, s as FakeAdapter, t as CreateTestEngineOptions, u as FakeResponder } from "./test-engine-
|
|
1
|
+
import { a as FAKE_MODEL, c as FakeAdapterOptions, d as FakeToolCallsValue, f as FakeWireErrorValue, i as createTestEngine, l as FakeCall, m as fakeWireError, n as TestEngine, o as FAKE_MODEL_REF, p as fakeToolCalls, r as TestRunHandle, s as FakeAdapter, t as CreateTestEngineOptions, u as FakeResponder } from "./test-engine-DAXwdoOQ.js";
|
|
2
2
|
import { AgentProfile, ChatEvent, ChatRequest, InvocationRole, JournalEntry, JournalStore, ModelCaps, ModelSpec, ProviderAdapter, ResumePreview, RunOutcome, WireError, Workflow, createEngine } from "@rulvar/core";
|
|
3
3
|
|
|
4
4
|
//#region src/replay-strict.d.ts
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,10 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
* FakeAdapter (M1-T14): a REAL ProviderAdapter that resolves calls from
|
|
8
8
|
* declared patterns instead of the network, behind the same seam as live
|
|
9
9
|
* adapters, so unit tests run through the full engine: journal, scheduler,
|
|
10
|
-
* budget layers, and event stream. Calls cost zero USD.
|
|
10
|
+
* budget layers, and event stream. Calls cost zero USD. Honors the
|
|
11
|
+
* caller's AbortSignal exactly like a live adapter: an abort ends the
|
|
12
|
+
* stream promptly with no terminal event, so cancellation, deadline, and
|
|
13
|
+
* budget tests observe the same journal shapes as production adapters.
|
|
11
14
|
*/
|
|
12
15
|
/** Scripts a tool-calling turn from a responder. */
|
|
13
16
|
function fakeToolCalls(...calls) {
|
|
@@ -49,6 +52,38 @@ const FAKE_CAPS = {
|
|
|
49
52
|
outputUsdPerMTok: 0
|
|
50
53
|
}
|
|
51
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Races a responder promise against the caller's abort. On abort the
|
|
57
|
+
* pending responder is detached: its eventual value is discarded and an
|
|
58
|
+
* eventual rejection is swallowed (the caller cancelled; a late failure
|
|
59
|
+
* of the abandoned work must not become an unhandled rejection). A
|
|
60
|
+
* rejection that settles first propagates to the caller unchanged.
|
|
61
|
+
*/
|
|
62
|
+
async function raceAbort(pending, signal) {
|
|
63
|
+
if (signal === void 0) return {
|
|
64
|
+
aborted: false,
|
|
65
|
+
value: await pending
|
|
66
|
+
};
|
|
67
|
+
if (signal.aborted) {
|
|
68
|
+
pending.catch(() => void 0);
|
|
69
|
+
return { aborted: true };
|
|
70
|
+
}
|
|
71
|
+
let onAbort = () => void 0;
|
|
72
|
+
const abortPromise = new Promise((resolve) => {
|
|
73
|
+
onAbort = () => resolve({ aborted: true });
|
|
74
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
const raced = await Promise.race([pending.then((value) => ({
|
|
78
|
+
aborted: false,
|
|
79
|
+
value
|
|
80
|
+
})), abortPromise]);
|
|
81
|
+
if (raced.aborted) pending.catch(() => void 0);
|
|
82
|
+
return raced;
|
|
83
|
+
} finally {
|
|
84
|
+
signal.removeEventListener("abort", onAbort);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
52
87
|
function lastUserText(req) {
|
|
53
88
|
for (let i = req.messages.length - 1; i >= 0; i -= 1) {
|
|
54
89
|
const msg = req.messages[i];
|
|
@@ -61,7 +96,10 @@ var FakeAdapter = class {
|
|
|
61
96
|
id = "fake";
|
|
62
97
|
agents;
|
|
63
98
|
mintId = createCanonicalIdMinter();
|
|
64
|
-
/**
|
|
99
|
+
/**
|
|
100
|
+
* Every request this adapter served, in order. A request whose signal
|
|
101
|
+
* was already aborted on arrival was never served and is not recorded.
|
|
102
|
+
*/
|
|
65
103
|
calls = [];
|
|
66
104
|
constructor(options) {
|
|
67
105
|
this.agents = options.agents;
|
|
@@ -83,7 +121,9 @@ var FakeAdapter = class {
|
|
|
83
121
|
}
|
|
84
122
|
return fallback;
|
|
85
123
|
}
|
|
86
|
-
async *stream(req) {
|
|
124
|
+
async *stream(req, signal) {
|
|
125
|
+
const aborted = () => signal?.aborted === true;
|
|
126
|
+
if (aborted()) return;
|
|
87
127
|
const telemetry = req.providerOptions?.rulvar ?? {};
|
|
88
128
|
const call = {
|
|
89
129
|
prompt: lastUserText(req),
|
|
@@ -107,8 +147,13 @@ var FakeAdapter = class {
|
|
|
107
147
|
}
|
|
108
148
|
let value;
|
|
109
149
|
try {
|
|
110
|
-
|
|
150
|
+
if (typeof responder === "function") {
|
|
151
|
+
const raced = await raceAbort(Promise.resolve(responder(call)), signal);
|
|
152
|
+
if (raced.aborted) return;
|
|
153
|
+
value = raced.value;
|
|
154
|
+
} else value = responder;
|
|
111
155
|
} catch (thrown) {
|
|
156
|
+
if (aborted()) return;
|
|
112
157
|
yield {
|
|
113
158
|
type: "error",
|
|
114
159
|
error: {
|
|
@@ -120,14 +165,12 @@ var FakeAdapter = class {
|
|
|
120
165
|
};
|
|
121
166
|
return;
|
|
122
167
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
if (isFakeToolCalls(value)) {
|
|
168
|
+
const events = [];
|
|
169
|
+
if (isFakeWireError(value)) events.push({
|
|
170
|
+
type: "error",
|
|
171
|
+
error: value.error
|
|
172
|
+
});
|
|
173
|
+
else if (isFakeToolCalls(value)) {
|
|
131
174
|
const usage = {
|
|
132
175
|
inputTokens: Math.max(1, Math.ceil(call.prompt.length / 4)),
|
|
133
176
|
outputTokens: Math.max(1, value.calls.length * 8),
|
|
@@ -136,66 +179,70 @@ var FakeAdapter = class {
|
|
|
136
179
|
};
|
|
137
180
|
for (const toolCall of value.calls) {
|
|
138
181
|
const id = this.mintId();
|
|
139
|
-
|
|
182
|
+
events.push({
|
|
140
183
|
type: "tool-call-start",
|
|
141
184
|
id,
|
|
142
185
|
name: toolCall.name
|
|
143
|
-
};
|
|
144
|
-
|
|
186
|
+
});
|
|
187
|
+
events.push({
|
|
145
188
|
type: "tool-call-end",
|
|
146
189
|
id,
|
|
147
190
|
args: toolCall.args
|
|
148
|
-
};
|
|
191
|
+
});
|
|
149
192
|
}
|
|
150
|
-
|
|
193
|
+
events.push({
|
|
151
194
|
type: "finish",
|
|
152
195
|
finish: { reason: "tool-calls" },
|
|
153
196
|
usage
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
200
|
+
const usage = {
|
|
201
|
+
inputTokens: Math.max(1, Math.ceil(call.prompt.length / 4)),
|
|
202
|
+
outputTokens: Math.max(1, Math.ceil(text.length / 4)),
|
|
203
|
+
cacheReadTokens: 0,
|
|
204
|
+
cacheWriteTokens: 0
|
|
154
205
|
};
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
const forcedName = typeof req.toolChoice === "object" ? req.toolChoice.name : void 0;
|
|
207
|
+
if (forcedName !== void 0) {
|
|
208
|
+
let args = value;
|
|
209
|
+
if (typeof value === "string") try {
|
|
210
|
+
args = JSON.parse(value);
|
|
211
|
+
} catch {
|
|
212
|
+
args = { text: value };
|
|
213
|
+
}
|
|
214
|
+
const id = this.mintId();
|
|
215
|
+
events.push({
|
|
216
|
+
type: "tool-call-start",
|
|
217
|
+
id,
|
|
218
|
+
name: forcedName
|
|
219
|
+
});
|
|
220
|
+
events.push({
|
|
221
|
+
type: "tool-call-end",
|
|
222
|
+
id,
|
|
223
|
+
args
|
|
224
|
+
});
|
|
225
|
+
events.push({
|
|
226
|
+
type: "finish",
|
|
227
|
+
finish: { reason: "tool-calls" },
|
|
228
|
+
usage
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
events.push({
|
|
232
|
+
type: "text-delta",
|
|
233
|
+
text
|
|
234
|
+
});
|
|
235
|
+
events.push({
|
|
236
|
+
type: "finish",
|
|
237
|
+
finish: { reason: "stop" },
|
|
238
|
+
usage
|
|
239
|
+
});
|
|
171
240
|
}
|
|
172
|
-
const id = this.mintId();
|
|
173
|
-
yield {
|
|
174
|
-
type: "tool-call-start",
|
|
175
|
-
id,
|
|
176
|
-
name: forcedName
|
|
177
|
-
};
|
|
178
|
-
yield {
|
|
179
|
-
type: "tool-call-end",
|
|
180
|
-
id,
|
|
181
|
-
args
|
|
182
|
-
};
|
|
183
|
-
yield {
|
|
184
|
-
type: "finish",
|
|
185
|
-
finish: { reason: "tool-calls" },
|
|
186
|
-
usage
|
|
187
|
-
};
|
|
188
|
-
return;
|
|
189
241
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
yield {
|
|
195
|
-
type: "finish",
|
|
196
|
-
finish: { reason: "stop" },
|
|
197
|
-
usage
|
|
198
|
-
};
|
|
242
|
+
for (const event of events) {
|
|
243
|
+
if (aborted()) return;
|
|
244
|
+
yield event;
|
|
245
|
+
}
|
|
199
246
|
}
|
|
200
247
|
};
|
|
201
248
|
//#endregion
|
package/dist/matchers.d.ts
CHANGED
|
@@ -48,12 +48,15 @@ declare class FakeAdapter implements ProviderAdapter {
|
|
|
48
48
|
readonly id = "fake";
|
|
49
49
|
private readonly agents;
|
|
50
50
|
private readonly mintId;
|
|
51
|
-
/**
|
|
51
|
+
/**
|
|
52
|
+
* Every request this adapter served, in order. A request whose signal
|
|
53
|
+
* was already aborted on arrival was never served and is not recorded.
|
|
54
|
+
*/
|
|
52
55
|
readonly calls: FakeCall[];
|
|
53
56
|
constructor(options: FakeAdapterOptions);
|
|
54
57
|
caps(this: void): ModelCaps;
|
|
55
58
|
private match;
|
|
56
|
-
stream(req: ChatRequest): AsyncIterable<ChatEvent>;
|
|
59
|
+
stream(req: ChatRequest, signal?: AbortSignal): AsyncIterable<ChatEvent>;
|
|
57
60
|
}
|
|
58
61
|
//#endregion
|
|
59
62
|
//#region src/test-engine.d.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/testing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Rulvar test harness: createTestEngine, FakeAdapter, VCR cassettes, replay-strict runs, matchers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"access": "public"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@rulvar/core": "1.
|
|
29
|
+
"@rulvar/core": "1.13.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.20.0",
|