@secondlayer/shared 11.20.0 → 11.21.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.
Files changed (33) hide show
  1. package/dist/src/archive/range-digest.d.ts +39 -0
  2. package/dist/src/archive/semantic-digest-builder.d.ts +39 -0
  3. package/dist/src/classify/index.d.ts +83 -0
  4. package/dist/src/classify/index.js +464 -0
  5. package/dist/src/classify/index.js.map +18 -0
  6. package/dist/src/coverage/index.d.ts +39 -0
  7. package/dist/src/db/index.d.ts +46 -1
  8. package/dist/src/db/index.js +3 -1
  9. package/dist/src/db/index.js.map +3 -3
  10. package/dist/src/db/queries/chain-reorgs.d.ts +39 -0
  11. package/dist/src/db/queries/chain-reorgs.js +3 -1
  12. package/dist/src/db/queries/chain-reorgs.js.map +3 -3
  13. package/dist/src/db/queries/contracts.d.ts +39 -0
  14. package/dist/src/db/queries/instance.d.ts +39 -0
  15. package/dist/src/db/queries/integrity.d.ts +39 -0
  16. package/dist/src/db/queries/subgraph-gaps.d.ts +39 -0
  17. package/dist/src/db/queries/subgraph-operations.d.ts +39 -0
  18. package/dist/src/db/queries/subgraphs.d.ts +39 -0
  19. package/dist/src/db/queries/sync-scope.d.ts +39 -0
  20. package/dist/src/db/queries/webhooks.d.ts +39 -0
  21. package/dist/src/db/schema.d.ts +44 -1
  22. package/dist/src/error-envelope.d.ts +48 -0
  23. package/dist/src/error-envelope.js +76 -0
  24. package/dist/src/error-envelope.js.map +10 -0
  25. package/dist/src/index.d.ts +46 -1
  26. package/dist/src/index.js +3 -1
  27. package/dist/src/index.js.map +3 -3
  28. package/dist/src/node/local-client.d.ts +39 -0
  29. package/dist/src/queue/listener.js +3 -1
  30. package/dist/src/queue/listener.js.map +3 -3
  31. package/migrations/0155_api_failed_requests.ts +37 -0
  32. package/migrations/0156_feedback_tickets.ts +47 -0
  33. package/package.json +17 -1
@@ -847,6 +847,8 @@ interface Database {
847
847
  sentinel_accounts: SentinelAccountsTable;
848
848
  waitlists: WaitlistsTable;
849
849
  waitlist_signups: WaitlistSignupsTable;
850
+ api_failed_requests: ApiFailedRequestsTable;
851
+ feedback_tickets: FeedbackTicketsTable;
850
852
  }
851
853
  interface ServiceHeartbeatsTable {
852
854
  name: string;
@@ -1055,6 +1057,43 @@ interface WaitlistSignupsTable {
1055
1057
  created_at: Generated<Date>;
1056
1058
  }
1057
1059
  /**
1060
+ * 24h hosted record of a failed API request, keyed by `request_id` and
1061
+ * scoped to the account that made it. `query` is redacted before insert.
1062
+ */
1063
+ interface ApiFailedRequestsTable {
1064
+ request_id: string;
1065
+ account_id: string;
1066
+ method: string;
1067
+ path: string;
1068
+ status: number;
1069
+ code: string;
1070
+ message: Generated<string>;
1071
+ query: Record<string, unknown>;
1072
+ origin: string | null;
1073
+ created_at: Generated<Date>;
1074
+ }
1075
+ /**
1076
+ * Hosted problem report. `attempted` is the server's own copy of the failed
1077
+ * request (never caller-supplied); `status` starts `new` until classified.
1078
+ */
1079
+ interface FeedbackTicketsTable {
1080
+ id: Generated<string>;
1081
+ account_id: string;
1082
+ idempotency_key: string | null;
1083
+ request_id: string | null;
1084
+ intent: string;
1085
+ expected: Record<string, unknown> | null;
1086
+ kind_hint: string | null;
1087
+ evidence: Record<string, unknown> | null;
1088
+ attempted: Record<string, unknown> | null;
1089
+ origin: string;
1090
+ status: Generated<"new" | "classified">;
1091
+ route: string | null;
1092
+ classification: Record<string, unknown> | null;
1093
+ classified_at: Date | null;
1094
+ created_at: Generated<Date>;
1095
+ }
1096
+ /**
1058
1097
  * Cheap, SQL-computed digests over a height range — the basis of a verify that
1059
1098
  * runs in seconds instead of hours.
1060
1099
  *
@@ -847,6 +847,8 @@ interface Database {
847
847
  sentinel_accounts: SentinelAccountsTable;
848
848
  waitlists: WaitlistsTable;
849
849
  waitlist_signups: WaitlistSignupsTable;
850
+ api_failed_requests: ApiFailedRequestsTable;
851
+ feedback_tickets: FeedbackTicketsTable;
850
852
  }
851
853
  interface ServiceHeartbeatsTable {
852
854
  name: string;
@@ -1055,6 +1057,43 @@ interface WaitlistSignupsTable {
1055
1057
  created_at: Generated<Date>;
1056
1058
  }
1057
1059
  /**
1060
+ * 24h hosted record of a failed API request, keyed by `request_id` and
1061
+ * scoped to the account that made it. `query` is redacted before insert.
1062
+ */
1063
+ interface ApiFailedRequestsTable {
1064
+ request_id: string;
1065
+ account_id: string;
1066
+ method: string;
1067
+ path: string;
1068
+ status: number;
1069
+ code: string;
1070
+ message: Generated<string>;
1071
+ query: Record<string, unknown>;
1072
+ origin: string | null;
1073
+ created_at: Generated<Date>;
1074
+ }
1075
+ /**
1076
+ * Hosted problem report. `attempted` is the server's own copy of the failed
1077
+ * request (never caller-supplied); `status` starts `new` until classified.
1078
+ */
1079
+ interface FeedbackTicketsTable {
1080
+ id: Generated<string>;
1081
+ account_id: string;
1082
+ idempotency_key: string | null;
1083
+ request_id: string | null;
1084
+ intent: string;
1085
+ expected: Record<string, unknown> | null;
1086
+ kind_hint: string | null;
1087
+ evidence: Record<string, unknown> | null;
1088
+ attempted: Record<string, unknown> | null;
1089
+ origin: string;
1090
+ status: Generated<"new" | "classified">;
1091
+ route: string | null;
1092
+ classification: Record<string, unknown> | null;
1093
+ classified_at: Date | null;
1094
+ created_at: Generated<Date>;
1095
+ }
1096
+ /**
1058
1097
  * v1 canonical byte encoding for chain identity — the shape that lets
1059
1098
  * independently-run runtimes agree on the same block.
1060
1099
  *
@@ -0,0 +1,83 @@
1
+ type ClassifierName = "jev" | "kev" | "clef" | "rules";
2
+ type Question = {
3
+ type: "choice"
4
+ instructions: string
5
+ criteria: Record<string, string>
6
+ } | {
7
+ type: "score"
8
+ instructions: string
9
+ criteria: readonly string[]
10
+ } | {
11
+ type: "boolean"
12
+ instructions: string
13
+ criteria: {
14
+ true: string
15
+ false: string
16
+ }
17
+ };
18
+ type Answer = {
19
+ type: "choice"
20
+ choice: string
21
+ probabilities: Record<string, number>
22
+ confidence: number
23
+ } | {
24
+ type: "score"
25
+ score: number
26
+ probabilities: Record<string, number>
27
+ confidence: number
28
+ } | {
29
+ type: "boolean"
30
+ probability: number
31
+ confidence: number
32
+ } | {
33
+ type: "refusal"
34
+ };
35
+ type Answers<Q extends Record<string, Question>> = { [K in keyof Q] : Answer };
36
+ type ClassifyResult<Q extends Record<string, Question>> = {
37
+ provider: ClassifierName
38
+ /** response.modelId when the backend reports one. */
39
+ modelId: string
40
+ answers: Answers<Q>
41
+ };
42
+ /** Deterministic answers for the offline provider. Return a value per question
43
+ * it can decide; omitted questions come back as `{ type: "refusal" }`. */
44
+ type RuleValue = string | number | boolean;
45
+ type RulesFn<Q extends Record<string, Question>> = (state: string | Record<string, unknown>) => { [K in keyof Q]? : RuleValue } | null;
46
+ declare class ClassifierInputError extends Error {
47
+ constructor(message: string);
48
+ }
49
+ /**
50
+ * One classifier call over jev, kev, clef or offline rules.
51
+ * Returns null on any provider failure (fail-open: callers decide what null
52
+ * means). Only ClassifierInputError, a bad question definition, escapes.
53
+ * Never logs state or response bodies.
54
+ */
55
+ declare function classify<const Q extends Record<string, Question>>(input: {
56
+ state: string | Record<string, unknown>
57
+ questions: Q
58
+ rules?: RulesFn<Q>
59
+ /** Overrides resolveProvider(); used by classifier-compare. */
60
+ provider?: ClassifierName
61
+ /** Overrides CLASSIFIER_MODEL / the provider default. */
62
+ modelId?: string
63
+ /** Default 5000. */
64
+ timeoutMs?: number
65
+ env?: Record<string, string | undefined>
66
+ /** Tests only (kev/clef). */
67
+ fetchImpl?: typeof fetch
68
+ }): Promise<ClassifyResult<Q> | null>;
69
+ type Env = Record<string, string | undefined>;
70
+ /** CLASSIFIER picks the backend; unset means jev when the gateway key is set,
71
+ * else offline rules. A backend missing credentials falls back to rules. */
72
+ declare function resolveProvider(env?: Env): {
73
+ name: ClassifierName
74
+ modelId: string
75
+ };
76
+ /** (p_max - 1/K) / (1 - 1/K). One option gives 1. */
77
+ declare function choiceConfidence(probabilities: Record<string, number>): number;
78
+ /** max(0, 1 - E|level - mode| / D); D = mean distance of a uniform
79
+ * distribution over the levels from the middle level. */
80
+ declare function scoreConfidence(probabilities: Record<string, number>, levels: number): number;
81
+ /** Host-defined: |2p - 1|. Not comparable with choice/score confidence. */
82
+ declare function booleanConfidence(p: number): number;
83
+ export { Answer, Answers, ClassifierInputError, ClassifierName, ClassifyResult, Question, RuleValue, RulesFn, booleanConfidence, choiceConfidence, classify, resolveProvider, scoreConfidence };
@@ -0,0 +1,464 @@
1
+ // src/env.ts
2
+ import { z } from "zod";
3
+ var networksSchema = z.string().transform((val) => {
4
+ const networks = val.split(",").map((n) => n.trim()).filter(Boolean);
5
+ const valid = ["mainnet", "testnet"];
6
+ for (const n of networks) {
7
+ if (!valid.includes(n)) {
8
+ throw new Error(`Invalid network: ${n}. Must be one of: ${valid.join(", ")}`);
9
+ }
10
+ }
11
+ return networks;
12
+ });
13
+ var envSchema = z.object({
14
+ DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
15
+ SOURCE_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
16
+ TARGET_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
17
+ NETWORK: z.enum(["mainnet", "testnet"]).optional(),
18
+ NETWORKS: networksSchema.optional(),
19
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
20
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development")
21
+ });
22
+ var cachedEnv = null;
23
+ function getEnv() {
24
+ if (cachedEnv) {
25
+ return cachedEnv;
26
+ }
27
+ const result = envSchema.safeParse(process.env);
28
+ if (!result.success) {
29
+ console.error("❌ Invalid environment configuration:");
30
+ console.error(z.treeifyError(result.error));
31
+ throw new Error("Invalid environment configuration");
32
+ }
33
+ let enabledNetworks;
34
+ if (result.data.NETWORKS && result.data.NETWORKS.length > 0) {
35
+ enabledNetworks = result.data.NETWORKS;
36
+ } else if (result.data.NETWORK) {
37
+ enabledNetworks = [result.data.NETWORK];
38
+ } else {
39
+ enabledNetworks = ["mainnet"];
40
+ }
41
+ cachedEnv = { ...result.data, enabledNetworks };
42
+ return cachedEnv;
43
+ }
44
+ function isProductionEnv() {
45
+ return process.env["NODE_ENV"] === "production";
46
+ }
47
+ function isPox4DecoderEnabled(env = process.env) {
48
+ return env.POX4_DECODER_ENABLED !== "false";
49
+ }
50
+ function isPox5DecoderEnabled(env = process.env) {
51
+ return env.POX5_DECODER_ENABLED !== "false";
52
+ }
53
+ function isSbtcDecoderEnabled(env = process.env) {
54
+ return env.SBTC_DECODER_ENABLED !== "false";
55
+ }
56
+ function isBnsDecoderEnabled(env = process.env) {
57
+ return env.BNS_DECODER_ENABLED === "true";
58
+ }
59
+ // src/logger.ts
60
+ var LOG_LEVELS = {
61
+ debug: 0,
62
+ info: 1,
63
+ warn: 2,
64
+ error: 3
65
+ };
66
+
67
+ class Logger {
68
+ _level;
69
+ _isProduction;
70
+ _initialized = false;
71
+ init() {
72
+ if (this._initialized)
73
+ return;
74
+ this._initialized = true;
75
+ try {
76
+ const env = getEnv();
77
+ this._level = env.LOG_LEVEL;
78
+ this._isProduction = env.NODE_ENV === "production";
79
+ } catch {
80
+ this._level = "info";
81
+ this._isProduction = false;
82
+ }
83
+ }
84
+ get level() {
85
+ this.init();
86
+ return this._level;
87
+ }
88
+ get isProduction() {
89
+ this.init();
90
+ return this._isProduction;
91
+ }
92
+ shouldLog(level) {
93
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
94
+ }
95
+ formatMessage(level, message, meta) {
96
+ const timestamp = new Date().toISOString();
97
+ if (this.isProduction) {
98
+ return JSON.stringify({
99
+ timestamp,
100
+ level,
101
+ message,
102
+ ...meta
103
+ });
104
+ }
105
+ const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
106
+ return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
107
+ }
108
+ debug(message, meta) {
109
+ if (this.shouldLog("debug")) {
110
+ console.debug(this.formatMessage("debug", message, meta));
111
+ }
112
+ }
113
+ info(message, meta) {
114
+ if (this.shouldLog("info")) {
115
+ console.info(this.formatMessage("info", message, meta));
116
+ }
117
+ }
118
+ warn(message, meta) {
119
+ if (this.shouldLog("warn")) {
120
+ console.warn(this.formatMessage("warn", message, meta));
121
+ }
122
+ }
123
+ error(message, meta) {
124
+ if (this.shouldLog("error")) {
125
+ console.error(this.formatMessage("error", message, meta));
126
+ }
127
+ }
128
+ }
129
+ var logger = new Logger;
130
+
131
+ // src/classify/confidence.ts
132
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
133
+ function choiceConfidence(probabilities) {
134
+ const ps = Object.values(probabilities);
135
+ const k = ps.length;
136
+ if (k <= 1)
137
+ return 1;
138
+ const pmax = Math.max(...ps);
139
+ return clamp01((pmax - 1 / k) / (1 - 1 / k));
140
+ }
141
+ function scoreConfidence(probabilities, levels) {
142
+ if (levels <= 1)
143
+ return 1;
144
+ let mode = 0;
145
+ let best = -1;
146
+ for (let i = 0;i < levels; i++) {
147
+ const p = probabilities[String(i)] ?? 0;
148
+ if (p > best) {
149
+ best = p;
150
+ mode = i;
151
+ }
152
+ }
153
+ let e = 0;
154
+ let d = 0;
155
+ for (let i = 0;i < levels; i++) {
156
+ e += (probabilities[String(i)] ?? 0) * Math.abs(i - mode);
157
+ d += Math.abs(i - (levels - 1) / 2);
158
+ }
159
+ d /= levels;
160
+ return clamp01(1 - e / d);
161
+ }
162
+ function booleanConfidence(p) {
163
+ return Math.abs(2 * p - 1);
164
+ }
165
+ function normalizeAnswer(question, raw) {
166
+ if (raw.type === "refusal")
167
+ return { type: "refusal" };
168
+ if (raw.type === "boolean" && question.type === "boolean") {
169
+ return {
170
+ type: "boolean",
171
+ probability: raw.probability,
172
+ confidence: booleanConfidence(raw.probability)
173
+ };
174
+ }
175
+ if (raw.type === "choice" && question.type === "choice") {
176
+ const probabilities = raw.probabilities ?? {};
177
+ return {
178
+ type: "choice",
179
+ choice: raw.choice,
180
+ probabilities,
181
+ confidence: raw.probabilities ? choiceConfidence(probabilities) : 0
182
+ };
183
+ }
184
+ if (raw.type === "score" && question.type === "score") {
185
+ const probabilities = raw.probabilities ?? {};
186
+ return {
187
+ type: "score",
188
+ score: raw.score,
189
+ probabilities,
190
+ confidence: raw.probabilities ? scoreConfidence(probabilities, question.criteria.length) : 0
191
+ };
192
+ }
193
+ return { type: "refusal" };
194
+ }
195
+
196
+ // src/classify/types.ts
197
+ class ClassifierInputError extends Error {
198
+ constructor(message) {
199
+ super(message);
200
+ this.name = "ClassifierInputError";
201
+ }
202
+ }
203
+
204
+ // src/classify/limits.ts
205
+ var MAX_QUESTIONS = 64;
206
+ var MIN_CHOICE = 2;
207
+ var MAX_CHOICE = 255;
208
+ var MIN_SCORE = 2;
209
+ var MAX_SCORE = 10;
210
+ var QUESTION_ID = /^[A-Za-z0-9_.-]{1,100}$/;
211
+ var MAX_STATE_CHARS = 24000;
212
+ function validateQuestions(questions) {
213
+ const ids = Object.keys(questions);
214
+ if (ids.length === 0)
215
+ throw new ClassifierInputError("no questions");
216
+ if (ids.length > MAX_QUESTIONS) {
217
+ throw new ClassifierInputError(`more than ${MAX_QUESTIONS} questions`);
218
+ }
219
+ for (const id of ids) {
220
+ if (!QUESTION_ID.test(id)) {
221
+ throw new ClassifierInputError(`invalid question id: ${id}`);
222
+ }
223
+ const q = questions[id];
224
+ if (q.type === "choice") {
225
+ const n = Object.keys(q.criteria).length;
226
+ if (n < MIN_CHOICE || n > MAX_CHOICE) {
227
+ throw new ClassifierInputError(`question ${id}: choice needs ${MIN_CHOICE}-${MAX_CHOICE} options`);
228
+ }
229
+ } else if (q.type === "score") {
230
+ const n = q.criteria.length;
231
+ if (n < MIN_SCORE || n > MAX_SCORE) {
232
+ throw new ClassifierInputError(`question ${id}: score needs ${MIN_SCORE}-${MAX_SCORE} levels`);
233
+ }
234
+ } else if (q.type !== "boolean") {
235
+ throw new ClassifierInputError(`question ${id}: unknown type`);
236
+ }
237
+ }
238
+ }
239
+ function stateSize(state) {
240
+ return typeof state === "string" ? state.length : JSON.stringify(state).length;
241
+ }
242
+
243
+ // src/classify/system-one.ts
244
+ function toWireState(state) {
245
+ const parts = state.map((p) => {
246
+ if (p.type === "text")
247
+ return p.text;
248
+ if (p.type === "json")
249
+ return p.value;
250
+ throw new Error("file state unsupported");
251
+ });
252
+ return parts.length === 1 ? parts[0] : parts;
253
+ }
254
+ function renormalize(p) {
255
+ const sum = Object.values(p).reduce((a, b) => a + b, 0);
256
+ if (!(sum > 0))
257
+ return p;
258
+ return Object.fromEntries(Object.entries(p).map(([k, v]) => [k, v / sum]));
259
+ }
260
+ function mapAnswer(a) {
261
+ if (a.type === "noul" && typeof a.noul === "number") {
262
+ return { type: "boolean", probability: a.noul };
263
+ }
264
+ if (a.type === "choice" && typeof a.choice === "string") {
265
+ return {
266
+ type: "choice",
267
+ choice: a.choice,
268
+ ...a.probabilities && { probabilities: renormalize(a.probabilities) }
269
+ };
270
+ }
271
+ if (a.type === "score" && typeof a.score === "number") {
272
+ if (!a.probabilities)
273
+ return { type: "score", score: a.score };
274
+ const probabilities = renormalize(a.probabilities);
275
+ const score = Object.entries(probabilities).reduce((s, [level, p]) => s + Number(level) * p, 0);
276
+ return { type: "score", score, probabilities };
277
+ }
278
+ return { type: "refusal" };
279
+ }
280
+ function systemOneModel(opts) {
281
+ const doFetch = opts.fetchImpl ?? fetch;
282
+ return {
283
+ specificationVersion: "v4",
284
+ provider: `secondlayer.${opts.provider}`,
285
+ modelId: opts.modelId,
286
+ supportedQuestionTypes: ["choice", "score", "boolean"],
287
+ async doDecide({ state, questions, abortSignal }) {
288
+ const wireQuestions = Object.fromEntries(Object.entries(questions).map(([id, q]) => [
289
+ id,
290
+ q.type === "boolean" ? { ...q, type: "noul" } : q
291
+ ]));
292
+ const res = await doFetch(opts.url, {
293
+ method: "POST",
294
+ headers: {
295
+ "content-type": "application/json",
296
+ ...opts.apiKey && { authorization: `Bearer ${opts.apiKey}` }
297
+ },
298
+ body: JSON.stringify({
299
+ model: opts.modelId,
300
+ state: toWireState(state),
301
+ questions: wireQuestions
302
+ }),
303
+ signal: abortSignal
304
+ });
305
+ if (!res.ok)
306
+ throw new Error(`${opts.provider} ${res.status}`);
307
+ const json = await res.json();
308
+ const body = json.result ?? json;
309
+ const answers = Object.fromEntries(Object.entries(body.answers ?? {}).map(([id, a]) => [id, mapAnswer(a)]));
310
+ return {
311
+ answers,
312
+ warnings: [],
313
+ usage: {
314
+ inputTokens: body.usage?.input_tokens,
315
+ outputTokens: body.usage?.output_tokens
316
+ },
317
+ response: { modelId: body.model ?? opts.modelId }
318
+ };
319
+ }
320
+ };
321
+ }
322
+
323
+ // src/classify/providers.ts
324
+ var DEFAULT_MODEL = {
325
+ jev: "typesafe-ai/jev",
326
+ kev: "kev-latest",
327
+ clef: "clef"
328
+ };
329
+ var REQUIRED_ENV = {
330
+ jev: ["AI_GATEWAY_API_KEY"],
331
+ kev: ["KEV_URL"],
332
+ clef: ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]
333
+ };
334
+ var envModel = (env) => env.CLASSIFIER_MODEL?.trim() || undefined;
335
+ function resolveProvider(env = process.env) {
336
+ const rules = { name: "rules", modelId: "rules" };
337
+ const requested = env.CLASSIFIER?.trim();
338
+ let name;
339
+ if (!requested) {
340
+ name = env.AI_GATEWAY_API_KEY ? "jev" : "rules";
341
+ } else if (requested === "jev" || requested === "kev" || requested === "clef" || requested === "rules") {
342
+ name = requested;
343
+ } else {
344
+ logger.warn(`classifier: unknown CLASSIFIER=${requested}; using rules`);
345
+ return rules;
346
+ }
347
+ if (name === "rules")
348
+ return rules;
349
+ const missing = REQUIRED_ENV[name].find((v) => !env[v]);
350
+ if (missing) {
351
+ logger.warn(`classifier: CLASSIFIER=${name} missing ${missing}; using rules`);
352
+ return rules;
353
+ }
354
+ return { name, modelId: envModel(env) ?? DEFAULT_MODEL[name] };
355
+ }
356
+ function buildModel(name, modelId, env = process.env, fetchImpl) {
357
+ if (name === "jev")
358
+ return modelId;
359
+ if (name === "kev") {
360
+ return systemOneModel({
361
+ provider: "kev",
362
+ url: `${(env.KEV_URL ?? "").replace(/\/$/, "")}/v1/systemone`,
363
+ modelId,
364
+ apiKey: env.KEV_API_KEY,
365
+ fetchImpl
366
+ });
367
+ }
368
+ return systemOneModel({
369
+ provider: "clef",
370
+ url: `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/cloudflare/${modelId}`,
371
+ modelId,
372
+ apiKey: env.CLOUDFLARE_API_TOKEN,
373
+ fetchImpl
374
+ });
375
+ }
376
+
377
+ // src/classify/rules.ts
378
+ function answerFor(question, value) {
379
+ if (value === undefined)
380
+ return { type: "refusal" };
381
+ if (question.type === "choice") {
382
+ if (typeof value !== "string" || !(value in question.criteria)) {
383
+ return { type: "refusal" };
384
+ }
385
+ const probabilities = Object.fromEntries(Object.keys(question.criteria).map((k) => [k, k === value ? 1 : 0]));
386
+ return { type: "choice", choice: value, probabilities, confidence: 1 };
387
+ }
388
+ if (question.type === "score") {
389
+ const n = question.criteria.length;
390
+ if (typeof value !== "number" || !Number.isInteger(value)) {
391
+ return { type: "refusal" };
392
+ }
393
+ if (value < 0 || value >= n)
394
+ return { type: "refusal" };
395
+ const probabilities = Object.fromEntries(Array.from({ length: n }, (_, i) => [String(i), i === value ? 1 : 0]));
396
+ return { type: "score", score: value, probabilities, confidence: 1 };
397
+ }
398
+ if (typeof value !== "boolean")
399
+ return { type: "refusal" };
400
+ return { type: "boolean", probability: value ? 1 : 0, confidence: 1 };
401
+ }
402
+ function runRules(questions, fn, state) {
403
+ const values = fn(state) ?? {};
404
+ const out = {};
405
+ for (const id of Object.keys(questions)) {
406
+ out[id] = answerFor(questions[id], values[id]);
407
+ }
408
+ return out;
409
+ }
410
+
411
+ // src/classify/classify.ts
412
+ async function classify(input) {
413
+ validateQuestions(input.questions);
414
+ const env = input.env ?? process.env;
415
+ const resolved = resolveProvider(env);
416
+ const name = input.provider ?? resolved.name;
417
+ if (name === "rules") {
418
+ if (!input.rules)
419
+ return null;
420
+ return {
421
+ provider: "rules",
422
+ modelId: "rules",
423
+ answers: runRules(input.questions, input.rules, input.state)
424
+ };
425
+ }
426
+ const modelId = input.modelId ?? (input.provider ? undefined : envModel(env)) ?? DEFAULT_MODEL[name];
427
+ if (stateSize(input.state) > MAX_STATE_CHARS) {
428
+ logger.warn(`classifier: state over ${MAX_STATE_CHARS} chars; skipped`);
429
+ return null;
430
+ }
431
+ try {
432
+ const { experimental_decide: decide } = await import("ai");
433
+ const r = await decide({
434
+ model: buildModel(name, modelId, env, input.fetchImpl),
435
+ state: input.state,
436
+ questions: input.questions,
437
+ maxRetries: 1,
438
+ abortSignal: AbortSignal.timeout(input.timeoutMs ?? 5000)
439
+ });
440
+ const answers = {};
441
+ for (const id of Object.keys(input.questions)) {
442
+ answers[id] = normalizeAnswer(input.questions[id], r.answers[id] ?? { type: "refusal" });
443
+ }
444
+ return {
445
+ provider: name,
446
+ modelId: r.response.modelId ?? modelId,
447
+ answers
448
+ };
449
+ } catch (err) {
450
+ logger.warn(`classifier: ${name} failed: ${err instanceof Error ? err.name : "error"}`);
451
+ return null;
452
+ }
453
+ }
454
+ export {
455
+ ClassifierInputError,
456
+ booleanConfidence,
457
+ choiceConfidence,
458
+ classify,
459
+ resolveProvider,
460
+ scoreConfidence
461
+ };
462
+
463
+ //# debugId=6AA8B48DE6A7F1DB64756E2164756E21
464
+ //# sourceMappingURL=index.js.map