@zenera/neo 1.1.0

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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/agent.d.ts +66 -0
  4. package/dist/agent.js +97 -0
  5. package/dist/architecture.d.ts +74 -0
  6. package/dist/architecture.js +105 -0
  7. package/dist/embedding.d.ts +60 -0
  8. package/dist/embedding.js +21 -0
  9. package/dist/embeddings/gemini.d.ts +24 -0
  10. package/dist/embeddings/gemini.js +71 -0
  11. package/dist/embeddings/index.d.ts +4 -0
  12. package/dist/embeddings/index.js +4 -0
  13. package/dist/embeddings/openai.d.ts +18 -0
  14. package/dist/embeddings/openai.js +27 -0
  15. package/dist/embeddings/openrouter.d.ts +21 -0
  16. package/dist/embeddings/openrouter.js +54 -0
  17. package/dist/events.d.ts +144 -0
  18. package/dist/events.js +110 -0
  19. package/dist/ids.d.ts +14 -0
  20. package/dist/ids.js +33 -0
  21. package/dist/index.d.ts +25 -0
  22. package/dist/index.js +25 -0
  23. package/dist/inspect/client.d.ts +2 -0
  24. package/dist/inspect/client.js +1326 -0
  25. package/dist/inspect/css.d.ts +2 -0
  26. package/dist/inspect/css.js +164 -0
  27. package/dist/inspect/index.d.ts +9 -0
  28. package/dist/inspect/index.js +12 -0
  29. package/dist/inspect/page.d.ts +3 -0
  30. package/dist/inspect/page.js +93 -0
  31. package/dist/inspect/report.d.ts +54 -0
  32. package/dist/inspect/report.js +62 -0
  33. package/dist/kernel.d.ts +178 -0
  34. package/dist/kernel.js +929 -0
  35. package/dist/memory-stores/factory.d.ts +15 -0
  36. package/dist/memory-stores/factory.js +29 -0
  37. package/dist/memory-stores/file.d.ts +37 -0
  38. package/dist/memory-stores/file.js +174 -0
  39. package/dist/memory-stores/in-memory.d.ts +17 -0
  40. package/dist/memory-stores/in-memory.js +80 -0
  41. package/dist/memory-stores/index.d.ts +4 -0
  42. package/dist/memory-stores/index.js +5 -0
  43. package/dist/memory.d.ts +115 -0
  44. package/dist/memory.js +213 -0
  45. package/dist/model.d.ts +25 -0
  46. package/dist/model.js +2 -0
  47. package/dist/models/anthropic.d.ts +36 -0
  48. package/dist/models/anthropic.js +293 -0
  49. package/dist/models/factory.d.ts +202 -0
  50. package/dist/models/factory.js +533 -0
  51. package/dist/models/gemini.d.ts +45 -0
  52. package/dist/models/gemini.js +368 -0
  53. package/dist/models/index.d.ts +7 -0
  54. package/dist/models/index.js +7 -0
  55. package/dist/models/openai-chat.d.ts +19 -0
  56. package/dist/models/openai-chat.js +203 -0
  57. package/dist/models/openai-responses.d.ts +31 -0
  58. package/dist/models/openai-responses.js +258 -0
  59. package/dist/models/openrouter.d.ts +44 -0
  60. package/dist/models/openrouter.js +230 -0
  61. package/dist/payload-stores/factory.d.ts +15 -0
  62. package/dist/payload-stores/factory.js +30 -0
  63. package/dist/payload-stores/file.d.ts +29 -0
  64. package/dist/payload-stores/file.js +112 -0
  65. package/dist/payload-stores/in-memory.d.ts +17 -0
  66. package/dist/payload-stores/in-memory.js +40 -0
  67. package/dist/payload-stores/index.d.ts +4 -0
  68. package/dist/payload-stores/index.js +5 -0
  69. package/dist/payload.d.ts +89 -0
  70. package/dist/payload.js +181 -0
  71. package/dist/project/config.d.ts +302 -0
  72. package/dist/project/config.js +290 -0
  73. package/dist/project/index.d.ts +4 -0
  74. package/dist/project/index.js +4 -0
  75. package/dist/project/load.d.ts +142 -0
  76. package/dist/project/load.js +430 -0
  77. package/dist/project/refs.d.ts +23 -0
  78. package/dist/project/refs.js +70 -0
  79. package/dist/prompt.d.ts +66 -0
  80. package/dist/prompt.js +62 -0
  81. package/dist/runner.d.ts +103 -0
  82. package/dist/runner.js +537 -0
  83. package/dist/services.d.ts +23 -0
  84. package/dist/services.js +44 -0
  85. package/dist/skill-providers/factory.d.ts +16 -0
  86. package/dist/skill-providers/factory.js +26 -0
  87. package/dist/skill-providers/file.d.ts +44 -0
  88. package/dist/skill-providers/file.js +209 -0
  89. package/dist/skill-providers/index.d.ts +4 -0
  90. package/dist/skill-providers/index.js +5 -0
  91. package/dist/skill-providers/static.d.ts +13 -0
  92. package/dist/skill-providers/static.js +47 -0
  93. package/dist/skills.d.ts +102 -0
  94. package/dist/skills.js +232 -0
  95. package/dist/state.d.ts +86 -0
  96. package/dist/state.js +49 -0
  97. package/dist/tools/exa.d.ts +23 -0
  98. package/dist/tools/exa.js +389 -0
  99. package/dist/tools/index.d.ts +4 -0
  100. package/dist/tools/index.js +6 -0
  101. package/dist/tools/sandbox.d.ts +172 -0
  102. package/dist/tools/sandbox.js +602 -0
  103. package/dist/tools/workspace.d.ts +53 -0
  104. package/dist/tools/workspace.js +1034 -0
  105. package/dist/trajectory.d.ts +213 -0
  106. package/dist/trajectory.js +232 -0
  107. package/dist/types.d.ts +195 -0
  108. package/dist/types.js +134 -0
  109. package/package.json +98 -0
@@ -0,0 +1,533 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { GeminiEmbedder } from "../embeddings/gemini.js";
4
+ import { OpenAIEmbedder } from "../embeddings/openai.js";
5
+ import { OpenRouterEmbedder } from "../embeddings/openrouter.js";
6
+ import { AnthropicModel } from "./anthropic.js";
7
+ import { GeminiModel } from "./gemini.js";
8
+ import { OpenAIModel } from "./openai-chat.js";
9
+ import { OpenAIResponsesModel } from "./openai-responses.js";
10
+ import { OpenRouterModel } from "./openrouter.js";
11
+ // ---------------------------------------------------------------------------
12
+ // The vendor SDKs
13
+ //
14
+ // All four are *optional peer dependencies*: an application installs the one
15
+ // vendor it talks to and pays for nothing else — which matters most for
16
+ // `@google/genai`, whose own tree carries google-auth-library and protobufjs.
17
+ //
18
+ // That only holds if importing this library does not reach for all four, so
19
+ // nothing above is a value import: the SDKs are pulled in here, when a client
20
+ // of that protocol is first built, and never at module scope.
21
+ //
22
+ // `createRequire` rather than `await import()` because it is synchronous, and
23
+ // so `createModel()` and `ModelRegistry.client()` keep handing back a usable
24
+ // object instead of a promise — which is also what keeps a bad credential
25
+ // throwing at the call that named it rather than at some later request. Three
26
+ // of the four publish a CommonJS build; `@openrouter/sdk` is ESM-only, which
27
+ // `require` has loaded since Node 22 for module graphs without a top-level
28
+ // await, and this library already requires Node 24.
29
+ // ---------------------------------------------------------------------------
30
+ const requirePeer = createRequire(import.meta.url);
31
+ /**
32
+ * An uninstalled optional peer is a setup mistake, not a bug, so it is reported
33
+ * as the one-line fix rather than as a resolution failure from inside Node.
34
+ */
35
+ function sdk(pkg, kind) {
36
+ try {
37
+ return requirePeer(pkg);
38
+ }
39
+ catch (cause) {
40
+ if (cause.code !== 'MODULE_NOT_FOUND') {
41
+ throw cause;
42
+ }
43
+ throw new Error(`provider kind "${kind}" needs ${pkg}, which is not installed — run: npm i ${pkg}`, { cause });
44
+ }
45
+ }
46
+ /**
47
+ * Only two of these need no adapter of their own.
48
+ *
49
+ * `openai-compatible` is the shim kind — vLLM, a self-hosted gateway — and the
50
+ * OpenAI client is exactly right for it. Everything else gets its vendor's own
51
+ * SDK, because the compatibility endpoints each vendor publishes are porting
52
+ * aids: Google's drops thinking budgets, thought signatures and cached-content
53
+ * accounting, Anthropic's drops cache accounting and extended thinking. Those
54
+ * are the things this runtime is built around, so paying for two more
55
+ * dependencies is the cheaper trade.
56
+ *
57
+ * `openrouter` used to be that shim with the two constants filled in, which
58
+ * cost nothing until the parts of OpenRouter worth having turned out to be the
59
+ * parts chat completions cannot say: which upstream provider serves a request,
60
+ * what to fall back to when it cannot, and what the call actually cost. Its
61
+ * own SDK carries those as first-class fields, so it now gets an adapter like
62
+ * every other vendor. `openai-compatible` against the same base url is still
63
+ * there for anyone who wants the older, laxer client back.
64
+ *
65
+ * `google` and `vertex` differ only in how the client authenticates and which
66
+ * backend it addresses; both speak the same API through the same adapter.
67
+ */
68
+ const KINDS = {
69
+ openai: {
70
+ protocol: 'openai',
71
+ apiKeyEnv: 'OPENAI_API_KEY',
72
+ baseURLEnv: 'OPENAI_BASE_URL',
73
+ apis: ['chat', 'responses'],
74
+ },
75
+ google: {
76
+ protocol: 'gemini',
77
+ apiKeyEnv: 'GEMINI_API_KEY',
78
+ baseURLEnv: 'GEMINI_BASE_URL',
79
+ apis: [],
80
+ },
81
+ vertex: {
82
+ // Vertex authenticates with Application Default Credentials, which the
83
+ // GenAI SDK resolves itself — so unlike every other kind this one
84
+ // usually has no key at all. `VERTEX_API_KEY` covers express mode.
85
+ protocol: 'gemini',
86
+ apiKeyEnv: 'VERTEX_API_KEY',
87
+ baseURLEnv: 'VERTEX_BASE_URL',
88
+ apis: [],
89
+ keyOptional: true,
90
+ },
91
+ anthropic: {
92
+ protocol: 'anthropic',
93
+ apiKeyEnv: 'ANTHROPIC_API_KEY',
94
+ baseURLEnv: 'ANTHROPIC_BASE_URL',
95
+ apis: [],
96
+ },
97
+ openrouter: {
98
+ protocol: 'openrouter',
99
+ apiKeyEnv: 'OPENROUTER_API_KEY',
100
+ baseURLEnv: 'OPENROUTER_BASE_URL',
101
+ baseURL: 'https://openrouter.ai/api/v1',
102
+ apis: [],
103
+ },
104
+ 'openai-compatible': {
105
+ protocol: 'openai',
106
+ apiKeyEnv: 'OPENAI_API_KEY',
107
+ baseURLEnv: 'OPENAI_BASE_URL',
108
+ apis: ['chat', 'responses'],
109
+ },
110
+ };
111
+ export const providerKinds = Object.keys(KINDS);
112
+ export function isProviderKind(name) {
113
+ return Object.hasOwn(KINDS, name);
114
+ }
115
+ // ---------------------------------------------------------------------------
116
+ // Environment references
117
+ // ---------------------------------------------------------------------------
118
+ // `${VAR}` and `${VAR:-fallback}`. Chosen over a bare `env.VAR` token because a
119
+ // literal secret never contains `${`, so there is no rule to remember about
120
+ // which strings are magic — and because it composes inside a longer value,
121
+ // which a whole-value token cannot: `https://${GATEWAY}/v1`.
122
+ const ENV_REF = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g;
123
+ /**
124
+ * Substitutes `${VAR}` references. An unset variable with no fallback is an
125
+ * error naming both the variable and where it was written, because the
126
+ * alternative is an empty api key and a 401 three layers away.
127
+ */
128
+ export function expandEnv(value, where) {
129
+ return value.replace(ENV_REF, (_match, name, fallback) => {
130
+ const found = fromEnv(name);
131
+ if (found !== undefined) {
132
+ return found;
133
+ }
134
+ if (fallback !== undefined) {
135
+ return fallback;
136
+ }
137
+ throw new Error(`${where}: \${${name}} is not set`);
138
+ });
139
+ }
140
+ function fromEnv(name) {
141
+ const value = process.env[name];
142
+ return value?.trim() ? value : undefined;
143
+ }
144
+ function expand(value, where) {
145
+ return value === undefined ? undefined : expandEnv(value, where);
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Registry
149
+ // ---------------------------------------------------------------------------
150
+ /**
151
+ * Named providers, and the clients behind them.
152
+ *
153
+ * The clients are why this is an object rather than a function: one `OpenAI`
154
+ * per provider name, built on first use and shared by every model and every
155
+ * agent that names it. A registry is also the seam a host uses to point a name
156
+ * at different credentials, or at a stub — neither of which should mean
157
+ * touching a `switch`.
158
+ */
159
+ export class ModelRegistry {
160
+ #providers = new Map();
161
+ #clients = new Map();
162
+ #default = 'openai';
163
+ /** Declares (or replaces) a provider. Nothing is contacted until it is used. */
164
+ provider(name, spec = {}) {
165
+ if (spec.kind !== undefined && !isProviderKind(spec.kind)) {
166
+ throw new TypeError(`provider "${name}": unknown kind "${spec.kind}" ` +
167
+ `(known: ${providerKinds.join(', ')})`);
168
+ }
169
+ this.#providers.set(name, spec);
170
+ // Re-declaring after a client was handed out would otherwise leave the
171
+ // old credentials in service for the rest of the process.
172
+ this.#clients.delete(name);
173
+ return this;
174
+ }
175
+ /** Names the provider a bare model id belongs to. */
176
+ setDefault(name) {
177
+ if (!this.has(name)) {
178
+ throw new Error(`default provider "${name}" is not declared (known: ${this.names().join(', ')})`);
179
+ }
180
+ this.#default = name;
181
+ return this;
182
+ }
183
+ get defaultProvider() {
184
+ return this.#default;
185
+ }
186
+ /** Declared here, or a built-in kind usable with no declaration at all. */
187
+ has(name) {
188
+ return this.#providers.has(name) || isProviderKind(name);
189
+ }
190
+ names() {
191
+ return [...new Set([...this.#providers.keys(), ...providerKinds])];
192
+ }
193
+ /** The shared client for a provider, built on first use. */
194
+ client(name) {
195
+ const existing = this.#clients.get(name);
196
+ if (existing) {
197
+ return existing;
198
+ }
199
+ const spec = this.#spec(name);
200
+ const built = spec.client ?? buildClient(name, this.kindOf(name), spec, spec);
201
+ this.#clients.set(name, built);
202
+ return built;
203
+ }
204
+ /** Turns a shorthand or a spec into a `Model`. */
205
+ model(ref) {
206
+ const spec = typeof ref === 'string' ? this.parse(ref) : ref;
207
+ const name = spec.provider ?? this.#default;
208
+ const provider = this.#spec(name);
209
+ const kind = this.kindOf(name);
210
+ const defaults = KINDS[kind];
211
+ // Inline credentials opt out of the shared client: they describe a
212
+ // different connection, and handing back the memoized one would
213
+ // silently ignore them.
214
+ const client = spec.client ??
215
+ (hasCredentials(spec) ? buildClient(name, kind, spec, provider) : this.client(name));
216
+ // Every protocol but OpenAI's has exactly one API, so naming one is a
217
+ // mistake worth reporting rather than a field to ignore.
218
+ if (defaults.protocol !== 'openai') {
219
+ if (spec.api) {
220
+ throw new TypeError(`provider "${name}" (${kind}) has one api, so "${spec.api}" means nothing here`);
221
+ }
222
+ switch (defaults.protocol) {
223
+ case 'anthropic':
224
+ return new AnthropicModel(spec.model, client, spec);
225
+ case 'gemini':
226
+ return new GeminiModel(spec.model, client, spec);
227
+ case 'openrouter':
228
+ return new OpenRouterModel(spec.model, client, spec);
229
+ }
230
+ }
231
+ const api = spec.api ?? defaults.apis[0];
232
+ if (!defaults.apis.includes(api)) {
233
+ throw new TypeError(`provider "${name}" (${kind}) does not speak the "${api}" api ` +
234
+ `(supported: ${defaults.apis.join(', ')})`);
235
+ }
236
+ return api === 'responses'
237
+ ? new OpenAIResponsesModel(spec.model, client, spec)
238
+ : new OpenAIModel(spec.model, client, spec);
239
+ }
240
+ /**
241
+ * Turns a shorthand or a spec into an `Embedder`, over the same providers
242
+ * and the same shared clients as `model()` — an `openai` key declared once
243
+ * generates and embeds without being mentioned twice.
244
+ */
245
+ embedder(ref) {
246
+ const spec = typeof ref === 'string' ? this.#parseEmbedding(ref) : ref;
247
+ const name = spec.provider ?? this.#default;
248
+ const provider = this.#spec(name);
249
+ const kind = this.kindOf(name);
250
+ const client = spec.client ??
251
+ (hasCredentials(spec) ? buildClient(name, kind, spec, provider) : this.client(name));
252
+ switch (KINDS[kind].protocol) {
253
+ case 'openai':
254
+ return new OpenAIEmbedder(spec.model, client, spec);
255
+ case 'gemini':
256
+ return new GeminiEmbedder(spec.model, client, spec);
257
+ case 'openrouter':
258
+ return new OpenRouterEmbedder(spec.model, client, spec);
259
+ case 'anthropic':
260
+ // Not an omission to be filled in later: Anthropic publishes no
261
+ // embeddings API at all, and points at third parties instead.
262
+ throw new TypeError(`provider "${name}" (${kind}) has no embeddings api — ` +
263
+ `declare a provider of another kind for \`embeddings:\``);
264
+ }
265
+ }
266
+ /**
267
+ * What a ref needs to reach its vendor, resolved but not contacted. An
268
+ * unknown provider still throws: that is a broken config, not a missing
269
+ * credential, and the two want different words.
270
+ */
271
+ requirement(ref) {
272
+ const spec = typeof ref === 'string' ? this.parse(ref) : ref;
273
+ const name = spec.provider ?? this.#default;
274
+ const provider = this.#spec(name);
275
+ const kind = this.kindOf(name);
276
+ const defaults = KINDS[kind];
277
+ const creds = hasCredentials(spec) ? spec : provider;
278
+ const apiKeyEnv = creds.apiKeyEnv ?? defaults.apiKeyEnv;
279
+ // An unset `${VAR}` is precisely the absent credential being asked
280
+ // about, so it answers the question rather than interrupting it.
281
+ let apiKey;
282
+ try {
283
+ apiKey = expand(creds.apiKey, `provider "${name}": apiKey`) ?? fromEnv(apiKeyEnv);
284
+ }
285
+ catch {
286
+ apiKey = undefined;
287
+ }
288
+ return {
289
+ provider: name,
290
+ kind,
291
+ apiKeyEnv,
292
+ satisfied: Boolean(apiKey ??
293
+ provider.token ??
294
+ (spec.client || provider.client) ??
295
+ defaults.keyOptional),
296
+ keyOptional: Boolean(defaults.keyOptional),
297
+ };
298
+ }
299
+ /** `[provider[/api]:]model` → spec. Exposed so a caller can validate a ref. */
300
+ parse(ref) {
301
+ // Only the first colon separates the prefix: model ids may contain colons.
302
+ const colon = ref.indexOf(':');
303
+ if (colon < 0) {
304
+ return { model: ref };
305
+ }
306
+ const [provider, api] = ref.slice(0, colon).split('/');
307
+ const model = ref.slice(colon + 1);
308
+ if (!model) {
309
+ throw new TypeError(`missing model id in "${ref}"`);
310
+ }
311
+ if (!provider || !this.has(provider)) {
312
+ throw new TypeError(`unknown provider "${provider}" in "${ref}" ` +
313
+ `(known: ${this.names().join(', ')}; a model id that itself contains a ` +
314
+ `colon must name its provider, e.g. "openai:${ref}")`);
315
+ }
316
+ if (api !== undefined && api !== 'chat' && api !== 'responses') {
317
+ throw new TypeError(`unknown api "${api}" in "${ref}" (expected chat or responses)`);
318
+ }
319
+ return { provider, api, model };
320
+ }
321
+ #parseEmbedding(ref) {
322
+ const { provider, api, model } = this.parse(ref);
323
+ if (api) {
324
+ throw new TypeError(`"${ref}": embeddings have one api, so "${api}" means nothing here`);
325
+ }
326
+ return { provider, model };
327
+ }
328
+ #spec(name) {
329
+ const declared = this.#providers.get(name);
330
+ if (declared) {
331
+ return declared;
332
+ }
333
+ if (isProviderKind(name)) {
334
+ return { kind: name };
335
+ }
336
+ throw new Error(`unknown provider "${name}" (known: ${this.names().join(', ')}; ` +
337
+ `declare it under \`providers:\` or in ProjectOptions.providers)`);
338
+ }
339
+ /** A declared kind wins; otherwise a built-in name is its own kind. */
340
+ kindOf(name) {
341
+ const declared = this.#providers.get(name)?.kind;
342
+ return declared ?? (isProviderKind(name) ? name : 'openai');
343
+ }
344
+ }
345
+ /**
346
+ * Credential resolution, in one place so a `${VAR}` reference behaves the same
347
+ * whether it came from yaml or from application code. `creds` may be a model
348
+ * spec overriding the connection; `opts` always carries the provider's
349
+ * transport settings, which an override does not get to reinvent.
350
+ */
351
+ function buildClient(name, kind, creds, opts) {
352
+ const where = `provider "${name}"`;
353
+ const defaults = KINDS[kind];
354
+ // A token callback replaces the key rather than supplementing it: the
355
+ // header is rewritten per request, so whatever the SDK was constructed with
356
+ // never reaches the wire.
357
+ const apiKeyEnv = creds.apiKeyEnv ?? defaults.apiKeyEnv;
358
+ const apiKey = expand(creds.apiKey, `${where}: apiKey`) ?? fromEnv(apiKeyEnv);
359
+ if (!apiKey && !opts.token && !defaults.keyOptional) {
360
+ throw new Error(`${where}: no api key — set \`apiKey\`, or set ${apiKeyEnv}`);
361
+ }
362
+ // An absent base url means "vendor default", so it may stay undefined.
363
+ const baseURL = expand(creds.baseURL, `${where}: baseURL`) ??
364
+ fromEnv(creds.baseURLEnv ?? defaults.baseURLEnv) ??
365
+ defaults.baseURL;
366
+ const headers = expandHeaders(opts.headers, where);
367
+ if (defaults.protocol === 'gemini') {
368
+ return buildGenAI(kind, apiKey, baseURL, headers, opts, where);
369
+ }
370
+ if (defaults.protocol === 'openrouter') {
371
+ return buildOpenRouter(kind, apiKey, baseURL, headers, opts);
372
+ }
373
+ const common = {
374
+ apiKey: apiKey ?? OAUTH_PLACEHOLDER,
375
+ baseURL,
376
+ defaultHeaders: headers,
377
+ timeout: opts.timeoutMs,
378
+ maxRetries: opts.maxRetries,
379
+ ...(opts.token ? { fetch: bearerFetch(opts.token) } : {}),
380
+ };
381
+ if (defaults.protocol === 'anthropic') {
382
+ const { Anthropic } = sdk('@anthropic-ai/sdk', kind);
383
+ return new Anthropic(common);
384
+ }
385
+ const { OpenAI } = sdk('openai', kind);
386
+ return new OpenAI(common);
387
+ }
388
+ /**
389
+ * OpenRouter's client takes its base url as `serverURL` and its retry policy as
390
+ * a strategy object rather than a count, so the shared `common` shape does not
391
+ * fit — but the two things that usually need a custom `fetch` are cheaper here.
392
+ *
393
+ * A `token` callback goes straight in as `apiKey`, which this SDK accepts in
394
+ * callable form and re-invokes per request; there is nothing to intercept.
395
+ * Default headers have no constructor slot at all, so they are added by a
396
+ * `beforeRequest` hook — the same seam the SDK uses for tracing headers.
397
+ */
398
+ function buildOpenRouter(kind, apiKey, baseURL, headers, opts) {
399
+ const { OpenRouter, HTTPClient } = sdk('@openrouter/sdk', kind);
400
+ let httpClient;
401
+ if (headers) {
402
+ httpClient = new HTTPClient();
403
+ httpClient.addHook('beforeRequest', (req) => {
404
+ for (const [key, value] of Object.entries(headers)) {
405
+ req.headers.set(key, value);
406
+ }
407
+ return req;
408
+ });
409
+ }
410
+ const { token } = opts;
411
+ return new OpenRouter({
412
+ apiKey: token ? async () => token() : apiKey,
413
+ serverURL: baseURL,
414
+ timeoutMs: opts.timeoutMs,
415
+ // A count of zero is the one value that has to change shape: this SDK
416
+ // says "do not retry" with a strategy, not with a number.
417
+ retryConfig: opts.maxRetries === 0 ? { strategy: 'none' } : undefined,
418
+ httpClient,
419
+ });
420
+ }
421
+ /**
422
+ * The GenAI client is configured by *backend* rather than by url: `vertexai`
423
+ * selects the Gemini Enterprise endpoint and the SDK derives the address from
424
+ * the project and region, so there is no base url to assemble here.
425
+ *
426
+ * Authentication differs the same way. The Gemini API takes a key; Vertex
427
+ * normally takes none, because the SDK resolves Application Default
428
+ * Credentials — a service account, a workload identity, `gcloud auth
429
+ * application-default login` — and refreshes them itself. That is why `vertex`
430
+ * is `keyOptional`, and why it needs none of the bearer-token machinery the
431
+ * OpenAI protocol uses for the same job.
432
+ */
433
+ function buildGenAI(kind, apiKey, baseURL, headers, opts, where) {
434
+ const { GoogleGenAI } = sdk('@google/genai', kind);
435
+ const httpOptions = { baseUrl: baseURL, headers, timeout: opts.timeoutMs };
436
+ if (kind !== 'vertex') {
437
+ return new GoogleGenAI({ apiKey, httpOptions });
438
+ }
439
+ const project = expand(opts.project, `${where}: project`) ??
440
+ fromEnv('GOOGLE_CLOUD_PROJECT') ??
441
+ projectFromKeyFile();
442
+ if (!project && !apiKey) {
443
+ throw new Error(`${where}: vertex needs \`project\`, or GOOGLE_CLOUD_PROJECT ` +
444
+ `(or \`apiKey\` for express mode)`);
445
+ }
446
+ return new GoogleGenAI({
447
+ vertexai: true,
448
+ apiKey,
449
+ project,
450
+ location: expand(opts.location, `${where}: location`) ??
451
+ fromEnv('GOOGLE_CLOUD_LOCATION') ??
452
+ 'global',
453
+ httpOptions,
454
+ });
455
+ }
456
+ /**
457
+ * `project_id` out of a service-account key file, when one is named.
458
+ *
459
+ * The GenAI SDK resolves Application Default Credentials itself but takes the
460
+ * project only from its constructor or `GOOGLE_CLOUD_PROJECT` — so a key file
461
+ * that already states which project it belongs to still has to have that
462
+ * repeated in the environment. Reading it here removes the second variable for
463
+ * the common case.
464
+ *
465
+ * Only that one route carries a project id: credentials from `gcloud auth
466
+ * application-default login` do not, and a metadata server answers a different
467
+ * endpoint entirely. Both still need the project named, which is why a
468
+ * failure to read anything here is not an error — the caller's own check is.
469
+ */
470
+ function projectFromKeyFile() {
471
+ const path = fromEnv('GOOGLE_APPLICATION_CREDENTIALS');
472
+ if (!path) {
473
+ return undefined;
474
+ }
475
+ try {
476
+ const key = JSON.parse(readFileSync(path, 'utf8'));
477
+ const id = key.project_id;
478
+ return typeof id === 'string' && id.trim() ? id : undefined;
479
+ }
480
+ catch {
481
+ // An unreadable or malformed file is the SDK's to report, at the point
482
+ // where it tries to authenticate with it and can say so precisely.
483
+ return undefined;
484
+ }
485
+ }
486
+ // Both SDKs refuse to construct without a key. When a token callback owns the
487
+ // Authorization header this value is never sent, so it only has to be non-empty.
488
+ const OAUTH_PLACEHOLDER = 'oauth';
489
+ /**
490
+ * Rewrites `Authorization` on every request from a callback, so a credential
491
+ * that expires — an Azure AD token, a gateway's short-lived grant — is
492
+ * refreshed without the client, and its connection pool, being rebuilt. Vertex
493
+ * has no need of it: its SDK does the same job from ambient credentials.
494
+ */
495
+ function bearerFetch(token) {
496
+ return async (input, init) => {
497
+ const headers = new Headers(init?.headers);
498
+ headers.set('Authorization', `Bearer ${await token()}`);
499
+ return fetch(input, { ...init, headers });
500
+ };
501
+ }
502
+ function expandHeaders(headers, where) {
503
+ if (!headers) {
504
+ return undefined;
505
+ }
506
+ return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, expandEnv(v, `${where}: headers.${k}`)]));
507
+ }
508
+ function hasCredentials(spec) {
509
+ return Boolean(spec.apiKey || spec.apiKeyEnv || spec.baseURL || spec.baseURLEnv);
510
+ }
511
+ // ---------------------------------------------------------------------------
512
+ // Default registry
513
+ // ---------------------------------------------------------------------------
514
+ /**
515
+ * The registry behind the bare `createModel`. An application that never
516
+ * declares a provider still gets shared clients through it, because the
517
+ * built-in names (`openai`, `google`, …) resolve without declaration.
518
+ */
519
+ export const defaultModels = new ModelRegistry();
520
+ /**
521
+ * One entry point for every provider. Accepts a shorthand string or a full
522
+ * spec, so `createModel('openai/responses:o3')` and
523
+ * `createModel({ model: 'o3', api: 'responses', apiKeyEnv: 'MY_KEY' })` are
524
+ * both valid.
525
+ */
526
+ export function createModel(ref, registry = defaultModels) {
527
+ return registry.model(ref);
528
+ }
529
+ /** `createModel`'s counterpart for vectors, over the same registry and clients. */
530
+ export function createEmbedder(ref, registry = defaultModels) {
531
+ return registry.embedder(ref);
532
+ }
533
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1,45 @@
1
+ import type { GoogleGenAI } from '@google/genai';
2
+ import type { StreamDelta } from '../events.ts';
3
+ import type { Model, ModelRequest, ModelResponse } from '../model.ts';
4
+ /** The coarse dial Gemini 3 takes in place of a token budget. */
5
+ export type GeminiThinkingLevel = 'minimal' | 'low' | 'medium' | 'high';
6
+ /**
7
+ * Provider-specific knobs.
8
+ *
9
+ * Thinking is configured two different ways depending on the generation, and
10
+ * both are exposed rather than unified: 2.5 takes a token `thinkingBudget`,
11
+ * 3 takes a coarse `thinkingLevel`, and passing the wrong one is an API error
12
+ * rather than something this adapter can paper over.
13
+ */
14
+ export interface GeminiModelOptions {
15
+ /** hard cap on output tokens; the model's own default applies when unset */
16
+ maxTokens?: number;
17
+ /** 2.5-era budget, in tokens: `0` disables thinking, `-1` lets the model decide */
18
+ thinkingBudget?: number;
19
+ /** 3-era dial; mutually exclusive with `thinkingBudget` */
20
+ thinkingLevel?: GeminiThinkingLevel;
21
+ /**
22
+ * Ask for thought *summaries* — the only reasoning text the API exposes.
23
+ * On by default: the runtime has a `thinking` channel and the tokens are
24
+ * billed whether or not the summary is returned.
25
+ */
26
+ includeThoughts?: boolean;
27
+ }
28
+ /**
29
+ * The `Model` contract over `generateContent`.
30
+ *
31
+ * Google publishes an OpenAI-compatible endpoint, and reaching Gemini through
32
+ * it costs nothing but a base url. What it costs *afterwards* is everything
33
+ * this file exists for: thinking budgets and thought summaries, thought
34
+ * signatures (without which Gemini 3 function calling is rejected outright),
35
+ * cached-content accounting, and the block reasons that come back when a safety
36
+ * filter fires. None of those have an OpenAI field to be carried in.
37
+ */
38
+ export declare class GeminiModel implements Model {
39
+ #private;
40
+ readonly id: string;
41
+ constructor(id: string, client: GoogleGenAI, options?: GeminiModelOptions);
42
+ generate(req: ModelRequest): Promise<ModelResponse>;
43
+ stream(req: ModelRequest, onDelta: (d: StreamDelta) => void): Promise<ModelResponse>;
44
+ }
45
+ //# sourceMappingURL=gemini.d.ts.map