@parcha/agentrun-jev 0.1.0-beta.17 → 0.1.0-beta.19
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.d.ts +1 -1
- package/dist/index.js +29 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Optional [TypeSafe Jev](https://docs.typesafe.ai/) support for AgentRun DSL. Jev answers focused questions with typed values and probability distributions. The workflow decides how to use them.
|
|
4
4
|
|
|
5
|
-
Beta
|
|
5
|
+
Beta, on npm under the `beta` dist-tag (`npm view @parcha/agentrun-jev dist-tags`). Requires Node.js 22.19 or later.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
npm install @parcha/agentrun-dsl@beta @parcha/agentrun-jev@beta
|
|
@@ -102,7 +102,7 @@ const { answers, usage, cost_usd } = await judge({
|
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
- `assets` maps each reference, a string that appears literally in `state` or in a question's `instructions` or `criteria`, to the image. The runner builds the SDK's image from the bytes and sends it as a multipart file part. Each reference must appear inside a string value of the state, an instruction or a criterion (a question id or an option name does not count), or the request is refused.
|
|
105
|
-
- A Box question needs `model: 'jev-1.14.0'
|
|
105
|
+
- A Box question needs a model named for it: `model: 'jev-1.14.0'`, a later version, or `'jev-preview'`. The runner checks the name, so it refuses a Box on `jev-latest` whatever version that alias points at. A Box on any other model, or a Box with no image, is refused with `JevError` code `invalid_request` before any request. `model` can also be named per call.
|
|
106
106
|
- Before any request, the runner also refuses what Jev would reject: a file that is not PNG or JPEG (the bytes are checked against `mime`), an empty file, a file over 16 MiB, a reference nothing uses. It does not check pixel dimensions (each side 280 to 1568, total 78,400 to 2,458,624 pixels) or the 32 MiB request limit; Jev rejects those with a 400 or 413, which surfaces as a typed `http` error.
|
|
107
107
|
- Images are billed as input tokens at the text price, and `usage.input_tokens` counts them. `cost_usd` is as for text: the provider's reported cost, else your `pricing` applied to the counted tokens, else `null`. `request_sha256` names each image by its own SHA-256, so the same request hashes the same and a different image does not.
|
|
108
108
|
- A Box answer is always a box (there is no "not found"): ask a noul question in the same request to check presence. Edges and confidence are checked to lie in [0, 1] and the box to run forward (`top <= bottom`, `left <= right`); a confidence the provider omits is `null`.
|
package/dist/index.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ type RunJudge = NonNullable<WorkflowDeps["runJudge"]>;
|
|
|
19
19
|
* every reference must appear, and a Box question needs at least one image. */
|
|
20
20
|
export type JevJudgeParams = Parameters<RunJudge>[0] & {
|
|
21
21
|
assets?: Record<string, JevImage>;
|
|
22
|
-
/** Names the model for this call, over the runner's `model`. A Box question needs `jev-1.14.0` or `jev-preview
|
|
22
|
+
/** Names the model for this call, over the runner's `model`. A Box question needs `jev-1.14.0` (or later) or `jev-preview` by name; the default `jev-latest` is refused for it whatever version that alias points at. */
|
|
23
23
|
model?: string;
|
|
24
24
|
};
|
|
25
25
|
/** The typed answers (choice, noul, score with their probabilities; box with its edges and confidence), the model that answered, its token usage, and
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export function jevProviderFromEnv(env = process.env) {
|
|
|
12
12
|
const IMAGE_HEADER = "TypeSafe-Danger-Zone-Images";
|
|
13
13
|
const MAX_IMAGE_BYTES = 16 * 1024 * 1024;
|
|
14
14
|
const BOX_MODEL = "jev-1.14.0";
|
|
15
|
-
/** Models that answer a Box question: Jev 1.14 and later by name, and the preview alias. `jev-latest` is
|
|
15
|
+
/** Models that answer a Box question: Jev 1.14 and later by name, and the preview alias. The check is by name, so `jev-latest` is refused whatever version it points at. */
|
|
16
16
|
function answersBox(model) {
|
|
17
17
|
if (model === "jev-preview")
|
|
18
18
|
return true;
|
|
@@ -82,6 +82,16 @@ function wait(ms, signal, error) {
|
|
|
82
82
|
abort();
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
/** A server's Retry-After in milliseconds from now, given as delta-seconds or an HTTP date; undefined when absent or unparseable. */
|
|
86
|
+
function retryAfterMs(headers) {
|
|
87
|
+
const value = headers instanceof Headers ? headers.get("retry-after")?.trim() : undefined;
|
|
88
|
+
if (!value)
|
|
89
|
+
return undefined;
|
|
90
|
+
if (/^\d+$/.test(value))
|
|
91
|
+
return Number(value) * 1000;
|
|
92
|
+
const date = Date.parse(value);
|
|
93
|
+
return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());
|
|
94
|
+
}
|
|
85
95
|
export function createJevRunner(options = {}) {
|
|
86
96
|
const timeoutMs = options.timeoutMs === null ? null : (options.timeoutMs ?? 30_000);
|
|
87
97
|
const maxStateBytes = options.maxStateBytes ?? null;
|
|
@@ -173,7 +183,7 @@ export function createJevRunner(options = {}) {
|
|
|
173
183
|
if ((carriesImage || hasBox) && provider !== "typesafe" && options.client === undefined)
|
|
174
184
|
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.");
|
|
175
185
|
if (hasBox && !answersBox(callModel))
|
|
176
|
-
throw invalid("unsupported_model", `A Box question needs a model that answers it: ${BOX_MODEL}
|
|
186
|
+
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.`);
|
|
177
187
|
if (hasBox && !carriesImage)
|
|
178
188
|
throw invalid("invalid_assets", "A Box question locates something in an image, and the request carries none. No request was sent.");
|
|
179
189
|
let assets;
|
|
@@ -200,6 +210,8 @@ export function createJevRunner(options = {}) {
|
|
|
200
210
|
body = JSON.stringify({ ...JSON.parse(body), assets: assetDigests });
|
|
201
211
|
}
|
|
202
212
|
const headers = carriesImage || hasBox ? { [IMAGE_HEADER]: "true" } : undefined;
|
|
213
|
+
// The first attempt that failed in a way the server may have billed (a lost answer, a 408, a 5xx). A later 429 does not make the call free: the give-up reports this one.
|
|
214
|
+
let billable;
|
|
203
215
|
for (attempt = 1;; attempt++) {
|
|
204
216
|
if (signal.aborted)
|
|
205
217
|
throw abortError();
|
|
@@ -214,10 +226,23 @@ export function createJevRunner(options = {}) {
|
|
|
214
226
|
throw abortError();
|
|
215
227
|
const status = error instanceof APIError ? error.status : undefined;
|
|
216
228
|
const retryable = status === 408 || status === 429 || (status !== undefined && status >= 500 && status <= 599) || error instanceof APIConnectionError;
|
|
229
|
+
if (retryable && status !== 429 && billable === undefined)
|
|
230
|
+
billable = status === undefined ? {} : { status };
|
|
217
231
|
if (retryable && attempt < maxAttempts) {
|
|
218
|
-
|
|
219
|
-
|
|
232
|
+
const backoff = Math.min(retryMaxMs, retryBaseMs * 2 ** (attempt - 1));
|
|
233
|
+
const asked = error instanceof APIError && (status === 429 || status === 503) ? retryAfterMs(error.headers) : undefined;
|
|
234
|
+
const delay = asked === undefined ? backoff : Math.max(asked, backoff);
|
|
235
|
+
// A server's wait is kept in full or the loop gives up now: it never runs past the deadline, or past the longest timer when there is none, so no
|
|
236
|
+
// retry is sent with no time left to answer (a request that times out has a cost no one knows).
|
|
237
|
+
const left = timeoutMs === null ? 2_147_483_647 : timeoutMs - (Date.now() - started);
|
|
238
|
+
if (asked === undefined || delay < left) {
|
|
239
|
+
await wait(delay, signal, abortError);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
220
242
|
}
|
|
243
|
+
// HTTP 429 means every attempt was refused unanswered; after one that may have been billed, the give-up names that one instead.
|
|
244
|
+
if (status === 429 && billable)
|
|
245
|
+
throw billable.status === undefined ? new JevError("connection", "Jev transport failed.", attempt) : new JevError("http", `Jev returned HTTP ${billable.status}.`, attempt, billable.status);
|
|
221
246
|
if (status !== undefined) {
|
|
222
247
|
const safeStatus = Number.isInteger(status) && status >= 100 && status <= 599 ? status : undefined;
|
|
223
248
|
const capacity = error instanceof APIError && record(error.body) && record(error.body.detail)
|
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.19",
|
|
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.19",
|
|
31
31
|
"@typesafe-ai/sdk": "0.7.0"
|
|
32
32
|
},
|
|
33
33
|
"repository": {
|