@codebam/jev-guardrails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/dist/batteries.d.ts +12 -0
  4. package/dist/batteries.d.ts.map +1 -0
  5. package/dist/batteries.js +170 -0
  6. package/dist/batteries.js.map +1 -0
  7. package/dist/cache.d.ts +26 -0
  8. package/dist/cache.d.ts.map +1 -0
  9. package/dist/cache.js +46 -0
  10. package/dist/cache.js.map +1 -0
  11. package/dist/client.d.ts +65 -0
  12. package/dist/client.d.ts.map +1 -0
  13. package/dist/client.js +197 -0
  14. package/dist/client.js.map +1 -0
  15. package/dist/guardrails.d.ts +58 -0
  16. package/dist/guardrails.d.ts.map +1 -0
  17. package/dist/guardrails.js +386 -0
  18. package/dist/guardrails.js.map +1 -0
  19. package/dist/heuristics.d.ts +33 -0
  20. package/dist/heuristics.d.ts.map +1 -0
  21. package/dist/heuristics.js +370 -0
  22. package/dist/heuristics.js.map +1 -0
  23. package/dist/index.d.ts +30 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +30 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/openrouter.d.ts +65 -0
  28. package/dist/openrouter.d.ts.map +1 -0
  29. package/dist/openrouter.js +205 -0
  30. package/dist/openrouter.js.map +1 -0
  31. package/dist/policies.d.ts +48 -0
  32. package/dist/policies.d.ts.map +1 -0
  33. package/dist/policies.js +167 -0
  34. package/dist/policies.js.map +1 -0
  35. package/dist/redact.d.ts +18 -0
  36. package/dist/redact.d.ts.map +1 -0
  37. package/dist/redact.js +124 -0
  38. package/dist/redact.js.map +1 -0
  39. package/dist/render.d.ts +26 -0
  40. package/dist/render.d.ts.map +1 -0
  41. package/dist/render.js +90 -0
  42. package/dist/render.js.map +1 -0
  43. package/dist/types.d.ts +329 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +2 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/util.d.ts +42 -0
  48. package/dist/util.d.ts.map +1 -0
  49. package/dist/util.js +102 -0
  50. package/dist/util.js.map +1 -0
  51. package/package.json +56 -0
package/dist/client.js ADDED
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The Jev call layer: official SDK client, state preparation, cache, timeout,
3
+ * abort handling, and response validation.
4
+ *
5
+ * @module @codebam/jev-guardrails/client
6
+ */
7
+ import { TypeSafeClient, TypeSafeError } from '@typesafe-ai/sdk';
8
+ import { createHash } from 'node:crypto';
9
+ import { ResponseCache } from './cache.js';
10
+ import { OpenRouterDecisionsTransport } from './openrouter.js';
11
+ import { redactState, redactString } from './redact.js';
12
+ import { isRecord, stableStringify, truncateMiddle } from './util.js';
13
+ /** Error raised by configuration, transport, or response-validation failures. */
14
+ export class GuardrailsError extends Error {
15
+ code;
16
+ constructor(code, message, cause) {
17
+ super(message, cause === undefined ? undefined : { cause });
18
+ this.name = 'GuardrailsError';
19
+ this.code = code;
20
+ }
21
+ }
22
+ /** Build the default Jev transport, or return an injected one unchanged. */
23
+ export function createTransport(options) {
24
+ if (options.client !== undefined)
25
+ return options.client;
26
+ if (options.provider === 'openrouter') {
27
+ const apiKey = options.apiKey ?? process.env.OPENROUTER_API_KEY ?? '';
28
+ if (apiKey.trim().length === 0) {
29
+ throw new GuardrailsError('CONFIG', 'an OpenRouter API key is required for provider "openrouter". Set OPENROUTER_API_KEY or pass apiKey explicitly.');
30
+ }
31
+ return new OpenRouterDecisionsTransport({
32
+ apiKey,
33
+ ...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
34
+ model: options.model,
35
+ ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),
36
+ ...(options.retries !== undefined ? { retry: options.retries } : {}),
37
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
38
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
39
+ });
40
+ }
41
+ try {
42
+ return new TypeSafeClient({
43
+ ...(options.apiKey !== undefined && options.apiKey.trim().length > 0 ? { apiKey: options.apiKey } : {}),
44
+ ...(options.baseURL !== undefined && options.baseURL.trim().length > 0 ? { baseURL: options.baseURL } : {}),
45
+ defaultModel: options.model,
46
+ timeout: options.timeoutMs,
47
+ ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),
48
+ ...(options.retries !== undefined ? { retry: options.retries } : {}),
49
+ dangerouslyAllowBrowser: false,
50
+ });
51
+ }
52
+ catch (error) {
53
+ const message = error instanceof TypeSafeError ? error.message : `could not create the TypeSafe client: ${String(error)}`;
54
+ throw new GuardrailsError('CONFIG', `${message} Set TYPESAFE_API_KEY or pass apiKey/client explicitly.`, error);
55
+ }
56
+ }
57
+ /** Owns exactly one transport and its local policy around calls. */
58
+ export class JevCaller {
59
+ transport;
60
+ model;
61
+ cache;
62
+ redactor;
63
+ maxStateChars;
64
+ timeoutMs;
65
+ retries;
66
+ constructor(options) {
67
+ this.transport = options.transport;
68
+ this.model = options.model;
69
+ this.cache = options.cache === false ? undefined : new ResponseCache(options.cache, options.now);
70
+ this.redactor = options.redact;
71
+ this.maxStateChars = Math.max(64, options.maxStateChars);
72
+ this.timeoutMs = Math.max(100, options.timeoutMs);
73
+ this.retries = options.retries;
74
+ }
75
+ /** Remove every cached answer. */
76
+ clearCache() {
77
+ this.cache?.clear();
78
+ }
79
+ /** Number of live cache entries. */
80
+ get cacheSize() {
81
+ return this.cache?.size ?? 0;
82
+ }
83
+ /** Prepare, call, validate, and optionally cache one System One request. */
84
+ async ask(request, options = {}) {
85
+ const prepared = this.prepareRequest(request);
86
+ const cacheKey = this.cacheKey(prepared);
87
+ if (this.cache !== undefined && options.cache !== false) {
88
+ const cached = this.cache.get(cacheKey);
89
+ if (cached !== undefined)
90
+ return { result: cached, cached: true };
91
+ }
92
+ const timeout = AbortSignal.timeout(this.timeoutMs);
93
+ const linked = linkSignals(options.signal, timeout);
94
+ let result;
95
+ try {
96
+ const requestOptions = {
97
+ signal: linked.signal,
98
+ timeout: this.timeoutMs,
99
+ ...(this.retries !== undefined ? { retry: this.retries } : {}),
100
+ };
101
+ result = (await this.transport.systemOne(prepared, requestOptions));
102
+ }
103
+ catch (error) {
104
+ if (options.signal?.aborted === true) {
105
+ throw new GuardrailsError('ABORTED', 'the Jev request was cancelled by the caller', error);
106
+ }
107
+ if (timeout.aborted) {
108
+ throw new GuardrailsError('TRANSPORT', `the Jev request timed out after ${this.timeoutMs}ms`, error);
109
+ }
110
+ const detail = error instanceof TypeSafeError ? error.message : error instanceof Error ? error.message : String(error);
111
+ throw new GuardrailsError('TRANSPORT', `the Jev request failed: ${detail}`, error);
112
+ }
113
+ finally {
114
+ linked.dispose();
115
+ }
116
+ assertAnswers(result, prepared.questions);
117
+ if (this.cache !== undefined && options.cache !== false)
118
+ this.cache.set(cacheKey, result);
119
+ return { result, cached: false };
120
+ }
121
+ prepareRequest(request) {
122
+ const model = request.model ?? this.model;
123
+ const state = this.prepareState(request.state);
124
+ return { ...request, model, state };
125
+ }
126
+ prepareState(state) {
127
+ if (state === null)
128
+ return null;
129
+ if (typeof state === 'string') {
130
+ const redacted = this.redactor === false ? state : redactString(state, this.redactor);
131
+ return truncateMiddle(redacted, this.maxStateChars).text;
132
+ }
133
+ const redacted = this.redactor === false ? state : redactState(state, this.redactor);
134
+ const serialized = stableStringify(redacted);
135
+ if (serialized.length <= this.maxStateChars)
136
+ return redacted;
137
+ return truncateMiddle(serialized, this.maxStateChars).text;
138
+ }
139
+ cacheKey(request) {
140
+ const hash = createHash('sha256');
141
+ hash.update(stableStringify({
142
+ model: request.model,
143
+ state: request.state,
144
+ questions: request.questions,
145
+ }));
146
+ return hash.digest('hex');
147
+ }
148
+ }
149
+ function linkSignals(...signals) {
150
+ const controller = new AbortController();
151
+ const listeners = [];
152
+ for (const signal of signals) {
153
+ if (signal === undefined)
154
+ continue;
155
+ if (signal.aborted) {
156
+ controller.abort(signal.reason);
157
+ break;
158
+ }
159
+ const listener = () => controller.abort(signal.reason);
160
+ signal.addEventListener('abort', listener, { once: true });
161
+ listeners.push({ signal, listener });
162
+ }
163
+ return {
164
+ signal: controller.signal,
165
+ dispose() {
166
+ for (const entry of listeners)
167
+ entry.signal.removeEventListener('abort', entry.listener);
168
+ },
169
+ };
170
+ }
171
+ function assertAnswers(result, questions) {
172
+ if (!isRecord(result) || !isRecord(result.answers)) {
173
+ throw new GuardrailsError('MALFORMED', 'the Jev response did not contain an answers object');
174
+ }
175
+ for (const [name, question] of Object.entries(questions)) {
176
+ const answer = result.answers[name];
177
+ if (!isRecord(answer)) {
178
+ throw new GuardrailsError('MALFORMED', `the Jev response is missing the answer "${name}"`);
179
+ }
180
+ if (answer.type !== question.type) {
181
+ throw new GuardrailsError('MALFORMED', `the Jev answer "${name}" has type "${String(answer.type)}", expected "${question.type}"`);
182
+ }
183
+ if (question.type === 'noul' && !isProbability(answer.noul)) {
184
+ throw new GuardrailsError('MALFORMED', `the Jev noul answer "${name}" is not a probability`);
185
+ }
186
+ if (question.type === 'score' && (typeof answer.score !== 'number' || !Number.isFinite(answer.score))) {
187
+ throw new GuardrailsError('MALFORMED', `the Jev score answer "${name}" is not finite`);
188
+ }
189
+ if (question.type === 'choice' && typeof answer.choice !== 'string') {
190
+ throw new GuardrailsError('MALFORMED', `the Jev choice answer "${name}" is not a label`);
191
+ }
192
+ }
193
+ }
194
+ function isProbability(value) {
195
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1;
196
+ }
197
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAShE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEvD,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAErE,iFAAiF;AACjF,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAC/B,IAAI,CAAkD;IAE/D,YAAY,IAA6B,EAAE,OAAe,EAAE,KAAe;QACzE,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;QAC3D,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAClB,CAAC;CACF;AAgBD,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,MAAM,CAAA;IACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,YAAY,EAAE,CAAC;QACtC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAA;QACrE,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,eAAe,CACvB,QAAQ,EACR,gHAAgH,CACjH,CAAA;QACH,CAAC;QACD,OAAO,IAAI,4BAA4B,CAAC;YACtC,MAAM;YACN,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CAAA;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,cAAc,CAAC;YACxB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvG,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3G,YAAY,EAAE,OAAO,CAAC,KAAK;YAC3B,OAAO,EAAE,OAAO,CAAC,SAAS;YAC1B,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpE,uBAAuB,EAAE,KAAK;SAC/B,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,yCAAyC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAA;QACzH,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,GAAG,OAAO,yDAAyD,EAAE,KAAK,CAAC,CAAA;IACjH,CAAC;AACH,CAAC;AA2BD,oEAAoE;AACpE,MAAM,OAAO,SAAS;IACH,SAAS,CAAc;IACvB,KAAK,CAAQ;IACb,KAAK,CAA2B;IAChC,QAAQ,CAAyB;IACjC,aAAa,CAAQ;IACrB,SAAS,CAAQ;IACjB,OAAO,CAAkC;IAE1D,YAAY,OAAsB;QAChC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;QAClC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;QAC1B,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAA;QAChG,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAA;QAC9B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC,aAAa,CAAC,CAAA;QACxD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QACjD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAA;IAChC,CAAC;IAED,kCAAkC;IAClC,UAAU;QACR,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,CAAA;IACrB,CAAC;IAED,oCAAoC;IACpC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,KAAK,EAAE,IAAI,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,4EAA4E;IAC5E,KAAK,CAAC,GAAG,CACP,OAA4B,EAC5B,UAAsB,EAAE;QAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,CAAA;QAC7C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QACxC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,QAAQ,CAAC,CAAA;YAC3D,IAAI,MAAM,KAAK,SAAS;gBAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;QACnE,CAAC;QAED,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACnD,IAAI,MAA0B,CAAA;QAC9B,IAAI,CAAC;YACH,MAAM,cAAc,GAAmB;gBACrC,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,OAAO,EAAE,IAAI,CAAC,SAAS;gBACvB,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC/D,CAAA;YACD,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAuB,CAAA;QAC3F,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,EAAE,CAAC;gBACrC,MAAM,IAAI,eAAe,CAAC,SAAS,EAAE,6CAA6C,EAAE,KAAK,CAAC,CAAA;YAC5F,CAAC;YACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,mCAAmC,IAAI,CAAC,SAAS,IAAI,EAAE,KAAK,CAAC,CAAA;YACtG,CAAC;YACD,MAAM,MAAM,GAAG,KAAK,YAAY,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACtH,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,2BAA2B,MAAM,EAAE,EAAE,KAAK,CAAC,CAAA;QACpF,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,OAAO,EAAE,CAAA;QAClB,CAAC;QAED,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAA;QACzC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,KAAK,KAAK;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;QACzF,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;IAClC,CAAC;IAEO,cAAc,CAAsB,OAA4B;QACtE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAA;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;QAC9C,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAA;IACrC,CAAC;IAEO,YAAY,CAAC,KAAgC;QACnD,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,IAAI,CAAA;QAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;YACrF,OAAO,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAA;QAC1D,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACpF,MAAM,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAA;QAC5C,IAAI,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC,aAAa;YAAE,OAAO,QAAQ,CAAA;QAC5D,OAAO,cAAc,CAAC,UAAU,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAA;IAC5D,CAAC;IAEO,QAAQ,CAAC,OAAyB;QACxC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAA;QACjC,IAAI,CAAC,MAAM,CACT,eAAe,CAAC;YACd,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC,CACH,CAAA;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC3B,CAAC;CACF;AAOD,SAAS,WAAW,CAAC,GAAG,OAAuC;IAC7D,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,MAAM,SAAS,GAAyD,EAAE,CAAA;IAC1E,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,SAAQ;QAClC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;YAC/B,MAAK;QACP,CAAC;QACD,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACtD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1D,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;IACtC,CAAC;IACD,OAAO;QACL,MAAM,EAAE,UAAU,CAAC,MAAM;QACzB,OAAO;YACL,KAAK,MAAM,KAAK,IAAI,SAAS;gBAAE,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAA;QAC1F,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,aAAa,CAAsB,MAA0B,EAAE,SAAY;IAClF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,oDAAoD,CAAC,CAAA;IAC9F,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;QACnC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,2CAA2C,IAAI,GAAG,CAAC,CAAA;QAC5F,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,mBAAmB,IAAI,eAAe,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAA;QACnI,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,wBAAwB,IAAI,wBAAwB,CAAC,CAAA;QAC9F,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YACtG,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,yBAAyB,IAAI,iBAAiB,CAAC,CAAA;QACxF,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YACpE,MAAM,IAAI,eAAe,CAAC,WAAW,EAAE,0BAA0B,IAAI,kBAAkB,CAAC,CAAA;QAC1F,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AACxF,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { Questions, SystemOneRequest } from '@typesafe-ai/sdk';
2
+ import type { AskOptions, AskResult } from './client.js';
3
+ import { DEFAULT_PRECEDENCE } from './policies.js';
4
+ import type { ActionDescriptor, AssessActionOptions, ClaimVerdict, GuardVerdict, GuardrailsStats, JevGuardrailsOptions, ScreenOptions, VerifyClaimOptions } from './types.js';
5
+ /**
6
+ * Jev-backed guardrails with one local cache, one policy set, and one
7
+ * transport.
8
+ *
9
+ * The class is safe to share across requests; each call is independent and
10
+ * cancellation is per call.
11
+ */
12
+ export declare class JevGuardrails {
13
+ private readonly caller;
14
+ private readonly model;
15
+ private readonly heuristics;
16
+ private readonly batteries;
17
+ private readonly policyOverrides;
18
+ private readonly onVerdict;
19
+ private readonly onError;
20
+ private readonly counters;
21
+ constructor(options?: JevGuardrailsOptions);
22
+ /** Low-level ask against the same transport, cache, and redactor. */
23
+ ask<Q extends Questions>(request: SystemOneRequest<Q>, options?: AskOptions): Promise<AskResult<Q>>;
24
+ /** Screen a user prompt before it reaches the model. */
25
+ screenInput(text: string, options?: ScreenOptions): Promise<GuardVerdict>;
26
+ /** Screen a model response before the user sees it. */
27
+ screenOutput(text: string, options?: ScreenOptions): Promise<GuardVerdict>;
28
+ /** Screen untrusted text a model is about to read. */
29
+ screenObservation(text: string, options?: ScreenOptions): Promise<GuardVerdict>;
30
+ /**
31
+ * Score one proposed tool call before execution.
32
+ *
33
+ * Local rules resolve routine and obviously dangerous calls without a model
34
+ * call; everything ambiguous goes to Jev.
35
+ */
36
+ assessAction(action: ActionDescriptor | string, options?: AssessActionOptions): Promise<GuardVerdict>;
37
+ /**
38
+ * Verify one claim against its evidence.
39
+ *
40
+ * A supplied quote that is not present in the evidence is `fabricated`
41
+ * without spending a model call; everything else goes to one Choice question.
42
+ */
43
+ verifyClaim(options: VerifyClaimOptions): Promise<ClaimVerdict>;
44
+ /** Drop every cached answer. */
45
+ clearCache(): void;
46
+ /** Aggregate counters since construction. */
47
+ get stats(): GuardrailsStats;
48
+ private policyFor;
49
+ private screenText;
50
+ private screenState;
51
+ private buildVerdict;
52
+ private localVerdict;
53
+ private failureVerdict;
54
+ }
55
+ /** Create a guardrails instance with the built-in batteries and policies. */
56
+ export declare function createGuardrails(options?: JevGuardrailsOptions): JevGuardrails;
57
+ export { DEFAULT_PRECEDENCE };
58
+ //# sourceMappingURL=guardrails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guardrails.d.ts","sourceRoot":"","sources":["../src/guardrails.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGnE,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAA8B,MAAM,eAAe,CAAA;AAE9E,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EAEnB,YAAY,EAKZ,YAAY,EACZ,eAAe,EACf,oBAAoB,EAEpB,aAAa,EACb,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAKnB;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA4B;IACtD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA8C;IAC9E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+C;IACzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuF;IAC/G,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAMxB;gBAEW,OAAO,GAAE,oBAAyB;IA4C9C,qEAAqE;IAC/D,GAAG,CAAC,CAAC,SAAS,SAAS,EAC3B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC5B,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAIxB,wDAAwD;IAClD,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAInF,uDAAuD;IACjD,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAIpF,sDAAsD;IAChD,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IAIzF;;;;;OAKG;IACG,YAAY,CAChB,MAAM,EAAE,gBAAgB,GAAG,MAAM,EACjC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,YAAY,CAAC;IAoBxB;;;;;OAKG;IACG,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC;IA2ErE,gCAAgC;IAChC,UAAU,IAAI,IAAI;IAIlB,6CAA6C;IAC7C,IAAI,KAAK,IAAI,eAAe,CAQ3B;IAED,OAAO,CAAC,SAAS;YAMH,UAAU;YAMV,WAAW;IAgDzB,OAAO,CAAC,YAAY;IAmDpB,OAAO,CAAC,YAAY;IAqBpB,OAAO,CAAC,cAAc;CAqBvB;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAElF;AAkED,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
@@ -0,0 +1,386 @@
1
+ /**
2
+ * `JevGuardrails`: the public entry point that ties batteries, policies, the
3
+ * Jev call layer, local heuristics, and claim verification together.
4
+ *
5
+ * @module @codebam/jev-guardrails/guardrails
6
+ */
7
+ import { choice } from '@typesafe-ai/sdk';
8
+ import { ACTION_BATTERY, INPUT_BATTERY, OBSERVATION_BATTERY, OUTPUT_BATTERY } from './batteries.js';
9
+ import { GuardrailsError, JevCaller, createTransport } from './client.js';
10
+ import { classifyActionLocally } from './heuristics.js';
11
+ import { DEFAULT_PRECEDENCE, resolvePolicy, routePolicy } from './policies.js';
12
+ import { DEFAULT_HAZARD_LABELS, describeVerdict } from './render.js';
13
+ import { contentToText, formatProbability, topEntries } from './util.js';
14
+ const ALL_SIDES = ['input', 'output', 'observation', 'action'];
15
+ /**
16
+ * Jev-backed guardrails with one local cache, one policy set, and one
17
+ * transport.
18
+ *
19
+ * The class is safe to share across requests; each call is independent and
20
+ * cancellation is per call.
21
+ */
22
+ export class JevGuardrails {
23
+ caller;
24
+ model;
25
+ heuristics;
26
+ batteries;
27
+ policyOverrides;
28
+ onVerdict;
29
+ onError;
30
+ counters = {
31
+ checks: 0,
32
+ cached: 0,
33
+ degraded: 0,
34
+ local: 0,
35
+ bySide: { input: 0, output: 0, observation: 0, action: 0 },
36
+ };
37
+ constructor(options = {}) {
38
+ this.model =
39
+ options.model?.trim() ||
40
+ (options.provider === 'openrouter' ? '~typesafe/jev-latest' : 'jev-latest');
41
+ this.heuristics = options.heuristics ?? true;
42
+ this.batteries = {
43
+ input: options.batteries?.input ?? INPUT_BATTERY,
44
+ output: options.batteries?.output ?? OUTPUT_BATTERY,
45
+ observation: options.batteries?.observation ?? OBSERVATION_BATTERY,
46
+ action: options.batteries?.action ?? ACTION_BATTERY,
47
+ };
48
+ for (const side of ALL_SIDES) {
49
+ resolvePolicy(side, options.policies?.[side] ?? options.policy, this.batteries[side]);
50
+ }
51
+ this.policyOverrides = {
52
+ ...(options.policy !== undefined ? { input: options.policy, output: options.policy, observation: options.policy, action: options.policy } : {}),
53
+ ...options.policies,
54
+ };
55
+ this.onVerdict = options.onVerdict;
56
+ this.onError = options.onError;
57
+ this.caller = new JevCaller({
58
+ transport: createTransport({
59
+ model: this.model,
60
+ ...(options.provider !== undefined ? { provider: options.provider } : {}),
61
+ ...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),
62
+ ...(options.baseURL !== undefined ? { baseURL: options.baseURL } : {}),
63
+ ...(options.client !== undefined ? { client: options.client } : {}),
64
+ ...(options.fetch !== undefined ? { fetch: options.fetch } : {}),
65
+ timeoutMs: options.timeoutMs ?? 5000,
66
+ ...(options.retries !== undefined ? { retries: options.retries } : {}),
67
+ ...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
68
+ ...(options.headers !== undefined ? { headers: options.headers } : {}),
69
+ }),
70
+ model: this.model,
71
+ cache: options.cache === false ? false : options.cache ?? {},
72
+ redact: normalizeRedaction(options.redact),
73
+ maxStateChars: options.maxStateChars ?? 20000,
74
+ timeoutMs: options.timeoutMs ?? 5000,
75
+ ...(options.retries !== undefined ? { retries: options.retries } : {}),
76
+ ...(options.now !== undefined ? { now: options.now } : {}),
77
+ });
78
+ }
79
+ /** Low-level ask against the same transport, cache, and redactor. */
80
+ async ask(request, options = {}) {
81
+ return this.caller.ask(request, options);
82
+ }
83
+ /** Screen a user prompt before it reaches the model. */
84
+ async screenInput(text, options = {}) {
85
+ return this.screenText('input', text, options);
86
+ }
87
+ /** Screen a model response before the user sees it. */
88
+ async screenOutput(text, options = {}) {
89
+ return this.screenText('output', text, options);
90
+ }
91
+ /** Screen untrusted text a model is about to read. */
92
+ async screenObservation(text, options = {}) {
93
+ return this.screenText('observation', text, options);
94
+ }
95
+ /**
96
+ * Score one proposed tool call before execution.
97
+ *
98
+ * Local rules resolve routine and obviously dangerous calls without a model
99
+ * call; everything ambiguous goes to Jev.
100
+ */
101
+ async assessAction(action, options = {}) {
102
+ const descriptor = typeof action === 'string' ? { tool: 'command', arguments: action } : action;
103
+ this.counters.checks += 1;
104
+ this.counters.bySide.action += 1;
105
+ this.counters.cached += 0;
106
+ const policy = this.policyFor('action', options);
107
+ if ((options.heuristics ?? this.heuristics) === true) {
108
+ const local = classifyActionLocally(descriptor);
109
+ if (local !== undefined) {
110
+ const verdict = this.localVerdict('action', local.action, local.reason, policy);
111
+ this.onVerdict?.(verdict);
112
+ return verdict;
113
+ }
114
+ }
115
+ const state = buildActionState(descriptor);
116
+ return this.screenState('action', state, options, policy);
117
+ }
118
+ /**
119
+ * Verify one claim against its evidence.
120
+ *
121
+ * A supplied quote that is not present in the evidence is `fabricated`
122
+ * without spending a model call; everything else goes to one Choice question.
123
+ */
124
+ async verifyClaim(options) {
125
+ const claim = options.claim?.trim();
126
+ if (claim === undefined || claim.length === 0)
127
+ throw new Error('jev-guardrails: verifyClaim requires a non-empty claim');
128
+ const evidence = options.evidence;
129
+ const quote = options.quote;
130
+ if (quote !== undefined && quote.trim().length > 0 && evidence !== undefined) {
131
+ if (!normalizeForQuoteMatch(evidence).includes(normalizeForQuoteMatch(quote))) {
132
+ return {
133
+ verdict: 'fabricated',
134
+ needsReview: true,
135
+ confidence: 1,
136
+ probabilities: { fabricated: 1 },
137
+ reason: 'The quoted text does not appear in the supplied evidence.',
138
+ method: 'string-match',
139
+ cached: false,
140
+ degraded: false,
141
+ };
142
+ }
143
+ }
144
+ const questions = {
145
+ relationship: choice('Given the evidence, what is the relationship between the evidence and the claim?', {
146
+ supported: 'The evidence clearly supports the claim.',
147
+ contradicted: 'The evidence clearly contradicts the claim.',
148
+ insufficient: 'The evidence does not contain enough information to judge the claim.',
149
+ }),
150
+ };
151
+ const autoAccept = options.autoAcceptConfidence ?? 0.8;
152
+ try {
153
+ const { result, cached } = await this.caller.ask({
154
+ state: {
155
+ claim,
156
+ quote: quote ?? null,
157
+ evidence: evidence ?? null,
158
+ context: options.context ?? null,
159
+ },
160
+ questions,
161
+ model: options.model ?? this.model,
162
+ }, { signal: options.signal, cache: options.cache });
163
+ const answer = result.answers.relationship;
164
+ if (answer.type !== 'choice')
165
+ throw new GuardrailsError('MALFORMED', 'the verification answer was not a choice');
166
+ const rawChoice = String(answer.choice);
167
+ const verdict = rawChoice === 'supported' || rawChoice === 'contradicted' || rawChoice === 'insufficient'
168
+ ? rawChoice
169
+ : 'insufficient';
170
+ const confidence = answer.confidence;
171
+ const needsReview = verdict === 'insufficient' || confidence < autoAccept;
172
+ const probabilities = { ...answer.probabilities };
173
+ return {
174
+ verdict,
175
+ needsReview,
176
+ confidence,
177
+ probabilities,
178
+ reason: describeClaimVerdict(verdict, confidence, needsReview),
179
+ method: 'jev',
180
+ model: result.model,
181
+ usage: result.usage,
182
+ cached,
183
+ degraded: false,
184
+ };
185
+ }
186
+ catch (error) {
187
+ this.counters.degraded += 1;
188
+ this.onError?.(error, { side: 'output', kind: 'response' });
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ throw error instanceof GuardrailsError
191
+ ? error
192
+ : new GuardrailsError('TRANSPORT', `claim verification failed: ${message}`, error);
193
+ }
194
+ }
195
+ /** Drop every cached answer. */
196
+ clearCache() {
197
+ this.caller.clearCache();
198
+ }
199
+ /** Aggregate counters since construction. */
200
+ get stats() {
201
+ return {
202
+ checks: this.counters.checks,
203
+ cached: this.counters.cached,
204
+ degraded: this.counters.degraded,
205
+ local: this.counters.local,
206
+ bySide: { ...this.counters.bySide },
207
+ };
208
+ }
209
+ policyFor(side, options) {
210
+ const battery = this.batteries[side];
211
+ const override = mergePolicies(this.policyOverrides[side], options.policy);
212
+ return resolvePolicy(side, override, battery);
213
+ }
214
+ async screenText(side, text, options) {
215
+ const policy = this.policyFor(side, options);
216
+ const state = typeof text === 'string' ? text : contentToText(text);
217
+ return this.screenState(side, state, options, policy);
218
+ }
219
+ async screenState(side, state, options, policy) {
220
+ this.counters.checks += 1;
221
+ this.counters.bySide[side] += 1;
222
+ const battery = this.batteries[side];
223
+ const kind = battery.kind;
224
+ if (typeof state === 'string' && state.trim().length === 0) {
225
+ this.counters.local += 1;
226
+ const verdict = this.localVerdict(side, 'allow', 'empty input has no hazard surface', policy);
227
+ this.onVerdict?.(verdict);
228
+ return verdict;
229
+ }
230
+ const questions = options.questions ?? battery.questions;
231
+ const severityKey = options.severityKey ?? battery.severityKey;
232
+ try {
233
+ const { result, cached } = await this.caller.ask({ state: state, questions, model: options.model ?? this.model }, { signal: options.signal, cache: options.cache });
234
+ if (cached)
235
+ this.counters.cached += 1;
236
+ const verdict = this.buildVerdict({
237
+ side,
238
+ kind,
239
+ battery,
240
+ labels: options.labels,
241
+ policy,
242
+ questions,
243
+ severityKey,
244
+ result: result,
245
+ cached,
246
+ });
247
+ this.onVerdict?.(verdict);
248
+ return verdict;
249
+ }
250
+ catch (error) {
251
+ this.counters.degraded += 1;
252
+ this.onError?.(error, { side, kind });
253
+ const verdict = this.failureVerdict(side, kind, policy, error);
254
+ this.onVerdict?.(verdict);
255
+ return verdict;
256
+ }
257
+ }
258
+ buildVerdict(input) {
259
+ const hazards = {};
260
+ for (const [name, question] of Object.entries(input.questions)) {
261
+ if (name === input.severityKey)
262
+ continue;
263
+ const answer = input.result.answers[name];
264
+ if (isNoulAnswer(answer))
265
+ hazards[name] = answer.noul;
266
+ }
267
+ const severityAnswer = input.severityKey === undefined ? undefined : input.result.answers[input.severityKey];
268
+ const severity = isScoreAnswer(severityAnswer) ? severityAnswer.score : undefined;
269
+ const severityConfidence = isScoreAnswer(severityAnswer) ? severityAnswer.confidence : undefined;
270
+ const route = routePolicy({ hazards, ...(severity !== undefined ? { severity } : {}), policy: input.policy });
271
+ const labels = { ...(input.battery.labels ?? {}), ...(input.labels ?? {}) };
272
+ const top = topEntries(hazards).find(([, probability]) => probability >= input.policy.reviewThreshold);
273
+ const topHazard = top === undefined
274
+ ? undefined
275
+ : { name: top[0], probability: top[1], label: labels[top[0]] ?? DEFAULT_HAZARD_LABELS[top[0]] ?? top[0] };
276
+ const draft = {
277
+ side: input.side,
278
+ kind: input.kind,
279
+ action: route.action,
280
+ source: 'jev',
281
+ hazards,
282
+ ...(topHazard !== undefined ? { topHazard } : {}),
283
+ ...(severity !== undefined ? { severity } : {}),
284
+ ...(severityConfidence !== undefined ? { severityConfidence } : {}),
285
+ reasons: route.reasons,
286
+ model: input.result.model,
287
+ ...(input.result.usage !== undefined ? { usage: input.result.usage } : {}),
288
+ cached: input.cached,
289
+ degraded: false,
290
+ rawAnswers: { ...input.result.answers },
291
+ };
292
+ return { ...draft, reason: describeVerdict(draft, labels) };
293
+ }
294
+ localVerdict(side, action, reason, policy) {
295
+ const draft = {
296
+ side,
297
+ kind: side === 'action' ? 'action' : this.batteries[side].kind,
298
+ action,
299
+ source: 'local',
300
+ hazards: {},
301
+ reasons: [reason],
302
+ cached: false,
303
+ degraded: false,
304
+ };
305
+ if (action === 'allow')
306
+ this.counters.local += 1;
307
+ void policy;
308
+ return { ...draft, reason: describeVerdict(draft) };
309
+ }
310
+ failureVerdict(side, kind, policy, error) {
311
+ const message = error instanceof Error ? error.message : String(error);
312
+ const action = policy.failMode === 'open' ? 'allow' : policy.failMode === 'review' ? 'review' : 'block';
313
+ const draft = {
314
+ side,
315
+ kind,
316
+ action,
317
+ source: 'jev',
318
+ hazards: {},
319
+ reasons: [`Jev request failed: ${message}`],
320
+ cached: false,
321
+ degraded: true,
322
+ error: message,
323
+ };
324
+ return { ...draft, reason: describeVerdict(draft) };
325
+ }
326
+ }
327
+ /** Create a guardrails instance with the built-in batteries and policies. */
328
+ export function createGuardrails(options = {}) {
329
+ return new JevGuardrails(options);
330
+ }
331
+ function normalizeRedaction(value) {
332
+ if (value === false)
333
+ return false;
334
+ if (value === undefined || value === true)
335
+ return {};
336
+ return value;
337
+ }
338
+ function mergePolicies(base, override) {
339
+ if (base === undefined)
340
+ return override;
341
+ if (override === undefined)
342
+ return base;
343
+ return {
344
+ ...base,
345
+ ...override,
346
+ ...(base.actions !== undefined || override.actions !== undefined
347
+ ? { actions: { ...(base.actions ?? {}), ...(override.actions ?? {}) } }
348
+ : {}),
349
+ };
350
+ }
351
+ function buildActionState(action) {
352
+ return {
353
+ kind: 'proposed_agent_action',
354
+ tool: action.tool,
355
+ arguments: action.arguments,
356
+ ...(action.workspace !== undefined ? { workspace: action.workspace } : {}),
357
+ ...(action.cwd !== undefined ? { cwd: action.cwd } : {}),
358
+ ...(action.description !== undefined ? { description: action.description } : {}),
359
+ ...(action.untrustedContext === true ? { untrusted_context_recently: true } : {}),
360
+ };
361
+ }
362
+ function isNoulAnswer(value) {
363
+ return typeof value === 'object' && value !== null && value.type === 'noul' && typeof value.noul === 'number';
364
+ }
365
+ function isScoreAnswer(value) {
366
+ return typeof value === 'object' && value !== null && value.type === 'score' && typeof value.score === 'number';
367
+ }
368
+ function normalizeForQuoteMatch(value) {
369
+ return value.replace(/\s+/g, ' ').trim().toLowerCase();
370
+ }
371
+ function describeClaimVerdict(verdict, confidence, needsReview) {
372
+ const confidenceText = `confidence ${formatProbability(confidence)}`;
373
+ if (verdict === 'supported') {
374
+ return needsReview
375
+ ? `The evidence appears to support the claim, but ${confidenceText} is below the acceptance threshold; review is recommended.`
376
+ : `The evidence supports the claim (${confidenceText}).`;
377
+ }
378
+ if (verdict === 'contradicted') {
379
+ return needsReview
380
+ ? `The evidence appears to contradict the claim, but ${confidenceText} is below the acceptance threshold; review is recommended.`
381
+ : `The evidence contradicts the claim (${confidenceText}).`;
382
+ }
383
+ return `The evidence is insufficient to judge the claim (${confidenceText}).`;
384
+ }
385
+ export { DEFAULT_PRECEDENCE };
386
+ //# sourceMappingURL=guardrails.js.map