@parcha/agentrun-jev 0.1.0-beta.21 → 0.1.0-beta.23
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 +2 -2
- package/dist/index.js +20 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ const result = await runJudge({
|
|
|
77
77
|
console.log(result.answers.team);
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Without a key, `createJevRunner()` throws `JevError` (code `configuration`: "Could not configure Jev. Supply apiKey or set TYPESAFE_API_KEY, and check transport options.") on that line, before any request is made. The same holds for `provider: "openrouter"` without `OPENROUTER_API_KEY`.
|
|
80
|
+
Without a key, or with an `apiKey` that is blank, `createJevRunner()` throws `JevError` (code `configuration`: "Could not configure Jev. Supply apiKey or set TYPESAFE_API_KEY, and check transport options.") on that line, before any request is made. The same holds for `provider: "openrouter"` without `OPENROUTER_API_KEY`.
|
|
81
81
|
|
|
82
82
|
`judge`, `pick`, `sift`, `route`, semantic predicates, and judgment verification use the same `runJudge` adapter. Choice, Noul, and Score answers retain their raw probabilities. The core validates answer IDs, probability keys and normalization, score rubrics, and value ranges before consuming a result. A typed answer records the model's decision. It does not prove that a real-world claim is true.
|
|
83
83
|
|
|
@@ -115,7 +115,7 @@ Cost: when the response reports `usage.cost` (USD) it is `cost_usd`; otherwise a
|
|
|
115
115
|
|
|
116
116
|
The official `@typesafe-ai/sdk` resolves `TYPESAFE_API_KEY` and `TYPESAFE_BASE_URL`. Explicit `apiKey` and `baseURL` options take precedence. An API root ends before `/v1/systemone`. Set `fetch` for a custom transport, or supply a `client` with a `systemOne(request, options)` method. A custom client must honor the request signal and disable its own retries; it owns its credentials and cannot be combined with transport options.
|
|
117
117
|
|
|
118
|
-
The adapter retries transient HTTP 408, 429, 5xx responses and connection failures with bounded exponential delays. On a 429 or 503 with a `Retry-After` (seconds or an HTTP date), it waits the larger of that and its own delay, and gives up at once, with attempts left, when that wait would not end before the timeout. When the attempts end on a 429 after an earlier 408, 5xx or lost connection, the error reports the first of those failures (its HTTP `status`, or code `connection`), since that attempt may have been billed; an `http` error with status 429 means every attempt was refused. SDK retries are disabled. Validation failures and other HTTP errors do not retry. The deadline and either the runner or per-call signal interrupt both requests and backoff. Cancellation bounds when the adapter returns; an uncooperative custom client can still continue its own work.
|
|
118
|
+
The adapter retries transient HTTP 408, 429, 5xx responses and connection failures with bounded exponential delays. On a 429 or 503 with a `Retry-After` (seconds or an HTTP date), it waits the larger of that and its own delay, and gives up at once, with attempts left, when that wait would not end before the timeout. When the attempts end on a 429 after an earlier 408, 5xx or lost connection, the error reports the first of those failures (its HTTP `status`, or code `connection`), since that attempt may have been billed; an `http` error with status 429 means every attempt was refused. SDK retries are disabled. Validation failures and other HTTP errors do not retry. The deadline and either the runner or per-call signal interrupt both requests and backoff. A call the deadline ends is `timeout`, whether the runner's timer or the SDK's per-attempt timer fired first, and is not retried. Cancellation bounds when the adapter returns; an uncooperative custom client can still continue its own work.
|
|
119
119
|
|
|
120
120
|
Request state has no local byte cap by default. Set `maxStateBytes` to a positive safe integer to reject serialized UTF-8 JSON state exceeding that host-owned limit before transport; omission or `null` disables this guard. This is a byte guard, not a token estimate. [Provider model context limits](https://docs.typesafe.ai/models.md) still apply. The adapter never truncates or splits state or questions.
|
|
121
121
|
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
|
-
import { APIConnectionError, APIError, Image, TypeSafeClient } from "@typesafe-ai/sdk";
|
|
3
|
+
import { APIConnectionError, APIError, APITimeoutError, Image, TypeSafeClient } from "@typesafe-ai/sdk";
|
|
4
4
|
import { validateAnswers, SystemOneError, isSystemOneResponseReason } from "@parcha/agentrun-dsl";
|
|
5
5
|
const keyVariable = { typesafe: "TYPESAFE_API_KEY", openrouter: "OPENROUTER_API_KEY" };
|
|
6
6
|
const OPENROUTER_BASE_URL = "https://openrouter.ai/api";
|
|
@@ -101,12 +101,14 @@ export function createJevRunner(options = {}) {
|
|
|
101
101
|
const maxAttempts = options.maxAttempts ?? 3;
|
|
102
102
|
const retryBaseMs = options.retryBaseMs ?? 500;
|
|
103
103
|
const retryMaxMs = options.retryMaxMs ?? 5_000;
|
|
104
|
+
// A JavaScript caller's `client: null` is no client, the same as an absent one.
|
|
105
|
+
const hostClient = options.client ?? undefined;
|
|
104
106
|
configuration(timeoutMs === null || milliseconds(timeoutMs), `timeoutMs must be null or a positive integer of at most ${MAX_TIMER_MS}.`);
|
|
105
107
|
configuration(maxStateBytes === null || (Number.isSafeInteger(maxStateBytes) && maxStateBytes > 0), "maxStateBytes must be null or a positive safe integer.");
|
|
106
|
-
configuration(timeoutMs !== null ||
|
|
108
|
+
configuration(timeoutMs !== null || hostClient !== undefined, "The TypeSafe SDK requires a finite timeout. For timeoutMs: null, supply a cancellation-only host client.");
|
|
107
109
|
configuration(Number.isInteger(maxAttempts) && maxAttempts >= 1 && maxAttempts <= 10, "maxAttempts must be an integer from 1 to 10.");
|
|
108
110
|
configuration(milliseconds(retryBaseMs, true) && milliseconds(retryMaxMs, true) && retryBaseMs <= retryMaxMs, "Retry delays must be nonnegative integers; retryBaseMs cannot exceed retryMaxMs.");
|
|
109
|
-
configuration(
|
|
111
|
+
configuration(hostClient === undefined || (options.apiKey === undefined && options.baseURL === undefined && options.fetch === undefined && options.provider === undefined), "Supply either client or transport options, not both.");
|
|
110
112
|
const provider = options.provider ?? "typesafe";
|
|
111
113
|
configuration(provider === "typesafe" || provider === "openrouter", "provider must be typesafe or openrouter.");
|
|
112
114
|
const pricing = options.pricing ? { ...options.pricing } : undefined;
|
|
@@ -117,10 +119,11 @@ export function createJevRunner(options = {}) {
|
|
|
117
119
|
let client;
|
|
118
120
|
try {
|
|
119
121
|
const key = options.apiKey ?? (provider === "openrouter" ? process.env[keyVariable.openrouter] : undefined);
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
+
// A blank key is refused on either provider: the SDK would send it as an empty bearer token. Given no key, the SDK falls back to
|
|
123
|
+
// TYPESAFE_API_KEY, so for OpenRouter an absent key is refused too: that would send one provider's key to another.
|
|
124
|
+
if ((key !== undefined || provider === "openrouter") && (typeof key !== "string" || key.trim().length === 0))
|
|
122
125
|
throw new Error("no key");
|
|
123
|
-
client =
|
|
126
|
+
client = hostClient ?? new TypeSafeClient({
|
|
124
127
|
apiKey: key, baseURL: options.baseURL ?? (provider === "openrouter" ? OPENROUTER_BASE_URL : undefined), fetch: options.fetch,
|
|
125
128
|
defaultModel: model, retry: { maxRetries: 0 }, timeout: timeoutMs ?? undefined, logLevel: "off",
|
|
126
129
|
// Bodies go as they always have: uncompressed JSON, which a host's own fetch, a gateway and OpenRouter all read.
|
|
@@ -150,24 +153,25 @@ export function createJevRunner(options = {}) {
|
|
|
150
153
|
const invalid = (reason, message, stateBytes) => new JevError("invalid_request", message, 0, undefined, { reason,
|
|
151
154
|
...(typeof params.label === "string" ? { label: params.label.slice(0, 200) } : {}),
|
|
152
155
|
...(stateBytes === undefined || maxStateBytes === null ? {} : { stateBytes, maxStateBytes }) });
|
|
153
|
-
const state = params.state
|
|
156
|
+
const state = params.state;
|
|
154
157
|
let serializedState;
|
|
155
158
|
try {
|
|
156
|
-
|
|
159
|
+
// The SDK refuses a request with no state, so a null or missing state is refused here, before anything is sent.
|
|
160
|
+
if (typeof state !== "string" && (typeof state !== "object" || state === null))
|
|
157
161
|
throw new Error();
|
|
158
162
|
serializedState = JSON.stringify(state);
|
|
159
163
|
if (typeof serializedState !== "string")
|
|
160
164
|
throw new Error();
|
|
161
165
|
}
|
|
162
166
|
catch {
|
|
163
|
-
throw invalid("invalid_state", "Jev state must be JSON-serializable text, an object
|
|
167
|
+
throw invalid("invalid_state", "Jev state must be JSON-serializable text, an object or an array.");
|
|
164
168
|
}
|
|
165
169
|
const stateBytes = Buffer.byteLength(serializedState, "utf8");
|
|
166
170
|
if (maxStateBytes !== null && stateBytes > maxStateBytes)
|
|
167
171
|
throw invalid("state_too_large", `Jev assembled state is ${stateBytes} UTF-8 bytes; configured host limit is ${maxStateBytes} bytes. No request was sent.`, stateBytes);
|
|
168
172
|
const callModel = params.model ?? model;
|
|
169
173
|
if (typeof callModel !== "string" || callModel.trim().length === 0)
|
|
170
|
-
throw invalid("
|
|
174
|
+
throw invalid("unsupported_model", "model must be a nonempty string; no request was sent.");
|
|
171
175
|
try {
|
|
172
176
|
body = JSON.stringify({ state: JSON.parse(serializedState), model: callModel, questions: params.questions });
|
|
173
177
|
request = JSON.parse(body);
|
|
@@ -184,7 +188,7 @@ export function createJevRunner(options = {}) {
|
|
|
184
188
|
throw invalid("invalid_assets", "assets must map each reference to an image; no request was sent.");
|
|
185
189
|
const references = Object.keys(given);
|
|
186
190
|
const carriesImage = references.length > 0;
|
|
187
|
-
if ((carriesImage || hasBox) && provider !== "typesafe"
|
|
191
|
+
if ((carriesImage || hasBox) && provider !== "typesafe")
|
|
188
192
|
throw invalid("unsupported_provider", "Images and Box questions are sent to TypeSafe's own API only; the OpenRouter path is not verified for them. No request was sent.");
|
|
189
193
|
if (hasBox && !answersBox(callModel))
|
|
190
194
|
throw invalid("unsupported_model", `A Box question needs a model that answers it, by name: ${BOX_MODEL} or later, or jev-preview; jev-latest is refused whatever version it points at. No request was sent.`);
|
|
@@ -226,7 +230,9 @@ export function createJevRunner(options = {}) {
|
|
|
226
230
|
}), signal, abortError);
|
|
227
231
|
}
|
|
228
232
|
catch (error) {
|
|
229
|
-
|
|
233
|
+
// Each attempt's timeout is the call's remaining budget, so the client's timer and the deadline end together: whichever fires first,
|
|
234
|
+
// the deadline ended the call. A timed-out request may have been billed, and its code is what says so.
|
|
235
|
+
if (signal.aborted || (timeoutMs !== null && error instanceof APITimeoutError))
|
|
230
236
|
throw abortError();
|
|
231
237
|
const status = error instanceof APIError ? error.status : undefined;
|
|
232
238
|
const retryable = status === 408 || status === 429 || (status !== undefined && status >= 500 && status <= 599) || error instanceof APIConnectionError;
|
|
@@ -265,8 +271,9 @@ export function createJevRunner(options = {}) {
|
|
|
265
271
|
if (!record(result))
|
|
266
272
|
throw invalidResponse("response_shape", "Jev returned an invalid response.");
|
|
267
273
|
const answers = result.answers;
|
|
274
|
+
// Checked against the questions as they were sent: a value JSON cannot carry (an undefined criterion) was never asked.
|
|
268
275
|
try {
|
|
269
|
-
validateAnswers(
|
|
276
|
+
validateAnswers(request.questions, answers);
|
|
270
277
|
}
|
|
271
278
|
catch (error) {
|
|
272
279
|
throw invalidResponse(error instanceof SystemOneError && isSystemOneResponseReason(error.responseReason) ? error.responseReason : "answer_validation", "Jev returned invalid answers or probability distributions.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcha/agentrun-jev",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.23",
|
|
4
4
|
"description": "Optional Jev system one decisions for AgentRun workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"test": "node --test test/*.test.mjs"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@parcha/agentrun-dsl": "0.1.0-beta.
|
|
30
|
+
"@parcha/agentrun-dsl": "0.1.0-beta.23",
|
|
31
31
|
"@typesafe-ai/sdk": "0.7.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|