@secondlayer/shared 11.20.0 → 11.22.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/src/archive/range-digest.d.ts +65 -0
- package/dist/src/archive/semantic-digest-builder.d.ts +65 -0
- package/dist/src/classify/index.d.ts +83 -0
- package/dist/src/classify/index.js +464 -0
- package/dist/src/classify/index.js.map +18 -0
- package/dist/src/coverage/index.d.ts +65 -0
- package/dist/src/db/index.d.ts +72 -1
- package/dist/src/db/index.js +3 -1
- package/dist/src/db/index.js.map +3 -3
- package/dist/src/db/queries/chain-reorgs.d.ts +65 -0
- package/dist/src/db/queries/chain-reorgs.js +3 -1
- package/dist/src/db/queries/chain-reorgs.js.map +3 -3
- package/dist/src/db/queries/contracts.d.ts +65 -0
- package/dist/src/db/queries/instance.d.ts +65 -0
- package/dist/src/db/queries/integrity.d.ts +65 -0
- package/dist/src/db/queries/subgraph-gaps.d.ts +65 -0
- package/dist/src/db/queries/subgraph-operations.d.ts +65 -0
- package/dist/src/db/queries/subgraphs.d.ts +69 -0
- package/dist/src/db/queries/subgraphs.js +10 -2
- package/dist/src/db/queries/subgraphs.js.map +4 -4
- package/dist/src/db/queries/sync-scope.d.ts +65 -0
- package/dist/src/db/queries/webhooks.d.ts +65 -0
- package/dist/src/db/schema.d.ts +70 -1
- package/dist/src/error-envelope.d.ts +48 -0
- package/dist/src/error-envelope.js +76 -0
- package/dist/src/error-envelope.js.map +10 -0
- package/dist/src/index.d.ts +288 -208
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +5 -5
- package/dist/src/node/local-client.d.ts +65 -0
- package/dist/src/queue/listener.js +3 -1
- package/dist/src/queue/listener.js.map +3 -3
- package/dist/src/schemas/index.d.ts +30 -1
- package/dist/src/schemas/index.js.map +2 -2
- package/dist/src/schemas/subgraphs.d.ts +30 -1
- package/dist/src/schemas/subgraphs.js.map +2 -2
- package/dist/src/subgraphs/spec.d.ts +27 -0
- package/dist/src/subgraphs/spec.js +4 -1
- package/dist/src/subgraphs/spec.js.map +3 -3
- package/migrations/0155_api_failed_requests.ts +37 -0
- package/migrations/0156_feedback_tickets.ts +47 -0
- package/migrations/0157_subgraph_pin_verification.ts +35 -0
- package/package.json +17 -1
|
@@ -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
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/env.ts", "../src/logger.ts", "../src/classify/confidence.ts", "../src/classify/types.ts", "../src/classify/limits.ts", "../src/classify/system-one.ts", "../src/classify/providers.ts", "../src/classify/rules.ts", "../src/classify/classify.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { z } from \"zod\";\n\n// Parse comma-separated networks\nconst networksSchema = z.string().transform((val) => {\n\tconst networks = val\n\t\t.split(\",\")\n\t\t.map((n) => n.trim())\n\t\t.filter(Boolean);\n\tconst valid = [\"mainnet\", \"testnet\"];\n\tfor (const n of networks) {\n\t\tif (!valid.includes(n)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid network: ${n}. Must be one of: ${valid.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn networks as (\"mainnet\" | \"testnet\")[];\n});\n\ninterface EnvSchemaOutput {\n\tDATABASE_URL?: string;\n\t/**\n\t * Shared indexer DB (blocks/txs/events). Falls back to DATABASE_URL.\n\t * Set this alongside TARGET_DATABASE_URL to enable dual-DB mode.\n\t */\n\tSOURCE_DATABASE_URL?: string;\n\t/**\n\t * Tenant DB (subgraph schemas + subgraphs table). Falls back to DATABASE_URL.\n\t * Set this alongside SOURCE_DATABASE_URL to enable dual-DB mode.\n\t */\n\tTARGET_DATABASE_URL?: string;\n\tNETWORK?: \"mainnet\" | \"testnet\";\n\tNETWORKS?: (\"mainnet\" | \"testnet\")[];\n\tLOG_LEVEL: \"debug\" | \"info\" | \"warn\" | \"error\";\n\tNODE_ENV: \"development\" | \"production\" | \"test\";\n}\n\n// Cast needed: z.preprocess / z.default create different _input vs _output types\n// that z.ZodType<T> can't represent without explicit input type param\nconst envSchema: z.ZodType<EnvSchemaOutput> = z.object({\n\tDATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tSOURCE_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tTARGET_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tNETWORK: z.enum([\"mainnet\", \"testnet\"]).optional(),\n\tNETWORKS: networksSchema.optional(),\n\tLOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n\tNODE_ENV: z\n\t\t.enum([\"development\", \"production\", \"test\"])\n\t\t.default(\"development\"),\n}) as unknown as z.ZodType<EnvSchemaOutput>;\n\nexport type Env = EnvSchemaOutput & {\n\tenabledNetworks: (\"mainnet\" | \"testnet\")[];\n};\n\nlet cachedEnv: Env | null = null;\n\nexport function getEnv(): Env {\n\tif (cachedEnv) {\n\t\treturn cachedEnv;\n\t}\n\n\tconst result = envSchema.safeParse(process.env);\n\n\tif (!result.success) {\n\t\tconsole.error(\"❌ Invalid environment configuration:\");\n\t\tconsole.error(z.treeifyError(result.error));\n\t\tthrow new Error(\"Invalid environment configuration\");\n\t}\n\n\t// Compute enabled networks from NETWORKS or NETWORK\n\tlet enabledNetworks: (\"mainnet\" | \"testnet\")[];\n\tif (result.data.NETWORKS && result.data.NETWORKS.length > 0) {\n\t\tenabledNetworks = result.data.NETWORKS;\n\t} else if (result.data.NETWORK) {\n\t\tenabledNetworks = [result.data.NETWORK];\n\t} else {\n\t\tenabledNetworks = [\"mainnet\"]; // Default\n\t}\n\n\tcachedEnv = { ...result.data, enabledNetworks };\n\treturn cachedEnv;\n}\n\n/**\n * True when `NODE_ENV=production`, read at RUNTIME — the single source of truth\n * for prod-vs-dev branching in this package.\n *\n * Why a helper instead of inlining `process.env.NODE_ENV === \"production\"`:\n * bunup/esbuild constant-folds the dot-access `process.env.NODE_ENV` to its\n * BUILD-time value. `@secondlayer/shared` is consumed by other services as its\n * built `dist`, so an inlined check freezes to a literal in the shipped bundle\n * (e.g. `const isProd = false`) and silently ignores the container's real\n * NODE_ENV. The bracket access below is NOT folded, so it stays a runtime read.\n * Route every prod check through here so the footgun lives in exactly one place.\n */\nexport function isProductionEnv(): boolean {\n\t// biome-ignore lint/complexity/useLiteralKeys: bracket access is deliberate — dot-access gets constant-folded by the bundler, freezing this to a build-time literal in the shipped dist.\n\treturn process.env[\"NODE_ENV\"] === \"production\";\n}\n\ntype EnvMap = NodeJS.ProcessEnv;\n\n/**\n * PoX-4 stacking decoder is ON by default — `/v1/index/stacking` is part of the\n * public surface, so the decoder that fills `pox4_calls` runs unless explicitly\n * opted out with `POX4_DECODER_ENABLED=false` (mirrors the sBTC decoder policy).\n */\nexport function isPox4DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX4_DECODER_ENABLED !== \"false\";\n}\n\n/**\n * PoX-5 print-event decoder is ON by default (mirrors pox-4/sBTC policy).\n * No activation gating needed: pox-5 events cannot exist before the Epoch 4.0\n * hard fork (Bitcoin block 960,230), so pre-activation the decoder idles at\n * tip. Opt out with `POX5_DECODER_ENABLED=false`.\n */\nexport function isPox5DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX5_DECODER_ENABLED !== \"false\";\n}\n\n/** Default ON. Opt out with `SBTC_DECODER_ENABLED=false`. */\nexport function isSbtcDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.SBTC_DECODER_ENABLED !== \"false\";\n}\n\n/** Default OFF. Opt in with `BNS_DECODER_ENABLED=true`. */\nexport function isBnsDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.BNS_DECODER_ENABLED === \"true\";\n}\n\n// Export for testing\nexport { envSchema };\n",
|
|
6
|
+
"import { getEnv } from \"./env.ts\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\nclass Logger {\n\tprivate _level?: LogLevel;\n\tprivate _isProduction?: boolean;\n\tprivate _initialized = false;\n\n\tprivate init() {\n\t\tif (this._initialized) return;\n\t\tthis._initialized = true;\n\t\ttry {\n\t\t\tconst env = getEnv();\n\t\t\tthis._level = env.LOG_LEVEL;\n\t\t\tthis._isProduction = env.NODE_ENV === \"production\";\n\t\t} catch {\n\t\t\t// Fallback when env is unavailable (e.g. tests without DATABASE_URL)\n\t\t\tthis._level = \"info\";\n\t\t\tthis._isProduction = false;\n\t\t}\n\t}\n\n\tprivate get level(): LogLevel {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._level!;\n\t}\n\n\tprivate get isProduction(): boolean {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._isProduction!;\n\t}\n\n\tprivate shouldLog(level: LogLevel): boolean {\n\t\treturn LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n\t}\n\n\tprivate formatMessage(\n\t\tlevel: LogLevel,\n\t\tmessage: string,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tmeta?: Record<string, any>,\n\t) {\n\t\tconst timestamp = new Date().toISOString();\n\n\t\tif (this.isProduction) {\n\t\t\t// JSON output for production\n\t\t\treturn JSON.stringify({\n\t\t\t\ttimestamp,\n\t\t\t\tlevel,\n\t\t\t\tmessage,\n\t\t\t\t...meta,\n\t\t\t});\n\t\t}\n\n\t\t// Human-readable output for development\n\t\tconst metaStr = meta ? ` ${JSON.stringify(meta)}` : \"\";\n\t\treturn `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tdebug(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"debug\")) {\n\t\t\tconsole.debug(this.formatMessage(\"debug\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tinfo(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"info\")) {\n\t\t\tconsole.info(this.formatMessage(\"info\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\twarn(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"warn\")) {\n\t\t\tconsole.warn(this.formatMessage(\"warn\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\terror(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"error\")) {\n\t\t\tconsole.error(this.formatMessage(\"error\", message, meta));\n\t\t}\n\t}\n}\n\n// Export singleton instance\nexport const logger: Logger = new Logger();\n",
|
|
7
|
+
"import type { Answer, Question } from \"./types.ts\";\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\n\n/** (p_max - 1/K) / (1 - 1/K). One option gives 1. */\nexport function choiceConfidence(\n\tprobabilities: Record<string, number>,\n): number {\n\tconst ps = Object.values(probabilities);\n\tconst k = ps.length;\n\tif (k <= 1) return 1;\n\tconst pmax = Math.max(...ps);\n\treturn clamp01((pmax - 1 / k) / (1 - 1 / k));\n}\n\n/** max(0, 1 - E|level - mode| / D); D = mean distance of a uniform\n * distribution over the levels from the middle level. */\nexport function scoreConfidence(\n\tprobabilities: Record<string, number>,\n\tlevels: number,\n): number {\n\tif (levels <= 1) return 1;\n\tlet mode = 0;\n\tlet best = -1;\n\tfor (let i = 0; i < levels; i++) {\n\t\tconst p = probabilities[String(i)] ?? 0;\n\t\tif (p > best) {\n\t\t\tbest = p;\n\t\t\tmode = i;\n\t\t}\n\t}\n\tlet e = 0;\n\tlet d = 0;\n\tfor (let i = 0; i < levels; i++) {\n\t\te += (probabilities[String(i)] ?? 0) * Math.abs(i - mode);\n\t\td += Math.abs(i - (levels - 1) / 2);\n\t}\n\td /= levels;\n\treturn clamp01(1 - e / d);\n}\n\n/** Host-defined: |2p - 1|. Not comparable with choice/score confidence. */\nexport function booleanConfidence(p: number): number {\n\treturn Math.abs(2 * p - 1);\n}\n\n/** Shape of an `ai` DecisionAnswer, or the rules output, before normalizing. */\nexport type RawAnswer =\n\t| { type: \"choice\"; choice: string; probabilities?: Record<string, number> }\n\t| { type: \"score\"; score: number; probabilities?: Record<string, number> }\n\t| { type: \"boolean\"; probability: number }\n\t| { type: \"refusal\" };\n\nexport function normalizeAnswer(question: Question, raw: RawAnswer): Answer {\n\tif (raw.type === \"refusal\") return { type: \"refusal\" };\n\tif (raw.type === \"boolean\" && question.type === \"boolean\") {\n\t\treturn {\n\t\t\ttype: \"boolean\",\n\t\t\tprobability: raw.probability,\n\t\t\tconfidence: booleanConfidence(raw.probability),\n\t\t};\n\t}\n\tif (raw.type === \"choice\" && question.type === \"choice\") {\n\t\tconst probabilities = raw.probabilities ?? {};\n\t\t// Never fabricate a distribution: no probabilities means confidence 0.\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tchoice: raw.choice,\n\t\t\tprobabilities,\n\t\t\tconfidence: raw.probabilities ? choiceConfidence(probabilities) : 0,\n\t\t};\n\t}\n\tif (raw.type === \"score\" && question.type === \"score\") {\n\t\tconst probabilities = raw.probabilities ?? {};\n\t\treturn {\n\t\t\ttype: \"score\",\n\t\t\tscore: raw.score,\n\t\t\tprobabilities,\n\t\t\tconfidence: raw.probabilities\n\t\t\t\t? scoreConfidence(probabilities, question.criteria.length)\n\t\t\t\t: 0,\n\t\t};\n\t}\n\treturn { type: \"refusal\" };\n}\n",
|
|
8
|
+
"export type ClassifierName = \"jev\" | \"kev\" | \"clef\" | \"rules\";\n\nexport type Question =\n\t| { type: \"choice\"; instructions: string; criteria: Record<string, string> }\n\t| { type: \"score\"; instructions: string; criteria: readonly string[] }\n\t| {\n\t\t\ttype: \"boolean\";\n\t\t\tinstructions: string;\n\t\t\tcriteria: { true: string; false: string };\n\t };\n\nexport type Answer =\n\t| {\n\t\t\ttype: \"choice\";\n\t\t\tchoice: string;\n\t\t\tprobabilities: Record<string, number>;\n\t\t\tconfidence: number;\n\t }\n\t| {\n\t\t\ttype: \"score\";\n\t\t\tscore: number;\n\t\t\tprobabilities: Record<string, number>;\n\t\t\tconfidence: number;\n\t }\n\t| { type: \"boolean\"; probability: number; confidence: number }\n\t| { type: \"refusal\" };\n\nexport type Answers<Q extends Record<string, Question>> = {\n\t[K in keyof Q]: Answer;\n};\n\nexport type ClassifyResult<Q extends Record<string, Question>> = {\n\tprovider: ClassifierName;\n\t/** response.modelId when the backend reports one. */\n\tmodelId: string;\n\tanswers: Answers<Q>;\n};\n\n/** Deterministic answers for the offline provider. Return a value per question\n * it can decide; omitted questions come back as `{ type: \"refusal\" }`. */\nexport type RuleValue =\n\t| string /* choice option */\n\t| number /* score level index */\n\t| boolean;\nexport type RulesFn<Q extends Record<string, Question>> = (\n\tstate: string | Record<string, unknown>,\n) => { [K in keyof Q]?: RuleValue } | null;\n\nexport class ClassifierInputError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ClassifierInputError\";\n\t}\n}\n",
|
|
9
|
+
"import { ClassifierInputError, type Question } from \"./types.ts\";\n\n// Strictest common limits across jev, kev and clef.\nexport const MAX_QUESTIONS = 64;\nexport const MIN_CHOICE = 2;\nexport const MAX_CHOICE = 255;\nexport const MIN_SCORE = 2;\nexport const MAX_SCORE = 10;\nexport const QUESTION_ID = /^[A-Za-z0-9_.-]{1,100}$/;\nexport const MAX_STATE_CHARS = 24_000;\n\n/** A bad question definition is a programmer bug: throw, never fail open. */\nexport function validateQuestions(questions: Record<string, Question>): void {\n\tconst ids = Object.keys(questions);\n\tif (ids.length === 0) throw new ClassifierInputError(\"no questions\");\n\tif (ids.length > MAX_QUESTIONS) {\n\t\tthrow new ClassifierInputError(`more than ${MAX_QUESTIONS} questions`);\n\t}\n\tfor (const id of ids) {\n\t\tif (!QUESTION_ID.test(id)) {\n\t\t\tthrow new ClassifierInputError(`invalid question id: ${id}`);\n\t\t}\n\t\tconst q = questions[id] as Question;\n\t\tif (q.type === \"choice\") {\n\t\t\tconst n = Object.keys(q.criteria).length;\n\t\t\tif (n < MIN_CHOICE || n > MAX_CHOICE) {\n\t\t\t\tthrow new ClassifierInputError(\n\t\t\t\t\t`question ${id}: choice needs ${MIN_CHOICE}-${MAX_CHOICE} options`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (q.type === \"score\") {\n\t\t\tconst n = q.criteria.length;\n\t\t\tif (n < MIN_SCORE || n > MAX_SCORE) {\n\t\t\t\tthrow new ClassifierInputError(\n\t\t\t\t\t`question ${id}: score needs ${MIN_SCORE}-${MAX_SCORE} levels`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (q.type !== \"boolean\") {\n\t\t\tthrow new ClassifierInputError(`question ${id}: unknown type`);\n\t\t}\n\t}\n}\n\nexport function stateSize(state: string | Record<string, unknown>): number {\n\treturn typeof state === \"string\"\n\t\t? state.length\n\t\t: JSON.stringify(state).length;\n}\n",
|
|
10
|
+
"import type { Experimental_DecisionModel } from \"ai\"; // type-only: erased at runtime\n\ntype DecisionModelV4 = Extract<\n\tExperimental_DecisionModel,\n\t{ doDecide: unknown }\n>;\ntype DoDecideOptions = Parameters<DecisionModelV4[\"doDecide\"]>[0];\ntype DoDecideResult = Awaited<ReturnType<DecisionModelV4[\"doDecide\"]>>;\n\ntype WireAnswer = {\n\ttype: string;\n\tchoice?: string;\n\tscore?: number;\n\tnoul?: number;\n\tprobabilities?: Record<string, number>;\n};\n\ntype WireBody = {\n\tmodel?: string;\n\tanswers?: Record<string, WireAnswer>;\n\tusage?: { input_tokens?: number; output_tokens?: number };\n};\n\nfunction toWireState(state: DoDecideOptions[\"state\"]): unknown {\n\tconst parts = state.map((p) => {\n\t\tif (p.type === \"text\") return p.text;\n\t\tif (p.type === \"json\") return p.value;\n\t\tthrow new Error(\"file state unsupported\");\n\t});\n\treturn parts.length === 1 ? parts[0] : parts;\n}\n\n/** Divide by the sum so the distribution sums to exactly 1. */\nfunction renormalize(p: Record<string, number>): Record<string, number> {\n\tconst sum = Object.values(p).reduce((a, b) => a + b, 0);\n\tif (!(sum > 0)) return p;\n\treturn Object.fromEntries(Object.entries(p).map(([k, v]) => [k, v / sum]));\n}\n\nfunction mapAnswer(a: WireAnswer): DoDecideResult[\"answers\"][string] {\n\tif (a.type === \"noul\" && typeof a.noul === \"number\") {\n\t\treturn { type: \"boolean\", probability: a.noul };\n\t}\n\tif (a.type === \"choice\" && typeof a.choice === \"string\") {\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tchoice: a.choice,\n\t\t\t...(a.probabilities && { probabilities: renormalize(a.probabilities) }),\n\t\t};\n\t}\n\tif (a.type === \"score\" && typeof a.score === \"number\") {\n\t\tif (!a.probabilities) return { type: \"score\", score: a.score };\n\t\tconst probabilities = renormalize(a.probabilities);\n\t\t// decide() checks score equals the weighted mean of the distribution.\n\t\tconst score = Object.entries(probabilities).reduce(\n\t\t\t(s, [level, p]) => s + Number(level) * p,\n\t\t\t0,\n\t\t);\n\t\treturn { type: \"score\", score, probabilities };\n\t}\n\treturn { type: \"refusal\" };\n}\n\n/** Adapter for the System One wire protocol spoken by kev and clef. */\nexport function systemOneModel(opts: {\n\tprovider: \"kev\" | \"clef\";\n\turl: string;\n\t/** Sent as body.model (\"kev-latest\", \"clef\", \"clef-flash\"). */\n\tmodelId: string;\n\tapiKey?: string;\n\tfetchImpl?: typeof fetch;\n}): DecisionModelV4 {\n\tconst doFetch = opts.fetchImpl ?? fetch;\n\treturn {\n\t\tspecificationVersion: \"v4\",\n\t\tprovider: `secondlayer.${opts.provider}`,\n\t\tmodelId: opts.modelId,\n\t\tsupportedQuestionTypes: [\"choice\", \"score\", \"boolean\"],\n\t\tasync doDecide({ state, questions, abortSignal }) {\n\t\t\tconst wireQuestions = Object.fromEntries(\n\t\t\t\tObject.entries(questions).map(([id, q]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tq.type === \"boolean\" ? { ...q, type: \"noul\" } : q,\n\t\t\t\t]),\n\t\t\t);\n\t\t\tconst res = await doFetch(opts.url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t...(opts.apiKey && { authorization: `Bearer ${opts.apiKey}` }),\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: opts.modelId,\n\t\t\t\t\tstate: toWireState(state),\n\t\t\t\t\tquestions: wireQuestions,\n\t\t\t\t}),\n\t\t\t\tsignal: abortSignal,\n\t\t\t});\n\t\t\t// Never include the body: it may echo state.\n\t\t\tif (!res.ok) throw new Error(`${opts.provider} ${res.status}`);\n\t\t\tconst json = (await res.json()) as { result?: WireBody } & WireBody;\n\t\t\tconst body = json.result ?? json;\n\t\t\tconst answers = Object.fromEntries(\n\t\t\t\tObject.entries(body.answers ?? {}).map(([id, a]) => [id, mapAnswer(a)]),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tanswers,\n\t\t\t\twarnings: [],\n\t\t\t\tusage: {\n\t\t\t\t\tinputTokens: body.usage?.input_tokens,\n\t\t\t\t\toutputTokens: body.usage?.output_tokens,\n\t\t\t\t},\n\t\t\t\tresponse: { modelId: body.model ?? opts.modelId },\n\t\t\t} as DoDecideResult;\n\t\t},\n\t};\n}\n",
|
|
11
|
+
"import type { Experimental_DecisionModel } from \"ai\"; // type-only: erased at runtime\nimport { logger } from \"../logger.ts\";\nimport { systemOneModel } from \"./system-one.ts\";\nimport type { ClassifierName } from \"./types.ts\";\n\ntype DecisionModelV4 = Extract<\n\tExperimental_DecisionModel,\n\t{ doDecide: unknown }\n>;\ntype Env = Record<string, string | undefined>;\n\nexport const DEFAULT_MODEL: Record<Exclude<ClassifierName, \"rules\">, string> = {\n\tjev: \"typesafe-ai/jev\",\n\tkev: \"kev-latest\",\n\tclef: \"clef\",\n};\n\nconst REQUIRED_ENV: Record<Exclude<ClassifierName, \"rules\">, string[]> = {\n\tjev: [\"AI_GATEWAY_API_KEY\"],\n\tkev: [\"KEV_URL\"],\n\tclef: [\"CLOUDFLARE_ACCOUNT_ID\", \"CLOUDFLARE_API_TOKEN\"],\n};\n\n/** Compose passes CLASSIFIER_MODEL=\"\" when unset; blank means unset. */\nexport const envModel = (env: Env): string | undefined =>\n\tenv.CLASSIFIER_MODEL?.trim() || undefined;\n\n/** CLASSIFIER picks the backend; unset means jev when the gateway key is set,\n * else offline rules. A backend missing credentials falls back to rules. */\nexport function resolveProvider(env: Env = process.env): {\n\tname: ClassifierName;\n\tmodelId: string;\n} {\n\tconst rules = { name: \"rules\", modelId: \"rules\" } as const;\n\tconst requested = env.CLASSIFIER?.trim();\n\tlet name: ClassifierName;\n\tif (!requested) {\n\t\tname = env.AI_GATEWAY_API_KEY ? \"jev\" : \"rules\";\n\t} else if (\n\t\trequested === \"jev\" ||\n\t\trequested === \"kev\" ||\n\t\trequested === \"clef\" ||\n\t\trequested === \"rules\"\n\t) {\n\t\tname = requested;\n\t} else {\n\t\tlogger.warn(`classifier: unknown CLASSIFIER=${requested}; using rules`);\n\t\treturn rules;\n\t}\n\tif (name === \"rules\") return rules;\n\tconst missing = REQUIRED_ENV[name].find((v) => !env[v]);\n\tif (missing) {\n\t\tlogger.warn(\n\t\t\t`classifier: CLASSIFIER=${name} missing ${missing}; using rules`,\n\t\t);\n\t\treturn rules;\n\t}\n\treturn { name, modelId: envModel(env) ?? DEFAULT_MODEL[name] };\n}\n\nexport function buildModel(\n\tname: \"jev\" | \"kev\" | \"clef\",\n\tmodelId: string,\n\tenv: Env = process.env,\n\tfetchImpl?: typeof fetch,\n): string | DecisionModelV4 {\n\t// The AI Gateway resolves a plain model id string.\n\tif (name === \"jev\") return modelId;\n\tif (name === \"kev\") {\n\t\treturn systemOneModel({\n\t\t\tprovider: \"kev\",\n\t\t\turl: `${(env.KEV_URL ?? \"\").replace(/\\/$/, \"\")}/v1/systemone`,\n\t\t\tmodelId,\n\t\t\tapiKey: env.KEV_API_KEY,\n\t\t\tfetchImpl,\n\t\t});\n\t}\n\treturn systemOneModel({\n\t\tprovider: \"clef\",\n\t\turl: `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/cloudflare/${modelId}`,\n\t\tmodelId,\n\t\tapiKey: env.CLOUDFLARE_API_TOKEN,\n\t\tfetchImpl,\n\t});\n}\n",
|
|
12
|
+
"import type { Answer, Answers, Question, RuleValue, RulesFn } from \"./types.ts\";\n\nfunction answerFor(question: Question, value: RuleValue | undefined): Answer {\n\tif (value === undefined) return { type: \"refusal\" };\n\tif (question.type === \"choice\") {\n\t\tif (typeof value !== \"string\" || !(value in question.criteria)) {\n\t\t\treturn { type: \"refusal\" };\n\t\t}\n\t\tconst probabilities = Object.fromEntries(\n\t\t\tObject.keys(question.criteria).map((k) => [k, k === value ? 1 : 0]),\n\t\t);\n\t\treturn { type: \"choice\", choice: value, probabilities, confidence: 1 };\n\t}\n\tif (question.type === \"score\") {\n\t\tconst n = question.criteria.length;\n\t\tif (typeof value !== \"number\" || !Number.isInteger(value)) {\n\t\t\treturn { type: \"refusal\" };\n\t\t}\n\t\tif (value < 0 || value >= n) return { type: \"refusal\" };\n\t\tconst probabilities = Object.fromEntries(\n\t\t\tArray.from({ length: n }, (_, i) => [String(i), i === value ? 1 : 0]),\n\t\t);\n\t\treturn { type: \"score\", score: value, probabilities, confidence: 1 };\n\t}\n\tif (typeof value !== \"boolean\") return { type: \"refusal\" };\n\treturn { type: \"boolean\", probability: value ? 1 : 0, confidence: 1 };\n}\n\n/** Offline provider: deterministic, never touches the network. */\nexport function runRules<Q extends Record<string, Question>>(\n\tquestions: Q,\n\tfn: RulesFn<Q>,\n\tstate: string | Record<string, unknown>,\n): Answers<Q> {\n\tconst values = fn(state) ?? {};\n\tconst out: Record<string, Answer> = {};\n\tfor (const id of Object.keys(questions)) {\n\t\tout[id] = answerFor(\n\t\t\tquestions[id] as Question,\n\t\t\t(values as Record<string, RuleValue | undefined>)[id],\n\t\t);\n\t}\n\treturn out as Answers<Q>;\n}\n",
|
|
13
|
+
"import { logger } from \"../logger.ts\";\nimport { type RawAnswer, normalizeAnswer } from \"./confidence.ts\";\nimport { MAX_STATE_CHARS, stateSize, validateQuestions } from \"./limits.ts\";\nimport {\n\tDEFAULT_MODEL,\n\tbuildModel,\n\tenvModel,\n\tresolveProvider,\n} from \"./providers.ts\";\nimport { runRules } from \"./rules.ts\";\nimport type {\n\tAnswers,\n\tClassifierName,\n\tClassifyResult,\n\tQuestion,\n\tRulesFn,\n} from \"./types.ts\";\n\n/**\n * One classifier call over jev, kev, clef or offline rules.\n * Returns null on any provider failure (fail-open: callers decide what null\n * means). Only ClassifierInputError, a bad question definition, escapes.\n * Never logs state or response bodies.\n */\nexport async function classify<\n\tconst Q extends Record<string, Question>,\n>(input: {\n\tstate: string | Record<string, unknown>;\n\tquestions: Q;\n\trules?: RulesFn<Q>;\n\t/** Overrides resolveProvider(); used by classifier-compare. */\n\tprovider?: ClassifierName;\n\t/** Overrides CLASSIFIER_MODEL / the provider default. */\n\tmodelId?: string;\n\t/** Default 5000. */\n\ttimeoutMs?: number;\n\tenv?: Record<string, string | undefined>;\n\t/** Tests only (kev/clef). */\n\tfetchImpl?: typeof fetch;\n}): Promise<ClassifyResult<Q> | null> {\n\tvalidateQuestions(input.questions);\n\tconst env = input.env ?? process.env;\n\tconst resolved = resolveProvider(env);\n\tconst name = input.provider ?? resolved.name;\n\tif (name === \"rules\") {\n\t\tif (!input.rules) return null;\n\t\treturn {\n\t\t\tprovider: \"rules\",\n\t\t\tmodelId: \"rules\",\n\t\t\tanswers: runRules(input.questions, input.rules, input.state),\n\t\t};\n\t}\n\t// An explicit provider override ignores CLASSIFIER_MODEL (it pins one backend).\n\tconst modelId =\n\t\tinput.modelId ??\n\t\t(input.provider ? undefined : envModel(env)) ??\n\t\tDEFAULT_MODEL[name];\n\n\tif (stateSize(input.state) > MAX_STATE_CHARS) {\n\t\tlogger.warn(`classifier: state over ${MAX_STATE_CHARS} chars; skipped`);\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst { experimental_decide: decide } = await import(\"ai\");\n\t\tconst r = await decide({\n\t\t\tmodel: buildModel(name, modelId, env, input.fetchImpl),\n\t\t\tstate: input.state as string,\n\t\t\t// Our Question is a structural subset of the SDK's question type.\n\t\t\tquestions: input.questions as Parameters<typeof decide>[0][\"questions\"],\n\t\t\tmaxRetries: 1,\n\t\t\tabortSignal: AbortSignal.timeout(input.timeoutMs ?? 5000),\n\t\t});\n\t\tconst answers: Record<string, ReturnType<typeof normalizeAnswer>> = {};\n\t\tfor (const id of Object.keys(input.questions)) {\n\t\t\tanswers[id] = normalizeAnswer(\n\t\t\t\tinput.questions[id] as Question,\n\t\t\t\t(r.answers as Record<string, RawAnswer>)[id] ?? { type: \"refusal\" },\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tprovider: name,\n\t\t\tmodelId: r.response.modelId ?? modelId,\n\t\t\tanswers: answers as Answers<Q>,\n\t\t};\n\t} catch (err) {\n\t\tlogger.warn(\n\t\t\t`classifier: ${name} failed: ${err instanceof Error ? err.name : \"error\"}`,\n\t\t);\n\t\treturn null;\n\t}\n}\n"
|
|
14
|
+
],
|
|
15
|
+
"mappings": "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",
|
|
16
|
+
"debugId": "6AA8B48DE6A7F1DB64756E2164756E21",
|
|
17
|
+
"names": []
|
|
18
|
+
}
|
|
@@ -336,6 +336,27 @@ declare function cursorIsBeforeBlock(cursor: StreamsCursor, height: number): boo
|
|
|
336
336
|
declare function planDecoderReceipts(input: DecoderClockInput): DecoderClockResult;
|
|
337
337
|
import { Kysely } from "kysely";
|
|
338
338
|
import { ColumnType, Generated } from "kysely";
|
|
339
|
+
/**
|
|
340
|
+
* How far a subgraph's rows can be checked without trusting the server,
|
|
341
|
+
* derived from its sources and a scan of its handler. Never configured.
|
|
342
|
+
*
|
|
343
|
+
* - `state`: every input is a named state write, provable from block headers.
|
|
344
|
+
* Handlers run in the deterministic realm. A deployed `state` subgraph is
|
|
345
|
+
* verifiable: one with any reason is refused at deploy.
|
|
346
|
+
* - `events`: an input (events, prints, tx results, `ctx.client`) needs
|
|
347
|
+
* re-execution proofs, which are not served yet.
|
|
348
|
+
* - `none`: a source can never be proven as written (trait scope, tip-first
|
|
349
|
+
* backfill).
|
|
350
|
+
*/
|
|
351
|
+
interface SubgraphVerification {
|
|
352
|
+
level: "state" | "events" | "none";
|
|
353
|
+
/** What stands between the subgraph and checkable rows, e.g.
|
|
354
|
+
* `print_event source "swaps" needs event proofs` or
|
|
355
|
+
* `subgraph.ts:41:12 Date: wall-clock time differs per run`. */
|
|
356
|
+
reasons: string[];
|
|
357
|
+
/** What stays unproven even at this level, e.g. tx attribution of writes. */
|
|
358
|
+
unproven: string[];
|
|
359
|
+
}
|
|
339
360
|
interface BlocksTable {
|
|
340
361
|
height: number;
|
|
341
362
|
hash: string;
|
|
@@ -496,6 +517,11 @@ interface SubgraphsTable {
|
|
|
496
517
|
expires_at: Date | null;
|
|
497
518
|
/** (event type, contract) probe pairs persisted at deploy for weight classification. */
|
|
498
519
|
sparse_probe_targets: unknown | null;
|
|
520
|
+
/** sha256 over everything that shapes rows (schema, handler bundle,
|
|
521
|
+
* startBlock, network, runtime). NULL for unbundled or older deploys. */
|
|
522
|
+
pin: string | null;
|
|
523
|
+
/** Level derived at deploy. NULL (older deploys) = not verifiable. */
|
|
524
|
+
verification: SubgraphVerification | null;
|
|
499
525
|
created_at: Generated<Date>;
|
|
500
526
|
updated_at: Generated<Date>;
|
|
501
527
|
}
|
|
@@ -1164,6 +1190,8 @@ interface Database {
|
|
|
1164
1190
|
sentinel_accounts: SentinelAccountsTable;
|
|
1165
1191
|
waitlists: WaitlistsTable;
|
|
1166
1192
|
waitlist_signups: WaitlistSignupsTable;
|
|
1193
|
+
api_failed_requests: ApiFailedRequestsTable;
|
|
1194
|
+
feedback_tickets: FeedbackTicketsTable;
|
|
1167
1195
|
}
|
|
1168
1196
|
interface ServiceHeartbeatsTable {
|
|
1169
1197
|
name: string;
|
|
@@ -1371,6 +1399,43 @@ interface WaitlistSignupsTable {
|
|
|
1371
1399
|
answers: Record<string, unknown>;
|
|
1372
1400
|
created_at: Generated<Date>;
|
|
1373
1401
|
}
|
|
1402
|
+
/**
|
|
1403
|
+
* 24h hosted record of a failed API request, keyed by `request_id` and
|
|
1404
|
+
* scoped to the account that made it. `query` is redacted before insert.
|
|
1405
|
+
*/
|
|
1406
|
+
interface ApiFailedRequestsTable {
|
|
1407
|
+
request_id: string;
|
|
1408
|
+
account_id: string;
|
|
1409
|
+
method: string;
|
|
1410
|
+
path: string;
|
|
1411
|
+
status: number;
|
|
1412
|
+
code: string;
|
|
1413
|
+
message: Generated<string>;
|
|
1414
|
+
query: Record<string, unknown>;
|
|
1415
|
+
origin: string | null;
|
|
1416
|
+
created_at: Generated<Date>;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Hosted problem report. `attempted` is the server's own copy of the failed
|
|
1420
|
+
* request (never caller-supplied); `status` starts `new` until classified.
|
|
1421
|
+
*/
|
|
1422
|
+
interface FeedbackTicketsTable {
|
|
1423
|
+
id: Generated<string>;
|
|
1424
|
+
account_id: string;
|
|
1425
|
+
idempotency_key: string | null;
|
|
1426
|
+
request_id: string | null;
|
|
1427
|
+
intent: string;
|
|
1428
|
+
expected: Record<string, unknown> | null;
|
|
1429
|
+
kind_hint: string | null;
|
|
1430
|
+
evidence: Record<string, unknown> | null;
|
|
1431
|
+
attempted: Record<string, unknown> | null;
|
|
1432
|
+
origin: string;
|
|
1433
|
+
status: Generated<"new" | "classified">;
|
|
1434
|
+
route: string | null;
|
|
1435
|
+
classification: Record<string, unknown> | null;
|
|
1436
|
+
classified_at: Date | null;
|
|
1437
|
+
created_at: Generated<Date>;
|
|
1438
|
+
}
|
|
1374
1439
|
declare const DECODER_COMMIT_STEPS: readonly ["output", "checkpoint", "receipt", "failure"];
|
|
1375
1440
|
type DecoderCommitStep = (typeof DECODER_COMMIT_STEPS)[number];
|
|
1376
1441
|
declare class DecoderAdapterCrash extends Error {
|