@zenera/neo 1.1.9 → 1.1.10
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.
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { embeddingResponse } from "../embedding.js";
|
|
2
|
+
import { RETRY_STATUS_CODES } from "../models/openrouter.js";
|
|
2
3
|
import { zeroUsage } from "../types.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// OpenRouter embeddings adapter
|
|
@@ -31,7 +32,7 @@ export class OpenRouterEmbedder {
|
|
|
31
32
|
// branch of `embedding` below is what catches a gateway that
|
|
32
33
|
// decides otherwise.
|
|
33
34
|
};
|
|
34
|
-
const res = await this.#client.embeddings.generate({ requestBody }, { fetchOptions: { signal: req.signal } });
|
|
35
|
+
const res = await this.#client.embeddings.generate({ requestBody }, { fetchOptions: { signal: req.signal }, retryCodes: RETRY_STATUS_CODES });
|
|
35
36
|
// The response is declared as the body *or* a bare string, so the
|
|
36
37
|
// parsed shape has to be established before anything is read off it.
|
|
37
38
|
if (typeof res === 'string') {
|
package/dist/models/factory.js
CHANGED
|
@@ -342,6 +342,48 @@ export class ModelRegistry {
|
|
|
342
342
|
return declared ?? (isProviderKind(name) ? name : 'openai');
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// Retries
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
/**
|
|
349
|
+
* One backoff policy for every vendor, because a 429 is not a failure of the
|
|
350
|
+
* run — it is the provider asking to be called again later, and an agent that
|
|
351
|
+
* gives up on the first one loses the whole trajectory to a wait of a few
|
|
352
|
+
* seconds. Every SDK here can retry, but none of them agree on what happens
|
|
353
|
+
* when nothing is configured: the OpenAI and Anthropic clients retry twice,
|
|
354
|
+
* `@google/genai` does not retry *at all* unless `retryOptions` is present, and
|
|
355
|
+
* OpenRouter's generated client retries `5XX` only — so a Vertex or OpenRouter
|
|
356
|
+
* 429 was fatal by default. These constants are what make the four behave the
|
|
357
|
+
* same; `ProviderSpec.maxRetries` (0 disables) is the per-provider override.
|
|
358
|
+
*
|
|
359
|
+
* Retries live at the transport, not around `Model.generate`, so a retried call
|
|
360
|
+
* is one that never started: no partial stream is replayed, and a `Retry-After`
|
|
361
|
+
* header is honoured by the SDK that read it.
|
|
362
|
+
*/
|
|
363
|
+
const DEFAULT_MAX_RETRIES = 4;
|
|
364
|
+
/** wait after the first failure; doubles per attempt up to `RETRY_MAX_MS` */
|
|
365
|
+
const RETRY_INITIAL_MS = 1_000;
|
|
366
|
+
const RETRY_MAX_MS = 60_000;
|
|
367
|
+
const RETRY_EXPONENT = 2;
|
|
368
|
+
function retriesOf(opts) {
|
|
369
|
+
return Math.max(0, opts.maxRetries ?? DEFAULT_MAX_RETRIES);
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* The same schedule expressed as a wall-clock budget, which is the only shape
|
|
373
|
+
* OpenRouter's client accepts. It is the sum of the waits a count of `retries`
|
|
374
|
+
* would spend, so the two agree on how long a hopeless call may take — not on
|
|
375
|
+
* exactly how many times it is made, since this budget also has to cover the
|
|
376
|
+
* requests themselves.
|
|
377
|
+
*/
|
|
378
|
+
function retryWindowMs(retries) {
|
|
379
|
+
let wait = RETRY_INITIAL_MS;
|
|
380
|
+
let total = 0;
|
|
381
|
+
for (let i = 0; i < retries; i++) {
|
|
382
|
+
total += Math.min(wait, RETRY_MAX_MS);
|
|
383
|
+
wait *= RETRY_EXPONENT;
|
|
384
|
+
}
|
|
385
|
+
return total;
|
|
386
|
+
}
|
|
345
387
|
/**
|
|
346
388
|
* Credential resolution, in one place so a `${VAR}` reference behaves the same
|
|
347
389
|
* whether it came from yaml or from application code. `creds` may be a model
|
|
@@ -375,7 +417,7 @@ function buildClient(name, kind, creds, opts) {
|
|
|
375
417
|
baseURL,
|
|
376
418
|
defaultHeaders: headers,
|
|
377
419
|
timeout: opts.timeoutMs,
|
|
378
|
-
maxRetries: opts
|
|
420
|
+
maxRetries: retriesOf(opts),
|
|
379
421
|
...(opts.token ? { fetch: bearerFetch(opts.token) } : {}),
|
|
380
422
|
};
|
|
381
423
|
if (defaults.protocol === 'anthropic') {
|
|
@@ -408,13 +450,25 @@ function buildOpenRouter(kind, apiKey, baseURL, headers, opts) {
|
|
|
408
450
|
});
|
|
409
451
|
}
|
|
410
452
|
const { token } = opts;
|
|
453
|
+
const retries = retriesOf(opts);
|
|
411
454
|
return new OpenRouter({
|
|
412
455
|
apiKey: token ? async () => token() : apiKey,
|
|
413
456
|
serverURL: baseURL,
|
|
414
457
|
timeoutMs: opts.timeoutMs,
|
|
415
458
|
// A count of zero is the one value that has to change shape: this SDK
|
|
416
459
|
// says "do not retry" with a strategy, not with a number.
|
|
417
|
-
retryConfig:
|
|
460
|
+
retryConfig: retries === 0
|
|
461
|
+
? { strategy: 'none' }
|
|
462
|
+
: {
|
|
463
|
+
strategy: 'backoff',
|
|
464
|
+
backoff: {
|
|
465
|
+
initialInterval: RETRY_INITIAL_MS,
|
|
466
|
+
maxInterval: RETRY_MAX_MS,
|
|
467
|
+
exponent: RETRY_EXPONENT,
|
|
468
|
+
maxElapsedTime: retryWindowMs(retries),
|
|
469
|
+
},
|
|
470
|
+
retryConnectionErrors: true,
|
|
471
|
+
},
|
|
418
472
|
httpClient,
|
|
419
473
|
});
|
|
420
474
|
}
|
|
@@ -438,7 +492,15 @@ function buildOpenRouter(kind, apiKey, baseURL, headers, opts) {
|
|
|
438
492
|
*/
|
|
439
493
|
function buildGenAI(kind, apiKey, baseURL, headers, opts, where) {
|
|
440
494
|
const { GoogleGenAI } = sdk('@google/genai', kind);
|
|
441
|
-
|
|
495
|
+
// `attempts` counts the initial call, so a retry budget of zero is one
|
|
496
|
+
// attempt. Delays are fractions of a second here, unlike everywhere else.
|
|
497
|
+
const retryOptions = {
|
|
498
|
+
attempts: retriesOf(opts) + 1,
|
|
499
|
+
initialDelay: RETRY_INITIAL_MS / 1000,
|
|
500
|
+
maxDelay: RETRY_MAX_MS / 1000,
|
|
501
|
+
expBase: RETRY_EXPONENT,
|
|
502
|
+
};
|
|
503
|
+
const httpOptions = { baseUrl: baseURL, headers, timeout: opts.timeoutMs, retryOptions };
|
|
442
504
|
if (kind !== 'vertex') {
|
|
443
505
|
return new GoogleGenAI({ apiKey, httpOptions });
|
|
444
506
|
}
|
|
@@ -2,6 +2,16 @@ import type { OpenRouter } from '@openrouter/sdk';
|
|
|
2
2
|
import type { ChatRequestPlugin, ProviderPreferences } from '@openrouter/sdk/models';
|
|
3
3
|
import type { StreamDelta } from '../events.ts';
|
|
4
4
|
import type { Model, ModelRequest, ModelResponse } from '../model.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Which failures the SDK's backoff applies to: rate limits, request timeouts,
|
|
7
|
+
* and the upstream's own errors. Families are written `5XX` in this matcher.
|
|
8
|
+
*
|
|
9
|
+
* It has to be passed per call. The generated client resolves `retryCodes` at
|
|
10
|
+
* the *operation*, defaulting it to `5XX` alone, and no client-wide option
|
|
11
|
+
* reaches it — so configuring a retry policy and leaving this out gives a
|
|
12
|
+
* client that backs off diligently on 500s and dies on the first 429.
|
|
13
|
+
*/
|
|
14
|
+
export declare const RETRY_STATUS_CODES: string[];
|
|
5
15
|
/**
|
|
6
16
|
* Effort levels, spelled out rather than imported: the SDK's is an *open* enum
|
|
7
17
|
* (its literals plus any string), which would let a typo through, and naming
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
var _a;
|
|
2
2
|
import { zeroUsage } from "../types.js";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// OpenRouter adapter
|
|
5
|
+
//
|
|
6
|
+
// Not the chat-completions adapter pointed at another base url. OpenRouter's
|
|
7
|
+
// own SDK is a different wire contract in TypeScript terms — camelCase fields
|
|
8
|
+
// remapped to snake_case on the way out, a response union that covers both
|
|
9
|
+
// streaming and not, assistant content that may arrive as an array — so it
|
|
10
|
+
// gets its own adapter rather than a widened `OpenAIModel`.
|
|
11
|
+
//
|
|
12
|
+
// What that buys is the part of OpenRouter the OpenAI client cannot express:
|
|
13
|
+
// provider routing, a model fallback chain, and per-call cost. What it costs
|
|
14
|
+
// is that responses are zod-parsed on the way in, so a provider returning a
|
|
15
|
+
// shape the schema does not know raises `ResponseValidationError` where the
|
|
16
|
+
// OpenAI client would have shrugged. `kind: openai-compatible` against the
|
|
17
|
+
// same base url remains the way back if that ever bites.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* Which failures the SDK's backoff applies to: rate limits, request timeouts,
|
|
21
|
+
* and the upstream's own errors. Families are written `5XX` in this matcher.
|
|
22
|
+
*
|
|
23
|
+
* It has to be passed per call. The generated client resolves `retryCodes` at
|
|
24
|
+
* the *operation*, defaulting it to `5XX` alone, and no client-wide option
|
|
25
|
+
* reaches it — so configuring a retry policy and leaving this out gives a
|
|
26
|
+
* client that backs off diligently on 500s and dies on the first 429.
|
|
27
|
+
*/
|
|
28
|
+
export const RETRY_STATUS_CODES = ['408', '429', '5XX'];
|
|
3
29
|
function toStopReason(finish) {
|
|
4
30
|
switch (finish) {
|
|
5
31
|
case 'tool_calls':
|
|
@@ -154,7 +180,7 @@ export class OpenRouterModel {
|
|
|
154
180
|
* SDK keeps transport concerns out of the body it validates.
|
|
155
181
|
*/
|
|
156
182
|
#send(req, stream) {
|
|
157
|
-
return this.#client.chat.send({ chatRequest: this.#request(req, stream) }, { fetchOptions: { signal: req.signal } });
|
|
183
|
+
return this.#client.chat.send({ chatRequest: this.#request(req, stream) }, { fetchOptions: { signal: req.signal }, retryCodes: RETRY_STATUS_CODES });
|
|
158
184
|
}
|
|
159
185
|
#request(req, stream) {
|
|
160
186
|
const o = this.#options;
|
package/dist/project/config.d.ts
CHANGED
|
@@ -47,10 +47,10 @@ declare const modelSpec: z.ZodObject<{
|
|
|
47
47
|
thinkingBudgetTokens: z.ZodOptional<z.ZodInt>;
|
|
48
48
|
thinkingBudget: z.ZodOptional<z.ZodInt>;
|
|
49
49
|
thinkingLevel: z.ZodOptional<z.ZodEnum<{
|
|
50
|
+
minimal: "minimal";
|
|
50
51
|
low: "low";
|
|
51
52
|
medium: "medium";
|
|
52
53
|
high: "high";
|
|
53
|
-
minimal: "minimal";
|
|
54
54
|
}>>;
|
|
55
55
|
includeThoughts: z.ZodOptional<z.ZodBoolean>;
|
|
56
56
|
routing: z.ZodOptional<z.ZodObject<{
|
|
@@ -185,10 +185,10 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
185
185
|
thinkingBudgetTokens: z.ZodOptional<z.ZodInt>;
|
|
186
186
|
thinkingBudget: z.ZodOptional<z.ZodInt>;
|
|
187
187
|
thinkingLevel: z.ZodOptional<z.ZodEnum<{
|
|
188
|
+
minimal: "minimal";
|
|
188
189
|
low: "low";
|
|
189
190
|
medium: "medium";
|
|
190
191
|
high: "high";
|
|
191
|
-
minimal: "minimal";
|
|
192
192
|
}>>;
|
|
193
193
|
includeThoughts: z.ZodOptional<z.ZodBoolean>;
|
|
194
194
|
routing: z.ZodOptional<z.ZodObject<{
|