bare-agent 0.46.0 → 0.46.1
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/bareagent.context.md +2 -2
- package/package.json +1 -1
- package/src/provider-jev.d.ts +10 -6
- package/src/provider-jev.js +30 -7
package/bareagent.context.md
CHANGED
|
@@ -254,9 +254,9 @@ const { answers, costUsd, raw } = await jev.classify('I was charged twice, pleas
|
|
|
254
254
|
// raw → the full unmodified parsed Jev response (a request id / warnings beyond answers/usage/model, if any)
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
Three question types, kept verbatim from Jev's own contract: `noul` (binary probability `0..1`), `choice` (pick one of `criteria`'s keys, plus untrusted `probabilities`/`confidence`), `score` (a position `0..N-1` on `criteria`'s legend). Jev's reply is **untrusted model output** — `classify()` schema-checks the discriminator field against the question that asked it (`ValidationError`, stamped `lib:'bare-agent'`, on a type mismatch or an out-of-range value) before returning it; `probabilities`/`confidence` pass through unvalidated.
|
|
257
|
+
Three question types, kept verbatim from Jev's own contract: `noul` (binary probability `0..1`), `choice` (pick one of `criteria`'s keys, plus untrusted `probabilities`/`confidence`), `score` (a position `0..N-1` on `criteria`'s legend). Jev's reply is **untrusted model output** — `classify()` schema-checks the discriminator field against the question that asked it (`ValidationError`, stamped `lib:'bare-agent'`, on a type mismatch or an out-of-range value) before returning it; `probabilities`/`confidence` pass through unvalidated. A question's `instructions` can be a string, or the documented TypeSafe structured forms — an array or a plain object with named parts (e.g. `question`/`inspect`/`focus`/`ignore`) — for giving a question multiple named parts; empty `{}`/`[]`/non-plain values are rejected as missing instructions.
|
|
258
258
|
|
|
259
|
-
**Injection hardening is on by default.** `state` is untrusted and can carry an embedded attack (`"you are now…"`, `"ignore previous instructions"`); `classify()`
|
|
259
|
+
**Injection hardening is on by default, and shape-aware.** `state` is untrusted and can carry an embedded attack (`"you are now…"`, `"ignore previous instructions"`); `classify()` wraps every question's `instructions` with a defensive preamble — prefixed onto a string, prepended as element 0 of an array, or added under a reserved `__hardening__` key on an object — so the classifier treats `state` as data, not commands (copy-on-write, never mutates your `questions` object; a caller-supplied `__hardening__` key is rejected so it can't overwrite the preamble). Opt out with `harden: false` on the constructor or per call.
|
|
260
260
|
|
|
261
261
|
**Calibrate a tier before you trust it.** `calibrateJev` (exported from `bare-agent`, mirrors `calibrate`/judge's harness) grades a Jev model against a frozen clear-case battery (`noul`/`choice`/`score` + a false-positive trap) **and** a multi-style injection battery, admitting only if the clear-case floor clears with zero reds **and** every injection style is resisted:
|
|
262
262
|
|
package/package.json
CHANGED
package/src/provider-jev.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export class JevProvider {
|
|
|
21
21
|
* @param {number} [options.deadlineMs] - Total call-duration deadline (ms); 0 disables (default).
|
|
22
22
|
* @param {{in: number, out: number, cacheReadMult?: number, cacheWriteMult?: number}} [options.rates] - Per-1K-token USD rates for authoritative pricing (Jev: `{ in: 0.042/1000, out: 0 }`).
|
|
23
23
|
* @param {boolean} [options.exposeErrorBody=false] - Include the raw error body on a ProviderError (default off).
|
|
24
|
-
* @param {boolean} [options.harden=true] -
|
|
24
|
+
* @param {boolean} [options.harden=true] - Wrap each question's instructions with a defensive preamble (prefixed for a string, prepended as element 0 for an array, or added under a reserved key for an object), treating `state` as untrusted data and resisting embedded role/label-override attempts. Overridable per-call via `opts.harden`.
|
|
25
25
|
*/
|
|
26
26
|
constructor(options?: {
|
|
27
27
|
apiKey?: string | undefined;
|
|
@@ -54,7 +54,7 @@ export class JevProvider {
|
|
|
54
54
|
/**
|
|
55
55
|
* Classify `state` against one or more typed `questions`. See the class doc for the primitive tags.
|
|
56
56
|
* @param {string|object|any[]} state - The shared input all questions judge (Jev's `state`).
|
|
57
|
-
* @param {Record<string, {type: 'noul'|'choice'|'score', instructions: string, criteria?: any}>} questions - Keyed questions; each judged independently against `state`.
|
|
57
|
+
* @param {Record<string, {type: 'noul'|'choice'|'score', instructions: string|object|any[], criteria?: any}>} questions - Keyed questions; each judged independently against `state`.
|
|
58
58
|
* @param {object} [opts]
|
|
59
59
|
* @param {string} [opts.model] - Override the model for this call.
|
|
60
60
|
* @param {{in: number, out: number, cacheReadMult?: number, cacheWriteMult?: number}} [opts.rates] - Override rates for this call.
|
|
@@ -68,7 +68,7 @@ export class JevProvider {
|
|
|
68
68
|
*/
|
|
69
69
|
classify(state: string | object | any[], questions: Record<string, {
|
|
70
70
|
type: "noul" | "choice" | "score";
|
|
71
|
-
instructions: string;
|
|
71
|
+
instructions: string | object | any[];
|
|
72
72
|
criteria?: any;
|
|
73
73
|
}>, opts?: {
|
|
74
74
|
model?: string | undefined;
|
|
@@ -101,9 +101,13 @@ export class JevProvider {
|
|
|
101
101
|
*/
|
|
102
102
|
_validateRequest(state: any, questions: any): void;
|
|
103
103
|
/**
|
|
104
|
-
* Build a hardened COPY of `questions` (new object, new nested question objects
|
|
105
|
-
* {@link HARDENING_PREAMBLE}
|
|
106
|
-
*
|
|
104
|
+
* Build a hardened COPY of `questions` (new object, new nested question objects, new
|
|
105
|
+
* nested instructions containers) with {@link HARDENING_PREAMBLE} woven into each
|
|
106
|
+
* `instructions`, shape-aware so a structured form is wrapped rather than stringified:
|
|
107
|
+
* a string gets the preamble prefixed, an array gets it prepended as element 0, and an
|
|
108
|
+
* object gets it added under the reserved {@link HARDENING_KEY}. Never mutates the
|
|
109
|
+
* caller's `questions` argument or its nested objects/arrays.
|
|
110
|
+
* @param {Record<string, any>} questions @returns {Record<string, any>}
|
|
107
111
|
*/
|
|
108
112
|
_hardenQuestions(questions: Record<string, any>): Record<string, any>;
|
|
109
113
|
/**
|
package/src/provider-jev.js
CHANGED
|
@@ -36,6 +36,9 @@ const CLASSIFY_PATH = '/v1/systemone';
|
|
|
36
36
|
const DEFAULT_MODEL = 'jev-latest';
|
|
37
37
|
const QUESTION_TYPES = new Set(['noul', 'choice', 'score']);
|
|
38
38
|
const JEV_USAGE_KEYS = ['input_tokens', 'output_tokens'];
|
|
39
|
+
// Reserved key hardening uses to carry the preamble on an object-shaped `instructions` — a
|
|
40
|
+
// caller-supplied key of the same name would silently overwrite the preamble on spread.
|
|
41
|
+
const HARDENING_KEY = '__hardening__';
|
|
39
42
|
|
|
40
43
|
// Injection hardening (default on): prepended to each question's `instructions` before the
|
|
41
44
|
// request is sent, so an attack embedded in `state` (untrusted) can't hijack the classifier's
|
|
@@ -73,7 +76,7 @@ class JevProvider {
|
|
|
73
76
|
* @param {number} [options.deadlineMs] - Total call-duration deadline (ms); 0 disables (default).
|
|
74
77
|
* @param {{in: number, out: number, cacheReadMult?: number, cacheWriteMult?: number}} [options.rates] - Per-1K-token USD rates for authoritative pricing (Jev: `{ in: 0.042/1000, out: 0 }`).
|
|
75
78
|
* @param {boolean} [options.exposeErrorBody=false] - Include the raw error body on a ProviderError (default off).
|
|
76
|
-
* @param {boolean} [options.harden=true] -
|
|
79
|
+
* @param {boolean} [options.harden=true] - Wrap each question's instructions with a defensive preamble (prefixed for a string, prepended as element 0 for an array, or added under a reserved key for an object), treating `state` as untrusted data and resisting embedded role/label-override attempts. Overridable per-call via `opts.harden`.
|
|
77
80
|
*/
|
|
78
81
|
constructor(options = {}) {
|
|
79
82
|
this.apiKey = options.apiKey;
|
|
@@ -89,7 +92,7 @@ class JevProvider {
|
|
|
89
92
|
/**
|
|
90
93
|
* Classify `state` against one or more typed `questions`. See the class doc for the primitive tags.
|
|
91
94
|
* @param {string|object|any[]} state - The shared input all questions judge (Jev's `state`).
|
|
92
|
-
* @param {Record<string, {type: 'noul'|'choice'|'score', instructions: string, criteria?: any}>} questions - Keyed questions; each judged independently against `state`.
|
|
95
|
+
* @param {Record<string, {type: 'noul'|'choice'|'score', instructions: string|object|any[], criteria?: any}>} questions - Keyed questions; each judged independently against `state`.
|
|
93
96
|
* @param {object} [opts]
|
|
94
97
|
* @param {string} [opts.model] - Override the model for this call.
|
|
95
98
|
* @param {{in: number, out: number, cacheReadMult?: number, cacheWriteMult?: number}} [opts.rates] - Override rates for this call.
|
|
@@ -139,7 +142,14 @@ class JevProvider {
|
|
|
139
142
|
const q = questions[id];
|
|
140
143
|
if (!isPlainObject(q)) throw invalid(`question "${id}" must be an object`);
|
|
141
144
|
if (!QUESTION_TYPES.has(q.type)) throw invalid(`question "${id}" has invalid type`, { type: q.type });
|
|
142
|
-
|
|
145
|
+
const ins = q.instructions;
|
|
146
|
+
const insValid = (typeof ins === 'string' && ins.length > 0) ||
|
|
147
|
+
(Array.isArray(ins) && ins.length > 0) ||
|
|
148
|
+
(isPlainObject(ins) && Object.keys(ins).length > 0);
|
|
149
|
+
if (!insValid) throw invalid(`question "${id}" missing instructions`);
|
|
150
|
+
if (isPlainObject(q.instructions) && Object.prototype.hasOwnProperty.call(q.instructions, HARDENING_KEY)) {
|
|
151
|
+
throw invalid(`question "${id}" may not use reserved instructions key`, { key: HARDENING_KEY });
|
|
152
|
+
}
|
|
143
153
|
if (q.type === 'choice') {
|
|
144
154
|
if (!isPlainObject(q.criteria) || Object.keys(q.criteria).length < 2) {
|
|
145
155
|
throw invalid(`choice "${id}" needs a criteria object of >=2 {key: description}`);
|
|
@@ -156,16 +166,29 @@ class JevProvider {
|
|
|
156
166
|
}
|
|
157
167
|
|
|
158
168
|
/**
|
|
159
|
-
* Build a hardened COPY of `questions` (new object, new nested question objects
|
|
160
|
-
* {@link HARDENING_PREAMBLE}
|
|
161
|
-
*
|
|
169
|
+
* Build a hardened COPY of `questions` (new object, new nested question objects, new
|
|
170
|
+
* nested instructions containers) with {@link HARDENING_PREAMBLE} woven into each
|
|
171
|
+
* `instructions`, shape-aware so a structured form is wrapped rather than stringified:
|
|
172
|
+
* a string gets the preamble prefixed, an array gets it prepended as element 0, and an
|
|
173
|
+
* object gets it added under the reserved {@link HARDENING_KEY}. Never mutates the
|
|
174
|
+
* caller's `questions` argument or its nested objects/arrays.
|
|
175
|
+
* @param {Record<string, any>} questions @returns {Record<string, any>}
|
|
162
176
|
*/
|
|
163
177
|
_hardenQuestions(questions) {
|
|
164
178
|
/** @type {Record<string, any>} */
|
|
165
179
|
const hardened = {};
|
|
166
180
|
for (const id of Object.keys(questions)) {
|
|
167
181
|
const q = questions[id];
|
|
168
|
-
|
|
182
|
+
const ins = q.instructions;
|
|
183
|
+
let hardenedIns;
|
|
184
|
+
if (typeof ins === 'string') {
|
|
185
|
+
hardenedIns = HARDENING_PREAMBLE + ins;
|
|
186
|
+
} else if (Array.isArray(ins)) {
|
|
187
|
+
hardenedIns = [HARDENING_PREAMBLE, ...ins];
|
|
188
|
+
} else {
|
|
189
|
+
hardenedIns = { [HARDENING_KEY]: HARDENING_PREAMBLE, ...ins };
|
|
190
|
+
}
|
|
191
|
+
hardened[id] = { ...q, instructions: hardenedIns };
|
|
169
192
|
}
|
|
170
193
|
return hardened;
|
|
171
194
|
}
|