@hraness/sys1 0.17.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/client.js ADDED
@@ -0,0 +1,667 @@
1
+ // src/http.ts
2
+ class HttpBodyLimitError extends Error {
3
+ maxBytes;
4
+ constructor(maxBytes) {
5
+ super(`HTTP body exceeds ${maxBytes} bytes`);
6
+ this.maxBytes = maxBytes;
7
+ this.name = "HttpBodyLimitError";
8
+ }
9
+ }
10
+ async function readBoundedText(source, maxBytes, signal) {
11
+ signal?.throwIfAborted();
12
+ if (source.body === null)
13
+ return "";
14
+ const reader = source.body.getReader();
15
+ const chunks = [];
16
+ let bytes = 0;
17
+ let rejectAbort;
18
+ const aborted = new Promise((_, reject) => {
19
+ rejectAbort = reject;
20
+ });
21
+ const onAbort = () => {
22
+ rejectAbort?.(signal?.reason);
23
+ reader.cancel(signal?.reason).catch(() => {});
24
+ };
25
+ signal?.addEventListener("abort", onAbort, { once: true });
26
+ try {
27
+ for (;; ) {
28
+ signal?.throwIfAborted();
29
+ const next = await Promise.race([reader.read(), aborted]);
30
+ signal?.throwIfAborted();
31
+ if (next.done)
32
+ break;
33
+ bytes += next.value.byteLength;
34
+ if (bytes > maxBytes) {
35
+ const error = new HttpBodyLimitError(maxBytes);
36
+ reader.cancel(error).catch(() => {});
37
+ throw error;
38
+ }
39
+ chunks.push(next.value);
40
+ }
41
+ const body = new Uint8Array(bytes);
42
+ let offset = 0;
43
+ for (const chunk of chunks) {
44
+ body.set(chunk, offset);
45
+ offset += chunk.byteLength;
46
+ }
47
+ return new TextDecoder().decode(body);
48
+ } finally {
49
+ signal?.removeEventListener("abort", onAbort);
50
+ reader.releaseLock();
51
+ }
52
+ }
53
+
54
+ // src/protocol.ts
55
+ import { z } from "zod";
56
+ var PROTOCOL_LIMITS = {
57
+ maxBodyBytes: 1048576,
58
+ maxStateBytes: 262144,
59
+ maxQuestions: 64,
60
+ maxQuestionNameChars: 128,
61
+ maxInstructionsChars: 4096,
62
+ maxCriterionChars: 1024,
63
+ maxOptionChars: 256,
64
+ maxChoiceOptions: 255,
65
+ minScoreLevels: 2,
66
+ maxScoreLevels: 10,
67
+ maxModelChars: 128
68
+ };
69
+ var jsonValueSchema = z.lazy(() => z.union([
70
+ z.string(),
71
+ z.number(),
72
+ z.boolean(),
73
+ z.null(),
74
+ z.array(jsonValueSchema),
75
+ z.record(z.string(), jsonValueSchema)
76
+ ]));
77
+ var entrySchema = z.union([
78
+ z.string(),
79
+ z.null(),
80
+ z.array(jsonValueSchema),
81
+ z.record(z.string(), jsonValueSchema)
82
+ ]);
83
+ function boundedEntry(maxBytes, message) {
84
+ return entrySchema.refine((value) => {
85
+ const text = typeof value === "string" ? value : JSON.stringify(value);
86
+ return new TextEncoder().encode(text).byteLength <= maxBytes;
87
+ }, message);
88
+ }
89
+ var instructionsSchema = boundedEntry(PROTOCOL_LIMITS.maxInstructionsChars, "instructions exceeds 4096 bytes");
90
+ var criterionSchema = boundedEntry(PROTOCOL_LIMITS.maxCriterionChars, "criterion exceeds 1024 bytes");
91
+ var noulQuestionSchema = z.object({
92
+ type: z.literal("noul"),
93
+ instructions: instructionsSchema.optional(),
94
+ criteria: z.object({
95
+ true: criterionSchema.optional(),
96
+ false: criterionSchema.optional()
97
+ }).nullable().optional()
98
+ });
99
+ var choiceQuestionSchema = z.object({
100
+ type: z.literal("choice"),
101
+ instructions: instructionsSchema.optional(),
102
+ criteria: z.record(z.string().min(1).max(PROTOCOL_LIMITS.maxOptionChars), criterionSchema).refine((options) => {
103
+ const count = Object.keys(options).length;
104
+ return count >= 1 && count <= PROTOCOL_LIMITS.maxChoiceOptions;
105
+ }, { message: "choice criteria needs 1..255 options" })
106
+ });
107
+ var scoreQuestionSchema = z.object({
108
+ type: z.literal("score"),
109
+ instructions: instructionsSchema.optional(),
110
+ criteria: z.array(criterionSchema).min(PROTOCOL_LIMITS.minScoreLevels).max(PROTOCOL_LIMITS.maxScoreLevels)
111
+ });
112
+ var questionSchema = z.discriminatedUnion("type", [
113
+ noulQuestionSchema,
114
+ choiceQuestionSchema,
115
+ scoreQuestionSchema
116
+ ]);
117
+ var systemOneRequestSchema = z.object({
118
+ model: z.string().min(1).max(PROTOCOL_LIMITS.maxModelChars).optional(),
119
+ state: entrySchema,
120
+ questions: z.record(z.string().min(1).max(PROTOCOL_LIMITS.maxQuestionNameChars), questionSchema).refine((questions) => {
121
+ const count = Object.keys(questions).length;
122
+ return count >= 1 && count <= PROTOCOL_LIMITS.maxQuestions;
123
+ }, { message: "questions needs 1..64 entries" })
124
+ });
125
+ var probabilitySchema = z.number().min(0).max(1);
126
+ var noulAnswerSchema = z.object({
127
+ type: z.literal("noul"),
128
+ noul: probabilitySchema
129
+ });
130
+ var choiceAnswerSchema = z.object({
131
+ type: z.literal("choice"),
132
+ choice: z.string().min(1),
133
+ probabilities: z.record(z.string().min(1), probabilitySchema),
134
+ confidence: probabilitySchema
135
+ }).superRefine((answer, ctx) => {
136
+ const count = Object.keys(answer.probabilities).length;
137
+ if (count < 1 || count > PROTOCOL_LIMITS.maxChoiceOptions) {
138
+ ctx.addIssue({ code: "custom", message: "choice probabilities needs 1..255 entries" });
139
+ }
140
+ if (!(answer.choice in answer.probabilities)) {
141
+ ctx.addIssue({ code: "custom", message: "choice is missing from probabilities" });
142
+ }
143
+ });
144
+ var scoreAnswerSchema = z.object({
145
+ type: z.literal("score"),
146
+ score: z.number().min(0),
147
+ legend: z.record(z.string().regex(/^(0|[1-9]\d*)$/), entrySchema),
148
+ probabilities: z.record(z.string().regex(/^(0|[1-9]\d*)$/), probabilitySchema),
149
+ confidence: probabilitySchema
150
+ }).superRefine((answer, ctx) => {
151
+ const legendKeys = Object.keys(answer.legend);
152
+ const probabilityKeys = Object.keys(answer.probabilities);
153
+ if (legendKeys.length < PROTOCOL_LIMITS.minScoreLevels || legendKeys.length > PROTOCOL_LIMITS.maxScoreLevels) {
154
+ ctx.addIssue({ code: "custom", message: "score legend needs 2..10 entries" });
155
+ }
156
+ if (legendKeys.length !== probabilityKeys.length || legendKeys.some((key) => !(key in answer.probabilities))) {
157
+ ctx.addIssue({ code: "custom", message: "score legend and probabilities must have identical keys" });
158
+ }
159
+ if (legendKeys.some((key, index) => key !== String(index))) {
160
+ ctx.addIssue({ code: "custom", message: "score keys must be contiguous from zero" });
161
+ }
162
+ if (legendKeys.length > 0 && answer.score > legendKeys.length - 1) {
163
+ ctx.addIssue({ code: "custom", message: "score exceeds the legend range" });
164
+ }
165
+ });
166
+ var answerSchema = z.discriminatedUnion("type", [
167
+ noulAnswerSchema,
168
+ choiceAnswerSchema,
169
+ scoreAnswerSchema
170
+ ]);
171
+ var systemOneResponseSchema = z.object({
172
+ model: z.string().min(1).max(PROTOCOL_LIMITS.maxModelChars),
173
+ answers: z.record(z.string().min(1).max(PROTOCOL_LIMITS.maxQuestionNameChars), answerSchema).refine((answers) => Object.keys(answers).length <= PROTOCOL_LIMITS.maxQuestions),
174
+ usage: z.object({
175
+ input_tokens: z.number().int().nonnegative(),
176
+ output_tokens: z.number().int().nonnegative()
177
+ })
178
+ });
179
+ function errorBody(type, message, details) {
180
+ return { error: { type, message, ...details === undefined ? {} : { details } } };
181
+ }
182
+ function serializedBytes(value) {
183
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
184
+ }
185
+
186
+ // src/response.ts
187
+ function sameEntry(left, right) {
188
+ if (left === right)
189
+ return true;
190
+ if (left === null || right === null || typeof left !== "object" || typeof right !== "object") {
191
+ return false;
192
+ }
193
+ if (Array.isArray(left) !== Array.isArray(right))
194
+ return false;
195
+ const a = left;
196
+ const b = right;
197
+ const keys = Object.keys(a);
198
+ return keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && sameEntry(a[key], b[key]));
199
+ }
200
+ function matchesRequest(request, response) {
201
+ const questionNames = Object.keys(request.questions);
202
+ if (questionNames.length !== Object.keys(response.answers).length)
203
+ return false;
204
+ return questionNames.every((name) => {
205
+ const question = request.questions[name];
206
+ if (!Object.hasOwn(response.answers, name))
207
+ return false;
208
+ const answer = response.answers[name];
209
+ if (question.type !== answer.type)
210
+ return false;
211
+ if (question.type === "choice" && answer.type === "choice") {
212
+ const options = Object.keys(question.criteria);
213
+ return Object.hasOwn(question.criteria, answer.choice) && options.length === Object.keys(answer.probabilities).length && options.every((option) => Object.hasOwn(answer.probabilities, option));
214
+ }
215
+ if (question.type === "score" && answer.type === "score") {
216
+ return question.criteria.length === Object.keys(answer.legend).length && question.criteria.every((criterion, index) => sameEntry(criterion, answer.legend[String(index)]));
217
+ }
218
+ return true;
219
+ });
220
+ }
221
+ function scoreEndpoint(bounds, lowerTotal, maximum) {
222
+ let expected = bounds.reduce((sum, bound, level) => sum + level * bound.lower, 0);
223
+ let remaining = Math.max(0, 1 - lowerTotal);
224
+ for (let position = 0;position < bounds.length && remaining > 0; position++) {
225
+ const level = maximum ? bounds.length - 1 - position : position;
226
+ const bound = bounds[level];
227
+ const added = Math.min(remaining, bound.upper - bound.lower);
228
+ expected += level * added;
229
+ remaining -= added;
230
+ }
231
+ return expected;
232
+ }
233
+ function validateResponseForRequest(request, value, roundingDecimals = 3) {
234
+ try {
235
+ if (roundingDecimals !== 2 && roundingDecimals !== 3)
236
+ throw new Error;
237
+ const rounding = 0.5 * 10 ** -roundingDecimals;
238
+ const response = systemOneResponseSchema.parse(value);
239
+ if (!matchesRequest(request, response))
240
+ throw new Error;
241
+ for (const answer of Object.values(response.answers)) {
242
+ if (answer.type === "noul")
243
+ continue;
244
+ const values = Object.values(answer.probabilities);
245
+ const total = values.reduce((sum, probability) => sum + probability, 0);
246
+ const bounds = values.map((probability) => ({
247
+ lower: Math.max(0, probability - rounding),
248
+ upper: Math.min(1, probability + rounding)
249
+ }));
250
+ const lowerTotal = bounds.reduce((sum, bound) => sum + bound.lower, 0);
251
+ const upperTotal = bounds.reduce((sum, bound) => sum + bound.upper, 0);
252
+ if (total <= 0 || lowerTotal > 1 + 0.000000001 || upperTotal < 1 - 0.000000001)
253
+ throw new Error;
254
+ if (answer.type === "choice") {
255
+ if (answer.probabilities[answer.choice] + 0.000000001 < Math.max(...values))
256
+ throw new Error;
257
+ } else {
258
+ const minimum = scoreEndpoint(bounds, lowerTotal, false);
259
+ const maximum = scoreEndpoint(bounds, lowerTotal, true);
260
+ if (answer.score + rounding < minimum - 0.000000001 || answer.score - rounding > maximum + 0.000000001) {
261
+ throw new Error;
262
+ }
263
+ }
264
+ }
265
+ return response;
266
+ } catch {
267
+ throw new Error("Invalid or mismatched System One response");
268
+ }
269
+ }
270
+
271
+ // src/kev.ts
272
+ var pythonLeadingWhitespace = /^[\u0009-\u000d\u001c-\u0020\u0085\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000]+/u;
273
+ function renderNumber(value) {
274
+ const wire = JSON.stringify(value);
275
+ if (!/[.e]/i.test(wire))
276
+ return wire;
277
+ if (Math.abs(value) < 0.0001 || Math.abs(value) >= 10000000000000000) {
278
+ return value.toExponential().replace(/e([+-])(\d)$/u, "e$10$2");
279
+ }
280
+ return wire;
281
+ }
282
+ function render(value, indent = 0) {
283
+ if (value === null)
284
+ return "";
285
+ if (typeof value === "string")
286
+ return value;
287
+ if (typeof value === "boolean")
288
+ return value ? "True" : "False";
289
+ if (typeof value === "number")
290
+ return renderNumber(value);
291
+ const pad = " ".repeat(indent);
292
+ if (Array.isArray(value)) {
293
+ return value.map((entry) => `${pad}- ${render(entry, indent + 1).replace(pythonLeadingWhitespace, "")}`).join(`
294
+ `);
295
+ }
296
+ return Object.entries(value).map(([key, entry]) => entry !== null && typeof entry === "object" ? `${pad}${key}:
297
+ ${render(entry, indent + 1)}` : `${pad}${key}: ${render(entry)}`).join(`
298
+ `);
299
+ }
300
+ function adaptKevRequest(request) {
301
+ const adapted = structuredClone(request);
302
+ for (const question of Object.values(adapted.questions)) {
303
+ if (question.instructions === undefined)
304
+ question.instructions = null;
305
+ }
306
+ return adapted;
307
+ }
308
+ function adaptKevResponse(request, value) {
309
+ try {
310
+ const response = systemOneResponseSchema.parse(value);
311
+ if (request.model !== undefined && response.model !== request.model)
312
+ throw new Error;
313
+ for (const [name, question] of Object.entries(request.questions)) {
314
+ if (question.type !== "score")
315
+ continue;
316
+ const answer = response.answers[name];
317
+ if (answer?.type !== "score" || Object.keys(answer.legend).length !== question.criteria.length) {
318
+ throw new Error;
319
+ }
320
+ if (!question.criteria.every((criterion, index) => answer.legend[String(index)] === render(criterion))) {
321
+ throw new Error;
322
+ }
323
+ answer.legend = Object.fromEntries(question.criteria.map((criterion, index) => [String(index), structuredClone(criterion)]));
324
+ }
325
+ return validateResponseForRequest(request, response, 2);
326
+ } catch {
327
+ throw new Error("Invalid or mismatched System One response");
328
+ }
329
+ }
330
+ // src/profile.ts
331
+ import { z as z2 } from "zod";
332
+ var identifier = z2.string().min(1).max(64).regex(/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/).refine((value) => value === value.trim());
333
+ var profileQuestionSchema = z2.discriminatedUnion("type", [
334
+ noulQuestionSchema.extend({
335
+ criteria: noulQuestionSchema.shape.criteria.unwrap().unwrap().strict().nullable().optional()
336
+ }).strict(),
337
+ choiceQuestionSchema.strict(),
338
+ scoreQuestionSchema.strict()
339
+ ]);
340
+ var profileDefinitionSchema = z2.object({
341
+ version: z2.literal(1),
342
+ id: identifier,
343
+ revision: identifier,
344
+ model: systemOneRequestSchema.shape.model.unwrap().regex(/^[a-z0-9][a-z0-9.-]*\/\S+$/).refine((value) => value === value.trim()),
345
+ questions: z2.record(z2.string().min(1).max(PROTOCOL_LIMITS.maxQuestionNameChars), profileQuestionSchema).refine((questions) => {
346
+ const count = Object.keys(questions).length;
347
+ return count >= 1 && count <= PROTOCOL_LIMITS.maxQuestions;
348
+ })
349
+ }).strict();
350
+
351
+ class Sys1ProfileError extends Error {
352
+ code;
353
+ constructor(code) {
354
+ super(code === "invalid_profile" ? "Invalid Sys1 profile" : "Invalid Sys1 profile request");
355
+ this.name = "Sys1ProfileError";
356
+ this.code = code;
357
+ }
358
+ }
359
+ function snapshotJson(input, maxBytes) {
360
+ const ancestors = new Set;
361
+ const encoder = new TextEncoder;
362
+ let bytes = 0;
363
+ const account = (count) => {
364
+ bytes += count;
365
+ if (bytes > maxBytes)
366
+ throw new Error;
367
+ };
368
+ const copy = (value) => {
369
+ if (value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number") {
370
+ if (typeof value === "number" && !Number.isFinite(value))
371
+ throw new Error;
372
+ if (typeof value === "string" && value.length > maxBytes - bytes)
373
+ throw new Error;
374
+ account(encoder.encode(JSON.stringify(value)).byteLength);
375
+ return value;
376
+ }
377
+ if (typeof value !== "object" || ancestors.has(value))
378
+ throw new Error;
379
+ const array = Array.isArray(value);
380
+ const prototype = Object.getPrototypeOf(value);
381
+ if (prototype !== (array ? Array.prototype : Object.prototype) && !(prototype === null && !array))
382
+ throw new Error;
383
+ const descriptors = Object.getOwnPropertyDescriptors(value);
384
+ const keys = Reflect.ownKeys(descriptors);
385
+ if (keys.some((key) => typeof key !== "string"))
386
+ throw new Error;
387
+ ancestors.add(value);
388
+ account(2);
389
+ let result;
390
+ if (array) {
391
+ if (keys.length !== value.length + 1)
392
+ throw new Error;
393
+ const items = [];
394
+ for (let index = 0;index < value.length; index++) {
395
+ const descriptor = descriptors[String(index)];
396
+ if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
397
+ throw new Error;
398
+ if (index > 0)
399
+ account(1);
400
+ items.push(copy(descriptor.value));
401
+ }
402
+ result = items;
403
+ } else {
404
+ const object = Object.create(null);
405
+ for (let index = 0;index < keys.length; index++) {
406
+ const key = keys[index];
407
+ const descriptor = descriptors[key];
408
+ if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor))
409
+ throw new Error;
410
+ if (index > 0)
411
+ account(1);
412
+ account(encoder.encode(JSON.stringify(key)).byteLength + 1);
413
+ object[key] = copy(descriptor.value);
414
+ }
415
+ result = object;
416
+ }
417
+ ancestors.delete(value);
418
+ return result;
419
+ };
420
+ return copy(input);
421
+ }
422
+ function freezeDeep(value) {
423
+ if (value !== null && typeof value === "object") {
424
+ for (const child of Object.values(value))
425
+ freezeDeep(child);
426
+ Object.freeze(value);
427
+ }
428
+ return value;
429
+ }
430
+ function createProfile(input) {
431
+ let definition;
432
+ try {
433
+ definition = freezeDeep(profileDefinitionSchema.parse(snapshotJson(input, PROTOCOL_LIMITS.maxBodyBytes)));
434
+ } catch {
435
+ throw new Sys1ProfileError("invalid_profile");
436
+ }
437
+ return Object.freeze({
438
+ definition,
439
+ request(state) {
440
+ try {
441
+ const request = systemOneRequestSchema.parse(snapshotJson({
442
+ model: definition.model,
443
+ state,
444
+ questions: definition.questions
445
+ }, PROTOCOL_LIMITS.maxBodyBytes));
446
+ if (serializedBytes(request.state) > PROTOCOL_LIMITS.maxStateBytes)
447
+ throw new Error;
448
+ return request;
449
+ } catch {
450
+ throw new Sys1ProfileError("invalid_request");
451
+ }
452
+ }
453
+ });
454
+ }
455
+
456
+ // src/client.ts
457
+ var DEFAULT_BASE_URL = "http://127.0.0.1:13900";
458
+ var MAX_RESPONSE_BYTES = 4194304;
459
+ var ERROR_MESSAGES = {
460
+ invalid_options: "Invalid Sys1 client options",
461
+ invalid_request: "Invalid System One request",
462
+ aborted: "Sys1 request aborted",
463
+ timeout: "Sys1 request timed out",
464
+ transport_error: "Sys1 transport failed",
465
+ http_error: "Sys1 endpoint returned an HTTP error",
466
+ response_too_large: "Sys1 response exceeded the byte limit",
467
+ invalid_response: "Sys1 endpoint returned an invalid or mismatched response"
468
+ };
469
+
470
+ class Sys1ClientError extends Error {
471
+ code;
472
+ status;
473
+ constructor(code, status) {
474
+ super(ERROR_MESSAGES[code]);
475
+ this.name = "Sys1ClientError";
476
+ this.code = code;
477
+ this.status = status;
478
+ }
479
+ }
480
+ function metadata(headers) {
481
+ const result = {};
482
+ const adapter = headers.get("x-sys1-adapter");
483
+ const decimals = headers.get("x-sys1-probability-decimals");
484
+ if (adapter !== null || decimals !== null) {
485
+ if (adapter !== "kev" || decimals !== "2")
486
+ throw new Sys1ClientError("invalid_response");
487
+ result.adapter = "kev";
488
+ result.probabilityDecimals = 2;
489
+ }
490
+ const name = (key) => {
491
+ const value = headers.get(key);
492
+ if (value === null)
493
+ return;
494
+ if (!/^[a-zA-Z0-9_.:/-]{1,128}$/.test(value))
495
+ throw new Sys1ClientError("invalid_response");
496
+ return value;
497
+ };
498
+ const backend = name("x-sys1-backend");
499
+ if (backend !== undefined)
500
+ result.backend = backend;
501
+ const attempts = headers.get("x-sys1-attempts");
502
+ if (attempts !== null) {
503
+ if (!/^[1-9]\d{0,5}$/.test(attempts))
504
+ throw new Sys1ClientError("invalid_response");
505
+ result.attempts = Number(attempts);
506
+ }
507
+ const localAdapter = name("x-sys1-local-adapter");
508
+ if (localAdapter !== undefined) {
509
+ result.local = { adapter: localAdapter };
510
+ for (const [header, key] of [
511
+ ["x-sys1-local-min-coverage", "minCoverage"],
512
+ ["x-sys1-local-min-concentration", "minConcentration"]
513
+ ]) {
514
+ const value = headers.get(header);
515
+ if (value === null)
516
+ continue;
517
+ if (!/^(0(?:\.\d{1,16})?|1(?:\.0{1,16})?)$/.test(value)) {
518
+ throw new Sys1ClientError("invalid_response");
519
+ }
520
+ result.local[key] = Number(value);
521
+ }
522
+ }
523
+ return result;
524
+ }
525
+ function cancelBody(response) {
526
+ response.body?.cancel().catch(() => {
527
+ return;
528
+ });
529
+ }
530
+ function fetchUntilAborted(fetchFn, url, init, signal) {
531
+ return new Promise((resolve, reject) => {
532
+ const abort = () => reject(new Sys1ClientError("aborted"));
533
+ signal.addEventListener("abort", abort, { once: true });
534
+ if (signal.aborted) {
535
+ signal.removeEventListener("abort", abort);
536
+ abort();
537
+ return;
538
+ }
539
+ Promise.resolve().then(() => fetchFn(url, init)).then((response) => {
540
+ if (signal.aborted)
541
+ cancelBody(response);
542
+ resolve(response);
543
+ }, reject).finally(() => signal.removeEventListener("abort", abort));
544
+ });
545
+ }
546
+ function createClient(options = {}) {
547
+ let endpoint;
548
+ let headers;
549
+ const timeoutMs = options.timeoutMs ?? 30000;
550
+ const fetchFn = options.fetch ?? globalThis.fetch;
551
+ const adapter = options.adapter ?? "systemone";
552
+ try {
553
+ if (adapter !== "systemone" && adapter !== "kev")
554
+ throw new Error;
555
+ const base = options.baseUrl ?? DEFAULT_BASE_URL;
556
+ if (base.length > 2048)
557
+ throw new Error;
558
+ const url = new URL(base);
559
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
560
+ throw new Error;
561
+ }
562
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}/v1/systemone`;
563
+ endpoint = url.href;
564
+ headers = new Headers(options.headers);
565
+ headers.set("content-type", "application/json");
566
+ headers.set("accept", "application/json");
567
+ let headerBytes = 0;
568
+ headers.forEach((value, key) => {
569
+ headerBytes += new TextEncoder().encode(key + value).byteLength;
570
+ });
571
+ if (headerBytes > 16384 || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300000 || typeof fetchFn !== "function") {
572
+ throw new Error;
573
+ }
574
+ } catch {
575
+ throw new Sys1ClientError("invalid_options");
576
+ }
577
+ return {
578
+ async evaluate(input, evaluation = {}) {
579
+ if (evaluation.signal?.aborted)
580
+ throw new Sys1ClientError("aborted");
581
+ let request;
582
+ let body;
583
+ try {
584
+ const serialized = JSON.stringify(input);
585
+ if (new TextEncoder().encode(serialized).byteLength > PROTOCOL_LIMITS.maxBodyBytes)
586
+ throw new Error;
587
+ request = systemOneRequestSchema.parse(JSON.parse(serialized));
588
+ const state = typeof request.state === "string" ? request.state : JSON.stringify(request.state);
589
+ if (new TextEncoder().encode(state).byteLength > PROTOCOL_LIMITS.maxStateBytes)
590
+ throw new Error;
591
+ body = JSON.stringify(adapter === "kev" ? adaptKevRequest(request) : request);
592
+ if (new TextEncoder().encode(body).byteLength > PROTOCOL_LIMITS.maxBodyBytes)
593
+ throw new Error;
594
+ } catch {
595
+ throw new Sys1ClientError("invalid_request");
596
+ }
597
+ const controller = new AbortController;
598
+ let timedOut = false;
599
+ const onAbort = () => controller.abort();
600
+ evaluation.signal?.addEventListener("abort", onAbort, { once: true });
601
+ const timer = setTimeout(() => {
602
+ timedOut = true;
603
+ controller.abort();
604
+ }, timeoutMs);
605
+ try {
606
+ const response = await fetchUntilAborted(fetchFn, endpoint, {
607
+ method: "POST",
608
+ headers: new Headers(headers),
609
+ body,
610
+ signal: controller.signal,
611
+ redirect: "error",
612
+ credentials: "omit"
613
+ }, controller.signal);
614
+ if (!response.ok) {
615
+ cancelBody(response);
616
+ throw new Sys1ClientError("http_error", response.status);
617
+ }
618
+ const text = await readBoundedText(response, MAX_RESPONSE_BYTES, controller.signal);
619
+ let parsed;
620
+ let route;
621
+ try {
622
+ route = metadata(response.headers);
623
+ const value = JSON.parse(text);
624
+ if (adapter === "kev") {
625
+ parsed = adaptKevResponse(request, value);
626
+ route.adapter = "kev";
627
+ route.probabilityDecimals = 2;
628
+ } else {
629
+ parsed = validateResponseForRequest(request, value, route.probabilityDecimals ?? 3);
630
+ }
631
+ } catch {
632
+ throw new Sys1ClientError("invalid_response");
633
+ }
634
+ return { response: parsed, metadata: route };
635
+ } catch (error) {
636
+ if (controller.signal.aborted)
637
+ throw new Sys1ClientError(timedOut ? "timeout" : "aborted");
638
+ if (error instanceof Sys1ClientError)
639
+ throw error;
640
+ if (error instanceof HttpBodyLimitError)
641
+ throw new Sys1ClientError("response_too_large");
642
+ throw new Sys1ClientError("transport_error");
643
+ } finally {
644
+ clearTimeout(timer);
645
+ evaluation.signal?.removeEventListener("abort", onAbort);
646
+ }
647
+ }
648
+ };
649
+ }
650
+ export {
651
+ systemOneResponseSchema,
652
+ systemOneRequestSchema,
653
+ scoreQuestionSchema,
654
+ scoreAnswerSchema,
655
+ questionSchema,
656
+ noulQuestionSchema,
657
+ noulAnswerSchema,
658
+ createProfile,
659
+ createClient,
660
+ choiceQuestionSchema,
661
+ choiceAnswerSchema,
662
+ answerSchema,
663
+ Sys1ProfileError,
664
+ Sys1ClientError,
665
+ PROTOCOL_LIMITS,
666
+ DEFAULT_BASE_URL
667
+ };