@siftline/core 0.0.3 → 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.
@@ -0,0 +1,139 @@
1
+ import { z } from "zod";
2
+ //#region src/recipe.ts
3
+ function choice(instructions, criteria) {
4
+ return {
5
+ type: "choice",
6
+ instructions,
7
+ criteria
8
+ };
9
+ }
10
+ function noul(instructions, criteria) {
11
+ return criteria ? {
12
+ type: "noul",
13
+ instructions,
14
+ criteria
15
+ } : {
16
+ type: "noul",
17
+ instructions
18
+ };
19
+ }
20
+ function score(instructions, criteria) {
21
+ return {
22
+ type: "score",
23
+ instructions,
24
+ criteria
25
+ };
26
+ }
27
+ const jsonValue = z.lazy(() => z.union([
28
+ z.string(),
29
+ z.number(),
30
+ z.boolean(),
31
+ z.null(),
32
+ z.array(jsonValue),
33
+ z.record(z.string(), jsonValue)
34
+ ]));
35
+ const entryType = z.union([
36
+ z.string(),
37
+ z.record(z.string(), jsonValue),
38
+ z.array(jsonValue),
39
+ z.null()
40
+ ]);
41
+ const entry = z.union([
42
+ z.string().min(1),
43
+ z.record(z.string(), jsonValue),
44
+ z.array(jsonValue)
45
+ ]);
46
+ const questionName = z.string().regex(/^[a-z][a-z0-9_]*$/);
47
+ const choiceQuestion = z.object({
48
+ type: z.literal("choice"),
49
+ instructions: entry,
50
+ criteria: z.record(z.string().min(1), entry).refine((c) => Object.keys(c).length >= 2, "at least two labels")
51
+ }).strict();
52
+ const noulQuestion = z.object({
53
+ type: z.literal("noul"),
54
+ instructions: entry,
55
+ criteria: z.object({
56
+ true: entry.optional(),
57
+ false: entry.optional()
58
+ }).strict().optional()
59
+ }).strict();
60
+ const scoreQuestion = z.object({
61
+ type: z.literal("score"),
62
+ instructions: entry,
63
+ criteria: z.tuple([entry, entry], entry)
64
+ }).strict();
65
+ const questionSchema = z.discriminatedUnion("type", [
66
+ choiceQuestion,
67
+ noulQuestion,
68
+ scoreQuestion
69
+ ]);
70
+ const recipeSchema = z.object({
71
+ format: z.literal(1),
72
+ name: z.string().min(1),
73
+ version: z.number().int().min(1),
74
+ model: z.string().min(1).refine((m) => m !== "jev-latest" && m !== "jev-preview", "pin a versioned model"),
75
+ reviewThreshold: z.number().min(0).max(1),
76
+ questions: z.record(questionName, questionSchema).refine((q) => Object.keys(q).length >= 1, "at least one question")
77
+ }).strict();
78
+ /** Returns the object it built, not `parse`'s copy, whose type is the erased `Recipe`. */
79
+ function defineRecipe(input) {
80
+ const recipe = {
81
+ format: 1,
82
+ name: input.name,
83
+ version: input.version,
84
+ model: input.model,
85
+ reviewThreshold: input.reviewThreshold ?? .7,
86
+ questions: input.questions
87
+ };
88
+ recipeSchema.parse(recipe);
89
+ return recipe;
90
+ }
91
+ function parseRecipe(text) {
92
+ return recipeSchema.parse(JSON.parse(text));
93
+ }
94
+ function serializeRecipe(recipe) {
95
+ const { format, name, version, model, reviewThreshold, questions } = recipe;
96
+ return `${JSON.stringify({
97
+ format,
98
+ name,
99
+ version,
100
+ model,
101
+ reviewThreshold,
102
+ questions
103
+ }, null, 2)}\n`;
104
+ }
105
+ //#endregion
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 : {};
119
+ }
120
+ //#endregion
121
+ //#region src/jsonl.ts
122
+ /**
123
+ * One value per non-blank line. A line that `parseLine` throws on is handed to `fail` with
124
+ * its 1-based number, and whatever `fail` returns is thrown.
125
+ */
126
+ function parseJsonLines(text, parseLine, fail) {
127
+ const values = [];
128
+ for (const [offset, line] of text.split("\n").entries()) {
129
+ if (line.trim() === "") continue;
130
+ try {
131
+ values.push(parseLine(line));
132
+ } catch (cause) {
133
+ throw fail(offset + 1, cause);
134
+ }
135
+ }
136
+ return values;
137
+ }
138
+ //#endregion
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 };
@@ -1,14 +1,133 @@
1
+ import { c as SystemOneRequest, h as JsonValue, l as SystemOneResult, s as SystemOneClient } from "./client-B_yz9_JS.mjs";
2
+ import { z } from "zod";
1
3
  //#region src/testing.d.ts
2
- /**
3
- * A stand-in for the TypeSafe client the Engine will judge through. Test code depends
4
- * on this shape so that swapping the real client in never reaches a test file.
5
- */
6
- export interface FakeTypeSafeClient {
7
- readonly kind: "fake";
4
+ export declare const replayLineSchema: z.ZodUnion<readonly [z.ZodObject<{
5
+ format: z.ZodLiteral<1>;
6
+ id: z.ZodString;
7
+ recordedAt: z.ZodISODateTime;
8
+ requestId: z.ZodOptional<z.ZodString>;
9
+ durationMs: z.ZodNumber;
10
+ request: z.ZodObject<{
11
+ state: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodNull]>;
12
+ questions: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
13
+ type: z.ZodLiteral<"choice">;
14
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
15
+ criteria: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
16
+ }, z.core.$strict>, z.ZodObject<{
17
+ type: z.ZodLiteral<"noul">;
18
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
19
+ criteria: z.ZodOptional<z.ZodObject<{
20
+ true: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
21
+ false: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
22
+ }, z.core.$strict>>;
23
+ }, z.core.$strict>, z.ZodObject<{
24
+ type: z.ZodLiteral<"score">;
25
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
26
+ criteria: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>], z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
27
+ }, z.core.$strict>], "type">>;
28
+ model: z.ZodString;
29
+ }, z.core.$strict>;
30
+ response: z.ZodObject<{
31
+ model: z.ZodString;
32
+ answers: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
33
+ type: z.ZodLiteral<"choice">;
34
+ choice: z.ZodString;
35
+ confidence: z.ZodNumber;
36
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
37
+ }, z.core.$strict>, z.ZodObject<{
38
+ type: z.ZodLiteral<"noul">;
39
+ noul: z.ZodNumber;
40
+ }, z.core.$strict>, z.ZodObject<{
41
+ type: z.ZodLiteral<"score">;
42
+ score: z.ZodNumber;
43
+ confidence: z.ZodNumber;
44
+ legend: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>>;
45
+ probabilities: z.ZodRecord<z.ZodString, z.ZodNumber>;
46
+ }, z.core.$strict>], "type">>;
47
+ usage: z.ZodObject<{
48
+ input_tokens: z.ZodNumber;
49
+ output_tokens: z.ZodNumber;
50
+ }, z.core.$strict>;
51
+ }, z.core.$strict>;
52
+ }, z.core.$strict>, z.ZodObject<{
53
+ format: z.ZodLiteral<1>;
54
+ id: z.ZodString;
55
+ recordedAt: z.ZodISODateTime;
56
+ requestId: z.ZodOptional<z.ZodString>;
57
+ durationMs: z.ZodNumber;
58
+ request: z.ZodObject<{
59
+ state: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodNull]>;
60
+ questions: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
61
+ type: z.ZodLiteral<"choice">;
62
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
63
+ criteria: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
64
+ }, z.core.$strict>, z.ZodObject<{
65
+ type: z.ZodLiteral<"noul">;
66
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
67
+ criteria: z.ZodOptional<z.ZodObject<{
68
+ true: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
69
+ false: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
70
+ }, z.core.$strict>>;
71
+ }, z.core.$strict>, z.ZodObject<{
72
+ type: z.ZodLiteral<"score">;
73
+ instructions: z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>;
74
+ criteria: z.ZodTuple<[z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>, z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>], z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>, z.ZodArray<z.ZodType<JsonValue, unknown, z.core.$ZodTypeInternals<JsonValue, unknown>>>]>>;
75
+ }, z.core.$strict>], "type">>;
76
+ model: z.ZodString;
77
+ }, z.core.$strict>;
78
+ error: z.ZodObject<{
79
+ name: z.ZodString;
80
+ message: z.ZodString;
81
+ status: z.ZodNullable<z.ZodNumber>;
82
+ retryAfterMs: z.ZodNullable<z.ZodNumber>;
83
+ requestId: z.ZodOptional<z.ZodString>;
84
+ body: z.ZodUnknown;
85
+ }, z.core.$strict>;
86
+ }, z.core.$strict>]>;
87
+ export interface ReplayError {
88
+ name: string;
89
+ message: string;
90
+ status: number | null;
91
+ retryAfterMs: number | null;
92
+ requestId?: string;
93
+ body?: unknown;
94
+ }
95
+ interface ReplayLineHead {
96
+ format: 1;
97
+ id: string;
98
+ recordedAt: string;
99
+ requestId?: string;
100
+ durationMs: number;
101
+ request: SystemOneRequest;
102
+ }
103
+ export type ReplayLine = (ReplayLineHead & {
104
+ response: SystemOneResult;
105
+ }) | (ReplayLineHead & {
106
+ error: ReplayError;
107
+ });
108
+ /** Parses JSONL, skipping blank lines. A bad line throws naming its 1-based number. */
109
+ export declare function parseReplayLines(text: string): ReplayLine[];
110
+ export type ScriptStep = {
111
+ response: SystemOneResult;
112
+ } | {
113
+ error: unknown;
114
+ };
115
+ export interface ScriptedClient extends SystemOneClient {
116
+ /** The requests received so far, in call order. */
117
+ readonly calls: readonly SystemOneRequest[];
8
118
  }
9
119
  /**
10
- * Walking-skeleton factory for the `./testing` entry. It proves the second export
11
- * condition resolves for consumers; the real fake arrives with the Engine.
120
+ * Answers in call order. The gate is FIFO, so a script and a judged batch line up.
121
+ * Failures reject rather than throw synchronously, as a real client does.
12
122
  */
13
- export declare function createFakeTypeSafeClient(): FakeTypeSafeClient;
123
+ export declare function createScriptedClient(script: readonly ScriptStep[]): ScriptedClient;
124
+ /** Answers the line whose `request` is deep-equal to the incoming one. Key order is free. */
125
+ export declare function createReplayClient(lines: readonly ReplayLine[]): SystemOneClient;
126
+ export interface RecordingOptions {
127
+ /** The `id` written on each line. Defaults to the 1-based call index. */
128
+ id?: (request: SystemOneRequest, index: number) => string;
129
+ now?: () => Date;
130
+ }
131
+ /** Wraps a live client and hands one replay line per call to `sink`. The live test's half. */
132
+ export declare function createRecordingClient(inner: SystemOneClient, sink: (line: ReplayLine) => void, options?: RecordingOptions): SystemOneClient;
14
133
  //#endregion
package/dist/testing.mjs CHANGED
@@ -1,10 +1,163 @@
1
+ import { a as entryType, n as decodeThrown, o as jsonValue, t as parseJsonLines, u as questionSchema } from "./jsonl-CzTtnvyn.mjs";
2
+ import { z } from "zod";
1
3
  //#region src/testing.ts
4
+ const probability = z.number().min(0).max(1);
5
+ const probabilities = z.record(z.string().min(1), z.number());
6
+ const answerSchema = z.discriminatedUnion("type", [
7
+ z.object({
8
+ type: z.literal("choice"),
9
+ choice: z.string().min(1),
10
+ confidence: probability,
11
+ probabilities
12
+ }).strict(),
13
+ z.object({
14
+ type: z.literal("noul"),
15
+ noul: probability
16
+ }).strict(),
17
+ z.object({
18
+ type: z.literal("score"),
19
+ score: z.number(),
20
+ confidence: probability,
21
+ legend: z.record(z.string().min(1), jsonValue).optional(),
22
+ probabilities
23
+ }).strict()
24
+ ]);
25
+ const systemOneRequestSchema = z.object({
26
+ state: entryType,
27
+ questions: z.record(z.string().min(1), questionSchema),
28
+ model: z.string().min(1)
29
+ }).strict();
30
+ const systemOneResultSchema = z.object({
31
+ model: z.string().min(1),
32
+ answers: z.record(z.string().min(1), answerSchema),
33
+ usage: z.object({
34
+ input_tokens: z.number().int().nonnegative(),
35
+ output_tokens: z.number().int().nonnegative()
36
+ }).strict()
37
+ }).strict();
38
+ const replayErrorSchema = z.object({
39
+ name: z.string().min(1),
40
+ message: z.string(),
41
+ status: z.number().int().nullable(),
42
+ retryAfterMs: z.number().int().nonnegative().nullable(),
43
+ requestId: z.string().min(1).optional(),
44
+ body: z.unknown()
45
+ }).strict();
46
+ const replayLineFields = {
47
+ format: z.literal(1),
48
+ id: z.string().min(1),
49
+ recordedAt: z.iso.datetime(),
50
+ requestId: z.string().min(1).optional(),
51
+ durationMs: z.number().int().nonnegative(),
52
+ request: systemOneRequestSchema
53
+ };
54
+ const replayLineSchema = z.union([z.object({
55
+ ...replayLineFields,
56
+ response: systemOneResultSchema
57
+ }).strict(), z.object({
58
+ ...replayLineFields,
59
+ error: replayErrorSchema
60
+ }).strict()]);
61
+ /** Parses JSONL, skipping blank lines. A bad line throws naming its 1-based number. */
62
+ function parseReplayLines(text) {
63
+ return parseJsonLines(text, (line) => replayLineSchema.parse(JSON.parse(line)), (line, cause) => cause instanceof SyntaxError ? new Error(`replay line ${line} is not JSON`, { cause }) : new Error(`replay line ${line} is invalid: ${messageOf(cause)}`, { cause }));
64
+ }
65
+ function messageOf(cause) {
66
+ return cause instanceof Error ? cause.message : String(cause);
67
+ }
2
68
  /**
3
- * Walking-skeleton factory for the `./testing` entry. It proves the second export
4
- * condition resolves for consumers; the real fake arrives with the Engine.
69
+ * Answers in call order. The gate is FIFO, so a script and a judged batch line up.
70
+ * Failures reject rather than throw synchronously, as a real client does.
5
71
  */
6
- function createFakeTypeSafeClient() {
7
- return { kind: "fake" };
72
+ function createScriptedClient(script) {
73
+ const calls = [];
74
+ return {
75
+ calls,
76
+ systemOne: async (request) => {
77
+ const step = script[calls.length];
78
+ calls.push(request);
79
+ if (!step) throw new Error(`scripted client exhausted after ${script.length} calls`);
80
+ if ("error" in step) throw step.error;
81
+ return step.response;
82
+ }
83
+ };
84
+ }
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))));
93
+ }
94
+ /** Rebuilds what the SDK threw closely enough for the Judge's duck typing to see it. */
95
+ function replayError(recorded) {
96
+ const error = new Error(recorded.message);
97
+ error.name = recorded.name;
98
+ return Object.assign(error, {
99
+ status: recorded.status,
100
+ retryAfterMs: recorded.retryAfterMs,
101
+ requestId: recorded.requestId,
102
+ body: recorded.body
103
+ });
104
+ }
105
+ /** Answers the line whose `request` is deep-equal to the incoming one. Key order is free. */
106
+ function createReplayClient(lines) {
107
+ const keyed = lines.map((line) => ({
108
+ key: requestKey(line.request),
109
+ line
110
+ }));
111
+ return { systemOne: async (request) => {
112
+ const key = requestKey(request);
113
+ const line = keyed.find((candidate) => candidate.key === key)?.line;
114
+ if (!line) throw new Error(`no replay line matches the request for model ${request.model} and questions ${Object.keys(request.questions).join(", ")}`);
115
+ if ("error" in line) throw replayError(line.error);
116
+ return line.response;
117
+ } };
118
+ }
119
+ /** Core cannot name the SDK's error classes, so it decodes their fields. */
120
+ function recordError(cause) {
121
+ const thrown = decodeThrown(cause);
122
+ return {
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
+ };
130
+ }
131
+ /** Wraps a live client and hands one replay line per call to `sink`. The live test's half. */
132
+ function createRecordingClient(inner, sink, options = {}) {
133
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
134
+ const nextId = options.id ?? ((_request, index) => `${index}`);
135
+ let calls = 0;
136
+ return { systemOne: async (request, callOptions) => {
137
+ const startedAt = now();
138
+ const head = {
139
+ format: 1,
140
+ id: nextId(request, ++calls),
141
+ recordedAt: startedAt.toISOString(),
142
+ request
143
+ };
144
+ try {
145
+ const response = await inner.systemOne(request, callOptions);
146
+ sink({
147
+ ...head,
148
+ durationMs: now().getTime() - startedAt.getTime(),
149
+ response
150
+ });
151
+ return response;
152
+ } catch (cause) {
153
+ sink({
154
+ ...head,
155
+ durationMs: now().getTime() - startedAt.getTime(),
156
+ error: recordError(cause)
157
+ });
158
+ throw cause;
159
+ }
160
+ } };
8
161
  }
9
162
  //#endregion
10
- export { createFakeTypeSafeClient };
163
+ export { createRecordingClient, createReplayClient, createScriptedClient, parseReplayLines, replayLineSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siftline/core",
3
- "version": "0.0.3",
3
+ "version": "0.1.1",
4
4
  "description": "The Siftline engine: recipes, rules, fixtures and the judge wrapper.",
5
5
  "keywords": [
6
6
  "classification",
@@ -51,6 +51,7 @@
51
51
  "devDependencies": {
52
52
  "@arethetypeswrong/core": "0.18.5",
53
53
  "@siftline/config": "workspace:*",
54
+ "@typesafe-ai/sdk": "0.6.0",
54
55
  "publint": "0.3.24",
55
56
  "tsdown": "0.23.0",
56
57
  "typescript": "7.0.2",