@zenera/neo 1.1.27 → 1.1.29
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 +11 -1
- package/dist/kernel.js +15 -0
- package/dist/models/factory.d.ts +2 -0
- package/dist/models/factory.js +122 -23
- package/dist/models/gateway.d.ts +29 -0
- package/dist/models/gateway.js +99 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/project/config.d.ts +5 -5
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img
|
|
3
|
+
src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/zenera-logo-with-media-queries.svg"
|
|
4
|
+
alt="Zenera Logo"
|
|
5
|
+
width="20"
|
|
6
|
+
/>
|
|
7
|
+
<div>Powered By Zenera</div>
|
|
8
|
+
</div>
|
|
9
|
+
<br>
|
|
10
|
+
|
|
1
11
|
<div align="center">
|
|
2
12
|
<picture>
|
|
3
13
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-neo-dark.svg">
|
|
4
|
-
<img src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-neo-
|
|
14
|
+
<img src="https://raw.githubusercontent.com/andreyryabov/ZeneraNeo/main/docs/imgs/banner-neo-with-media-queries.svg" alt="ZENERA NEO" width="720">
|
|
5
15
|
</picture>
|
|
6
16
|
|
|
7
17
|
[](https://www.npmjs.com/package/@zenera/neo)
|
package/dist/kernel.js
CHANGED
|
@@ -770,6 +770,21 @@ export async function createChildState(state, forkId, branch, env) {
|
|
|
770
770
|
};
|
|
771
771
|
const b = begin(child, env);
|
|
772
772
|
if (prefix.length) {
|
|
773
|
+
// Gemini signs only a parallel turn's first call: answer the siblings so none is dropped.
|
|
774
|
+
const turn = prefix.findLast((n) => n.type === 'llm_call' && n.toolCalls.some((c) => c.callId === fork.callId));
|
|
775
|
+
const answered = new Set(prefix.flatMap((n) => (n.type === 'tool_result' ? [n.callId] : [])));
|
|
776
|
+
for (const c of turn?.toolCalls ?? []) {
|
|
777
|
+
if (c.callId === fork.callId || answered.has(c.callId)) {
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
b.add({
|
|
781
|
+
type: 'tool_result',
|
|
782
|
+
callId: c.callId,
|
|
783
|
+
name: c.name,
|
|
784
|
+
result: await put(env, 'Not run in this branch: the parent runs this call, and its result is not visible here.'),
|
|
785
|
+
isError: false,
|
|
786
|
+
});
|
|
787
|
+
}
|
|
773
788
|
// The branch inherited the turn that called `fork`, so the honest way to
|
|
774
789
|
// hand it its assignment is to answer that call: the model sees a tool
|
|
775
790
|
// result for a tool it just called, instead of a second user message
|
package/dist/models/factory.d.ts
CHANGED
|
@@ -147,6 +147,8 @@ export interface ModelRequirement {
|
|
|
147
147
|
satisfied: boolean;
|
|
148
148
|
/** the SDK authenticates without a key, so `satisfied` says little */
|
|
149
149
|
keyOptional: boolean;
|
|
150
|
+
/** set when the request goes through a gateway, whose key `apiKeyEnv` then names */
|
|
151
|
+
gateway?: string;
|
|
150
152
|
}
|
|
151
153
|
export declare const providerKinds: ProviderKind[];
|
|
152
154
|
export declare function isProviderKind(name: string): name is ProviderKind;
|
package/dist/models/factory.js
CHANGED
|
@@ -6,6 +6,7 @@ import { OpenAIEmbedder } from "../embeddings/openai.js";
|
|
|
6
6
|
import { OpenRouterEmbedder } from "../embeddings/openrouter.js";
|
|
7
7
|
import { unfundedSymbol } from "../embeddings/rate-limit.js";
|
|
8
8
|
import { AnthropicModel } from "./anthropic.js";
|
|
9
|
+
import { GATEWAY_KEY_ENV, GATEWAY_KEY_HEADER, GATEWAY_NAME, GATEWAY_PATHS, GATEWAY_UPSTREAMS, gatewayOf, upstreamFetch, } from "./gateway.js";
|
|
9
10
|
import { GeminiModel } from "./gemini.js";
|
|
10
11
|
import { OpenAIModel } from "./openai-chat.js";
|
|
11
12
|
import { OpenAIResponsesModel } from "./openai-responses.js";
|
|
@@ -128,6 +129,12 @@ function protocolOf(kind, model) {
|
|
|
128
129
|
function apisOf(kind, protocol) {
|
|
129
130
|
return kind === 'vertex' && protocol === 'openai' ? ['chat'] : KINDS[kind].apis;
|
|
130
131
|
}
|
|
132
|
+
/** Bifrost has no OpenRouter drop-in, so OpenRouter models go through its OpenAI one. */
|
|
133
|
+
function gatewayProtocol(kind, model) {
|
|
134
|
+
const protocol = protocolOf(kind, model);
|
|
135
|
+
return protocol === 'openrouter' ? 'openai' : protocol;
|
|
136
|
+
}
|
|
137
|
+
const GATEWAY_APIS = ['chat', 'responses'];
|
|
131
138
|
// ---------------------------------------------------------------------------
|
|
132
139
|
// Tuning knobs
|
|
133
140
|
// ---------------------------------------------------------------------------
|
|
@@ -265,6 +272,31 @@ export class ModelRegistry {
|
|
|
265
272
|
this.#clients.set(name, built);
|
|
266
273
|
return built;
|
|
267
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* Gateway mode takes every provider, declared ones included; only a
|
|
277
|
+
* pre-built client stays put, since there is nothing in it to repoint.
|
|
278
|
+
*/
|
|
279
|
+
#route(spec, name) {
|
|
280
|
+
const kind = this.kindOf(name);
|
|
281
|
+
const gateway = spec.client || this.#spec(name).client ? undefined : gatewayOf();
|
|
282
|
+
if (gateway) {
|
|
283
|
+
const protocol = gatewayProtocol(kind, spec.model);
|
|
284
|
+
return { protocol, apis: protocol === 'openai' ? GATEWAY_APIS : [], gateway };
|
|
285
|
+
}
|
|
286
|
+
const protocol = protocolOf(kind, spec.model);
|
|
287
|
+
return { protocol, apis: apisOf(kind, protocol) };
|
|
288
|
+
}
|
|
289
|
+
/** One client per provider and protocol, keyed by the gateway it points at. */
|
|
290
|
+
#gateway(name, protocol, at) {
|
|
291
|
+
const key = `${name}\0gateway\0${protocol}\0${at.baseURL}\0${at.apiKey ?? ''}`;
|
|
292
|
+
const existing = this.#clients.get(key);
|
|
293
|
+
if (existing) {
|
|
294
|
+
return existing;
|
|
295
|
+
}
|
|
296
|
+
const built = buildGatewayClient(name, this.kindOf(name), protocol, this.#spec(name), at);
|
|
297
|
+
this.#clients.set(key, built);
|
|
298
|
+
return built;
|
|
299
|
+
}
|
|
268
300
|
/** A Vertex provider's Claude or partner client: built once, beside its GenAI one. */
|
|
269
301
|
#partner(name, protocol, spec) {
|
|
270
302
|
const key = `${name}\0${protocol}`;
|
|
@@ -304,14 +336,12 @@ export class ModelRegistry {
|
|
|
304
336
|
*/
|
|
305
337
|
knobs(ref) {
|
|
306
338
|
const spec = typeof ref === 'string' ? this.parse(ref) : ref;
|
|
307
|
-
const
|
|
308
|
-
const protocol = protocolOf(kind, spec.model);
|
|
339
|
+
const { protocol, apis } = this.#route(spec, spec.provider ?? this.#default);
|
|
309
340
|
if (protocol !== 'openai') {
|
|
310
341
|
return unreadKnobs(spec, protocol);
|
|
311
342
|
}
|
|
312
343
|
// An api this vendor does not speak already has an error of its own;
|
|
313
344
|
// answering about its knobs would only bury it.
|
|
314
|
-
const apis = apisOf(kind, protocol);
|
|
315
345
|
const api = spec.api ?? apis[0];
|
|
316
346
|
return api && apis.includes(api) ? unreadKnobs(spec, `openai/${api}`) : [];
|
|
317
347
|
}
|
|
@@ -321,7 +351,7 @@ export class ModelRegistry {
|
|
|
321
351
|
const name = spec.provider ?? this.#default;
|
|
322
352
|
const provider = this.#spec(name);
|
|
323
353
|
const kind = this.kindOf(name);
|
|
324
|
-
const protocol =
|
|
354
|
+
const { protocol, apis, gateway } = this.#route(spec, name);
|
|
325
355
|
// Before the client, so a knob for the wrong vendor is reported as the
|
|
326
356
|
// config mistake it is rather than behind a missing credential.
|
|
327
357
|
const [stray] = this.knobs(spec);
|
|
@@ -333,15 +363,17 @@ export class ModelRegistry {
|
|
|
333
363
|
// different connection, and handing back the memoized one would
|
|
334
364
|
// silently ignore them.
|
|
335
365
|
const client = spec.client ??
|
|
336
|
-
(
|
|
337
|
-
? this.#
|
|
338
|
-
:
|
|
339
|
-
?
|
|
340
|
-
:
|
|
366
|
+
(gateway
|
|
367
|
+
? this.#gateway(name, protocol, gateway)
|
|
368
|
+
: protocol !== KINDS[kind].protocol
|
|
369
|
+
? this.#partner(name, protocol, provider)
|
|
370
|
+
: hasCredentials(spec)
|
|
371
|
+
? buildClient(name, kind, spec, provider)
|
|
372
|
+
: this.client(name));
|
|
341
373
|
// The *resolved* name, not `spec.provider`: a bare ref carries none,
|
|
342
374
|
// and a failure that cannot say which connection refused it is the
|
|
343
375
|
// thing this is here to prevent.
|
|
344
|
-
const options = { ...spec, provider: name };
|
|
376
|
+
const options = { ...spec, provider: gateway ? `${name} via ${GATEWAY_NAME}` : name };
|
|
345
377
|
// Every protocol but OpenAI's has exactly one API, so naming one is a
|
|
346
378
|
// mistake worth reporting rather than a field to ignore.
|
|
347
379
|
if (protocol !== 'openai') {
|
|
@@ -357,7 +389,6 @@ export class ModelRegistry {
|
|
|
357
389
|
return new OpenRouterModel(spec.model, client, options);
|
|
358
390
|
}
|
|
359
391
|
}
|
|
360
|
-
const apis = apisOf(kind, protocol);
|
|
361
392
|
const api = spec.api ?? apis[0];
|
|
362
393
|
if (!apis.includes(api)) {
|
|
363
394
|
throw new TypeError(`provider "${name}" (${kind}) does not speak the "${api}" api ` +
|
|
@@ -377,16 +408,22 @@ export class ModelRegistry {
|
|
|
377
408
|
const name = spec.provider ?? this.#default;
|
|
378
409
|
const provider = this.#spec(name);
|
|
379
410
|
const kind = this.kindOf(name);
|
|
411
|
+
const { protocol, gateway } = this.#route(spec, name);
|
|
380
412
|
const client = spec.client ??
|
|
381
|
-
(
|
|
413
|
+
(gateway
|
|
414
|
+
? this.#gateway(name, protocol, gateway)
|
|
415
|
+
: hasCredentials(spec)
|
|
416
|
+
? buildClient(name, kind, spec, provider)
|
|
417
|
+
: this.client(name));
|
|
382
418
|
// Inline credentials are a different connection, and so a different
|
|
383
419
|
// budget: they get pacing of their own rather than the provider's.
|
|
420
|
+
const own = hasCredentials(spec) && !gateway;
|
|
384
421
|
const options = {
|
|
385
422
|
...spec,
|
|
386
|
-
provider: name,
|
|
387
|
-
limiter: spec.limiter ?? (
|
|
423
|
+
provider: gateway ? `${name} via ${GATEWAY_NAME}` : name,
|
|
424
|
+
limiter: spec.limiter ?? (own ? undefined : this.#limiter(name)),
|
|
388
425
|
};
|
|
389
|
-
switch (KINDS[kind].protocol) {
|
|
426
|
+
switch (gateway ? protocol : KINDS[kind].protocol) {
|
|
390
427
|
case 'openai':
|
|
391
428
|
return new OpenAIEmbedder(spec.model, client, options);
|
|
392
429
|
case 'gemini':
|
|
@@ -410,6 +447,17 @@ export class ModelRegistry {
|
|
|
410
447
|
const name = spec.provider ?? this.#default;
|
|
411
448
|
const provider = this.#spec(name);
|
|
412
449
|
const kind = this.kindOf(name);
|
|
450
|
+
const gateway = this.#route(spec, name).gateway;
|
|
451
|
+
if (gateway) {
|
|
452
|
+
return {
|
|
453
|
+
provider: name,
|
|
454
|
+
kind,
|
|
455
|
+
apiKeyEnv: GATEWAY_KEY_ENV,
|
|
456
|
+
satisfied: Boolean(gateway.apiKey),
|
|
457
|
+
keyOptional: false,
|
|
458
|
+
gateway: GATEWAY_NAME,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
413
461
|
const defaults = KINDS[kind];
|
|
414
462
|
const creds = hasCredentials(spec) ? spec : provider;
|
|
415
463
|
const apiKeyEnv = creds.apiKeyEnv ?? defaults.apiKeyEnv;
|
|
@@ -633,15 +681,12 @@ function buildOpenRouter(kind, apiKey, baseURL, headers, opts) {
|
|
|
633
681
|
*/
|
|
634
682
|
function buildGenAI(kind, apiKey, baseURL, headers, opts, where) {
|
|
635
683
|
const { GoogleGenAI } = sdk('@google/genai', kind);
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
maxDelay: RETRY_MAX_MS / 1000,
|
|
642
|
-
expBase: RETRY_EXPONENT,
|
|
684
|
+
const httpOptions = {
|
|
685
|
+
baseUrl: baseURL,
|
|
686
|
+
headers,
|
|
687
|
+
timeout: opts.timeoutMs,
|
|
688
|
+
retryOptions: genaiRetries(opts),
|
|
643
689
|
};
|
|
644
|
-
const httpOptions = { baseUrl: baseURL, headers, timeout: opts.timeoutMs, retryOptions };
|
|
645
690
|
if (kind !== 'vertex') {
|
|
646
691
|
return new GoogleGenAI({ apiKey, httpOptions });
|
|
647
692
|
}
|
|
@@ -664,6 +709,60 @@ function buildGenAI(kind, apiKey, baseURL, headers, opts, where) {
|
|
|
664
709
|
httpOptions,
|
|
665
710
|
});
|
|
666
711
|
}
|
|
712
|
+
/**
|
|
713
|
+
* `attempts` counts the initial call, so a retry budget of zero is one attempt.
|
|
714
|
+
* Delays are fractions of a second here, unlike everywhere else.
|
|
715
|
+
*/
|
|
716
|
+
function genaiRetries(opts) {
|
|
717
|
+
return {
|
|
718
|
+
attempts: retriesOf(opts) + 1,
|
|
719
|
+
initialDelay: RETRY_INITIAL_MS / 1000,
|
|
720
|
+
maxDelay: RETRY_MAX_MS / 1000,
|
|
721
|
+
expBase: RETRY_EXPONENT,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* A client aimed at the gateway's drop-in endpoint for `protocol`, with the
|
|
726
|
+
* virtual key in place of the vendor's. The provider keeps its transport
|
|
727
|
+
* settings and headers; its own key and base url are what the gateway replaces.
|
|
728
|
+
*/
|
|
729
|
+
function buildGatewayClient(name, kind, protocol, opts, at) {
|
|
730
|
+
const baseURL = `${at.baseURL}${GATEWAY_PATHS[protocol]}`;
|
|
731
|
+
// Every SDK refuses to construct without a key, even for a gateway that wants none.
|
|
732
|
+
const apiKey = at.apiKey ?? GATEWAY_NAME;
|
|
733
|
+
const headers = {
|
|
734
|
+
...expandHeaders(opts.headers, `provider "${name}"`),
|
|
735
|
+
...(at.apiKey ? { [GATEWAY_KEY_HEADER]: at.apiKey } : {}),
|
|
736
|
+
};
|
|
737
|
+
const routed = upstreamFetch(GATEWAY_UPSTREAMS[kind], protocol);
|
|
738
|
+
if (protocol === 'gemini') {
|
|
739
|
+
const { GoogleGenAI } = sdk('@google/genai', kind);
|
|
740
|
+
return new GoogleGenAI({
|
|
741
|
+
apiKey,
|
|
742
|
+
httpOptions: {
|
|
743
|
+
baseUrl: baseURL,
|
|
744
|
+
headers,
|
|
745
|
+
timeout: opts.timeoutMs,
|
|
746
|
+
retryOptions: genaiRetries(opts),
|
|
747
|
+
fetch: routed,
|
|
748
|
+
},
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
const common = {
|
|
752
|
+
apiKey,
|
|
753
|
+
baseURL,
|
|
754
|
+
defaultHeaders: headers,
|
|
755
|
+
timeout: opts.timeoutMs,
|
|
756
|
+
maxRetries: retriesOf(opts),
|
|
757
|
+
fetch: funded(routed),
|
|
758
|
+
};
|
|
759
|
+
if (protocol === 'anthropic') {
|
|
760
|
+
const { Anthropic } = sdk('@anthropic-ai/sdk', kind);
|
|
761
|
+
return new Anthropic(common);
|
|
762
|
+
}
|
|
763
|
+
const { OpenAI } = sdk('openai', kind);
|
|
764
|
+
return new OpenAI(common);
|
|
765
|
+
}
|
|
667
766
|
/**
|
|
668
767
|
* `project_id` out of a service-account key file, when one is named.
|
|
669
768
|
*
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ProviderKind } from './factory.ts';
|
|
2
|
+
export declare const GATEWAY_NAME = "bifrost";
|
|
3
|
+
export declare const GATEWAY_URL_ENV = "BIFROST_BASE_URL";
|
|
4
|
+
export declare const GATEWAY_KEY_ENV = "BIFROST_API_KEY";
|
|
5
|
+
/** Bifrost's own virtual-key header: still read when `Authorization` is taken by gateway login. */
|
|
6
|
+
export declare const GATEWAY_KEY_HEADER = "x-bf-vk";
|
|
7
|
+
export interface Gateway {
|
|
8
|
+
/** no trailing slash */
|
|
9
|
+
baseURL: string;
|
|
10
|
+
/** the virtual key; absent for a gateway that enforces none */
|
|
11
|
+
apiKey?: string;
|
|
12
|
+
}
|
|
13
|
+
/** The gateway this process is configured for, or undefined when vendors are called directly. */
|
|
14
|
+
export declare function gatewayOf(): Gateway | undefined;
|
|
15
|
+
/** The wire formats Bifrost has a drop-in endpoint for. */
|
|
16
|
+
export type GatewayProtocol = 'openai' | 'anthropic' | 'gemini';
|
|
17
|
+
export declare const GATEWAY_PATHS: Record<GatewayProtocol, string>;
|
|
18
|
+
/** Bifrost's name for the upstream each kind reaches; it routes on `<upstream>/<model>`. */
|
|
19
|
+
export declare const GATEWAY_UPSTREAMS: Record<ProviderKind, string>;
|
|
20
|
+
/**
|
|
21
|
+
* Qualifies the model id on the wire only. A `Model`'s or `Embedder`'s id stays
|
|
22
|
+
* the one the config names, because a memory or an index records the embedder
|
|
23
|
+
* id and refuses a different one — flipping gateway mode must not orphan them.
|
|
24
|
+
*
|
|
25
|
+
* Gemini names its model in the url; the other two in the body. `inner` is
|
|
26
|
+
* resolved per call so a stubbed global `fetch` is honoured.
|
|
27
|
+
*/
|
|
28
|
+
export declare function upstreamFetch(upstream: string, protocol: GatewayProtocol, inner?: typeof fetch): typeof fetch;
|
|
29
|
+
//# sourceMappingURL=gateway.d.ts.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Gateway mode
|
|
3
|
+
//
|
|
4
|
+
// With `BIFROST_BASE_URL` set, every provider is reached through one Bifrost
|
|
5
|
+
// gateway and one virtual key, and `agents.yaml` does not change: a
|
|
6
|
+
// `vertex:gemini-3.5-flash` still goes out through the GenAI SDK, in Gemini's
|
|
7
|
+
// own wire format, only to Bifrost's `/genai` drop-in endpoint instead of to
|
|
8
|
+
// Google. Keeping each vendor's protocol is the point — Bifrost's unified
|
|
9
|
+
// chat-completions endpoint would cost the thinking budgets, thought signatures
|
|
10
|
+
// and cache accounting the native adapters exist for.
|
|
11
|
+
//
|
|
12
|
+
// The switch is the environment rather than the config because it describes the
|
|
13
|
+
// machine, not the project: the same project runs direct on a laptop with
|
|
14
|
+
// vendor keys and through the gateway on a machine that only holds a virtual
|
|
15
|
+
// key.
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const GATEWAY_NAME = 'bifrost';
|
|
18
|
+
export const GATEWAY_URL_ENV = 'BIFROST_BASE_URL';
|
|
19
|
+
export const GATEWAY_KEY_ENV = 'BIFROST_API_KEY';
|
|
20
|
+
/** Bifrost's own virtual-key header: still read when `Authorization` is taken by gateway login. */
|
|
21
|
+
export const GATEWAY_KEY_HEADER = 'x-bf-vk';
|
|
22
|
+
/** The gateway this process is configured for, or undefined when vendors are called directly. */
|
|
23
|
+
export function gatewayOf() {
|
|
24
|
+
const url = process.env[GATEWAY_URL_ENV]?.trim();
|
|
25
|
+
if (!url) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const apiKey = process.env[GATEWAY_KEY_ENV]?.trim();
|
|
29
|
+
return { baseURL: url.replace(/\/+$/, ''), ...(apiKey ? { apiKey } : {}) };
|
|
30
|
+
}
|
|
31
|
+
export const GATEWAY_PATHS = {
|
|
32
|
+
openai: '/openai',
|
|
33
|
+
anthropic: '/anthropic',
|
|
34
|
+
gemini: '/genai',
|
|
35
|
+
};
|
|
36
|
+
/** Bifrost's name for the upstream each kind reaches; it routes on `<upstream>/<model>`. */
|
|
37
|
+
export const GATEWAY_UPSTREAMS = {
|
|
38
|
+
openai: 'openai',
|
|
39
|
+
'openai-compatible': 'openai',
|
|
40
|
+
anthropic: 'anthropic',
|
|
41
|
+
google: 'gemini',
|
|
42
|
+
vertex: 'vertex',
|
|
43
|
+
openrouter: 'openrouter',
|
|
44
|
+
};
|
|
45
|
+
const MODEL_PATH = /\/models\/([^/:?]+):(\w+)/;
|
|
46
|
+
/**
|
|
47
|
+
* Qualifies the model id on the wire only. A `Model`'s or `Embedder`'s id stays
|
|
48
|
+
* the one the config names, because a memory or an index records the embedder
|
|
49
|
+
* id and refuses a different one — flipping gateway mode must not orphan them.
|
|
50
|
+
*
|
|
51
|
+
* Gemini names its model in the url; the other two in the body. `inner` is
|
|
52
|
+
* resolved per call so a stubbed global `fetch` is honoured.
|
|
53
|
+
*/
|
|
54
|
+
export function upstreamFetch(upstream, protocol, inner) {
|
|
55
|
+
const prefix = `${upstream}/`;
|
|
56
|
+
const qualify = (id) => (id.startsWith(prefix) ? id : prefix + id);
|
|
57
|
+
return async (input, init) => {
|
|
58
|
+
const send = inner ?? fetch;
|
|
59
|
+
const body = typeof init?.body === 'string' ? qualifyBody(init.body, protocol, qualify) : init?.body;
|
|
60
|
+
if (protocol !== 'gemini') {
|
|
61
|
+
return send(input, body === init?.body ? init : { ...init, body });
|
|
62
|
+
}
|
|
63
|
+
const url = input instanceof Request ? input.url : String(input);
|
|
64
|
+
const routed = url.replace(MODEL_PATH, (_match, id, verb) => `/models/${qualify(id)}:${verb}`);
|
|
65
|
+
return send(routed, { ...init, body });
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function qualifyBody(body, protocol, qualify) {
|
|
69
|
+
let parsed;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(body);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return body;
|
|
75
|
+
}
|
|
76
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
77
|
+
return body;
|
|
78
|
+
}
|
|
79
|
+
const doc = parsed;
|
|
80
|
+
if (protocol !== 'gemini') {
|
|
81
|
+
if (typeof doc.model !== 'string') {
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
doc.model = qualify(doc.model);
|
|
85
|
+
return JSON.stringify(doc);
|
|
86
|
+
}
|
|
87
|
+
// batchEmbedContents repeats the model per request, as `models/<id>`.
|
|
88
|
+
if (!Array.isArray(doc.requests)) {
|
|
89
|
+
return body;
|
|
90
|
+
}
|
|
91
|
+
for (const request of doc.requests) {
|
|
92
|
+
const id = typeof request?.model === 'string' ? /^models\/(.+)$/.exec(request.model) : null;
|
|
93
|
+
if (id) {
|
|
94
|
+
request.model = `models/${qualify(id[1])}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return JSON.stringify(doc);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=gateway.js.map
|
package/dist/models/index.d.ts
CHANGED
package/dist/models/index.js
CHANGED
package/dist/project/config.d.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { z } from 'zod';
|
|
|
7
7
|
declare const provider: z.ZodObject<{
|
|
8
8
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
9
9
|
openai: "openai";
|
|
10
|
+
anthropic: "anthropic";
|
|
10
11
|
google: "google";
|
|
11
12
|
vertex: "vertex";
|
|
12
|
-
anthropic: "anthropic";
|
|
13
13
|
openrouter: "openrouter";
|
|
14
14
|
"openai-compatible": "openai-compatible";
|
|
15
15
|
}>>;
|
|
@@ -72,9 +72,9 @@ declare const modelSpec: z.ZodObject<{
|
|
|
72
72
|
serviceTier: z.ZodOptional<z.ZodEnum<{
|
|
73
73
|
default: "default";
|
|
74
74
|
auto: "auto";
|
|
75
|
+
priority: "priority";
|
|
75
76
|
fast: "fast";
|
|
76
77
|
flex: "flex";
|
|
77
|
-
priority: "priority";
|
|
78
78
|
scale: "scale";
|
|
79
79
|
}>>;
|
|
80
80
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
@@ -184,9 +184,9 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
184
184
|
providers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
185
185
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
186
186
|
openai: "openai";
|
|
187
|
+
anthropic: "anthropic";
|
|
187
188
|
google: "google";
|
|
188
189
|
vertex: "vertex";
|
|
189
|
-
anthropic: "anthropic";
|
|
190
190
|
openrouter: "openrouter";
|
|
191
191
|
"openai-compatible": "openai-compatible";
|
|
192
192
|
}>>;
|
|
@@ -244,9 +244,9 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
244
244
|
serviceTier: z.ZodOptional<z.ZodEnum<{
|
|
245
245
|
default: "default";
|
|
246
246
|
auto: "auto";
|
|
247
|
+
priority: "priority";
|
|
247
248
|
fast: "fast";
|
|
248
249
|
flex: "flex";
|
|
249
|
-
priority: "priority";
|
|
250
250
|
scale: "scale";
|
|
251
251
|
}>>;
|
|
252
252
|
apiKey: z.ZodOptional<z.ZodString>;
|
|
@@ -347,7 +347,7 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
347
347
|
autoRecall: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
|
|
348
348
|
limit: z.ZodDefault<z.ZodInt>;
|
|
349
349
|
}, z.core.$strict>]>>;
|
|
350
|
-
}, z.core.$strict
|
|
350
|
+
}, z.core.$strict>>, unknown>>;
|
|
351
351
|
sandbox: z.ZodOptional<z.ZodObject<{
|
|
352
352
|
image: z.ZodOptional<z.ZodString>;
|
|
353
353
|
build: z.ZodOptional<z.ZodObject<{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenera/neo",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.29",
|
|
4
4
|
"description": "A multi-agent runtime for Node.js: agents, models, tools, skills, memory and an append-only trajectory.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"graphology": "^0.26.0",
|
|
66
|
-
"yaml": "^2.9.
|
|
67
|
-
"zod": "^4.
|
|
66
|
+
"yaml": "^2.9.1",
|
|
67
|
+
"zod": "^4.6.7"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@anthropic-ai/sdk": "^0.120.0",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@anthropic-ai/sdk": "^0.120.0",
|
|
91
|
-
"@google/genai": "^2.
|
|
92
|
-
"@openrouter/sdk": "^1.
|
|
91
|
+
"@google/genai": "^2.30.0",
|
|
92
|
+
"@openrouter/sdk": "^1.4.25",
|
|
93
93
|
"openai": "^6.0.0"
|
|
94
94
|
}
|
|
95
95
|
}
|