@corbits/system-one 0.3.2 → 0.4.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.
@@ -0,0 +1,332 @@
1
+ import { type } from "arktype";
2
+ const CLAIRIC_MODEL = "@cf/cloudflare/clairic";
3
+ function definedString(value) {
4
+ if (value === undefined || value === "")
5
+ return undefined;
6
+ return value;
7
+ }
8
+ function text(instructions) {
9
+ return instructions === undefined
10
+ ? { type: "text" }
11
+ : { type: "text", instructions };
12
+ }
13
+ function date(instructions) {
14
+ return instructions === undefined
15
+ ? { type: "date" }
16
+ : { type: "date", instructions };
17
+ }
18
+ function number(options) {
19
+ const field = { type: "number" };
20
+ if (options?.decimals !== undefined)
21
+ field.decimals = options.decimals;
22
+ if (options?.unit !== undefined)
23
+ field.unit = options.unit;
24
+ if (options?.instructions !== undefined)
25
+ field.instructions = options.instructions;
26
+ return field;
27
+ }
28
+ function list(items) {
29
+ return { type: "list", items };
30
+ }
31
+ function estimate(options) {
32
+ const field = { type: "estimate" };
33
+ if (options?.min !== undefined)
34
+ field.min = options.min;
35
+ if (options?.max !== undefined)
36
+ field.max = options.max;
37
+ if (options?.step !== undefined)
38
+ field.step = options.step;
39
+ if (options?.instructions !== undefined)
40
+ field.instructions = options.instructions;
41
+ return field;
42
+ }
43
+ export { date, estimate, list, number, text };
44
+ function isRecord(value) {
45
+ return typeof value === "object" && value !== null && !Array.isArray(value);
46
+ }
47
+ const finiteNumber = type("number").narrow((value) => Number.isFinite(value));
48
+ const finiteOrNull = type("number | null").narrow((value) => value === null || Number.isFinite(value));
49
+ const TextWire = type({
50
+ "type?": "string",
51
+ "value?": "string | null",
52
+ "text?": "string | null",
53
+ "start?": finiteNumber,
54
+ "end?": finiteNumber,
55
+ "span?": [finiteNumber, finiteNumber],
56
+ "offsets?": {
57
+ "start?": finiteNumber,
58
+ "end?": finiteNumber,
59
+ },
60
+ });
61
+ const DateWire = type("string").or(type({
62
+ "type?": "string",
63
+ "value?": "string | null",
64
+ "date?": "string | null",
65
+ }));
66
+ const NumberWire = finiteNumber.or(type({
67
+ "type?": "string",
68
+ "value?": finiteOrNull,
69
+ "number?": finiteOrNull,
70
+ "unit?": "string",
71
+ }));
72
+ const stringItems = type("string[]").or("null");
73
+ const numberItems = finiteNumber.array().or("null");
74
+ const StringListWire = type("string[]").or(type({
75
+ "type?": "string",
76
+ "value?": stringItems,
77
+ "items?": stringItems,
78
+ }));
79
+ const NumberListWire = finiteNumber.array().or(type({
80
+ "type?": "string",
81
+ "value?": numberItems,
82
+ "items?": numberItems,
83
+ }));
84
+ const EstimateInterval = type([finiteNumber, finiteNumber]).or(type({
85
+ "p10?": finiteNumber,
86
+ "low?": finiteNumber,
87
+ "p90?": finiteNumber,
88
+ "high?": finiteNumber,
89
+ }));
90
+ const EstimateWire = type({
91
+ "type?": "string",
92
+ "value?": finiteOrNull,
93
+ "median?": finiteOrNull,
94
+ "p10?": finiteNumber,
95
+ "p90?": finiteNumber,
96
+ "interval?": EstimateInterval,
97
+ });
98
+ function isParseError(value) {
99
+ return (typeof value === "object" &&
100
+ value !== null &&
101
+ "parseError" in value &&
102
+ typeof value.parseError === "string");
103
+ }
104
+ function textAnswer(raw) {
105
+ if (typeof raw === "string")
106
+ return { value: raw };
107
+ if (raw === null || raw === undefined)
108
+ return { value: null };
109
+ const parsed = TextWire(raw);
110
+ if (parsed instanceof type.errors)
111
+ return { parseError: parsed.summary };
112
+ const value = typeof parsed.value === "string"
113
+ ? parsed.value
114
+ : typeof parsed.text === "string"
115
+ ? parsed.text
116
+ : null;
117
+ const start = parsed.start ?? parsed.span?.[0] ?? parsed.offsets?.start;
118
+ const end = parsed.end ?? parsed.span?.[1] ?? parsed.offsets?.end;
119
+ const answer = { value };
120
+ if (start !== undefined)
121
+ answer.start = start;
122
+ if (end !== undefined)
123
+ answer.end = end;
124
+ return answer;
125
+ }
126
+ function dateAnswer(raw) {
127
+ if (raw === null || raw === undefined)
128
+ return { value: null };
129
+ const parsed = DateWire(raw);
130
+ if (parsed instanceof type.errors)
131
+ return { parseError: parsed.summary };
132
+ if (typeof parsed === "string")
133
+ return { value: parsed };
134
+ const value = typeof parsed.value === "string"
135
+ ? parsed.value
136
+ : typeof parsed.date === "string"
137
+ ? parsed.date
138
+ : null;
139
+ return { value };
140
+ }
141
+ function numberAnswer(raw) {
142
+ if (raw === null || raw === undefined)
143
+ return { value: null };
144
+ const parsed = NumberWire(raw);
145
+ if (parsed instanceof type.errors)
146
+ return { parseError: parsed.summary };
147
+ if (typeof parsed === "number")
148
+ return { value: parsed };
149
+ const value = typeof parsed.value === "number"
150
+ ? parsed.value
151
+ : typeof parsed.number === "number"
152
+ ? parsed.number
153
+ : null;
154
+ const answer = { value };
155
+ if (parsed.unit !== undefined)
156
+ answer.unit = parsed.unit;
157
+ return answer;
158
+ }
159
+ function listValues(parsed) {
160
+ if (Array.isArray(parsed))
161
+ return parsed;
162
+ if (parsed.value !== undefined)
163
+ return parsed.value;
164
+ return parsed.items ?? null;
165
+ }
166
+ function listAnswer(items, raw) {
167
+ if (raw === null || raw === undefined)
168
+ return { value: null };
169
+ const parsed = (items === "number" ? NumberListWire : StringListWire)(raw);
170
+ if (parsed instanceof type.errors)
171
+ return { parseError: parsed.summary };
172
+ const values = listValues(parsed);
173
+ if (values === null)
174
+ return { value: null };
175
+ if (items === "number") {
176
+ if (!values.every((item) => typeof item === "number")) {
177
+ return { parseError: "list items must be numbers" };
178
+ }
179
+ return { value: values };
180
+ }
181
+ if (!values.every((item) => typeof item === "string")) {
182
+ return { parseError: "list items must be strings" };
183
+ }
184
+ return { value: values };
185
+ }
186
+ function isNumberPair(value) {
187
+ return Array.isArray(value);
188
+ }
189
+ function estimateInterval(parsed) {
190
+ if (parsed.p10 !== undefined && parsed.p90 !== undefined) {
191
+ return { p10: parsed.p10, p90: parsed.p90 };
192
+ }
193
+ const interval = parsed.interval;
194
+ if (interval === undefined)
195
+ return undefined;
196
+ if (isNumberPair(interval)) {
197
+ const low = interval[0];
198
+ const high = interval[1];
199
+ if (low === undefined || high === undefined)
200
+ return undefined;
201
+ return { p10: low, p90: high };
202
+ }
203
+ const low = interval.p10 ?? interval.low;
204
+ const high = interval.p90 ?? interval.high;
205
+ if (low === undefined || high === undefined)
206
+ return undefined;
207
+ return { p10: low, p90: high };
208
+ }
209
+ function estimateAnswer(raw) {
210
+ if (raw === null || raw === undefined)
211
+ return { median: null };
212
+ const parsed = EstimateWire(raw);
213
+ if (parsed instanceof type.errors)
214
+ return { parseError: parsed.summary };
215
+ const median = parsed.median ?? parsed.value ?? null;
216
+ const answer = { median };
217
+ const interval = estimateInterval(parsed);
218
+ if (interval !== undefined)
219
+ answer.interval = interval;
220
+ return answer;
221
+ }
222
+ function mapAnswer(field, raw) {
223
+ if (field.type === "text")
224
+ return textAnswer(raw);
225
+ if (field.type === "date")
226
+ return dateAnswer(raw);
227
+ if (field.type === "number")
228
+ return numberAnswer(raw);
229
+ if (field.type === "list")
230
+ return listAnswer(field.items, raw);
231
+ return estimateAnswer(raw);
232
+ }
233
+ function isExtractionAnswers(fields, answers) {
234
+ return Object.keys(fields).every((key) => Object.hasOwn(answers, key));
235
+ }
236
+ function answersOf(fields, raw) {
237
+ const envelope = isRecord(raw) && isRecord(raw["result"]) ? raw["result"] : raw;
238
+ if (!isRecord(envelope) || !isRecord(envelope["answers"])) {
239
+ return { parseError: "response has no answers" };
240
+ }
241
+ const source = envelope["answers"];
242
+ const answers = {};
243
+ for (const [name, field] of Object.entries(fields)) {
244
+ const parsed = mapAnswer(field, source[name]);
245
+ if (isParseError(parsed)) {
246
+ return {
247
+ parseError: `answer "${name}" failed schema validation: ${parsed.parseError}`,
248
+ };
249
+ }
250
+ answers[name] = parsed;
251
+ }
252
+ if (!isExtractionAnswers(fields, answers)) {
253
+ return { parseError: "response has no answers" };
254
+ }
255
+ return answers;
256
+ }
257
+ function fallback(reason, started, extra) {
258
+ const result = {
259
+ fallback: true,
260
+ reason,
261
+ latencyMs: Math.max(0, Date.now() - started),
262
+ backendAttempted: "cloudflare-workers-ai",
263
+ };
264
+ if (extra?.httpStatus !== undefined)
265
+ result.httpStatus = extra.httpStatus;
266
+ if (extra?.detail !== undefined)
267
+ result.detail = extra.detail;
268
+ return result;
269
+ }
270
+ /**
271
+ * Keyed extraction against Cloudflare Workers AI Clairic. Missing credentials,
272
+ * non-2xx responses, and bodies that are not JSON return `fallback` and do
273
+ * not throw.
274
+ */
275
+ export async function extract(input, options) {
276
+ const started = Date.now();
277
+ const accountId = definedString(options?.accountId) ??
278
+ definedString(process.env["CLOUDFLARE_ACCOUNT_ID"]);
279
+ const token = definedString(options?.token) ??
280
+ definedString(process.env["CLOUDFLARE_API_TOKEN"]);
281
+ if (accountId === undefined || token === undefined) {
282
+ return fallback("no-key", started, {
283
+ detail: "no Cloudflare credentials; set accountId and token or CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN",
284
+ });
285
+ }
286
+ const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/run/${CLAIRIC_MODEL}`;
287
+ const fetchImpl = options?.fetch ?? fetch;
288
+ let response;
289
+ try {
290
+ response = await fetchImpl(url, {
291
+ method: "POST",
292
+ headers: {
293
+ authorization: `Bearer ${token}`,
294
+ "content-type": "application/json",
295
+ },
296
+ body: JSON.stringify({
297
+ model: "clairic",
298
+ state: input.state,
299
+ fields: input.fields,
300
+ }),
301
+ });
302
+ }
303
+ catch (cause) {
304
+ return fallback("network", started, {
305
+ detail: cause instanceof Error ? cause.message : String(cause),
306
+ });
307
+ }
308
+ if (!response.ok) {
309
+ return fallback("http-error", started, { httpStatus: response.status });
310
+ }
311
+ let body;
312
+ try {
313
+ body = await response.json();
314
+ }
315
+ catch (cause) {
316
+ return fallback("parse-error", started, {
317
+ detail: cause instanceof Error ? cause.message : String(cause),
318
+ });
319
+ }
320
+ const answers = answersOf(input.fields, body);
321
+ if (isParseError(answers)) {
322
+ return fallback("parse-error", started, {
323
+ detail: answers.parseError.slice(0, 240),
324
+ });
325
+ }
326
+ return {
327
+ fallback: false,
328
+ answers,
329
+ modelId: "clairic",
330
+ latencyMs: Math.max(0, Date.now() - started),
331
+ };
332
+ }
package/dist/index.d.ts CHANGED
@@ -2,8 +2,11 @@ export { DEFAULT_TIMEOUT_MS } from "./config.js";
2
2
  export type { EvaluateDeps } from "./client.js";
3
3
  export { evaluate } from "./evaluate.js";
4
4
  export type { EvaluateOptions } from "./evaluate.js";
5
+ export { choice, noul, score, box, systemOne } from "./decisions.js";
6
+ export type { SystemOneResult, SystemOneSuccess } from "./decisions.js";
7
+ export { Image, image, withAssets } from "./assets.js";
5
8
  export { createSystemOneAdapter, createSystemOneAdapterFactory, SYSTEM_ONE_PROVIDER, } from "./adapter.js";
6
9
  export { SystemOneError } from "./errors.js";
7
10
  export type { SystemOneErrorCode } from "./errors.js";
8
11
  export { SystemOneTelemetryEvent } from "./telemetry.js";
9
- export { BooleanQuestion, ChoiceQuestion, Decision, Description, EndpointConfig, EvaluateConfig, EvaluateInput, EvaluateResult, FallbackReason, FallbackResult, NoulQuestion, ProbabilityMap, Question, QuestionList, ScoreQuestion, State, Usage, } from "./schemas.js";
12
+ export { BooleanQuestion, ChoiceQuestion, Decision, DecisionQuestion, DecisionQuestionRecord, Description, EndpointConfig, EvaluateConfig, EvaluateInput, EvaluateResult, FallbackReason, FallbackResult, NoulQuestion, ProbabilityMap, Question, QuestionList, ScoreQuestion, State, Usage, } from "./schemas.js";
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export { DEFAULT_TIMEOUT_MS } from "./config.js";
2
2
  export { evaluate } from "./evaluate.js";
3
+ export { choice, noul, score, box, systemOne } from "./decisions.js";
4
+ export { Image, image, withAssets } from "./assets.js";
3
5
  export { createSystemOneAdapter, createSystemOneAdapterFactory, SYSTEM_ONE_PROVIDER, } from "./adapter.js";
4
6
  export { SystemOneError } from "./errors.js";
5
7
  export { SystemOneTelemetryEvent } from "./telemetry.js";
6
- export { BooleanQuestion, ChoiceQuestion, Decision, Description, EndpointConfig, EvaluateConfig, EvaluateInput, EvaluateResult, FallbackReason, FallbackResult, NoulQuestion, ProbabilityMap, Question, QuestionList, ScoreQuestion, State, Usage, } from "./schemas.js";
8
+ export { BooleanQuestion, ChoiceQuestion, Decision, DecisionQuestion, DecisionQuestionRecord, Description, EndpointConfig, EvaluateConfig, EvaluateInput, EvaluateResult, FallbackReason, FallbackResult, NoulQuestion, ProbabilityMap, Question, QuestionList, ScoreQuestion, State, Usage, } from "./schemas.js";
package/dist/schemas.d.ts CHANGED
@@ -66,6 +66,50 @@ export declare const BooleanQuestion: import("arktype/internal/variants/object.t
66
66
  };
67
67
  }, {}>;
68
68
  export type BooleanQuestion = typeof BooleanQuestion.infer;
69
+ export declare const BoxQuestion: import("arktype/internal/variants/object.ts").ObjectType<{
70
+ id: string;
71
+ type: "box";
72
+ instructions: string | {
73
+ [x: string]: unknown;
74
+ } | unknown[];
75
+ }, {}>;
76
+ export type BoxQuestion = typeof BoxQuestion.infer;
77
+ export declare const DecisionQuestion: import("arktype/internal/variants/object.ts").ObjectType<{
78
+ type: "choice";
79
+ instructions: unknown;
80
+ criteria: unknown;
81
+ } | {
82
+ type: "noul";
83
+ instructions?: unknown;
84
+ criteria?: unknown;
85
+ } | {
86
+ type: "score";
87
+ instructions: unknown;
88
+ criteria: unknown;
89
+ } | {
90
+ type: "box";
91
+ instructions: unknown;
92
+ }, {}>;
93
+ export type DecisionQuestion = typeof DecisionQuestion.infer;
94
+ export declare const DecisionQuestionRecord: import("arktype/internal/variants/object.ts").ObjectType<{
95
+ [x: string]: {
96
+ type: "choice";
97
+ instructions: unknown;
98
+ criteria: unknown;
99
+ } | {
100
+ type: "noul";
101
+ instructions?: unknown;
102
+ criteria?: unknown;
103
+ } | {
104
+ type: "score";
105
+ instructions: unknown;
106
+ criteria: unknown;
107
+ } | {
108
+ type: "box";
109
+ instructions: unknown;
110
+ };
111
+ }, {}>;
112
+ export type DecisionQuestionRecord = typeof DecisionQuestionRecord.infer;
69
113
  export declare const Question: import("arktype/internal/variants/object.ts").ObjectType<{
70
114
  id: string;
71
115
  type: "choice";
@@ -114,6 +158,12 @@ export declare const Question: import("arktype/internal/variants/object.ts").Obj
114
158
  [x: string]: unknown;
115
159
  } | unknown[];
116
160
  };
161
+ } | {
162
+ id: string;
163
+ type: "box";
164
+ instructions: string | {
165
+ [x: string]: unknown;
166
+ } | unknown[];
117
167
  }, {}>;
118
168
  export type Question = typeof Question.infer;
119
169
  export declare const QuestionList: import("arktype/internal/variants/array.ts").ArrayType<({
@@ -164,12 +214,19 @@ export declare const QuestionList: import("arktype/internal/variants/array.ts").
164
214
  [x: string]: unknown;
165
215
  } | unknown[];
166
216
  };
217
+ } | {
218
+ id: string;
219
+ type: "box";
220
+ instructions: string | {
221
+ [x: string]: unknown;
222
+ } | unknown[];
167
223
  })[], {}>;
168
224
  export type QuestionList = typeof QuestionList.infer;
169
225
  export declare const EndpointConfig: import("arktype/internal/variants/object.ts").ObjectType<{
170
226
  kind: "custom";
171
227
  url: string;
172
228
  model?: string;
229
+ auth?: "none";
173
230
  } | {
174
231
  kind: "official" | "gateway";
175
232
  model?: string;
@@ -180,6 +237,7 @@ export declare const EvaluateConfig: import("arktype/internal/variants/object.ts
180
237
  kind: "custom";
181
238
  url: string;
182
239
  model?: string;
240
+ auth?: "none";
183
241
  } | {
184
242
  kind: "official" | "gateway";
185
243
  model?: string;
@@ -240,12 +298,19 @@ export declare const EvaluateInput: import("arktype/internal/variants/object.ts"
240
298
  [x: string]: unknown;
241
299
  } | unknown[];
242
300
  };
301
+ } | {
302
+ id: string;
303
+ type: "box";
304
+ instructions: string | {
305
+ [x: string]: unknown;
306
+ } | unknown[];
243
307
  })[];
244
308
  config?: {
245
309
  endpoint?: {
246
310
  kind: "custom";
247
311
  url: string;
248
312
  model?: string;
313
+ auth?: "none";
249
314
  } | {
250
315
  kind: "official" | "gateway";
251
316
  model?: string;
@@ -257,14 +322,14 @@ export declare const EvaluateInput: import("arktype/internal/variants/object.ts"
257
322
  export type EvaluateInput = typeof EvaluateInput.infer;
258
323
  /** One question in wire form: no id (the map key carries it). */
259
324
  export declare const WireQuestion: import("arktype/internal/variants/object.ts").ObjectType<{
260
- type: "choice" | "score" | "noul";
325
+ type: "choice" | "score" | "noul" | "box";
261
326
  instructions: string | {
262
327
  [x: string]: unknown;
263
328
  } | unknown[];
264
329
  criteria?: unknown;
265
330
  }, {}>;
266
331
  export type WireQuestion = typeof WireQuestion.infer;
267
- /** The exact POST body: model + state + id-keyed questions. */
332
+ /** The exact POST body: model + state + id-keyed questions, plus image parts. */
268
333
  export declare const WireRequest: import("arktype/internal/variants/object.ts").ObjectType<{
269
334
  model: string;
270
335
  state: string | {
@@ -272,13 +337,19 @@ export declare const WireRequest: import("arktype/internal/variants/object.ts").
272
337
  } | unknown[];
273
338
  questions: {
274
339
  [x: string]: {
275
- type: "choice" | "score" | "noul";
340
+ type: "choice" | "score" | "noul" | "box";
276
341
  instructions: string | {
277
342
  [x: string]: unknown;
278
343
  } | unknown[];
279
344
  criteria?: unknown;
280
345
  };
281
346
  };
347
+ assets?: {
348
+ [x: string]: {
349
+ type: "image";
350
+ part: string;
351
+ };
352
+ };
282
353
  }, {}>;
283
354
  export type WireRequest = typeof WireRequest.infer;
284
355
  /**
@@ -295,19 +366,32 @@ export type ProbabilityMap = typeof ProbabilityMap.infer;
295
366
  /** Tolerance for probability-sum checks (backend rounds to 2 decimals). */
296
367
  export declare const APPROXIMATE_SUM_TOLERANCE = 0.02;
297
368
  export declare const WireAnswer: import("arktype/internal/variants/object.ts").ObjectType<{
298
- type: "choice" | "score" | "noul";
369
+ type: "choice" | "score" | "noul" | "box";
299
370
  choice?: string;
300
371
  score?: number;
301
372
  noul?: number;
302
373
  probabilities?: {
303
374
  [x: string]: number;
304
375
  };
305
- confidence?: number;
376
+ confidence?: number | null;
306
377
  legend?: {
307
378
  [x: string]: string;
308
379
  };
380
+ box?: {
381
+ top: number;
382
+ bottom: number;
383
+ left: number;
384
+ right: number;
385
+ };
309
386
  }, {}>;
310
387
  export type WireAnswer = typeof WireAnswer.infer;
388
+ /** Box edges are fractions of the image. Shared by `evaluate` and the adapter. */
389
+ export declare function boxEdgesOutOfRange(id: string, box: {
390
+ top: number;
391
+ bottom: number;
392
+ left: number;
393
+ right: number;
394
+ }): string | undefined;
311
395
  export declare const WireUsage: import("arktype/internal/variants/object.ts").ObjectType<{
312
396
  input_tokens: number;
313
397
  output_tokens: number;
@@ -316,17 +400,23 @@ export type WireUsage = typeof WireUsage.infer;
316
400
  export declare const WireResponseBody: import("arktype/internal/variants/object.ts").ObjectType<{
317
401
  answers: {
318
402
  [x: string]: {
319
- type: "choice" | "score" | "noul";
403
+ type: "choice" | "score" | "noul" | "box";
320
404
  choice?: string;
321
405
  score?: number;
322
406
  noul?: number;
323
407
  probabilities?: {
324
408
  [x: string]: number;
325
409
  };
326
- confidence?: number;
410
+ confidence?: number | null;
327
411
  legend?: {
328
412
  [x: string]: string;
329
413
  };
414
+ box?: {
415
+ top: number;
416
+ bottom: number;
417
+ left: number;
418
+ right: number;
419
+ };
330
420
  };
331
421
  };
332
422
  model?: string;
@@ -359,6 +449,16 @@ export declare const Decision: import("arktype/internal/variants/object.ts").Obj
359
449
  [x: string]: number;
360
450
  };
361
451
  confidence: number;
452
+ } | {
453
+ id: string;
454
+ type: "box";
455
+ box: {
456
+ top: number;
457
+ bottom: number;
458
+ left: number;
459
+ right: number;
460
+ };
461
+ confidence: number | null;
362
462
  }, {}>;
363
463
  export type Decision = typeof Decision.infer;
364
464
  /**
@@ -397,6 +497,16 @@ export declare const EvaluateResult: import("arktype/internal/variants/object.ts
397
497
  [x: string]: number;
398
498
  };
399
499
  confidence: number;
500
+ } | {
501
+ id: string;
502
+ type: "box";
503
+ box: {
504
+ top: number;
505
+ bottom: number;
506
+ left: number;
507
+ right: number;
508
+ };
509
+ confidence: number | null;
400
510
  })[];
401
511
  modelId: string;
402
512
  backend: "custom" | "gateway" | "corbits-system-one";