@siftline/core 0.1.0 → 0.1.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 +1 -1
- package/dist/index.mjs +34 -44
- package/dist/{jsonl-Cyqrt9-a.mjs → jsonl-CzTtnvyn.mjs} +14 -5
- package/dist/testing.mjs +23 -23
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as entryType, c as parseRecipe, d as recipeSchema, f as score, i as defineRecipe, l as questionName, n as
|
|
1
|
+
import { a as entryType, c as parseRecipe, d as recipeSchema, f as score, i as defineRecipe, l as questionName, n as decodeThrown, p as serializeRecipe, r as choice, s as noul, t as parseJsonLines, u as questionSchema } from "./jsonl-CzTtnvyn.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region package.json
|
|
4
|
-
var version = "0.1.
|
|
4
|
+
var version = "0.1.1";
|
|
5
5
|
//#endregion
|
|
6
6
|
//#region src/decision.ts
|
|
7
7
|
/** Strict: an unknown key is a bad Record, not a field to ignore. */
|
|
@@ -15,6 +15,8 @@ const answerValue = z.union([
|
|
|
15
15
|
z.boolean(),
|
|
16
16
|
z.number()
|
|
17
17
|
]);
|
|
18
|
+
const label = z.string();
|
|
19
|
+
const level = z.number().int().min(0);
|
|
18
20
|
/**
|
|
19
21
|
* Why `value` is not an answer to `asked`, or `null` when it is. Rules and Fixtures both
|
|
20
22
|
* validate against the Recipe with it, so the two report the same words.
|
|
@@ -22,13 +24,13 @@ const answerValue = z.union([
|
|
|
22
24
|
function answerValueProblem(asked, value) {
|
|
23
25
|
const shown = JSON.stringify(value);
|
|
24
26
|
if (asked.type === "choice") {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
const parsed = label.safeParse(value);
|
|
28
|
+
if (!parsed.success) return `value ${shown} is not a label`;
|
|
29
|
+
return Object.hasOwn(asked.criteria, parsed.data) ? null : `unknown label ${shown}`;
|
|
27
30
|
}
|
|
28
|
-
if (asked.type === "noul") return
|
|
31
|
+
if (asked.type === "noul") return value === true || value === false ? null : `value ${shown} is not a boolean`;
|
|
29
32
|
const last = asked.criteria.length - 1;
|
|
30
|
-
|
|
31
|
-
return null;
|
|
33
|
+
return level.max(last).safeParse(value).success ? null : `level index ${shown} is out of range (0-${last})`;
|
|
32
34
|
}
|
|
33
35
|
const unit = z.number().min(0).max(1);
|
|
34
36
|
const levelIndex = z.string().regex(/^\d+$/);
|
|
@@ -163,13 +165,15 @@ const ruleSchema = z.object({
|
|
|
163
165
|
condition: ruleConditionSchema,
|
|
164
166
|
action: z.string().min(1).nullable()
|
|
165
167
|
}).strict();
|
|
168
|
+
const levelAnswer = z.number();
|
|
166
169
|
function matches(answers, condition) {
|
|
167
170
|
const actual = answers[condition.question];
|
|
168
171
|
if (actual === void 0) return false;
|
|
169
172
|
if (condition.comparator === "is") return actual === condition.value;
|
|
170
|
-
if (condition.comparator === "isOneOf") return
|
|
171
|
-
|
|
172
|
-
|
|
173
|
+
if (condition.comparator === "isOneOf") return condition.value.some((label) => label === actual);
|
|
174
|
+
const level = levelAnswer.safeParse(actual);
|
|
175
|
+
if (!level.success) return false;
|
|
176
|
+
return condition.comparator === "atLeast" ? level.data >= condition.value : level.data <= condition.value;
|
|
173
177
|
}
|
|
174
178
|
/** Pure, first match wins, no review gate. Cloud runs it again on corrected answers. */
|
|
175
179
|
function evaluateRules(answers, rules) {
|
|
@@ -221,7 +225,7 @@ function validateRules(rules, recipe) {
|
|
|
221
225
|
report(`unknown question "${question}"`);
|
|
222
226
|
continue;
|
|
223
227
|
}
|
|
224
|
-
if (!comparatorsFor[asked.type].
|
|
228
|
+
if (!comparatorsFor[asked.type].some((allowed) => allowed === comparator)) {
|
|
225
229
|
report(`comparator "${comparator}" is not valid for a ${asked.type} question`);
|
|
226
230
|
continue;
|
|
227
231
|
}
|
|
@@ -312,40 +316,25 @@ function createGate(limit) {
|
|
|
312
316
|
return release;
|
|
313
317
|
};
|
|
314
318
|
}
|
|
315
|
-
|
|
316
|
-
const body = thrown["body"];
|
|
317
|
-
if (!isObjectLike(body)) return void 0;
|
|
318
|
-
const detail = body["detail"];
|
|
319
|
-
if (!isObjectLike(detail)) return void 0;
|
|
320
|
-
const type = detail["error_type"];
|
|
321
|
-
return typeof type === "string" ? type : void 0;
|
|
322
|
-
}
|
|
319
|
+
const errorBodySchema = z.object({ detail: z.object({ error_type: z.string() }) });
|
|
323
320
|
function reasonOf(thrown, status) {
|
|
324
|
-
if (status === null)
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
const type = errorType(thrown);
|
|
321
|
+
if (status === null) return thrown.name !== void 0 && /timeout/i.test(thrown.name) ? "timeout" : "network";
|
|
322
|
+
const body = errorBodySchema.safeParse(thrown.body);
|
|
323
|
+
const type = body.success ? body.data.detail.error_type : void 0;
|
|
329
324
|
if (type === "api_usage_error" || type === "max_tokens_exceeded") return type;
|
|
330
325
|
return "unknown";
|
|
331
326
|
}
|
|
332
327
|
function isAbort(cause, thrown, signal) {
|
|
333
328
|
if (signal?.aborted === true && cause === signal.reason) return true;
|
|
334
|
-
|
|
335
|
-
return name === "AbortError" || name === "APIUserAbortError";
|
|
329
|
+
return thrown.name === "AbortError" || thrown.name === "APIUserAbortError";
|
|
336
330
|
}
|
|
337
|
-
/**
|
|
331
|
+
/** Core cannot import the SDK's error classes, so it decodes their fields. Never returns. */
|
|
338
332
|
function mapClientError(cause, signal) {
|
|
339
|
-
const thrown =
|
|
333
|
+
const thrown = decodeThrown(cause);
|
|
340
334
|
if (isAbort(cause, thrown, signal)) throw cause;
|
|
341
|
-
const
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
const message = typeof rawMessage === "string" && rawMessage !== "" ? rawMessage : String(cause);
|
|
345
|
-
if (status === 429 || status === 529) {
|
|
346
|
-
const retryAfterMs = thrown["retryAfterMs"];
|
|
347
|
-
throw new JudgeExhaustedError(message, typeof retryAfterMs === "number" ? retryAfterMs : null, { cause });
|
|
348
|
-
}
|
|
335
|
+
const status = thrown.status ?? null;
|
|
336
|
+
const message = thrown.message !== void 0 && thrown.message !== "" ? thrown.message : String(cause);
|
|
337
|
+
if (status === 429 || status === 529) throw new JudgeExhaustedError(message, thrown.retryAfterMs ?? null, { cause });
|
|
349
338
|
throw new JudgeError(message, reasonOf(thrown, status), status, { cause });
|
|
350
339
|
}
|
|
351
340
|
function invalidAnswers(message) {
|
|
@@ -365,7 +354,7 @@ function rebuild(name, keys, source) {
|
|
|
365
354
|
const probabilities = {};
|
|
366
355
|
for (const key of keys) {
|
|
367
356
|
const probability = source[key];
|
|
368
|
-
if (
|
|
357
|
+
if (probability === void 0) throw invalidAnswers(`question ${name} has no probability for ${key}`);
|
|
369
358
|
probabilities[key] = probability;
|
|
370
359
|
}
|
|
371
360
|
return probabilities;
|
|
@@ -505,13 +494,14 @@ function parseFixture(line) {
|
|
|
505
494
|
}
|
|
506
495
|
/** The only writer. One compact line, no trailing newline, absent optionals omitted. */
|
|
507
496
|
function serializeFixture(fixture) {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
497
|
+
const { id, origin, by, state, expect } = fixture;
|
|
498
|
+
return JSON.stringify({
|
|
499
|
+
id,
|
|
500
|
+
origin,
|
|
501
|
+
by,
|
|
502
|
+
state,
|
|
503
|
+
expect
|
|
504
|
+
});
|
|
515
505
|
}
|
|
516
506
|
function parseFixtures(text) {
|
|
517
507
|
return parseJsonLines(text, parseFixture, (line, cause) => new FixtureParseError(`fixture line ${line} is not a valid Fixture`, line, { cause }));
|
|
@@ -103,10 +103,19 @@ function serializeRecipe(recipe) {
|
|
|
103
103
|
}, null, 2)}\n`;
|
|
104
104
|
}
|
|
105
105
|
//#endregion
|
|
106
|
-
//#region src/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
//#region src/thrown.ts
|
|
107
|
+
const thrownSchema = z.object({
|
|
108
|
+
name: z.string().optional().catch(void 0),
|
|
109
|
+
message: z.string().optional().catch(void 0),
|
|
110
|
+
status: z.number().optional().catch(void 0),
|
|
111
|
+
retryAfterMs: z.number().optional().catch(void 0),
|
|
112
|
+
requestId: z.string().optional().catch(void 0),
|
|
113
|
+
body: z.unknown().optional()
|
|
114
|
+
});
|
|
115
|
+
/** Anything that is not an object carries no fields. */
|
|
116
|
+
function decodeThrown(cause) {
|
|
117
|
+
const decoded = thrownSchema.safeParse(cause);
|
|
118
|
+
return decoded.success ? decoded.data : {};
|
|
110
119
|
}
|
|
111
120
|
//#endregion
|
|
112
121
|
//#region src/jsonl.ts
|
|
@@ -127,4 +136,4 @@ function parseJsonLines(text, parseLine, fail) {
|
|
|
127
136
|
return values;
|
|
128
137
|
}
|
|
129
138
|
//#endregion
|
|
130
|
-
export { entryType as a, parseRecipe as c, recipeSchema as d, score as f, defineRecipe as i, questionName as l,
|
|
139
|
+
export { entryType as a, parseRecipe as c, recipeSchema as d, score as f, defineRecipe as i, questionName as l, decodeThrown as n, jsonValue as o, serializeRecipe as p, choice as r, noul as s, parseJsonLines as t, questionSchema as u };
|
package/dist/testing.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as entryType, n as
|
|
1
|
+
import { a as entryType, n as decodeThrown, o as jsonValue, t as parseJsonLines, u as questionSchema } from "./jsonl-CzTtnvyn.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/testing.ts
|
|
4
4
|
const probability = z.number().min(0).max(1);
|
|
@@ -82,13 +82,14 @@ function createScriptedClient(script) {
|
|
|
82
82
|
}
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
if (Array.isArray(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
/** Objects write their keys sorted, so two values that differ only in key order match. */
|
|
86
|
+
function canonicalJson(value) {
|
|
87
|
+
if (value === null || !(value instanceof Object)) return JSON.stringify(value);
|
|
88
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
89
|
+
return `{${Object.entries(value).toSorted(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, member]) => `${JSON.stringify(key)}:${canonicalJson(member)}`).join(",")}}`;
|
|
90
|
+
}
|
|
91
|
+
function requestKey(request) {
|
|
92
|
+
return canonicalJson(jsonValue.parse(JSON.parse(JSON.stringify(request))));
|
|
92
93
|
}
|
|
93
94
|
/** Rebuilds what the SDK threw closely enough for the Judge's duck typing to see it. */
|
|
94
95
|
function replayError(recorded) {
|
|
@@ -103,29 +104,28 @@ function replayError(recorded) {
|
|
|
103
104
|
}
|
|
104
105
|
/** Answers the line whose `request` is deep-equal to the incoming one. Key order is free. */
|
|
105
106
|
function createReplayClient(lines) {
|
|
107
|
+
const keyed = lines.map((line) => ({
|
|
108
|
+
key: requestKey(line.request),
|
|
109
|
+
line
|
|
110
|
+
}));
|
|
106
111
|
return { systemOne: async (request) => {
|
|
107
|
-
const
|
|
112
|
+
const key = requestKey(request);
|
|
113
|
+
const line = keyed.find((candidate) => candidate.key === key)?.line;
|
|
108
114
|
if (!line) throw new Error(`no replay line matches the request for model ${request.model} and questions ${Object.keys(request.questions).join(", ")}`);
|
|
109
115
|
if ("error" in line) throw replayError(line.error);
|
|
110
116
|
return line.response;
|
|
111
117
|
} };
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
return typeof value === "string" ? value : void 0;
|
|
115
|
-
}
|
|
116
|
-
function asNumber(value) {
|
|
117
|
-
return typeof value === "number" ? value : null;
|
|
118
|
-
}
|
|
119
|
-
/** Duck typing, because core cannot name the SDK's error classes. */
|
|
119
|
+
/** Core cannot name the SDK's error classes, so it decodes their fields. */
|
|
120
120
|
function recordError(cause) {
|
|
121
|
-
const thrown =
|
|
121
|
+
const thrown = decodeThrown(cause);
|
|
122
122
|
return {
|
|
123
|
-
name:
|
|
124
|
-
message:
|
|
125
|
-
status:
|
|
126
|
-
retryAfterMs:
|
|
127
|
-
requestId:
|
|
128
|
-
body: thrown
|
|
123
|
+
name: thrown.name ?? "Error",
|
|
124
|
+
message: thrown.message ?? String(cause),
|
|
125
|
+
status: thrown.status ?? null,
|
|
126
|
+
retryAfterMs: thrown.retryAfterMs ?? null,
|
|
127
|
+
requestId: thrown.requestId,
|
|
128
|
+
body: thrown.body
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
131
|
/** Wraps a live client and hands one replay line per call to `sink`. The live test's half. */
|