@openclaw/typesafe 2026.9.6 → 2026.9.7
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 +6 -3
- package/assets/icon.png +0 -0
- package/dist/.setup/client-CRLm45hB.mjs +395 -0
- package/dist/.setup/errors-BdIwQubl.mjs +83 -0
- package/dist/index.js +8 -506
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/openclaw.plugin.json +37 -26
- package/package.json +7 -8
- package/skills/typesafe-evaluate/SKILL.md +0 -48
- package/skills/typesafe-evaluate/references/request-example.json +0 -65
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
package/README.md
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
Official external plugin for typed decisions with hosted TypeSafe AI Jev models
|
|
4
4
|
or a local Kev System One server.
|
|
5
5
|
It provides Choice, Score, and Boolean judgments through OpenClaw's shared
|
|
6
|
-
decision-model API
|
|
6
|
+
decision-model API. Core supplies the provider-neutral `decision_evaluate` agent
|
|
7
|
+
tool when the agent has an effective `decisionModel` selection, subject to normal
|
|
8
|
+
tool policy and harness capabilities. No separate tool enablement is required.
|
|
7
9
|
|
|
8
10
|
Requires OpenClaw and plugin API **2026.9.6 or later**. Released OpenClaw
|
|
9
11
|
2026.9.5 does not include the decision API.
|
|
@@ -14,8 +16,9 @@ openclaw plugins install @openclaw/typesafe
|
|
|
14
16
|
|
|
15
17
|
The ClawHub install spec is `clawhub:@openclaw/typesafe`. First publication is
|
|
16
18
|
pending a supporting release. Enable the plugin, configure a protected TypeSafe credential, and select
|
|
17
|
-
`typesafe/jev-latest` as your agent's `decisionModel`.
|
|
18
|
-
supplied evidence to TypeSafe AI and
|
|
19
|
+
`typesafe/jev-latest` as your agent's `decisionModel`. With that selection,
|
|
20
|
+
`decision_evaluate` sends supplied evidence to TypeSafe AI and incurs its normal
|
|
21
|
+
usage charges. Other providers route evidence according to their own configuration.
|
|
19
22
|
|
|
20
23
|
For local Kev, configure `plugins.entries.typesafe.config.baseUrl` with the
|
|
21
24
|
server's loopback origin, such as `http://127.0.0.1:8009`, omit `apiKey`, and select
|
package/assets/icon.png
CHANGED
|
Binary file
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { a as localBaseUrl, r as evaluationError, t as EvaluationError } from "./errors-BdIwQubl.mjs";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
import { Compile } from "typebox/compile";
|
|
5
|
+
import { Check } from "typebox/value";
|
|
6
|
+
import { buildTimeoutAbortSignal } from "openclaw/plugin-sdk/extension-shared";
|
|
7
|
+
import { responseWithRelease, shouldUseEnvHttpProxyForUrl, withTrustedEnvProxyGuardedFetchMode } from "openclaw/plugin-sdk/fetch-runtime";
|
|
8
|
+
import { parseRetryAfterHeaderSeconds } from "openclaw/plugin-sdk/retry-runtime";
|
|
9
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
10
|
+
const MAX_JSON_NODES = 262144;
|
|
11
|
+
const MAX_JSON_DEPTH = 64;
|
|
12
|
+
const MAX_CHOICE_OPTIONS = 255;
|
|
13
|
+
const MAX_SCORE_LEVELS = 10;
|
|
14
|
+
function map(value, options = {}) {
|
|
15
|
+
return Type.Unsafe({
|
|
16
|
+
type: "object",
|
|
17
|
+
properties: {},
|
|
18
|
+
additionalProperties: value,
|
|
19
|
+
...options
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
const entry = Type.Union([
|
|
23
|
+
Type.String(),
|
|
24
|
+
map(Type.Unknown()),
|
|
25
|
+
Type.Array(Type.Unknown()),
|
|
26
|
+
Type.Null()
|
|
27
|
+
], { description: "Text, a JSON object or array, or null. Nested values must be finite JSON. Use structure for rules, examples, and exclusions." });
|
|
28
|
+
const instructions = Type.Optional(Type.Union(entry.anyOf, { description: "The complete judgment to make; question IDs are not read by the model. Text, structured object/array, or null. May be omitted when criteria express the judgment." }));
|
|
29
|
+
const model = Type.String({
|
|
30
|
+
minLength: 1,
|
|
31
|
+
maxLength: 128,
|
|
32
|
+
pattern: "^[a-zA-Z0-9._/-]+$"
|
|
33
|
+
});
|
|
34
|
+
const probability = Type.Number({
|
|
35
|
+
minimum: 0,
|
|
36
|
+
maximum: 1
|
|
37
|
+
});
|
|
38
|
+
const objectOptions = { additionalProperties: false };
|
|
39
|
+
const noulQuestion = Type.Object({
|
|
40
|
+
type: Type.Literal("noul"),
|
|
41
|
+
instructions,
|
|
42
|
+
criteria: Type.Optional(Type.Union([Type.Object({
|
|
43
|
+
true: Type.Optional(entry),
|
|
44
|
+
false: Type.Optional(entry)
|
|
45
|
+
}, objectOptions), Type.Null()], { description: "Optional true (yes) and false (no) outcome definitions; each accepts text, object, array, or null." }))
|
|
46
|
+
}, {
|
|
47
|
+
...objectOptions,
|
|
48
|
+
description: "Probability of yes (0–1), not intensity; no separate confidence. Use separate Nouls for independent labels."
|
|
49
|
+
});
|
|
50
|
+
const choiceQuestion = Type.Object({
|
|
51
|
+
type: Type.Literal("choice"),
|
|
52
|
+
instructions,
|
|
53
|
+
criteria: map(entry, {
|
|
54
|
+
minProperties: 2,
|
|
55
|
+
maxProperties: MAX_CHOICE_OPTIONS,
|
|
56
|
+
description: "2–255 competing labels mapped to descriptions. Labels may include spaces, punctuation, or Unicode. Null leaves a label undescribed. Include a no-match option when needed."
|
|
57
|
+
})
|
|
58
|
+
}, {
|
|
59
|
+
...objectOptions,
|
|
60
|
+
description: "Choose one alternative; returns its label, full probability distribution, and confidence."
|
|
61
|
+
});
|
|
62
|
+
const scoreQuestion = Type.Object({
|
|
63
|
+
type: Type.Literal("score"),
|
|
64
|
+
instructions,
|
|
65
|
+
criteria: Type.Array(entry, {
|
|
66
|
+
minItems: 2,
|
|
67
|
+
maxItems: MAX_SCORE_LEVELS,
|
|
68
|
+
description: "2–10 ordered rubric levels, indexed from zero. Returns a fractional probability-weighted position, not an integer category or a normalized 0–1 score."
|
|
69
|
+
})
|
|
70
|
+
}, objectOptions);
|
|
71
|
+
const question = Type.Union([
|
|
72
|
+
noulQuestion,
|
|
73
|
+
choiceQuestion,
|
|
74
|
+
scoreQuestion
|
|
75
|
+
]);
|
|
76
|
+
/** Explicit shared state; independently evaluated questions never see other answers. */
|
|
77
|
+
const EvaluateInput = Type.Object({
|
|
78
|
+
state: Type.Union(entry.anyOf, { description: "Shared evidence/context for every question. Only supplied state is sent; no ambient conversation is collected." }),
|
|
79
|
+
questions: map(question, {
|
|
80
|
+
minProperties: 1,
|
|
81
|
+
description: "Nonempty map of question IDs to Choice, Score, or Noul questions. Mix types in one call. IDs only match answers; put all meaning in instructions/criteria. Questions are independent. The server enforces token limits; plugin JSON guard is 4 MiB."
|
|
82
|
+
}),
|
|
83
|
+
model: Type.Optional(Type.String({
|
|
84
|
+
...model,
|
|
85
|
+
description: "Host-selected System One model ID or alias. A local Kev server uses its loaded checkpoint regardless of this label."
|
|
86
|
+
}))
|
|
87
|
+
}, objectOptions);
|
|
88
|
+
const inputValidator = Compile(EvaluateInput);
|
|
89
|
+
const distribution = map(probability, {
|
|
90
|
+
minProperties: 2,
|
|
91
|
+
maxProperties: MAX_CHOICE_OPTIONS
|
|
92
|
+
});
|
|
93
|
+
const Answer = Type.Union([
|
|
94
|
+
Type.Object({
|
|
95
|
+
type: Type.Literal("noul"),
|
|
96
|
+
noul: probability
|
|
97
|
+
}, objectOptions),
|
|
98
|
+
Type.Object({
|
|
99
|
+
type: Type.Literal("choice"),
|
|
100
|
+
choice: Type.String(),
|
|
101
|
+
confidence: probability,
|
|
102
|
+
probabilities: distribution
|
|
103
|
+
}, objectOptions),
|
|
104
|
+
Type.Object({
|
|
105
|
+
type: Type.Literal("score"),
|
|
106
|
+
score: Type.Number({
|
|
107
|
+
minimum: 0,
|
|
108
|
+
maximum: 9
|
|
109
|
+
}),
|
|
110
|
+
confidence: probability,
|
|
111
|
+
probabilities: distribution,
|
|
112
|
+
legend: map(entry, {
|
|
113
|
+
minProperties: 2,
|
|
114
|
+
maxProperties: MAX_SCORE_LEVELS
|
|
115
|
+
})
|
|
116
|
+
}, objectOptions)
|
|
117
|
+
]);
|
|
118
|
+
const VendorResult = Type.Object({
|
|
119
|
+
model,
|
|
120
|
+
answers: map(Answer, { minProperties: 1 }),
|
|
121
|
+
usage: Type.Object({
|
|
122
|
+
input_tokens: Type.Integer({
|
|
123
|
+
minimum: 0,
|
|
124
|
+
maximum: Number.MAX_SAFE_INTEGER
|
|
125
|
+
}),
|
|
126
|
+
output_tokens: Type.Integer({
|
|
127
|
+
minimum: 0,
|
|
128
|
+
maximum: Number.MAX_SAFE_INTEGER
|
|
129
|
+
})
|
|
130
|
+
}, objectOptions)
|
|
131
|
+
}, objectOptions);
|
|
132
|
+
const resultValidator = Compile(VendorResult);
|
|
133
|
+
/** Reject non-JSON values and excessive structure before schema walking or serialization. */
|
|
134
|
+
function assertBoundedJson(value) {
|
|
135
|
+
let nodes = 0;
|
|
136
|
+
const visit = (node, depth) => {
|
|
137
|
+
if (++nodes > MAX_JSON_NODES || depth > MAX_JSON_DEPTH) throw new EvaluationError("TypeSafe JSON exceeds resource limits (262144 nodes or depth 64).", "unsupported-input");
|
|
138
|
+
if (node === null || typeof node === "string" || typeof node === "boolean") return;
|
|
139
|
+
if (typeof node === "number" && Number.isFinite(node)) return;
|
|
140
|
+
if (typeof node !== "object" || !node) throw new EvaluationError("TypeSafe input must be JSON.", "unsupported-input");
|
|
141
|
+
const array = Array.isArray(node);
|
|
142
|
+
const prototype = Object.getPrototypeOf(node);
|
|
143
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) throw new EvaluationError("TypeSafe input must be plain JSON.", "unsupported-input");
|
|
144
|
+
if (array) {
|
|
145
|
+
if (node.length > MAX_JSON_NODES || Object.keys(node).length !== node.length || Object.hasOwn(node, "toJSON")) throw new EvaluationError("TypeSafe input must be a bounded JSON array.", "unsupported-input");
|
|
146
|
+
for (let index = 0; index < node.length; index++) {
|
|
147
|
+
const descriptor = Object.getOwnPropertyDescriptor(node, index);
|
|
148
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) throw new EvaluationError("TypeSafe input must be plain JSON.", "unsupported-input");
|
|
149
|
+
visit(descriptor.value, depth + 1);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
for (const key of Object.getOwnPropertyNames(node)) {
|
|
154
|
+
if ([
|
|
155
|
+
"__proto__",
|
|
156
|
+
"constructor",
|
|
157
|
+
"prototype"
|
|
158
|
+
].includes(key)) throw new EvaluationError("TypeSafe input contains a reserved key.", "unsupported-input");
|
|
159
|
+
const descriptor = Object.getOwnPropertyDescriptor(node, key);
|
|
160
|
+
if (!descriptor?.enumerable || !("value" in descriptor)) throw new EvaluationError("TypeSafe input must be plain JSON.", "unsupported-input");
|
|
161
|
+
visit(descriptor.value, depth + 1);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
visit(value, 0);
|
|
165
|
+
if (Buffer.byteLength(JSON.stringify(value), "utf8") > 4194304) throw new EvaluationError("TypeSafe JSON exceeds the plugin resource limit of 4 MiB.", "unsupported-input");
|
|
166
|
+
}
|
|
167
|
+
/** Validate without including supplied state in diagnostics. */
|
|
168
|
+
function parseInput(value) {
|
|
169
|
+
assertBoundedJson(value);
|
|
170
|
+
if (!inputValidator.Check(value)) {
|
|
171
|
+
if (value && typeof value === "object" && "questions" in value && value.questions && typeof value.questions === "object" && !Array.isArray(value.questions)) {
|
|
172
|
+
for (const [index, q] of Object.values(value.questions).entries()) if (!Check(question, q)) {
|
|
173
|
+
const kind = q && typeof q === "object" && "type" in q ? q.type : void 0;
|
|
174
|
+
const hint = kind === "choice" ? "Choice requires 2–255 criteria descriptions." : kind === "score" ? "Score requires 2–10 ordered criteria descriptions." : kind === "noul" ? "Noul criteria may contain only true/false descriptions, or null." : "type must be choice, score, or noul.";
|
|
175
|
+
throw new EvaluationError(`Invalid TypeSafe questions entry #${index + 1}: ${hint} Instructions/descriptions accept text, object, array, or null; no extra question fields.`, "unsupported-input");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
throw new EvaluationError("Invalid TypeSafe evaluation input: supply state (text/object/array/null), a nonempty questions map, and optionally a valid model ID; no extra fields.", "unsupported-input");
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
182
|
+
/** Check the response schema and correspondence to this exact question batch. */
|
|
183
|
+
function parseResult(value, input) {
|
|
184
|
+
try {
|
|
185
|
+
assertBoundedJson(value);
|
|
186
|
+
if (!resultValidator.Check(value)) throw new Error();
|
|
187
|
+
const questions = Object.entries(input.questions);
|
|
188
|
+
if (Object.keys(value.answers).length !== questions.length) throw new Error();
|
|
189
|
+
for (const [id, expected] of questions) {
|
|
190
|
+
const answer = value.answers[id];
|
|
191
|
+
if (!answer || answer.type !== expected.type) throw new Error();
|
|
192
|
+
if (answer.type === "noul") continue;
|
|
193
|
+
const labels = expected.type === "score" ? expected.criteria.map((_, index) => String(index)) : expected.type === "choice" ? Object.keys(expected.criteria) : [];
|
|
194
|
+
if (Object.keys(answer.probabilities).length !== labels.length || labels.some((label) => !Object.hasOwn(answer.probabilities, label))) throw new Error();
|
|
195
|
+
if (!(Object.values(answer.probabilities).reduce((sum, item) => sum + item, 0) > 0)) throw new Error();
|
|
196
|
+
if (answer.type === "choice" && !labels.includes(answer.choice)) throw new Error();
|
|
197
|
+
if (answer.type === "score" && expected.type === "score") {
|
|
198
|
+
if (answer.score > labels.length - 1 || Object.keys(answer.legend).length !== labels.length || labels.some((label, index) => !isDeepStrictEqual(answer.legend[label], expected.criteria[index]))) throw new Error();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
} catch {
|
|
203
|
+
throw new Error("TypeSafe returned an invalid evaluation response.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//#endregion
|
|
207
|
+
//#region extensions/typesafe/src/local.ts
|
|
208
|
+
function localInput(input) {
|
|
209
|
+
const questions = {};
|
|
210
|
+
for (const [id, question] of Object.entries(input.questions)) {
|
|
211
|
+
const instructions = question.instructions ?? null;
|
|
212
|
+
if (question.type === "score") questions[id] = {
|
|
213
|
+
...question,
|
|
214
|
+
instructions,
|
|
215
|
+
criteria: question.criteria.map((level) => typeof level === "string" ? level : level === null ? "" : JSON.stringify(level))
|
|
216
|
+
};
|
|
217
|
+
else questions[id] = {
|
|
218
|
+
...question,
|
|
219
|
+
instructions
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
...input,
|
|
224
|
+
questions
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function parseLocalResult(value, wireInput, originalInput) {
|
|
228
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid local System One response.");
|
|
229
|
+
let result = value;
|
|
230
|
+
if ("latency_ms" in value) {
|
|
231
|
+
const { latency_ms, ...payload } = value;
|
|
232
|
+
if (typeof latency_ms !== "number" || !Number.isFinite(latency_ms) || latency_ms < 0) throw new Error("Invalid local System One latency metadata.");
|
|
233
|
+
result = payload;
|
|
234
|
+
}
|
|
235
|
+
const evaluation = parseResult(result, wireInput);
|
|
236
|
+
for (const [id, question] of Object.entries(originalInput.questions)) {
|
|
237
|
+
const answer = evaluation.answers[id];
|
|
238
|
+
if (question.type === "score" && answer?.type === "score") answer.legend = Object.fromEntries(question.criteria.map((level, index) => [String(index), level]));
|
|
239
|
+
}
|
|
240
|
+
return evaluation;
|
|
241
|
+
}
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region extensions/typesafe/src/transport.ts
|
|
244
|
+
const ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
245
|
+
function httpError(response) {
|
|
246
|
+
if (response.status === 401 || response.status === 403) return new EvaluationError("TypeSafe authentication failed; check the configured credential and account access.", "authentication");
|
|
247
|
+
if (response.status === 429) {
|
|
248
|
+
const milliseconds = response.headers.get("retry-after-ms");
|
|
249
|
+
const parsedMilliseconds = milliseconds?.trim() ? Number(milliseconds) : NaN;
|
|
250
|
+
const seconds = parseRetryAfterHeaderSeconds(response.headers.get("retry-after"));
|
|
251
|
+
const retryAfterMs = Number.isFinite(parsedMilliseconds) && parsedMilliseconds >= 0 ? parsedMilliseconds : seconds === void 0 ? void 0 : seconds * 1e3;
|
|
252
|
+
return new EvaluationError("TypeSafe rate limit reached; retry later.", "rate-limited", retryAfterMs);
|
|
253
|
+
}
|
|
254
|
+
if (response.status === 413 || response.status === 422) return new EvaluationError("TypeSafe rejected the supplied input.", "unsupported-input");
|
|
255
|
+
return new EvaluationError("TypeSafe service rejected the evaluation request.", "transport");
|
|
256
|
+
}
|
|
257
|
+
async function readBody(response, signal) {
|
|
258
|
+
const reader = response.body?.getReader();
|
|
259
|
+
if (!reader) {
|
|
260
|
+
signal?.throwIfAborted();
|
|
261
|
+
return Buffer.alloc(0);
|
|
262
|
+
}
|
|
263
|
+
let cancellation;
|
|
264
|
+
const cancel = () => {
|
|
265
|
+
cancellation ??= reader.cancel().catch(() => {});
|
|
266
|
+
};
|
|
267
|
+
signal?.addEventListener("abort", cancel, { once: true });
|
|
268
|
+
const chunks = [];
|
|
269
|
+
let length = 0;
|
|
270
|
+
try {
|
|
271
|
+
while (true) {
|
|
272
|
+
signal?.throwIfAborted();
|
|
273
|
+
const chunk = await reader.read();
|
|
274
|
+
signal?.throwIfAborted();
|
|
275
|
+
if (chunk.done) break;
|
|
276
|
+
length += chunk.value.byteLength;
|
|
277
|
+
if (length > 4194304) throw new EvaluationError("TypeSafe response exceeds its limit.", "invalid-response");
|
|
278
|
+
chunks.push(chunk.value);
|
|
279
|
+
}
|
|
280
|
+
return Buffer.concat(chunks, length);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
cancel();
|
|
283
|
+
throw error;
|
|
284
|
+
} finally {
|
|
285
|
+
signal?.removeEventListener("abort", cancel);
|
|
286
|
+
await cancellation;
|
|
287
|
+
reader.releaseLock();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function requestEvaluation(params) {
|
|
291
|
+
const baseUrl = localBaseUrl(params.baseUrl);
|
|
292
|
+
const endpoint = baseUrl ? `${baseUrl}/v1/systemone` : ENDPOINT;
|
|
293
|
+
const body = JSON.stringify(params.body);
|
|
294
|
+
if (Buffer.byteLength(body) > 4194304) throw new EvaluationError("TypeSafe request exceeds its limit.", "unsupported-input");
|
|
295
|
+
const timeoutMs = params.deadlineMonotonicMs === void 0 ? params.timeoutMs : Math.min(params.timeoutMs, params.deadlineMonotonicMs - performance.now());
|
|
296
|
+
if (timeoutMs <= 0) throw new EvaluationError("TypeSafe evaluation timed out.", "transport");
|
|
297
|
+
const { signal, cleanup } = buildTimeoutAbortSignal({
|
|
298
|
+
signal: params.signal,
|
|
299
|
+
timeoutMs,
|
|
300
|
+
operation: "TypeSafe evaluation"
|
|
301
|
+
});
|
|
302
|
+
const assertActive = () => {
|
|
303
|
+
signal?.throwIfAborted();
|
|
304
|
+
if (params.deadlineMonotonicMs !== void 0 && performance.now() >= params.deadlineMonotonicMs) throw new EvaluationError("TypeSafe evaluation timed out.", "transport");
|
|
305
|
+
};
|
|
306
|
+
try {
|
|
307
|
+
assertActive();
|
|
308
|
+
const request = {
|
|
309
|
+
url: endpoint,
|
|
310
|
+
fetchImpl: globalThis.fetch,
|
|
311
|
+
requireHttps: !baseUrl,
|
|
312
|
+
...baseUrl ? { policy: { allowedOrigins: [baseUrl] } } : {},
|
|
313
|
+
...baseUrl && new URL(baseUrl).hostname === "localhost" ? { lookupFn: async () => [{
|
|
314
|
+
address: "127.0.0.1",
|
|
315
|
+
family: 4
|
|
316
|
+
}, {
|
|
317
|
+
address: "::1",
|
|
318
|
+
family: 6
|
|
319
|
+
}] } : {},
|
|
320
|
+
maxRedirects: 0,
|
|
321
|
+
signal,
|
|
322
|
+
beforeRequest: assertActive,
|
|
323
|
+
init: {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
...!baseUrl ? { Authorization: `Bearer ${params.apiKey}` } : {},
|
|
327
|
+
Accept: "application/json",
|
|
328
|
+
"Content-Type": "application/json"
|
|
329
|
+
},
|
|
330
|
+
body
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
const guarded = await fetchWithSsrFGuard(!baseUrl && shouldUseEnvHttpProxyForUrl(endpoint) ? withTrustedEnvProxyGuardedFetchMode(request) : request);
|
|
334
|
+
let releasePromise;
|
|
335
|
+
const release = () => releasePromise ??= Promise.resolve().then(() => guarded.release());
|
|
336
|
+
let payload;
|
|
337
|
+
try {
|
|
338
|
+
const response = responseWithRelease(guarded.response, release);
|
|
339
|
+
if (!response.ok) {
|
|
340
|
+
await response.body?.cancel();
|
|
341
|
+
throw httpError(response);
|
|
342
|
+
}
|
|
343
|
+
const bytes = await readBody(response, signal);
|
|
344
|
+
try {
|
|
345
|
+
payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
346
|
+
} catch {
|
|
347
|
+
throw new EvaluationError("TypeSafe returned invalid JSON.", "invalid-response");
|
|
348
|
+
}
|
|
349
|
+
} finally {
|
|
350
|
+
await release();
|
|
351
|
+
}
|
|
352
|
+
signal?.throwIfAborted();
|
|
353
|
+
return payload;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (params.signal?.aborted) throw new EvaluationError("TypeSafe evaluation cancelled.", "transport");
|
|
356
|
+
if (signal?.aborted) throw new EvaluationError("TypeSafe evaluation timed out.", "transport");
|
|
357
|
+
if (error instanceof EvaluationError) throw error;
|
|
358
|
+
throw new EvaluationError("TypeSafe transport unavailable.", "transport");
|
|
359
|
+
} finally {
|
|
360
|
+
cleanup();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region extensions/typesafe/src/client.ts
|
|
365
|
+
/** Evaluate explicit state without ambient credentials, retries, or vendor diagnostics. */
|
|
366
|
+
async function evaluate(input, config, signal, deadlineMonotonicMs) {
|
|
367
|
+
if (signal?.aborted) throw evaluationError(void 0, true);
|
|
368
|
+
const parsed = parseInput(input);
|
|
369
|
+
const model = parsed.model;
|
|
370
|
+
if (!model) throw new EvaluationError("TypeSafe requires a host-selected model.", "unsupported-input");
|
|
371
|
+
if (model === "kev-latest" && !config.baseUrl) throw new EvaluationError("Kev requires a local System One server. Configure baseUrl in TypeSafe plugin Settings.", "unsupported-input");
|
|
372
|
+
if (!config.baseUrl && !config.apiKey) throw new Error("TypeSafe API key is missing. Configure a SecretRef in plugin Settings.");
|
|
373
|
+
try {
|
|
374
|
+
const wireInput = config.baseUrl ? localInput(parsed) : parsed;
|
|
375
|
+
const response = await requestEvaluation({
|
|
376
|
+
body: {
|
|
377
|
+
...wireInput,
|
|
378
|
+
model
|
|
379
|
+
},
|
|
380
|
+
apiKey: config.baseUrl ? void 0 : config.apiKey,
|
|
381
|
+
baseUrl: config.baseUrl,
|
|
382
|
+
timeoutMs: config.timeoutMs,
|
|
383
|
+
signal,
|
|
384
|
+
deadlineMonotonicMs
|
|
385
|
+
});
|
|
386
|
+
signal?.throwIfAborted();
|
|
387
|
+
const evaluation = config.baseUrl ? parseLocalResult(response, wireInput, parsed) : parseResult(response, parsed);
|
|
388
|
+
if (!config.baseUrl && config.apiKey && JSON.stringify(evaluation).includes(config.apiKey)) throw new Error("Invalid TypeSafe response.");
|
|
389
|
+
return { evaluation };
|
|
390
|
+
} catch (error) {
|
|
391
|
+
throw evaluationError(error, signal?.aborted ?? false);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
//#endregion
|
|
395
|
+
export { evaluate };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
//#region extensions/typesafe/src/config.ts
|
|
3
|
+
const LOCAL_BASE_URL_PATTERN = "^https?://(?:localhost|127\\.0\\.0\\.1|\\[::1\\])(?::[0-9]{1,5})?/?$";
|
|
4
|
+
const localBaseUrlPattern = new RegExp(LOCAL_BASE_URL_PATTERN);
|
|
5
|
+
const ConfigSchema = Type.Object({
|
|
6
|
+
baseUrl: Type.Optional(Type.String({
|
|
7
|
+
maxLength: 128,
|
|
8
|
+
pattern: LOCAL_BASE_URL_PATTERN
|
|
9
|
+
})),
|
|
10
|
+
apiKey: Type.Optional(Type.Object({
|
|
11
|
+
source: Type.Union([
|
|
12
|
+
Type.Literal("env"),
|
|
13
|
+
Type.Literal("store"),
|
|
14
|
+
Type.Literal("file"),
|
|
15
|
+
Type.Literal("exec")
|
|
16
|
+
]),
|
|
17
|
+
provider: Type.String({
|
|
18
|
+
minLength: 1,
|
|
19
|
+
maxLength: 128
|
|
20
|
+
}),
|
|
21
|
+
id: Type.String({
|
|
22
|
+
minLength: 1,
|
|
23
|
+
maxLength: 1024
|
|
24
|
+
})
|
|
25
|
+
}, { additionalProperties: false })),
|
|
26
|
+
timeoutMs: Type.Optional(Type.Integer({
|
|
27
|
+
minimum: 1e3,
|
|
28
|
+
maximum: 6e4,
|
|
29
|
+
default: 3e4
|
|
30
|
+
}))
|
|
31
|
+
}, { additionalProperties: false });
|
|
32
|
+
/** A configured endpoint grants access to one loopback origin, never arbitrary private hosts. */
|
|
33
|
+
function localBaseUrl(value) {
|
|
34
|
+
if (value === void 0) return;
|
|
35
|
+
try {
|
|
36
|
+
if (typeof value !== "string" || value !== value.trim() || !localBaseUrlPattern.test(value)) throw new Error();
|
|
37
|
+
return new URL(value).origin;
|
|
38
|
+
} catch {
|
|
39
|
+
throw new Error("Invalid TypeSafe baseUrl; use an http(s) loopback origin without a path, credentials, query, or fragment.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Validate runtime settings and recognize materialized credentials without resolving inputs. */
|
|
43
|
+
function runtimeConfig(config) {
|
|
44
|
+
const baseUrl = localBaseUrl(config?.baseUrl);
|
|
45
|
+
const timeoutMs = config?.timeoutMs ?? 3e4;
|
|
46
|
+
if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1e3 || timeoutMs > 6e4) throw new Error("Invalid TypeSafe configuration; check plugin Settings.");
|
|
47
|
+
if (baseUrl) return {
|
|
48
|
+
baseUrl,
|
|
49
|
+
timeoutMs
|
|
50
|
+
};
|
|
51
|
+
const key = config?.apiKey;
|
|
52
|
+
return {
|
|
53
|
+
apiKey: typeof key === "string" && key.trim() ? key : void 0,
|
|
54
|
+
timeoutMs
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region extensions/typesafe/src/errors.ts
|
|
59
|
+
var EvaluationError = class extends Error {
|
|
60
|
+
constructor(message, reason, retryAfterMs) {
|
|
61
|
+
super(message);
|
|
62
|
+
this.reason = reason;
|
|
63
|
+
this.retryAfterMs = retryAfterMs;
|
|
64
|
+
this.name = "EvaluationError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
/** Only fixed plugin diagnostics cross the provider boundary; raw causes stay discarded. */
|
|
68
|
+
function evaluationError(error, aborted) {
|
|
69
|
+
if (aborted) return new EvaluationError("TypeSafe evaluation cancelled.", "transport");
|
|
70
|
+
if (error instanceof EvaluationError) return error;
|
|
71
|
+
return new EvaluationError("TypeSafe evaluation failed or returned an invalid response.", "invalid-response");
|
|
72
|
+
}
|
|
73
|
+
/** Unexpected failures reject without retaining raw credentials or submitted state. */
|
|
74
|
+
function decisionFailure(error) {
|
|
75
|
+
if (error instanceof EvaluationError) return {
|
|
76
|
+
status: "unavailable",
|
|
77
|
+
reason: error.reason,
|
|
78
|
+
...error.retryAfterMs !== void 0 ? { retryAfterMs: error.retryAfterMs } : {}
|
|
79
|
+
};
|
|
80
|
+
throw new Error("TypeSafe decision adapter contract failure.");
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
export { localBaseUrl as a, ConfigSchema as i, decisionFailure as n, runtimeConfig as o, evaluationError as r, EvaluationError as t };
|