@jevable/core 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/cel/check.d.ts +1 -1
- package/dist/cel/check.js +1 -1
- package/dist/engine.d.ts +1 -1
- package/dist/engine.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/typesafe/client.d.ts +4 -1
- package/dist/typesafe/client.js +19 -5
- package/dist/typesafe/providers.d.ts +28 -0
- package/dist/typesafe/providers.js +32 -0
- package/package.json +10 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @jevable/core
|
|
2
2
|
|
|
3
|
-
The rule engine behind [`
|
|
3
|
+
The rule engine behind [`jevable`](../cli/README.md): CEL expressions over a
|
|
4
4
|
record (`line`, `json`) or a window (`window`) that can ask
|
|
5
5
|
[Jev](https://docs.typesafe.ai) semantic questions through `judge.boolean`,
|
|
6
6
|
`judge.choice` and `judge.score`. Plain conditions decide first; Jev is asked
|
package/dist/cel/check.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Planned } from "./env.ts";
|
|
|
2
2
|
/** A rule that cannot work as written. */
|
|
3
3
|
export declare class RuleError extends Error {
|
|
4
4
|
}
|
|
5
|
-
export declare const HINT = "Variables: line, json (the parsed line) and, with --window, window. Run `
|
|
5
|
+
export declare const HINT = "Variables: line, json (the parsed line) and, with --window, window. Run `jevable guide` for functions and examples.";
|
|
6
6
|
/** Throws a RuleError when the parsed expression cannot work as a rule (or key). */
|
|
7
7
|
export declare function checkParsed(expr: unknown, kind: "rule" | "key"): void;
|
|
8
8
|
/** Dry-runs a planned expression with neutral answers; throws a RuleError on what only evaluation shows. */
|
package/dist/cel/check.js
CHANGED
|
@@ -8,7 +8,7 @@ export class RuleError extends Error {
|
|
|
8
8
|
const VARIABLES = new Set(["line", "json", "window"]);
|
|
9
9
|
// Identifiers CEL itself provides: type names usable in `type(x) == int`.
|
|
10
10
|
const BUILTIN_IDENTS = new Set(["int", "uint", "double", "bool", "string", "bytes", "list", "map", "null_type", "type"]);
|
|
11
|
-
export const HINT = "Variables: line, json (the parsed line) and, with --window, window. Run `
|
|
11
|
+
export const HINT = "Variables: line, json (the parsed line) and, with --window, window. Run `jevable guide` for functions and examples.";
|
|
12
12
|
const COMPARE = `compare it, e.g. judge.boolean(line, "...") >= 0.7`;
|
|
13
13
|
// Values for the dry run, shaped like real ones.
|
|
14
14
|
const PROBE = {
|
package/dist/engine.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Answers } from "./cel/env.ts";
|
|
2
2
|
import { Program } from "./program.ts";
|
|
3
3
|
import { type Answer, type Client, type Question } from "./typesafe/client.ts";
|
|
4
|
-
export declare const NO_KEY = "no Jev API key:
|
|
4
|
+
export declare const NO_KEY = "no Jev API key: run `jevable key`";
|
|
5
5
|
/**
|
|
6
6
|
* What every program shares: the Jev client, an answer cache (the same
|
|
7
7
|
* question on the same material is asked once), and counters.
|
package/dist/engine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { checkParsed, checkPlanned, HINT, RuleError } from "./cel/check.js";
|
|
|
3
3
|
import { env } from "./cel/env.js";
|
|
4
4
|
import { Program } from "./program.js";
|
|
5
5
|
import { HttpError } from "./typesafe/client.js";
|
|
6
|
-
export const NO_KEY = "no Jev API key:
|
|
6
|
+
export const NO_KEY = "no Jev API key: run `jevable key`";
|
|
7
7
|
/**
|
|
8
8
|
* What every program shares: the Jev client, an answer cache (the same
|
|
9
9
|
* question on the same material is asked once), and counters.
|
package/dist/index.d.ts
CHANGED
|
@@ -4,5 +4,6 @@ export { RuleError, isRuleBug } from "./cel/check.ts";
|
|
|
4
4
|
export { toCel, fromCel } from "./cel/values.ts";
|
|
5
5
|
export { clip, cutLongStrings, fingerprint, parseRecord } from "./text.ts";
|
|
6
6
|
export { Client, HttpError, DEFAULT_BASE_URL, DEFAULT_MODEL, type Answer, type Question, type QuestionType, type Result } from "./typesafe/client.ts";
|
|
7
|
+
export { PROVIDERS, lookup, choose, whose, type Provider, type Found } from "./typesafe/providers.ts";
|
|
7
8
|
export type { Bindings, Call, Outcome } from "./types.ts";
|
|
8
9
|
export type { CelInput } from "@bufbuild/cel";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// @jevable/core: compile a rule once, match records against it.
|
|
2
2
|
//
|
|
3
3
|
// const engine = new Engine(new Client({ apiKey: process.env.TYPESAFE_API_KEY }));
|
|
4
|
+
// // or another provider of Jev: new Client({ apiKey, baseUrl: p.baseUrl, model: p.model, provider: p.label })
|
|
4
5
|
// const rule = engine.compile(`judge.boolean(line, "Is this an outage?") >= 0.7`);
|
|
5
6
|
// const { pass, calls } = await rule.match({ line: "checkout returns 500 for everyone" });
|
|
6
7
|
export { Engine, NO_KEY } from "./engine.js";
|
|
@@ -9,3 +10,4 @@ export { RuleError, isRuleBug } from "./cel/check.js";
|
|
|
9
10
|
export { toCel, fromCel } from "./cel/values.js";
|
|
10
11
|
export { clip, cutLongStrings, fingerprint, parseRecord } from "./text.js";
|
|
11
12
|
export { Client, HttpError, DEFAULT_BASE_URL, DEFAULT_MODEL } from "./typesafe/client.js";
|
|
13
|
+
export { PROVIDERS, lookup, choose, whose } from "./typesafe/providers.js";
|
|
@@ -29,7 +29,7 @@ export interface Result {
|
|
|
29
29
|
/** A non-2xx answer from the API. */
|
|
30
30
|
export declare class HttpError extends Error {
|
|
31
31
|
readonly status: number;
|
|
32
|
-
constructor(status: number, body: string);
|
|
32
|
+
constructor(provider: string, status: number, body: string);
|
|
33
33
|
/** Retrying cannot help: a bad key or a malformed request. */
|
|
34
34
|
get fatal(): boolean;
|
|
35
35
|
}
|
|
@@ -37,10 +37,13 @@ export declare class Client {
|
|
|
37
37
|
readonly baseUrl: string;
|
|
38
38
|
readonly apiKey: string;
|
|
39
39
|
readonly model: string;
|
|
40
|
+
/** Who serves Jev here, for messages. */
|
|
41
|
+
readonly provider: string;
|
|
40
42
|
constructor(opts?: {
|
|
41
43
|
apiKey?: string;
|
|
42
44
|
baseUrl?: string;
|
|
43
45
|
model?: string;
|
|
46
|
+
provider?: string;
|
|
44
47
|
});
|
|
45
48
|
/** Evaluates every question against state in one call, retrying once on anything but a fatal error. */
|
|
46
49
|
ask(state: unknown, questions: Record<string, Question>, signal?: AbortSignal): Promise<Result>;
|
package/dist/typesafe/client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A minimal client for TypeSafe's System One endpoint
|
|
2
2
|
// (https://docs.typesafe.ai/api): one state, a map of typed questions, one
|
|
3
|
-
// typed answer per question.
|
|
3
|
+
// typed answer per question. Other providers serve the same API (providers.ts).
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.typesafe.ai";
|
|
5
5
|
// Pinned rather than `jev-latest`: thresholds are tuned against one model,
|
|
6
6
|
// and an alias moves without a change on our side.
|
|
@@ -8,8 +8,8 @@ export const DEFAULT_MODEL = "jev-1.13.0";
|
|
|
8
8
|
/** A non-2xx answer from the API. */
|
|
9
9
|
export class HttpError extends Error {
|
|
10
10
|
status;
|
|
11
|
-
constructor(status, body) {
|
|
12
|
-
super(
|
|
11
|
+
constructor(provider, status, body) {
|
|
12
|
+
super(`${provider} returned ${status}: ${reason(body)}`);
|
|
13
13
|
this.status = status;
|
|
14
14
|
}
|
|
15
15
|
/** Retrying cannot help: a bad key or a malformed request. */
|
|
@@ -17,14 +17,28 @@ export class HttpError extends Error {
|
|
|
17
17
|
return this.status === 401 || this.status === 403 || this.status === 422;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
/** The message in an error body — TypeSafe's {message}, a gateway's {error: {message}} — else the body itself. */
|
|
21
|
+
function reason(body) {
|
|
22
|
+
try {
|
|
23
|
+
const j = JSON.parse(body);
|
|
24
|
+
const m = j?.error?.message ?? j?.message ?? j?.error;
|
|
25
|
+
if (typeof m === "string")
|
|
26
|
+
return m;
|
|
27
|
+
}
|
|
28
|
+
catch { }
|
|
29
|
+
return body.slice(0, 512);
|
|
30
|
+
}
|
|
20
31
|
export class Client {
|
|
21
32
|
baseUrl;
|
|
22
33
|
apiKey;
|
|
23
34
|
model;
|
|
35
|
+
/** Who serves Jev here, for messages. */
|
|
36
|
+
provider;
|
|
24
37
|
constructor(opts = {}) {
|
|
25
38
|
this.apiKey = opts.apiKey ?? "";
|
|
26
39
|
this.baseUrl = (opts.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
27
40
|
this.model = opts.model || DEFAULT_MODEL;
|
|
41
|
+
this.provider = opts.provider || "TypeSafe";
|
|
28
42
|
}
|
|
29
43
|
/** Evaluates every question against state in one call, retrying once on anything but a fatal error. */
|
|
30
44
|
async ask(state, questions, signal) {
|
|
@@ -48,11 +62,11 @@ export class Client {
|
|
|
48
62
|
});
|
|
49
63
|
const text = await res.text();
|
|
50
64
|
if (!res.ok)
|
|
51
|
-
throw new HttpError(res.status, text);
|
|
65
|
+
throw new HttpError(this.provider, res.status, text);
|
|
52
66
|
const out = JSON.parse(text);
|
|
53
67
|
for (const key of Object.keys(questions)) {
|
|
54
68
|
if (!out.answers?.[key])
|
|
55
|
-
throw new Error(
|
|
69
|
+
throw new Error(`${this.provider}: answer ${JSON.stringify(key)} missing`);
|
|
56
70
|
}
|
|
57
71
|
return out;
|
|
58
72
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Provider {
|
|
2
|
+
name: string;
|
|
3
|
+
label: string;
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
/** Jev as this provider names it, pinned where the provider allows. */
|
|
6
|
+
model: string;
|
|
7
|
+
/** Variables holding its credential, the first set one wins. */
|
|
8
|
+
env: readonly string[];
|
|
9
|
+
/** How its keys start, to tell whose a pasted key is. */
|
|
10
|
+
prefix: string;
|
|
11
|
+
}
|
|
12
|
+
/** In the order jevable prefers them. */
|
|
13
|
+
export declare const PROVIDERS: readonly Provider[];
|
|
14
|
+
/** The provider a key belongs to, told by how it starts. */
|
|
15
|
+
export declare function whose(key: string): Provider | undefined;
|
|
16
|
+
export interface Found {
|
|
17
|
+
provider: Provider;
|
|
18
|
+
/** The variable the credential came from, when one is set. */
|
|
19
|
+
variable?: string;
|
|
20
|
+
key?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Every provider with the credential found for it in `vars`. JEV_BASE_URL
|
|
24
|
+
* adds a custom endpoint (a proxy) ahead of the rest, keyed by JEV_API_KEY.
|
|
25
|
+
*/
|
|
26
|
+
export declare function lookup(vars: Readonly<Record<string, string | undefined>>): Found[];
|
|
27
|
+
/** The provider to use: the one named (JEV_PROVIDER), else the first with a credential. */
|
|
28
|
+
export declare function choose(found: Found[], name?: string): Found | undefined;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Where Jev can be reached. Every provider here speaks TypeSafe's System One
|
|
2
|
+
// API (POST <baseUrl>/v1/systemone) with the same request and answer shapes;
|
|
3
|
+
// they differ in base URL, credential and model id.
|
|
4
|
+
import { DEFAULT_BASE_URL, DEFAULT_MODEL } from "./client.js";
|
|
5
|
+
/** In the order jevable prefers them. */
|
|
6
|
+
export const PROVIDERS = [
|
|
7
|
+
{ name: "typesafe", label: "TypeSafe", baseUrl: DEFAULT_BASE_URL, model: DEFAULT_MODEL, env: ["TYPESAFE_API_KEY", "JEV_API_KEY"], prefix: "apikey_" },
|
|
8
|
+
// AI Gateway lists Jev under one id only; it does not take a version.
|
|
9
|
+
{ name: "vercel", label: "Vercel AI Gateway", baseUrl: "https://ai-gateway.vercel.sh/typesafe", model: "typesafe-ai/jev", env: ["AI_GATEWAY_API_KEY", "VERCEL_OIDC_TOKEN"], prefix: "vck_" },
|
|
10
|
+
{ name: "openrouter", label: "OpenRouter", baseUrl: "https://openrouter.ai/api", model: "typesafe/jev-1.13", env: ["OPENROUTER_API_KEY"], prefix: "sk-or-" },
|
|
11
|
+
];
|
|
12
|
+
/** The provider a key belongs to, told by how it starts. */
|
|
13
|
+
export function whose(key) {
|
|
14
|
+
return PROVIDERS.find((p) => key.startsWith(p.prefix));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Every provider with the credential found for it in `vars`. JEV_BASE_URL
|
|
18
|
+
* adds a custom endpoint (a proxy) ahead of the rest, keyed by JEV_API_KEY.
|
|
19
|
+
*/
|
|
20
|
+
export function lookup(vars) {
|
|
21
|
+
const custom = vars.JEV_BASE_URL
|
|
22
|
+
? [{ name: "custom", label: vars.JEV_BASE_URL, baseUrl: vars.JEV_BASE_URL, model: DEFAULT_MODEL, env: ["JEV_API_KEY", "TYPESAFE_API_KEY"], prefix: "" }]
|
|
23
|
+
: [];
|
|
24
|
+
return [...custom, ...PROVIDERS].map((provider) => {
|
|
25
|
+
const variable = provider.env.find((v) => vars[v]);
|
|
26
|
+
return { provider, variable, key: variable && vars[variable] };
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/** The provider to use: the one named (JEV_PROVIDER), else the first with a credential. */
|
|
30
|
+
export function choose(found, name) {
|
|
31
|
+
return name ? found.find((f) => f.provider.name === name) : found.find((f) => f.key);
|
|
32
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jevable/core",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "The
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "The jevable rule engine: CEL rules that ask Jev, TypeSafe's classification model, semantic questions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
8
|
-
"
|
|
8
|
+
"jevable-source": "./src/index.ts",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
11
|
},
|
|
12
12
|
"./testing": {
|
|
13
|
-
"
|
|
13
|
+
"jevable-source": "./src/testing.ts",
|
|
14
14
|
"types": "./dist/testing.d.ts",
|
|
15
15
|
"default": "./dist/testing.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
19
21
|
"engines": {
|
|
20
|
-
"node": ">=20"
|
|
22
|
+
"node": ">=20.3"
|
|
21
23
|
},
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "tsc -p tsconfig.build.json",
|
|
24
26
|
"typecheck": "tsc --noEmit",
|
|
25
|
-
"test": "node --conditions=
|
|
27
|
+
"test": "node --conditions=jevable-source --test test/*.test.ts",
|
|
26
28
|
"prepack": "npm run build"
|
|
27
29
|
},
|
|
28
30
|
"dependencies": {
|
|
29
31
|
"@bufbuild/cel": "0.6.1"
|
|
30
32
|
},
|
|
31
|
-
"license": "
|
|
33
|
+
"license": "UNLICENSED"
|
|
32
34
|
}
|