jevkit-core 0.2.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/README.md +27 -0
- package/dist/answers.d.ts +66 -0
- package/dist/answers.d.ts.map +1 -0
- package/dist/answers.js +152 -0
- package/dist/answers.js.map +1 -0
- package/dist/canonical.d.ts +18 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +73 -0
- package/dist/canonical.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/question.d.ts +33 -0
- package/dist/question.d.ts.map +1 -0
- package/dist/question.js +107 -0
- package/dist/question.js.map +1 -0
- package/dist/record.d.ts +66 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +168 -0
- package/dist/record.js.map +1 -0
- package/dist/tokens.d.ts +30 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +53 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# jevkit-core
|
|
2
|
+
|
|
3
|
+
Shared substrate for [jevkit](https://github.com/pjdurden/jevkit-js), a set of
|
|
4
|
+
tools for building on TypeSafe's Jev. Nothing in this package calls the API.
|
|
5
|
+
|
|
6
|
+
- **`.jevl` record format** — one JSON Lines format that serves as test
|
|
7
|
+
cassette, drift golden set, benchmark instance, and calibration observation.
|
|
8
|
+
See [the spec](../../docs/specs/record-format-v1.md).
|
|
9
|
+
- **Canonical digests** — RFC 8785 canonicalization, so the same request always
|
|
10
|
+
hashes the same way. `record_id()` includes the model; `request_id()` does
|
|
11
|
+
not, which is what lets you pair a golden record with its replay on a new
|
|
12
|
+
model version.
|
|
13
|
+
- **Question normalization** — one view over SDK objects and plain dicts alike.
|
|
14
|
+
- **Token budgets** — estimates against Jev's two documented limits: 64k for
|
|
15
|
+
state plus all questions, 32k for state plus the longest single question.
|
|
16
|
+
|
|
17
|
+
> Unofficial and unaffiliated with TypeSafe.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install jevkit-core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## License
|
|
26
|
+
|
|
27
|
+
MIT
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform view of a jev answer, and how it compares to a ground-truth label.
|
|
3
|
+
*
|
|
4
|
+
* `drift`, `calibrate` and `bench` all need the same three things from an
|
|
5
|
+
* answer: what it predicted, how much probability sat on a given outcome, and
|
|
6
|
+
* whether it matched a label. Those live here so the three packages agree on
|
|
7
|
+
* the definitions rather than each inventing its own.
|
|
8
|
+
*
|
|
9
|
+
* The vocabulary is deliberately narrow:
|
|
10
|
+
*
|
|
11
|
+
* - **predicted** is the outcome the answer selects. For a Choice it is the
|
|
12
|
+
* option key. For a Noul it is `true` when `noul` clears the threshold. For a
|
|
13
|
+
* Score it is the index of the most probable level, which is *not* the same
|
|
14
|
+
* as rounding `score`.
|
|
15
|
+
* - **confidence** is what the API returned, and Nouls do not have one. A
|
|
16
|
+
* Noul's distance from 0.5 is a different quantity, exposed as `decisiveness`
|
|
17
|
+
* rather than pretending it is the same number.
|
|
18
|
+
*/
|
|
19
|
+
export declare const NOUL_THRESHOLD = 0.5;
|
|
20
|
+
export type AnswerType = "choice" | "score" | "noul";
|
|
21
|
+
type Raw = {
|
|
22
|
+
[key: string]: unknown;
|
|
23
|
+
};
|
|
24
|
+
export declare class Answer {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly type: AnswerType;
|
|
27
|
+
readonly raw: Raw;
|
|
28
|
+
constructor(id: string, type: AnswerType, raw: Raw);
|
|
29
|
+
/**
|
|
30
|
+
* Outcome -> probability.
|
|
31
|
+
*
|
|
32
|
+
* Choice returns its options, Score its level indices as strings. Noul has no
|
|
33
|
+
* distribution from the API, so the two-outcome distribution it implies is
|
|
34
|
+
* synthesized here.
|
|
35
|
+
*/
|
|
36
|
+
get probabilities(): Record<string, number>;
|
|
37
|
+
/** As returned by the API. `null` for a Noul, which carries none. */
|
|
38
|
+
get confidence(): number | null;
|
|
39
|
+
/**
|
|
40
|
+
* How far from maximally uncertain this answer is, on 0..1.
|
|
41
|
+
*
|
|
42
|
+
* For a Noul this is `|noul - 0.5| * 2`. This is *not* confidence and is not
|
|
43
|
+
* comparable to the API's confidence across question types; it exists so
|
|
44
|
+
* Nouls can be thresholded on something with a defined meaning.
|
|
45
|
+
*/
|
|
46
|
+
get decisiveness(): number;
|
|
47
|
+
/** The outcome this answer selects. */
|
|
48
|
+
predicted(noulThreshold?: number): unknown;
|
|
49
|
+
/** The probability-weighted score, for a Score answer. */
|
|
50
|
+
get score(): number | null;
|
|
51
|
+
private labelKey;
|
|
52
|
+
/**
|
|
53
|
+
* Probability this answer assigned to `label`.
|
|
54
|
+
*
|
|
55
|
+
* Returns 0 for an outcome the question never offered, which is the honest
|
|
56
|
+
* reading: the model could not have selected it.
|
|
57
|
+
*/
|
|
58
|
+
probabilityOf(label: unknown): number;
|
|
59
|
+
isCorrect(label: unknown, noulThreshold?: number): boolean;
|
|
60
|
+
/** Probability mass on the predicted outcome. */
|
|
61
|
+
get topProbability(): number;
|
|
62
|
+
}
|
|
63
|
+
export declare function parseAnswer(qid: string, raw: unknown): Answer;
|
|
64
|
+
export declare function parseAnswers(answers: Record<string, unknown>): Record<string, Answer>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=answers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answers.d.ts","sourceRoot":"","sources":["../src/answers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAErD,KAAK,GAAG,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,CAAC;AAEtC,qBAAa,MAAM;IAEf,QAAQ,CAAC,EAAE,EAAE,MAAM;IACnB,QAAQ,CAAC,IAAI,EAAE,UAAU;IACzB,QAAQ,CAAC,GAAG,EAAE,GAAG;gBAFR,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,UAAU,EAChB,GAAG,EAAE,GAAG;IAGnB;;;;;;OAMG;IACH,IAAI,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1C;IAED,qEAAqE;IACrE,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAG9B;IAED;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,MAAM,CAKzB;IAED,uCAAuC;IACvC,SAAS,CAAC,aAAa,GAAE,MAAuB,GAAG,OAAO;IAe1D,0DAA0D;IAC1D,IAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAIzB;IAED,OAAO,CAAC,QAAQ;IAKhB;;;;;OAKG;IACH,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;IAIrC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,aAAa,GAAE,MAAuB,GAAG,OAAO;IAY1E,iDAAiD;IACjD,IAAI,cAAc,IAAI,MAAM,CAG3B;CACF;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAmB7D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIrF"}
|
package/dist/answers.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform view of a jev answer, and how it compares to a ground-truth label.
|
|
3
|
+
*
|
|
4
|
+
* `drift`, `calibrate` and `bench` all need the same three things from an
|
|
5
|
+
* answer: what it predicted, how much probability sat on a given outcome, and
|
|
6
|
+
* whether it matched a label. Those live here so the three packages agree on
|
|
7
|
+
* the definitions rather than each inventing its own.
|
|
8
|
+
*
|
|
9
|
+
* The vocabulary is deliberately narrow:
|
|
10
|
+
*
|
|
11
|
+
* - **predicted** is the outcome the answer selects. For a Choice it is the
|
|
12
|
+
* option key. For a Noul it is `true` when `noul` clears the threshold. For a
|
|
13
|
+
* Score it is the index of the most probable level, which is *not* the same
|
|
14
|
+
* as rounding `score`.
|
|
15
|
+
* - **confidence** is what the API returned, and Nouls do not have one. A
|
|
16
|
+
* Noul's distance from 0.5 is a different quantity, exposed as `decisiveness`
|
|
17
|
+
* rather than pretending it is the same number.
|
|
18
|
+
*/
|
|
19
|
+
export const NOUL_THRESHOLD = 0.5;
|
|
20
|
+
export class Answer {
|
|
21
|
+
id;
|
|
22
|
+
type;
|
|
23
|
+
raw;
|
|
24
|
+
constructor(id, type, raw) {
|
|
25
|
+
this.id = id;
|
|
26
|
+
this.type = type;
|
|
27
|
+
this.raw = raw;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Outcome -> probability.
|
|
31
|
+
*
|
|
32
|
+
* Choice returns its options, Score its level indices as strings. Noul has no
|
|
33
|
+
* distribution from the API, so the two-outcome distribution it implies is
|
|
34
|
+
* synthesized here.
|
|
35
|
+
*/
|
|
36
|
+
get probabilities() {
|
|
37
|
+
if (this.type === "noul") {
|
|
38
|
+
const p = Number(this.raw["noul"] ?? 0);
|
|
39
|
+
return { true: p, false: 1 - p };
|
|
40
|
+
}
|
|
41
|
+
const probs = (this.raw["probabilities"] ?? {});
|
|
42
|
+
const out = {};
|
|
43
|
+
for (const [k, v] of Object.entries(probs))
|
|
44
|
+
out[String(k)] = Number(v);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** As returned by the API. `null` for a Noul, which carries none. */
|
|
48
|
+
get confidence() {
|
|
49
|
+
const value = this.raw["confidence"];
|
|
50
|
+
return value === undefined || value === null ? null : Number(value);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* How far from maximally uncertain this answer is, on 0..1.
|
|
54
|
+
*
|
|
55
|
+
* For a Noul this is `|noul - 0.5| * 2`. This is *not* confidence and is not
|
|
56
|
+
* comparable to the API's confidence across question types; it exists so
|
|
57
|
+
* Nouls can be thresholded on something with a defined meaning.
|
|
58
|
+
*/
|
|
59
|
+
get decisiveness() {
|
|
60
|
+
if (this.type === "noul") {
|
|
61
|
+
return Math.abs(Number(this.raw["noul"] ?? 0) - NOUL_THRESHOLD) * 2;
|
|
62
|
+
}
|
|
63
|
+
return this.confidence ?? 0;
|
|
64
|
+
}
|
|
65
|
+
/** The outcome this answer selects. */
|
|
66
|
+
predicted(noulThreshold = NOUL_THRESHOLD) {
|
|
67
|
+
if (this.type === "noul")
|
|
68
|
+
return Number(this.raw["noul"] ?? 0) >= noulThreshold;
|
|
69
|
+
if (this.type === "choice")
|
|
70
|
+
return this.raw["choice"];
|
|
71
|
+
if (this.type === "score") {
|
|
72
|
+
const probs = this.probabilities;
|
|
73
|
+
const keys = Object.keys(probs);
|
|
74
|
+
if (!keys.length)
|
|
75
|
+
return null;
|
|
76
|
+
let best = keys[0];
|
|
77
|
+
for (const k of keys)
|
|
78
|
+
if (probs[k] > probs[best])
|
|
79
|
+
best = k;
|
|
80
|
+
const asInt = Number.parseInt(best, 10);
|
|
81
|
+
return Number.isNaN(asInt) ? best : asInt;
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
/** The probability-weighted score, for a Score answer. */
|
|
86
|
+
get score() {
|
|
87
|
+
if (this.type !== "score")
|
|
88
|
+
return null;
|
|
89
|
+
const value = this.raw["score"];
|
|
90
|
+
return value === undefined || value === null ? null : Number(value);
|
|
91
|
+
}
|
|
92
|
+
labelKey(label) {
|
|
93
|
+
if (this.type === "noul")
|
|
94
|
+
return label ? "true" : "false";
|
|
95
|
+
return String(label);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Probability this answer assigned to `label`.
|
|
99
|
+
*
|
|
100
|
+
* Returns 0 for an outcome the question never offered, which is the honest
|
|
101
|
+
* reading: the model could not have selected it.
|
|
102
|
+
*/
|
|
103
|
+
probabilityOf(label) {
|
|
104
|
+
return this.probabilities[this.labelKey(label)] ?? 0;
|
|
105
|
+
}
|
|
106
|
+
isCorrect(label, noulThreshold = NOUL_THRESHOLD) {
|
|
107
|
+
const predicted = this.predicted(noulThreshold);
|
|
108
|
+
if (this.type === "noul")
|
|
109
|
+
return Boolean(predicted) === Boolean(label);
|
|
110
|
+
if (this.type === "score") {
|
|
111
|
+
const a = Number(predicted);
|
|
112
|
+
const b = Number(label);
|
|
113
|
+
if (!Number.isNaN(a) && !Number.isNaN(b))
|
|
114
|
+
return a === b;
|
|
115
|
+
return predicted === label;
|
|
116
|
+
}
|
|
117
|
+
return predicted === label;
|
|
118
|
+
}
|
|
119
|
+
/** Probability mass on the predicted outcome. */
|
|
120
|
+
get topProbability() {
|
|
121
|
+
const values = Object.values(this.probabilities);
|
|
122
|
+
return values.length ? Math.max(...values) : 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function parseAnswer(qid, raw) {
|
|
126
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
127
|
+
throw new TypeError(`answer "${qid}": expected an object, got ${typeof raw}`);
|
|
128
|
+
}
|
|
129
|
+
const obj = raw;
|
|
130
|
+
let type = obj["type"];
|
|
131
|
+
if (type !== "choice" && type !== "score" && type !== "noul") {
|
|
132
|
+
// Infer from shape when the API response omits the discriminator.
|
|
133
|
+
if ("choice" in obj)
|
|
134
|
+
type = "choice";
|
|
135
|
+
else if ("noul" in obj)
|
|
136
|
+
type = "noul";
|
|
137
|
+
else if ("score" in obj)
|
|
138
|
+
type = "score";
|
|
139
|
+
else {
|
|
140
|
+
throw new TypeError(`answer "${qid}": cannot determine type; expected a 'type' field or one of ` +
|
|
141
|
+
`'choice'/'score'/'noul'`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return new Answer(qid, type, obj);
|
|
145
|
+
}
|
|
146
|
+
export function parseAnswers(answers) {
|
|
147
|
+
const out = {};
|
|
148
|
+
for (const [qid, raw] of Object.entries(answers))
|
|
149
|
+
out[qid] = parseAnswer(qid, raw);
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=answers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"answers.js","sourceRoot":"","sources":["../src/answers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAMlC,MAAM,OAAO,MAAM;IAEN;IACA;IACA;IAHX,YACW,EAAU,EACV,IAAgB,EAChB,GAAQ;QAFR,OAAE,GAAF,EAAE,CAAQ;QACV,SAAI,GAAJ,IAAI,CAAY;QAChB,QAAG,GAAH,GAAG,CAAK;IAChB,CAAC;IAEJ;;;;;;OAMG;IACH,IAAI,aAAa;QACf,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;YACxC,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,CAAC;QACD,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAA4B,CAAC;QAC3E,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,GAAG,CAAC;IACb,CAAC;IAED,qEAAqE;IACrE,IAAI,UAAU;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACrC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IAED;;;;;;OAMG;IACH,IAAI,YAAY;QACd,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACtE,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,uCAAuC;IACvC,SAAS,CAAC,gBAAwB,cAAc;QAC9C,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC;QAChF,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAC9B,IAAI,IAAI,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,IAAI;gBAAE,IAAI,KAAK,CAAC,CAAC,CAAE,GAAG,KAAK,CAAC,IAAI,CAAE;oBAAE,IAAI,GAAG,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACxC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;QAC5C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0DAA0D;IAC1D,IAAI,KAAK;QACP,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;YAAE,OAAO,IAAI,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACtE,CAAC;IAEO,QAAQ,CAAC,KAAc;QAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;QAC1D,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,KAAc;QAC1B,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;IAED,SAAS,CAAC,KAAc,EAAE,gBAAwB,cAAc;QAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM;YAAE,OAAO,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,CAAC,KAAK,CAAC,CAAC;QACvE,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;YAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACzD,OAAO,SAAS,KAAK,KAAK,CAAC;QAC7B,CAAC;QACD,OAAO,SAAS,KAAK,KAAK,CAAC;IAC7B,CAAC;IAED,iDAAiD;IACjD,IAAI,cAAc;QAChB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACjD,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,GAAY;IACnD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,SAAS,CAAC,WAAW,GAAG,8BAA8B,OAAO,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,MAAM,GAAG,GAAG,GAAU,CAAC;IACvB,IAAI,IAAI,GAAG,GAAG,CAAC,MAAM,CAA2B,CAAC;IACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAC7D,kEAAkE;QAClE,IAAI,QAAQ,IAAI,GAAG;YAAE,IAAI,GAAG,QAAQ,CAAC;aAChC,IAAI,MAAM,IAAI,GAAG;YAAE,IAAI,GAAG,MAAM,CAAC;aACjC,IAAI,OAAO,IAAI,GAAG;YAAE,IAAI,GAAG,OAAO,CAAC;aACnC,CAAC;YACJ,MAAM,IAAI,SAAS,CACjB,WAAW,GAAG,8DAA8D;gBAC1E,yBAAyB,CAC5B,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAgC;IAC3D,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACnF,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** RFC 8785 JSON Canonicalization, and the digests jevkit builds on it. */
|
|
2
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
3
|
+
[k: string]: Json;
|
|
4
|
+
};
|
|
5
|
+
/** Serialize to RFC 8785 canonical form. */
|
|
6
|
+
export declare function canonicalJson(value: unknown): string;
|
|
7
|
+
/** `sha256:<hex>` over the canonical form of `value`. */
|
|
8
|
+
export declare function digest(value: unknown): string;
|
|
9
|
+
/**
|
|
10
|
+
* Digest of state + questions, with no model.
|
|
11
|
+
*
|
|
12
|
+
* This is what pairs a golden-set record with its replay on a different model
|
|
13
|
+
* version, since the full record id includes the model and would differ.
|
|
14
|
+
*/
|
|
15
|
+
export declare function requestId(state: unknown, questions: Record<string, unknown>): string;
|
|
16
|
+
/** Digest of model + state + questions. The record's `id` field. */
|
|
17
|
+
export declare function recordId(model: string, state: unknown, questions: Record<string, unknown>): string;
|
|
18
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAI3E,MAAM,MAAM,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,EAAE,GAAG;IAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAwDrF,4CAA4C;AAC5C,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED,yDAAyD;AACzD,wBAAgB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEpF;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,MAAM,CAER"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** RFC 8785 JSON Canonicalization, and the digests jevkit builds on it. */
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Sort keys by Unicode code point.
|
|
5
|
+
*
|
|
6
|
+
* `Array.prototype.sort` on strings compares UTF-16 code units, which orders
|
|
7
|
+
* astral-plane characters wrongly relative to the code-point order RFC 8785
|
|
8
|
+
* requires. Comparing the code-point arrays directly fixes that.
|
|
9
|
+
*/
|
|
10
|
+
function compareCodePoints(a, b) {
|
|
11
|
+
const ca = Array.from(a).map((c) => c.codePointAt(0));
|
|
12
|
+
const cb = Array.from(b).map((c) => c.codePointAt(0));
|
|
13
|
+
const n = Math.min(ca.length, cb.length);
|
|
14
|
+
for (let i = 0; i < n; i++) {
|
|
15
|
+
const x = ca[i];
|
|
16
|
+
const y = cb[i];
|
|
17
|
+
if (x !== y)
|
|
18
|
+
return x < y ? -1 : 1;
|
|
19
|
+
}
|
|
20
|
+
return ca.length - cb.length;
|
|
21
|
+
}
|
|
22
|
+
function serialize(value) {
|
|
23
|
+
if (value === null)
|
|
24
|
+
return "null";
|
|
25
|
+
const type = typeof value;
|
|
26
|
+
if (type === "boolean")
|
|
27
|
+
return value ? "true" : "false";
|
|
28
|
+
if (type === "number") {
|
|
29
|
+
const n = value;
|
|
30
|
+
if (!Number.isFinite(n)) {
|
|
31
|
+
throw new TypeError(`cannot canonicalize non-finite number: ${n}`);
|
|
32
|
+
}
|
|
33
|
+
// JSON.stringify emits ECMAScript shortest-round-trip form, which is what
|
|
34
|
+
// RFC 8785 specifies for numbers.
|
|
35
|
+
return JSON.stringify(n);
|
|
36
|
+
}
|
|
37
|
+
if (type === "string")
|
|
38
|
+
return JSON.stringify(value);
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return "[" + value.map((v) => serialize(v === undefined ? null : v)).join(",") + "]";
|
|
41
|
+
}
|
|
42
|
+
if (type === "object") {
|
|
43
|
+
const obj = value;
|
|
44
|
+
const keys = Object.keys(obj)
|
|
45
|
+
.filter((k) => obj[k] !== undefined)
|
|
46
|
+
.sort(compareCodePoints);
|
|
47
|
+
const body = keys.map((k) => `${JSON.stringify(k)}:${serialize(obj[k])}`).join(",");
|
|
48
|
+
return "{" + body + "}";
|
|
49
|
+
}
|
|
50
|
+
throw new TypeError(`cannot canonicalize value of type ${type}`);
|
|
51
|
+
}
|
|
52
|
+
/** Serialize to RFC 8785 canonical form. */
|
|
53
|
+
export function canonicalJson(value) {
|
|
54
|
+
return serialize(value);
|
|
55
|
+
}
|
|
56
|
+
/** `sha256:<hex>` over the canonical form of `value`. */
|
|
57
|
+
export function digest(value) {
|
|
58
|
+
return "sha256:" + createHash("sha256").update(canonicalJson(value), "utf8").digest("hex");
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Digest of state + questions, with no model.
|
|
62
|
+
*
|
|
63
|
+
* This is what pairs a golden-set record with its replay on a different model
|
|
64
|
+
* version, since the full record id includes the model and would differ.
|
|
65
|
+
*/
|
|
66
|
+
export function requestId(state, questions) {
|
|
67
|
+
return digest({ questions, state });
|
|
68
|
+
}
|
|
69
|
+
/** Digest of model + state + questions. The record's `id` field. */
|
|
70
|
+
export function recordId(model, state, questions) {
|
|
71
|
+
return digest({ model, questions, state });
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared substrate for the jevkit packages.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here calls the jev API. This package holds the pieces every other
|
|
5
|
+
* jevkit tool needs: canonical digests, the `.jevl` record format, a uniform
|
|
6
|
+
* view of a question, and token budget estimation.
|
|
7
|
+
*/
|
|
8
|
+
export { Answer, NOUL_THRESHOLD, parseAnswer, parseAnswers, type AnswerType, } from "./answers.js";
|
|
9
|
+
export { canonicalJson, digest, recordId, requestId, type Json } from "./canonical.js";
|
|
10
|
+
export { Question, flattenText, normalizeQuestion, normalizeQuestions, type QuestionType, } from "./question.js";
|
|
11
|
+
export { FORMAT_VERSION, Record, RecordFormatError, appendRecord, loadCassette, parseRecords, readRecords, writeRecords, type RecordInit, } from "./record.js";
|
|
12
|
+
export { STATE_BUDGET, TOTAL_BUDGET, checkBudget, estimateTokens, type BudgetReport, } from "./tokens.js";
|
|
13
|
+
export declare const VERSION = "0.2.0";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACvF,OAAO,EACL,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,YAAY,GAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,cAAc,EACd,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,aAAa,CAAC;AAErB,eAAO,MAAM,OAAO,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared substrate for the jevkit packages.
|
|
3
|
+
*
|
|
4
|
+
* Nothing here calls the jev API. This package holds the pieces every other
|
|
5
|
+
* jevkit tool needs: canonical digests, the `.jevl` record format, a uniform
|
|
6
|
+
* view of a question, and token budget estimation.
|
|
7
|
+
*/
|
|
8
|
+
export { Answer, NOUL_THRESHOLD, parseAnswer, parseAnswers, } from "./answers.js";
|
|
9
|
+
export { canonicalJson, digest, recordId, requestId } from "./canonical.js";
|
|
10
|
+
export { Question, flattenText, normalizeQuestion, normalizeQuestions, } from "./question.js";
|
|
11
|
+
export { FORMAT_VERSION, Record, RecordFormatError, appendRecord, loadCassette, parseRecords, readRecords, writeRecords, } from "./record.js";
|
|
12
|
+
export { STATE_BUDGET, TOTAL_BUDGET, checkBudget, estimateTokens, } from "./tokens.js";
|
|
13
|
+
export const VERSION = "0.2.0";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,YAAY,GAEb,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAa,MAAM,gBAAgB,CAAC;AACvF,OAAO,EACL,QAAQ,EACR,WAAW,EACX,iBAAiB,EACjB,kBAAkB,GAEnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,cAAc,EACd,MAAM,EACN,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,GAEb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,cAAc,GAEf,MAAM,aAAa,CAAC;AAErB,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform view of a jev question.
|
|
3
|
+
*
|
|
4
|
+
* Questions arrive as plain objects sent to the HTTP API, as SDK objects, or
|
|
5
|
+
* from a user's own helper. Every jevkit tool wants the same few facts, so they
|
|
6
|
+
* are normalized once here.
|
|
7
|
+
*
|
|
8
|
+
* `instructions` and `criteria` both accept JSON structure, not just strings,
|
|
9
|
+
* so the normalized form keeps the raw value and exposes flattened text
|
|
10
|
+
* separately for textual analysis.
|
|
11
|
+
*/
|
|
12
|
+
export type QuestionType = "choice" | "score" | "noul";
|
|
13
|
+
/** Collapse a string / object / array into one string for textual analysis. */
|
|
14
|
+
export declare function flattenText(value: unknown): string;
|
|
15
|
+
export declare class Question {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly type: QuestionType;
|
|
18
|
+
readonly instructions: unknown;
|
|
19
|
+
readonly criteria: unknown;
|
|
20
|
+
readonly raw: unknown;
|
|
21
|
+
constructor(id: string, type: QuestionType, instructions: unknown, criteria: unknown, raw?: unknown);
|
|
22
|
+
get instructionsText(): string;
|
|
23
|
+
get criteriaText(): string;
|
|
24
|
+
/** Everything the model reads, as one string. */
|
|
25
|
+
get text(): string;
|
|
26
|
+
/** Choice option keys, or Score level descriptions. Empty otherwise. */
|
|
27
|
+
get options(): string[];
|
|
28
|
+
/** [option key, description] pairs for a Choice. */
|
|
29
|
+
optionDescriptions(): Array<[string, string]>;
|
|
30
|
+
}
|
|
31
|
+
export declare function normalizeQuestion(qid: string, obj: unknown): Question;
|
|
32
|
+
export declare function normalizeQuestions(questions: Record<string, unknown>): Question[];
|
|
33
|
+
//# sourceMappingURL=question.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.d.ts","sourceRoot":"","sources":["../src/question.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAIvD,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAgBlD;AAED,qBAAa,QAAQ;IAEjB,QAAQ,CAAC,EAAE,EAAE,MAAM;IACnB,QAAQ,CAAC,IAAI,EAAE,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,OAAO;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO;IAC1B,QAAQ,CAAC,GAAG,EAAE,OAAO;gBAJZ,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,YAAY,EAClB,YAAY,EAAE,OAAO,EACrB,QAAQ,EAAE,OAAO,EACjB,GAAG,GAAE,OAAmB;IAGnC,IAAI,gBAAgB,IAAI,MAAM,CAE7B;IAED,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,iDAAiD;IACjD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,wEAAwE;IACxE,IAAI,OAAO,IAAI,MAAM,EAAE,CAYtB;IAED,oDAAoD;IACpD,kBAAkB,IAAI,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAS9C;AAkBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,QAAQ,CASrE;AAED,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,EAAE,CAEjF"}
|
package/dist/question.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A uniform view of a jev question.
|
|
3
|
+
*
|
|
4
|
+
* Questions arrive as plain objects sent to the HTTP API, as SDK objects, or
|
|
5
|
+
* from a user's own helper. Every jevkit tool wants the same few facts, so they
|
|
6
|
+
* are normalized once here.
|
|
7
|
+
*
|
|
8
|
+
* `instructions` and `criteria` both accept JSON structure, not just strings,
|
|
9
|
+
* so the normalized form keeps the raw value and exposes flattened text
|
|
10
|
+
* separately for textual analysis.
|
|
11
|
+
*/
|
|
12
|
+
const VALID_TYPES = ["choice", "score", "noul"];
|
|
13
|
+
/** Collapse a string / object / array into one string for textual analysis. */
|
|
14
|
+
export function flattenText(value) {
|
|
15
|
+
if (value === null || value === undefined)
|
|
16
|
+
return "";
|
|
17
|
+
if (typeof value === "string")
|
|
18
|
+
return value;
|
|
19
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
20
|
+
return String(value);
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
return value.map(flattenText).filter(Boolean).join(" ");
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === "object") {
|
|
25
|
+
const parts = [];
|
|
26
|
+
for (const [k, v] of Object.entries(value)) {
|
|
27
|
+
// Keys carry meaning: in a Choice they are the option names.
|
|
28
|
+
parts.push(k, flattenText(v));
|
|
29
|
+
}
|
|
30
|
+
return parts.filter(Boolean).join(" ");
|
|
31
|
+
}
|
|
32
|
+
return String(value);
|
|
33
|
+
}
|
|
34
|
+
export class Question {
|
|
35
|
+
id;
|
|
36
|
+
type;
|
|
37
|
+
instructions;
|
|
38
|
+
criteria;
|
|
39
|
+
raw;
|
|
40
|
+
constructor(id, type, instructions, criteria, raw = undefined) {
|
|
41
|
+
this.id = id;
|
|
42
|
+
this.type = type;
|
|
43
|
+
this.instructions = instructions;
|
|
44
|
+
this.criteria = criteria;
|
|
45
|
+
this.raw = raw;
|
|
46
|
+
}
|
|
47
|
+
get instructionsText() {
|
|
48
|
+
return flattenText(this.instructions);
|
|
49
|
+
}
|
|
50
|
+
get criteriaText() {
|
|
51
|
+
return flattenText(this.criteria);
|
|
52
|
+
}
|
|
53
|
+
/** Everything the model reads, as one string. */
|
|
54
|
+
get text() {
|
|
55
|
+
return `${this.instructionsText} ${this.criteriaText}`.trim();
|
|
56
|
+
}
|
|
57
|
+
/** Choice option keys, or Score level descriptions. Empty otherwise. */
|
|
58
|
+
get options() {
|
|
59
|
+
const c = this.criteria;
|
|
60
|
+
if (this.type === "choice" && c && typeof c === "object" && !Array.isArray(c)) {
|
|
61
|
+
return Object.keys(c);
|
|
62
|
+
}
|
|
63
|
+
if (this.type === "score" && Array.isArray(c)) {
|
|
64
|
+
return c.map(flattenText);
|
|
65
|
+
}
|
|
66
|
+
if (this.type === "score" && c && typeof c === "object") {
|
|
67
|
+
return Object.values(c).map(flattenText);
|
|
68
|
+
}
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
/** [option key, description] pairs for a Choice. */
|
|
72
|
+
optionDescriptions() {
|
|
73
|
+
const c = this.criteria;
|
|
74
|
+
if (this.type === "choice" && c && typeof c === "object" && !Array.isArray(c)) {
|
|
75
|
+
return Object.entries(c).map(([k, v]) => [k, flattenText(v)]);
|
|
76
|
+
}
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function get(obj, name) {
|
|
81
|
+
if (obj && typeof obj === "object")
|
|
82
|
+
return obj[name];
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function inferType(obj) {
|
|
86
|
+
const declared = get(obj, "type");
|
|
87
|
+
if (typeof declared === "string" && VALID_TYPES.includes(declared.toLowerCase())) {
|
|
88
|
+
return declared.toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
// SDK objects carry no `type` field; fall back to the constructor name.
|
|
91
|
+
const ctor = obj?.constructor?.name?.toLowerCase();
|
|
92
|
+
if (ctor && VALID_TYPES.includes(ctor))
|
|
93
|
+
return ctor;
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
export function normalizeQuestion(qid, obj) {
|
|
97
|
+
const type = inferType(obj);
|
|
98
|
+
if (type === null) {
|
|
99
|
+
throw new TypeError(`question "${qid}": cannot determine type. Expected a "type" key of ` +
|
|
100
|
+
`${VALID_TYPES.join(" | ")}, or a Choice/Score/Noul object.`);
|
|
101
|
+
}
|
|
102
|
+
return new Question(qid, type, get(obj, "instructions"), get(obj, "criteria"), obj);
|
|
103
|
+
}
|
|
104
|
+
export function normalizeQuestions(questions) {
|
|
105
|
+
return Object.entries(questions).map(([qid, obj]) => normalizeQuestion(qid, obj));
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=question.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"question.js","sourceRoot":"","sources":["../src/question.ts"],"names":[],"mappings":"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"}
|
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Reading and writing `.jevl` records. See docs/specs/record-format-v1.md. */
|
|
2
|
+
/**
|
|
3
|
+
* Local alias for the built-in utility type.
|
|
4
|
+
*
|
|
5
|
+
* This module exports a class named `Record`, which shadows TypeScript's
|
|
6
|
+
* `Record<K, V>` inside the file. Aliasing keeps the public class name while
|
|
7
|
+
* leaving the utility type usable.
|
|
8
|
+
*/
|
|
9
|
+
type Dict<V = unknown> = {
|
|
10
|
+
[key: string]: V;
|
|
11
|
+
};
|
|
12
|
+
export declare const FORMAT_VERSION = 1;
|
|
13
|
+
/** A `.jevl` line is malformed, or its version is unreadable. */
|
|
14
|
+
export declare class RecordFormatError extends Error {
|
|
15
|
+
constructor(message: string);
|
|
16
|
+
}
|
|
17
|
+
export interface RecordInit {
|
|
18
|
+
model: string;
|
|
19
|
+
state: unknown;
|
|
20
|
+
questions: Dict;
|
|
21
|
+
answers: Dict;
|
|
22
|
+
id?: string;
|
|
23
|
+
ts?: string;
|
|
24
|
+
usage?: Dict | null;
|
|
25
|
+
label?: Dict | null;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
meta?: Dict;
|
|
28
|
+
extra?: Dict;
|
|
29
|
+
}
|
|
30
|
+
export declare class Record {
|
|
31
|
+
readonly model: string;
|
|
32
|
+
readonly state: unknown;
|
|
33
|
+
readonly questions: Dict;
|
|
34
|
+
readonly answers: Dict;
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly ts: string;
|
|
37
|
+
readonly usage: Dict | null;
|
|
38
|
+
readonly label: Dict | null;
|
|
39
|
+
readonly tags: string[];
|
|
40
|
+
readonly meta: Dict;
|
|
41
|
+
readonly extra: Dict;
|
|
42
|
+
constructor(init: RecordInit);
|
|
43
|
+
/** Model-independent digest, used to pair across model versions. */
|
|
44
|
+
get requestId(): string;
|
|
45
|
+
toJSON(): Dict;
|
|
46
|
+
static fromJSON(raw: unknown, source?: string, lineNo?: number): Record;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse `.jevl` text into records.
|
|
50
|
+
*
|
|
51
|
+
* A line that fails to parse throws. Skipping silently would let a truncated
|
|
52
|
+
* golden set look like a passing one.
|
|
53
|
+
*/
|
|
54
|
+
export declare function parseRecords(text: string, source?: string): Record[];
|
|
55
|
+
export declare function readRecords(path: string): Record[];
|
|
56
|
+
export declare function writeRecords(path: string, records: Iterable<Record>): number;
|
|
57
|
+
export declare function appendRecord(path: string, record: Record): void;
|
|
58
|
+
/**
|
|
59
|
+
* Map record id -> Record, last occurrence winning.
|
|
60
|
+
*
|
|
61
|
+
* Re-recording a request appends rather than rewrites, so the last line for an
|
|
62
|
+
* id is the current answer.
|
|
63
|
+
*/
|
|
64
|
+
export declare function loadCassette(path: string): Map<string, Record>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAM/E;;;;;;GAMG;AACH,KAAK,IAAI,CAAC,CAAC,GAAG,OAAO,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,CAAC;AAE9C,eAAO,MAAM,cAAc,IAAI,CAAC;AAIhC,iEAAiE;AACjE,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,IAAI,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAMD,qBAAa,MAAM;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC;gBAET,IAAI,EAAE,UAAU;IAc5B,oEAAoE;IACpE,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,MAAM,IAAI,IAAI;IAoBd,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,SAAa,EAAE,MAAM,SAAI,GAAG,MAAM;CAsDvE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,SAAa,GAAG,MAAM,EAAE,CAiBxE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAElD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,CAS5E;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAI9D"}
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** Reading and writing `.jevl` records. See docs/specs/record-format-v1.md. */
|
|
2
|
+
import { appendFileSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { recordId, requestId } from "./canonical.js";
|
|
4
|
+
export const FORMAT_VERSION = 1;
|
|
5
|
+
const REQUIRED = ["v", "id", "ts", "model", "request", "answers"];
|
|
6
|
+
/** A `.jevl` line is malformed, or its version is unreadable. */
|
|
7
|
+
export class RecordFormatError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "RecordFormatError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function utcNow() {
|
|
14
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15
|
+
}
|
|
16
|
+
export class Record {
|
|
17
|
+
model;
|
|
18
|
+
state;
|
|
19
|
+
questions;
|
|
20
|
+
answers;
|
|
21
|
+
id;
|
|
22
|
+
ts;
|
|
23
|
+
usage;
|
|
24
|
+
label;
|
|
25
|
+
tags;
|
|
26
|
+
meta;
|
|
27
|
+
extra;
|
|
28
|
+
constructor(init) {
|
|
29
|
+
this.model = init.model;
|
|
30
|
+
this.state = init.state;
|
|
31
|
+
this.questions = init.questions;
|
|
32
|
+
this.answers = init.answers;
|
|
33
|
+
this.ts = init.ts ?? utcNow();
|
|
34
|
+
this.usage = init.usage ?? null;
|
|
35
|
+
this.label = init.label ?? null;
|
|
36
|
+
this.tags = init.tags ?? [];
|
|
37
|
+
this.meta = init.meta ?? {};
|
|
38
|
+
this.extra = init.extra ?? {};
|
|
39
|
+
this.id = init.id || recordId(this.model, this.state, this.questions);
|
|
40
|
+
}
|
|
41
|
+
/** Model-independent digest, used to pair across model versions. */
|
|
42
|
+
get requestId() {
|
|
43
|
+
return requestId(this.state, this.questions);
|
|
44
|
+
}
|
|
45
|
+
toJSON() {
|
|
46
|
+
const out = {
|
|
47
|
+
v: FORMAT_VERSION,
|
|
48
|
+
id: this.id,
|
|
49
|
+
ts: this.ts,
|
|
50
|
+
model: this.model,
|
|
51
|
+
request: { state: this.state, questions: this.questions },
|
|
52
|
+
answers: this.answers,
|
|
53
|
+
};
|
|
54
|
+
if (this.usage !== null)
|
|
55
|
+
out["usage"] = this.usage;
|
|
56
|
+
if (this.label !== null)
|
|
57
|
+
out["label"] = this.label;
|
|
58
|
+
if (this.tags.length)
|
|
59
|
+
out["tags"] = [...this.tags];
|
|
60
|
+
if (Object.keys(this.meta).length)
|
|
61
|
+
out["meta"] = { ...this.meta };
|
|
62
|
+
// Unknown keys survive a read/write round trip.
|
|
63
|
+
for (const [k, v] of Object.entries(this.extra)) {
|
|
64
|
+
if (!(k in out))
|
|
65
|
+
out[k] = v;
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
static fromJSON(raw, source = "<memory>", lineNo = 0) {
|
|
70
|
+
const where = lineNo ? `${source}:${lineNo}` : source;
|
|
71
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
72
|
+
throw new RecordFormatError(`${where}: record must be a JSON object`);
|
|
73
|
+
}
|
|
74
|
+
const obj = raw;
|
|
75
|
+
const version = obj["v"];
|
|
76
|
+
if (version === undefined) {
|
|
77
|
+
throw new RecordFormatError(`${where}: missing required key 'v'`);
|
|
78
|
+
}
|
|
79
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
80
|
+
throw new RecordFormatError(`${where}: 'v' must be an integer, got ${JSON.stringify(version)}`);
|
|
81
|
+
}
|
|
82
|
+
if (version > FORMAT_VERSION) {
|
|
83
|
+
throw new RecordFormatError(`${where}: record format v${version} is newer than this reader ` +
|
|
84
|
+
`(v${FORMAT_VERSION}). Upgrade jevkit-core rather than guessing.`);
|
|
85
|
+
}
|
|
86
|
+
const missing = REQUIRED.filter((k) => !(k in obj));
|
|
87
|
+
if (missing.length) {
|
|
88
|
+
throw new RecordFormatError(`${where}: missing required key(s): ${missing.join(", ")}`);
|
|
89
|
+
}
|
|
90
|
+
const request = obj["request"];
|
|
91
|
+
if (!request || typeof request !== "object" || !("questions" in request)) {
|
|
92
|
+
throw new RecordFormatError(`${where}: 'request' must be an object with 'questions'`);
|
|
93
|
+
}
|
|
94
|
+
const req = request;
|
|
95
|
+
const known = new Set([...REQUIRED, "usage", "label", "tags", "meta"]);
|
|
96
|
+
const extra = {};
|
|
97
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
98
|
+
if (!known.has(k))
|
|
99
|
+
extra[k] = v;
|
|
100
|
+
}
|
|
101
|
+
return new Record({
|
|
102
|
+
model: obj["model"],
|
|
103
|
+
state: req.state,
|
|
104
|
+
questions: req.questions,
|
|
105
|
+
answers: obj["answers"],
|
|
106
|
+
id: obj["id"],
|
|
107
|
+
ts: obj["ts"],
|
|
108
|
+
usage: obj["usage"] ?? null,
|
|
109
|
+
label: obj["label"] ?? null,
|
|
110
|
+
tags: obj["tags"] ?? [],
|
|
111
|
+
meta: obj["meta"] ?? {},
|
|
112
|
+
extra,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Parse `.jevl` text into records.
|
|
118
|
+
*
|
|
119
|
+
* A line that fails to parse throws. Skipping silently would let a truncated
|
|
120
|
+
* golden set look like a passing one.
|
|
121
|
+
*/
|
|
122
|
+
export function parseRecords(text, source = "<memory>") {
|
|
123
|
+
const out = [];
|
|
124
|
+
const lines = text.split("\n");
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
const line = lines[i].trim();
|
|
127
|
+
if (!line)
|
|
128
|
+
continue;
|
|
129
|
+
let raw;
|
|
130
|
+
try {
|
|
131
|
+
raw = JSON.parse(line);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
throw new RecordFormatError(`${source}:${i + 1}: invalid JSON: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
out.push(Record.fromJSON(raw, source, i + 1));
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
export function readRecords(path) {
|
|
141
|
+
return parseRecords(readFileSync(path, "utf8"), path);
|
|
142
|
+
}
|
|
143
|
+
export function writeRecords(path, records) {
|
|
144
|
+
let count = 0;
|
|
145
|
+
const lines = [];
|
|
146
|
+
for (const record of records) {
|
|
147
|
+
lines.push(JSON.stringify(record.toJSON()));
|
|
148
|
+
count++;
|
|
149
|
+
}
|
|
150
|
+
writeFileSync(path, lines.length ? lines.join("\n") + "\n" : "", "utf8");
|
|
151
|
+
return count;
|
|
152
|
+
}
|
|
153
|
+
export function appendRecord(path, record) {
|
|
154
|
+
appendFileSync(path, JSON.stringify(record.toJSON()) + "\n", "utf8");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Map record id -> Record, last occurrence winning.
|
|
158
|
+
*
|
|
159
|
+
* Re-recording a request appends rather than rewrites, so the last line for an
|
|
160
|
+
* id is the current answer.
|
|
161
|
+
*/
|
|
162
|
+
export function loadCassette(path) {
|
|
163
|
+
const table = new Map();
|
|
164
|
+
for (const record of readRecords(path))
|
|
165
|
+
table.set(record.id, record);
|
|
166
|
+
return table;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token budget estimation for jev requests.
|
|
3
|
+
*
|
|
4
|
+
* Jev ingests the state once and evaluates every question against it, so two
|
|
5
|
+
* budgets apply, both documented on the model card:
|
|
6
|
+
*
|
|
7
|
+
* 64k state + every question combined
|
|
8
|
+
* 32k state + the single longest question
|
|
9
|
+
*
|
|
10
|
+
* These are estimates. jevkit deliberately ships no tokenizer: TypeSafe does
|
|
11
|
+
* not publish which one Jev uses, and a confidently wrong count is worse than
|
|
12
|
+
* an honest approximation. The estimator errs conservative.
|
|
13
|
+
*/
|
|
14
|
+
export declare const TOTAL_BUDGET = 64000;
|
|
15
|
+
export declare const STATE_BUDGET = 32000;
|
|
16
|
+
/** Conservative token estimate for any JSON-serializable value. */
|
|
17
|
+
export declare function estimateTokens(value: unknown): number;
|
|
18
|
+
export interface BudgetReport {
|
|
19
|
+
stateTokens: number;
|
|
20
|
+
questionTokens: Record<string, number>;
|
|
21
|
+
longestQuestionId: string | null;
|
|
22
|
+
/** state + all questions. */
|
|
23
|
+
total: number;
|
|
24
|
+
/** state + the single longest question. */
|
|
25
|
+
longestPair: number;
|
|
26
|
+
overTotal: boolean;
|
|
27
|
+
overState: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function checkBudget(state: unknown, questions: Record<string, unknown>): BudgetReport;
|
|
30
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,eAAO,MAAM,YAAY,QAAS,CAAC;AACnC,eAAO,MAAM,YAAY,QAAS,CAAC;AAUnC,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,YAAY,CA4Bd"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token budget estimation for jev requests.
|
|
3
|
+
*
|
|
4
|
+
* Jev ingests the state once and evaluates every question against it, so two
|
|
5
|
+
* budgets apply, both documented on the model card:
|
|
6
|
+
*
|
|
7
|
+
* 64k state + every question combined
|
|
8
|
+
* 32k state + the single longest question
|
|
9
|
+
*
|
|
10
|
+
* These are estimates. jevkit deliberately ships no tokenizer: TypeSafe does
|
|
11
|
+
* not publish which one Jev uses, and a confidently wrong count is worse than
|
|
12
|
+
* an honest approximation. The estimator errs conservative.
|
|
13
|
+
*/
|
|
14
|
+
import { canonicalJson } from "./canonical.js";
|
|
15
|
+
export const TOTAL_BUDGET = 64_000;
|
|
16
|
+
export const STATE_BUDGET = 32_000;
|
|
17
|
+
// Bytes per token. English on BPE-family tokenizers runs ~4.0; 3.5 buys
|
|
18
|
+
// headroom for punctuation-dense JSON without being absurd.
|
|
19
|
+
const BYTES_PER_TOKEN = 3.5;
|
|
20
|
+
function byteLength(text) {
|
|
21
|
+
return Buffer.byteLength(text, "utf8");
|
|
22
|
+
}
|
|
23
|
+
/** Conservative token estimate for any JSON-serializable value. */
|
|
24
|
+
export function estimateTokens(value) {
|
|
25
|
+
const text = typeof value === "string" ? value : canonicalJson(value);
|
|
26
|
+
return Math.max(1, Math.floor(byteLength(text) / BYTES_PER_TOKEN) + 1);
|
|
27
|
+
}
|
|
28
|
+
export function checkBudget(state, questions) {
|
|
29
|
+
const stateTokens = estimateTokens(state);
|
|
30
|
+
const questionTokens = {};
|
|
31
|
+
for (const [qid, q] of Object.entries(questions)) {
|
|
32
|
+
questionTokens[qid] = estimateTokens(q);
|
|
33
|
+
}
|
|
34
|
+
let longestQuestionId = null;
|
|
35
|
+
for (const [qid, n] of Object.entries(questionTokens)) {
|
|
36
|
+
if (longestQuestionId === null || n > questionTokens[longestQuestionId]) {
|
|
37
|
+
longestQuestionId = qid;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const questionSum = Object.values(questionTokens).reduce((a, b) => a + b, 0);
|
|
41
|
+
const total = stateTokens + questionSum;
|
|
42
|
+
const longestPair = longestQuestionId === null ? stateTokens : stateTokens + questionTokens[longestQuestionId];
|
|
43
|
+
return {
|
|
44
|
+
stateTokens,
|
|
45
|
+
questionTokens,
|
|
46
|
+
longestQuestionId,
|
|
47
|
+
total,
|
|
48
|
+
longestPair,
|
|
49
|
+
overTotal: total > TOTAL_BUDGET,
|
|
50
|
+
overState: longestPair > STATE_BUDGET,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AACnC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AAEnC,wEAAwE;AACxE,4DAA4D;AAC5D,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;AACzE,CAAC;AAcD,MAAM,UAAU,WAAW,CACzB,KAAc,EACd,SAAkC;IAElC,MAAM,WAAW,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,cAAc,GAA2B,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACjD,cAAc,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,IAAI,iBAAiB,GAAkB,IAAI,CAAC;IAC5C,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACtD,IAAI,iBAAiB,KAAK,IAAI,IAAI,CAAC,GAAG,cAAc,CAAC,iBAAiB,CAAE,EAAE,CAAC;YACzE,iBAAiB,GAAG,GAAG,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,WAAW,GAAG,WAAW,CAAC;IACxC,MAAM,WAAW,GACf,iBAAiB,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,GAAG,cAAc,CAAC,iBAAiB,CAAE,CAAC;IAE9F,OAAO;QACL,WAAW;QACX,cAAc;QACd,iBAAiB;QACjB,KAAK;QACL,WAAW;QACX,SAAS,EAAE,KAAK,GAAG,YAAY;QAC/B,SAAS,EAAE,WAAW,GAAG,YAAY;KACtC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jevkit-core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Shared substrate for jevkit: the .jevl record format, canonical request digests, question normalization, and token budgets for TypeSafe's Jev.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jev",
|
|
7
|
+
"typesafe",
|
|
8
|
+
"system-one",
|
|
9
|
+
"llm",
|
|
10
|
+
"calibration"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/pjdurden/jevkit-js.git"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc -b"
|
|
35
|
+
}
|
|
36
|
+
}
|