@gullabs/any-llm 0.8.1 → 0.8.3

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/dist/index.cjs CHANGED
@@ -6,7 +6,7 @@ var google = require('@gullabs/google');
6
6
  // src/index.ts
7
7
 
8
8
  // package.json
9
- var version = "0.8.1";
9
+ var version = "0.8.3";
10
10
 
11
11
  // src/index.ts
12
12
  var ANY_LLM_VERSION = version;
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/index.ts"],"names":[],"mappings":";;;;;;;;AAEE,IAAA,OAAA,GAAW,OAAA;;;ACcN,IAAM,eAAA,GAA0B","file":"index.cjs","sourcesContent":["{\n \"name\": \"@gullabs/any-llm\",\n \"version\": \"0.8.1\",\n \"description\": \"Batteries-included any-llm client for Gemini: engine, adapter, and Google SDK in one install.\",\n \"type\": \"module\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/gullabs/any-llm.git\",\n \"directory\": \"packages/any-llm\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\"\n },\n \"dependencies\": {\n \"@google/genai\": \"^1.45.0 || ^2\",\n \"@gullabs/core\": \"workspace:*\",\n \"@gullabs/google\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=20.9.0\"\n },\n \"sideEffects\": false,\n \"keywords\": [\n \"llm\",\n \"gemini\",\n \"google-genai\",\n \"ai\",\n \"tokens\",\n \"cost\",\n \"usage\",\n \"observability\",\n \"typescript\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"homepage\": \"https://github.com/gullabs/any-llm/tree/main/packages/any-llm#readme\",\n \"bugs\": \"https://github.com/gullabs/any-llm/issues\"\n}\n","/**\n * @gullabs/any-llm — batteries-included public entrypoint.\n *\n * This package is the default client install path. It re-exports the core\n * engine and Gemini adapter while depending on the Gemini SDK for a one-package\n * setup.\n *\n * @module\n */\n\nexport * from '@gullabs/core'\nexport * from '@gullabs/google'\n\nimport { version } from '../package.json'\n\n/** Library version, sourced from package.json at build time. */\nexport const ANY_LLM_VERSION: string = version\n"]}
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"names":[],"mappings":";;;;;;;;AAEE,IAAA,OAAA,GAAW,OAAA;;;ACcN,IAAM,eAAA,GAA0B","file":"index.cjs","sourcesContent":["{\n \"name\": \"@gullabs/any-llm\",\n \"version\": \"0.8.3\",\n \"description\": \"Batteries-included any-llm client for Gemini: engine, adapter, and Google SDK in one install.\",\n \"type\": \"module\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/gullabs/any-llm.git\",\n \"directory\": \"packages/any-llm\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\"\n },\n \"dependencies\": {\n \"@google/genai\": \"^1.45.0 || ^2\",\n \"@gullabs/core\": \"workspace:*\",\n \"@gullabs/google\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=20.9.0\"\n },\n \"sideEffects\": false,\n \"keywords\": [\n \"llm\",\n \"gemini\",\n \"google-genai\",\n \"ai\",\n \"tokens\",\n \"cost\",\n \"usage\",\n \"observability\",\n \"typescript\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"homepage\": \"https://github.com/gullabs/any-llm/tree/main/packages/any-llm#readme\",\n \"bugs\": \"https://github.com/gullabs/any-llm/issues\"\n}\n","/**\n * @gullabs/any-llm — batteries-included public entrypoint.\n *\n * This package is the default client install path. It re-exports the core\n * engine and Gemini adapter while depending on the Gemini SDK for a one-package\n * setup.\n *\n * @module\n */\n\nexport * from '@gullabs/core'\nexport * from '@gullabs/google'\n\nimport { version } from '../package.json'\n\n/** Library version, sourced from package.json at build time. */\nexport const ANY_LLM_VERSION: string = version\n"]}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export * from '@gullabs/google';
4
4
  // src/index.ts
5
5
 
6
6
  // package.json
7
- var version = "0.8.1";
7
+ var version = "0.8.3";
8
8
 
9
9
  // src/index.ts
10
10
  var ANY_LLM_VERSION = version;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../package.json","../src/index.ts"],"names":[],"mappings":";;;;;;AAEE,IAAA,OAAA,GAAW,OAAA;;;ACcN,IAAM,eAAA,GAA0B","file":"index.js","sourcesContent":["{\n \"name\": \"@gullabs/any-llm\",\n \"version\": \"0.8.1\",\n \"description\": \"Batteries-included any-llm client for Gemini: engine, adapter, and Google SDK in one install.\",\n \"type\": \"module\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/gullabs/any-llm.git\",\n \"directory\": \"packages/any-llm\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\"\n },\n \"dependencies\": {\n \"@google/genai\": \"^1.45.0 || ^2\",\n \"@gullabs/core\": \"workspace:*\",\n \"@gullabs/google\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=20.9.0\"\n },\n \"sideEffects\": false,\n \"keywords\": [\n \"llm\",\n \"gemini\",\n \"google-genai\",\n \"ai\",\n \"tokens\",\n \"cost\",\n \"usage\",\n \"observability\",\n \"typescript\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"homepage\": \"https://github.com/gullabs/any-llm/tree/main/packages/any-llm#readme\",\n \"bugs\": \"https://github.com/gullabs/any-llm/issues\"\n}\n","/**\n * @gullabs/any-llm — batteries-included public entrypoint.\n *\n * This package is the default client install path. It re-exports the core\n * engine and Gemini adapter while depending on the Gemini SDK for a one-package\n * setup.\n *\n * @module\n */\n\nexport * from '@gullabs/core'\nexport * from '@gullabs/google'\n\nimport { version } from '../package.json'\n\n/** Library version, sourced from package.json at build time. */\nexport const ANY_LLM_VERSION: string = version\n"]}
1
+ {"version":3,"sources":["../package.json","../src/index.ts"],"names":[],"mappings":";;;;;;AAEE,IAAA,OAAA,GAAW,OAAA;;;ACcN,IAAM,eAAA,GAA0B","file":"index.js","sourcesContent":["{\n \"name\": \"@gullabs/any-llm\",\n \"version\": \"0.8.3\",\n \"description\": \"Batteries-included any-llm client for Gemini: engine, adapter, and Google SDK in one install.\",\n \"type\": \"module\",\n \"license\": \"Apache-2.0\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/gullabs/any-llm.git\",\n \"directory\": \"packages/any-llm\"\n },\n \"main\": \"./dist/index.cjs\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n }\n },\n \"files\": [\n \"dist\",\n \"skills\"\n ],\n \"scripts\": {\n \"build\": \"tsup\"\n },\n \"dependencies\": {\n \"@google/genai\": \"^1.45.0 || ^2\",\n \"@gullabs/core\": \"workspace:*\",\n \"@gullabs/google\": \"workspace:*\"\n },\n \"engines\": {\n \"node\": \">=20.9.0\"\n },\n \"sideEffects\": false,\n \"keywords\": [\n \"llm\",\n \"gemini\",\n \"google-genai\",\n \"ai\",\n \"tokens\",\n \"cost\",\n \"usage\",\n \"observability\",\n \"typescript\"\n ],\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"homepage\": \"https://github.com/gullabs/any-llm/tree/main/packages/any-llm#readme\",\n \"bugs\": \"https://github.com/gullabs/any-llm/issues\"\n}\n","/**\n * @gullabs/any-llm — batteries-included public entrypoint.\n *\n * This package is the default client install path. It re-exports the core\n * engine and Gemini adapter while depending on the Gemini SDK for a one-package\n * setup.\n *\n * @module\n */\n\nexport * from '@gullabs/core'\nexport * from '@gullabs/google'\n\nimport { version } from '../package.json'\n\n/** Library version, sourced from package.json at build time. */\nexport const ANY_LLM_VERSION: string = version\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gullabs/any-llm",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Batteries-included any-llm client for Gemini: engine, adapter, and Google SDK in one install.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -25,8 +25,8 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@google/genai": "^1.45.0 || ^2",
28
- "@gullabs/core": "0.8.0",
29
- "@gullabs/google": "0.8.0"
28
+ "@gullabs/core": "0.9.0",
29
+ "@gullabs/google": "0.8.2"
30
30
  },
31
31
  "engines": {
32
32
  "node": ">=20.9.0"
@@ -229,6 +229,17 @@ const result = await client.generate(
229
229
  `system` is derived only from the explicit `systemInstruction` input — never inferred
230
230
  from `contents`.
231
231
 
232
+ ## Testing with `@gullabs/testing`
233
+
234
+ Real hosts don't call `createClient()` at call sites — they own a factory module that
235
+ assembles the client once and hand call sites the built client. `@gullabs/testing`'s
236
+ fakes (`makeFakeGemini`, `FakeAdapter`, `RecordingSink`, `FakeClock`, `FakeIds`, ...) are
237
+ designed to inject through that same host-owned factory unchanged, via injectable
238
+ override parameters with production defaults — not via `vi.mock()`. See
239
+ `packages/testing/README.md` § "Wiring fakes through a host-owned factory" for a
240
+ complete two-file (factory + vitest test) example, including the port-level
241
+ `FakeAdapter` variant for bypassing the Gemini SDK shape entirely.
242
+
232
243
  ## `defineCallSite` — reusable prompt templates
233
244
 
234
245
  ```ts
@@ -248,12 +259,106 @@ const result = await client.runStructured(summarize, { article: text }, { auth }
248
259
 
249
260
  `{{var}}` interpolation is non-recursive (substituted values are never re-scanned for
250
261
  further `{{...}}`, preventing template injection) and applies to both `system` and
251
- `userTemplate`. A missing var is left as the literal `{{var}}` placeholder, not an
252
- empty string. `runStructured` also accepts a two-arg form, `(callSite, opts)`, when the
253
- template has no vars. Config resolution order everywhere is
262
+ `userTemplate`. **Strict by default (no opt-out):** every `{{var}}` placeholder must have
263
+ a string-typed value in `vars`, or `runStructured` throws before any request is built —
264
+ see "Input contracts" below. `runStructured` also accepts a two-arg form, `(callSite,
265
+ opts)`, when the template has no vars. Config resolution order everywhere is
254
266
  `clientDefaults → callSite.config → opts.config`, and the merged config must still pass
255
267
  the selected descriptor's strict runtime schema before dispatch.
256
268
 
269
+ ## Input contracts — strict interpolation, `inputSchema`, `inputContract`
270
+
271
+ **Strict template interpolation is the default, with no opt-out.** Every `{{var}}`
272
+ placeholder referenced by `callSite.system` or `callSite.userTemplate` must have a
273
+ string-typed value present in `vars`, or `runStructured` refuses the call before any
274
+ request is built — zero tokens spent:
275
+
276
+ ```ts
277
+ import { defineCallSite, LlmError } from '@gullabs/core'
278
+
279
+ const summarize = defineCallSite({
280
+ id: 'summarize-article',
281
+ provider: 'google',
282
+ model: 'gemini-2.5-flash',
283
+ userTemplate: 'Summarize this article in 3 sentences:\n\n{{article}}',
284
+ })
285
+
286
+ try {
287
+ // Missing `article` — throws before any I/O.
288
+ await client.runStructured(summarize, {}, { auth })
289
+ } catch (e) {
290
+ if (e instanceof LlmError && e.kind === 'bad_request') {
291
+ console.log(e.issues) // [{ path: 'article', message: '...' }]
292
+ }
293
+ }
294
+ ```
295
+
296
+ `null`, `undefined`, and non-string values (numbers, objects) are all violations — never
297
+ coerced to a string. Unused `vars` entries (present in `vars` but not referenced by any
298
+ template) are allowed. There is no escape syntax for literal `{{...}}` text.
299
+
300
+ **`CallSite.inputSchema`** validates `vars` with a `StandardSchemaV1` validator (zod,
301
+ valibot, ...) before interpolation runs, so a missing business field surfaces in your own
302
+ schema's vocabulary instead of as a downstream placeholder violation:
303
+
304
+ ```ts
305
+ import { z } from 'zod'
306
+
307
+ const reviewCallSite = defineCallSite({
308
+ id: 'code-review',
309
+ provider: 'google',
310
+ model: 'gemini-2.5-flash',
311
+ userTemplate: 'Review this diff as {{reviewer}}:\n\n{{diff}}',
312
+ inputSchema: z.object({
313
+ reviewer: z.string().min(1),
314
+ diff: z.string().min(1),
315
+ }),
316
+ })
317
+
318
+ await client.runStructured(
319
+ reviewCallSite,
320
+ { reviewer: 'senior-reviewer', diff },
321
+ { auth },
322
+ )
323
+ ```
324
+
325
+ **`LlmRequest.inputContract`** is the equivalent opt-in contract for the `generate()`
326
+ path (callers who render their own prompt strings and never touch `CallSite`):
327
+
328
+ ```ts
329
+ const result = await client.generate(
330
+ {
331
+ provider: 'google',
332
+ model: 'gemini-2.5-flash',
333
+ messages: [{ role: 'user', parts: [{ kind: 'text', text: renderedPrompt }] }],
334
+ inputContract: { schema: myZodSchema, value: sourceContext },
335
+ },
336
+ { auth },
337
+ )
338
+ ```
339
+
340
+ `inputContract` is validated once per logical call, before `@gullabs/quota` or any retry
341
+ middleware runs — a violation never consumes rate-limit budget and is never retried.
342
+ `generate()` and `runStructured()` are independent paths: `runStructured` never
343
+ auto-populates `inputContract` from `inputSchema`, and `generate()` never reads
344
+ `inputSchema`.
345
+
346
+ **`createClient({ requireInputContract: true })`** is a fleet-wide toggle: every
347
+ `generate()` call must carry `inputContract`, and every `runStructured()` call site must
348
+ carry `inputSchema`, or the call is refused. Off by default.
349
+
350
+ **All violations throw `LlmError('bad_request')`** with a structured `issues` array
351
+ (`{ path, message }[]`, one entry per violation) on top of the usual `.message` string.
352
+
353
+ **Ledger semantics of refusals.** A refusal that never got a `callId` (unresolved
354
+ placeholders, `CallSite.inputSchema`, or `requireInputContract` on the `runStructured`
355
+ path — all thrown in the `runStructured` prologue) writes **no** ledger row. A refusal
356
+ that already has a `callId` (`inputContract` violations and `requireInputContract` on the
357
+ `generate()` path, thrown inside the pipeline after `callId` assignment) writes **one**
358
+ zero-usage record with `attemptNumber: 0` — including `@gullabs/quota` denials, which get
359
+ the same treatment with no `@gullabs/quota` code changes. See ADR-025 in `DECISIONS.md`
360
+ for the full boundary table.
361
+
257
362
  ## Strict model-config boundary
258
363
 
259
364
  Treat model config as descriptor-owned:
@@ -405,7 +510,8 @@ try {
405
510
  ```
406
511
 
407
512
  `LlmError` also carries `httpStatus?`, `retryAfterMs?`, `provider?`, `callId?`,
408
- `attemptId?`, `servedServiceTier?`, and `cause` (the original thrown value).
513
+ `attemptId?`, `servedServiceTier?`, `issues?` (structured `{ path, message }[]` — see
514
+ "Input contracts" above), and `cause` (the original thrown value).
409
515
 
410
516
  ## Reject, don't map
411
517