vern-llm 0.2.1 → 0.3.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.
package/README.md CHANGED
@@ -1,6 +1,28 @@
1
- # vern-llm
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/LakBud/vernLLM/main/apps/docs/public/logo.png" alt="vern-llm logo" width="96" />
3
+ </p>
2
4
 
3
- Retry + timeout + cache wrapper for OpenAI-compatible chat completion calls (OpenAI, Groq, Anthropic via adapter).
5
+ <h1 align="center">vern-llm</h1>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/LakBud/vernLLM">GitHub</a> ·
9
+ <a href="https://vern-llm.vercel.app">Documentation</a> ·
10
+ <a href="https://www.npmjs.com/package/vern-llm">npm</a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="https://www.npmjs.com/package/vern-llm"><img src="https://img.shields.io/npm/v/vern-llm.svg" alt="npm version" /></a>
15
+ <a href="https://www.npmjs.com/package/vern-llm"><img src="https://img.shields.io/npm/dm/vern-llm.svg" alt="npm downloads" /></a>
16
+ <a href="https://bundlephobia.com/package/vern-llm"><img src="https://img.shields.io/bundlephobia/minzip/vern-llm.svg" alt="bundle size" /></a>
17
+ <a href="https://github.com/LakBud/vernLLM/actions/workflows/test.yml"><img src="https://github.com/LakBud/vernLLM/actions/workflows/test.yml/badge.svg" alt="test status" /></a>
18
+ <a href="https://github.com/LakBud/vernLLM/blob/main/LICENSE.md"><img src="https://img.shields.io/npm/l/vern-llm.svg" alt="license" /></a>
19
+ <img src="https://img.shields.io/node/v/vern-llm.svg" alt="node version" />
20
+ <img src="https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white" alt="TypeScript" />
21
+ </p>
22
+
23
+ Production-ready resilience for LLM calls; Retries, timeouts, caching, and circuit breaking for any OpenAI-compatible chat completion API (OpenAI, Groq, Anthropic, Gemini, Bedrock, and more via adapters).
24
+
25
+ **Full documentation: [vern-llm.vercel.app](https://vern-llm.vercel.app)** — installation, structured output, caching, circuit breaker, every adapter, and the complete API reference all live there and are kept up to date. This README is a quick pitch, not the manual.
4
26
 
5
27
  ## Install
6
28
 
@@ -8,300 +30,37 @@ Retry + timeout + cache wrapper for OpenAI-compatible chat completion calls (Ope
8
30
  pnpm add vern-llm openai
9
31
  ```
10
32
 
11
- ## Basic usage
33
+ ## Quick start
12
34
 
13
35
  ```ts
14
36
  import OpenAI from 'openai';
15
37
  import { VernLLM } from 'vern-llm';
16
38
 
17
- const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
18
-
19
39
  const llm = new VernLLM({
20
- client: openai,
40
+ client: new OpenAI({ apiKey: process.env.OPENAI_API_KEY }),
21
41
  model: 'gpt-4o',
42
+ maxRetries: 3,
43
+ timeoutMs: 10_000,
44
+ circuitBreaker: true,
22
45
  });
23
46
 
24
- const parsed = await llm.call({
47
+ const result = await llm.call({
25
48
  systemPrompt: 'Return JSON: { "skills": string[] }',
26
49
  userContent: 'Extract skills from: ...',
27
50
  });
28
-
29
- // jsonMode: false returns the raw string, no parsing
30
- const text = await llm.call({
31
- systemPrompt: 'Summarize this.',
32
- userContent: '...',
33
- jsonMode: false,
34
- });
35
- ```
36
-
37
- `VernLLM` is also exported as `RetryLLM` — same class, same behavior, pick whichever name reads better in your codebase:
38
-
39
- ```ts
40
- import { RetryLLM } from 'vern-llm';
41
-
42
- const llm = new RetryLLM({ client: openai, model: 'gpt-4o' });
43
- ```
44
-
45
- ## Structured output with Zod
46
-
47
- Pass a `schema` and get a typed, validated result. Works with any validator exposing `safeParse` (Zod v3/v4).
48
-
49
- ```ts
50
- import { z } from 'zod';
51
-
52
- const CandidateSchema = z.object({
53
- name: z.string(),
54
- skills: z.array(z.string()),
55
- });
56
-
57
- // result is typed as z.infer<typeof CandidateSchema>
58
- const result = await llm.call({
59
- systemPrompt: 'Extract the candidate name and skills as JSON.',
60
- userContent: resumeText,
61
- schema: CandidateSchema,
62
- });
63
- ```
64
-
65
- On a schema mismatch, `call` throws `LLMError('validation')` with `.issues` set to the validator's error object, without burning a retry (validation failures are deterministic).
66
-
67
- ## Provider-native JSON Schema mode
68
-
69
- `schema` above only validates client-side, after the model has already responded. `jsonSchema` sends the schema to the provider (OpenAI/Groq `response_format: { type: 'json_schema' }`), constraining generation itself — the model can't produce a shape that violates it. Combine both for provider-level constraint plus typed client-side inference:
70
-
71
- ```ts
72
- const result = await llm.call({
73
- systemPrompt: 'Extract the candidate name and skills.',
74
- userContent: resumeText,
75
- jsonSchema: {
76
- name: 'Candidate',
77
- schema: {
78
- type: 'object',
79
- properties: {
80
- name: { type: 'string' },
81
- skills: { type: 'array', items: { type: 'string' } },
82
- },
83
- required: ['name', 'skills'],
84
- },
85
- },
86
- schema: CandidateSchema, // optional client-side type/validation on top
87
- });
88
- ```
89
-
90
- `jsonSchema` implies JSON mode. The Anthropic adapter has no native structured-output equivalent, so it embeds the schema in the system prompt as an instruction instead of provider-enforcing it.
91
-
92
- ## Per-call model override and reasoning effort
93
-
94
- ```ts
95
- const llm = new VernLLM({ client: openai, model: 'gpt-4o-mini' }); // default model
96
-
97
- await llm.call({
98
- systemPrompt: '...',
99
- userContent: '...',
100
- model: 'o3', // overrides the instance default for this call only
101
- reasoningEffort: 'high', // passed through as `reasoning_effort`; ignored by models that don't support it
102
- });
103
51
  ```
104
52
 
105
- `reasoningEffort` is dropped by the Anthropic adapter — Claude's extended thinking uses a token budget, not an effort tier, so there's no faithful 1:1 mapping.
106
-
107
- ## Caching + usage metering
108
-
109
- ```ts
110
- import { InMemoryCacheAdapter } from 'vern-llm';
111
-
112
- const llm = new VernLLM({
113
- client: openai,
114
- model: 'gpt-4o',
115
- cache: new InMemoryCacheAdapter(), // swap for your own Redis/Upstash adapter
116
- });
117
-
118
- const result = await llm.cachedCall({
119
- cacheKey: `cv:${cvId}`,
120
- ttl: 3600,
121
- fn: () => llm.call({ systemPrompt, userContent }),
122
- reserveUsage: () => quota.reserve(userId),
123
- refundUsage: () => quota.refund(userId),
124
- });
125
- ```
126
-
127
- If `refundUsage` itself throws, that failure is logged and swallowed — the original error from `fn` (or the cache write) is what propagates, so a broken refund path never masks the real failure.
128
-
129
- ### `cachedLLMCall` — cached + retried in one call
53
+ ## Why vern-llm?
130
54
 
131
- `cachedCall` is a thin cache wrapper; it doesn't apply retry/timeout/circuit-breaker behavior on its own that's up to whatever `fn` does. If `fn` is always going to be `() => llm.call(...)`, `cachedLLMCall` wires that up for you:
132
-
133
- ```ts
134
- const result = await llm.cachedLLMCall({
135
- cacheKey: `cv:${cvId}`,
136
- ttl: 3600,
137
- call: { systemPrompt, userContent }, // same shape as call()'s params
138
- reserveUsage: () => quota.reserve(userId),
139
- refundUsage: () => quota.refund(userId),
140
- });
141
- ```
142
-
143
- ### Custom cache adapter
144
-
145
- ```ts
146
- import type { CacheAdapter } from 'vern-llm';
147
-
148
- class UpstashCacheAdapter implements CacheAdapter {
149
- async get(key: string) {
150
- /* ... */
151
- }
152
- async set(key: string, value: unknown, ttl: number) {
153
- /* ... */
154
- }
155
- }
156
- ```
157
-
158
- ## Token usage tracking
159
-
160
- ```ts
161
- const llm = new VernLLM({
162
- client: openai,
163
- model: 'gpt-4o',
164
- onUsage: (usage) => {
165
- // { promptTokens, completionTokens, totalTokens, requestId, model }
166
- billing.record(usage);
167
- },
168
- });
169
- ```
55
+ - **Retries with backoff**: transient failures retry automatically; validation errors and non-retryable status codes fail fast instead
56
+ - **Structured output**: pass a Zod schema, get a typed, validated result back
57
+ - **Provider-native JSON Schema mode**: constrain generation itself, not just validate after the fact
58
+ - **Caching**: wrap any call with `cachedCall`/`cachedLLMCall`, bring your own cache adapter
59
+ - **Circuit breaker**: trips after repeated failures, recovers automatically once the provider's back
60
+ - **One interface, every provider**: OpenAI, Groq, Mistral, DeepSeek, Cerebras, Together, Fireworks, Ollama, Anthropic, Gemini, Bedrock, or raw HTTP via `fromFetch`
61
+ - **Zero bundled deps**: `zod` and provider SDKs are peer dependencies; this package only relies on their shapes structurally
170
62
 
171
- ## Circuit breaker
172
-
173
- Stops hammering a provider that's down instead of retrying every call.
174
-
175
- ```ts
176
- const llm = new VernLLM({
177
- client: openai,
178
- model: 'gpt-4o',
179
- circuitBreaker: { threshold: 5, cooldownMs: 30_000 }, // or `true` for defaults
180
- });
181
-
182
- llm.getCircuitState(); // 'closed' | 'open' | 'half-open' | undefined
183
- ```
184
-
185
- Once `threshold` consecutive failures occur, further calls fail immediately with `LLMError('circuit_open')` until `cooldownMs` elapses, at which point one trial call is allowed through (half-open). A successful trial closes the circuit; a failed one reopens it.
186
-
187
- ## Pluggable logger
188
-
189
- ```ts
190
- import type { Logger } from 'vern-llm';
191
-
192
- const pinoLogger: Logger = {
193
- debug: (msg) => logger.debug(msg),
194
- warn: (msg) => logger.warn(msg),
195
- error: (msg, meta) => logger.error(meta, msg),
196
- };
197
-
198
- const llm = new VernLLM({ client: openai, model: 'gpt-4o', logger: pinoLogger });
199
- ```
200
-
201
- Defaults to a console logger; `debug` logging is gated by the `debug` option (`NODE_ENV !== 'production'` by default), `warn`/`error` always fire.
202
-
203
- ## Multi-provider adapters
204
-
205
- `VernLLM` expects a client shaped like OpenAI's `chat.completions.create`. Some providers already match that shape and need no adapter; others need one that translates their native API into it.
206
-
207
- ### Already OpenAI-wire-compatible — zero-transform passthrough
208
-
209
- Groq, Mistral, DeepSeek, Cerebras, Together AI, Fireworks AI, and Ollama (via its `/v1/chat/completions` endpoint) all speak the same wire format as OpenAI. These are thin named wrappers around the same passthrough — pick whichever name matches your provider for readability:
210
-
211
- ```ts
212
- import { VernLLM, fromGroq, fromMistral, fromTogether } from 'vern-llm';
213
-
214
- const llm = new VernLLM({
215
- client: fromGroq(new Groq({ apiKey: process.env.GROQ_API_KEY })),
216
- model: 'llama-3.3-70b-versatile',
217
- });
218
- ```
219
-
220
- `fromOpenAICompatible()` is the underlying function if your provider isn't in the named list — same thing, generic name.
221
-
222
- ### Anthropic
223
-
224
- ```ts
225
- import Anthropic from '@anthropic-ai/sdk';
226
- import { VernLLM, fromAnthropic } from 'vern-llm';
227
-
228
- const llm = new VernLLM({
229
- client: fromAnthropic(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })),
230
- model: 'claude-sonnet-4-6',
231
- });
232
- ```
233
-
234
- No native `response_format` or `reasoning_effort` equivalent — JSON mode/schema are emulated via a system-prompt instruction (schema text embedded, not provider-enforced); `reasoning_effort` is dropped.
235
-
236
- ### Gemini
237
-
238
- ```ts
239
- import { GoogleGenerativeAI } from '@google/generative-ai';
240
- import { VernLLM, fromGemini } from 'vern-llm';
241
-
242
- const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
243
- const model = genAI.getGenerativeModel({ model: 'gemini-2.5-flash' });
244
-
245
- const llm = new VernLLM({ client: fromGemini(model), model: 'gemini-2.5-flash' });
246
- ```
247
-
248
- Unlike Anthropic, `jsonSchema` maps to Gemini's native `responseSchema` + `responseMimeType: 'application/json'`, so it's actually provider-enforced here. `reasoning_effort` is dropped (Gemini's thinking models use a token budget instead).
249
-
250
- ### AWS Bedrock
251
-
252
- Bedrock's SDK v3 exposes `.send(command)`, not a direct method, so wrap it:
253
-
254
- ```ts
255
- import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';
256
- import { VernLLM, fromBedrock } from 'vern-llm';
257
-
258
- const client = new BedrockRuntimeClient({ region: 'us-east-1' });
259
-
260
- const llm = new VernLLM({
261
- client: fromBedrock({
262
- converse: (params, options) =>
263
- client.send(new ConverseCommand(params), { abortSignal: options.signal }),
264
- }),
265
- model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
266
- });
267
- ```
268
-
269
- Uses Bedrock's Converse API, which is unified across model families (Anthropic, Titan, Llama, Mistral, etc. all speak the same request/response shape), so this one adapter works regardless of which `model` you point it at. JSON mode is emulated via system prompt, same as Anthropic; `reasoning_effort` is dropped.
270
-
271
- ### Anything else — `fromFetch`
272
-
273
- For a provider with no SDK, or where pulling one in isn't worth it, `fromFetch` is a raw HTTP escape hatch — supply the URL, headers, and two small mapping functions, and retries/timeouts/circuit-breaker/JSON handling all still apply:
274
-
275
- ```ts
276
- import { VernLLM, fromFetch } from 'vern-llm';
277
-
278
- const llm = new VernLLM({
279
- client: fromFetch({
280
- url: 'https://api.example.com/v1/generate',
281
- headers: () => ({ Authorization: `Bearer ${process.env.EXAMPLE_API_KEY}` }),
282
- mapRequest: (params) => ({
283
- model: params.model,
284
- prompt: params.messages.map((m) => m.content).join('\n\n'),
285
- max_tokens: params.max_tokens,
286
- }),
287
- mapResponse: (json) => ({
288
- content: json.output,
289
- usage: { promptTokens: json.usage?.input, completionTokens: json.usage?.output },
290
- }),
291
- }),
292
- model: 'example-model-v1',
293
- });
294
- ```
295
-
296
- Non-2xx responses throw with `.status` set, so `nonRetryableStatus` still fails fast on 401/403 as usual.
297
-
298
- ## Notes
299
-
300
- - Requires Node 20+ (`AbortSignal.any`).
301
- - Non-retryable status codes (default `400, 401, 403`) fail fast instead of burning a retry, and are reported as `LLMError('api', status)` rather than a generic unknown error.
302
- - An already-aborted `signal` rejects immediately with `LLMError('aborted')` before any request is dispatched.
303
- - `jsonMode: false` skips JSON parsing entirely and returns the raw string.
304
- - `zod` and provider SDKs (`openai`, `groq-sdk`, `@anthropic-ai/sdk`) are not bundled — bring your own; this package only relies on their shapes structurally.
63
+ See the [docs](https://vern-llm.vercel.app) for adapter setup, caching, the circuit breaker, and structured output in depth.
305
64
 
306
65
  ## Development
307
66
 
@@ -309,11 +68,13 @@ Non-2xx responses throw with `.status` set, so `nonRetryableStatus` still fails
309
68
  pnpm install
310
69
  pnpm run build # tsdown → dist (ESM + CJS + types)
311
70
  pnpm run typecheck # tsc --noEmit on src, since tsdown doesn't fully type-check
312
- pnpm run typecheck:test # tsc --noEmit on src + test (separate tsconfig, no rootDir conflict)
313
71
  pnpm run test # vitest run
314
- pnpm run test:watch # vitest, watch mode
315
72
  pnpm run test:coverage # vitest run --coverage (v8 provider)
316
73
  pnpm run changeset # record a change for the next release
317
74
  ```
318
75
 
319
- Tests live in `tests/`, mirroring `src/`: `VernLLM.call.test.ts` and `VernLLM.schema.test.ts` cover retry/backoff/timeout/abort/schema/model-override/usage behavior, `circuitBreaker.test.ts` covers the breaker as a unit and its integration with `call()`, `cachedCall.test.ts` covers caching and usage reservation/refund, `logger.test.ts` covers the injectable logger, and `test/adapters/*.test.ts` cover each provider adapter's request/response translation against a fake client, no real API calls are made anywhere in the suite.
76
+ Tests live in `tests/`, mirroring `src/`, and cover retry/backoff/timeout/abort/schema/model-override/usage behavior, the circuit breaker (unit + integration), caching, the injectable logger, and every provider adapter's request/response translation against a fake client no real API calls anywhere in the suite.
77
+
78
+ ## License
79
+
80
+ [MIT](https://github.com/LakBud/vernLLM/blob/main/LICENSE.md) © LakBud
package/dist/index.cjs CHANGED
@@ -135,9 +135,12 @@ function extractStatus(err) {
135
135
  * Runs an async function and cancels it if it takes longer than the given
136
136
  * timeout. Creates an internal abort controller that fires after the
137
137
  * timeout elapses, and combines it with any external signal the caller
138
- * passed in so either one can cancel the underlying call. The internal
139
- * timer is always cleared afterward, whether the function succeeds,
140
- * fails, or is aborted, so nothing is left running in the background
138
+ * passed in so either one can cancel the underlying call. If the internal
139
+ * timeout triggers and the underlying operation aborts, the error is
140
+ * converted into an LLMError with type "timeout". External cancellations
141
+ * continue to propagate as aborted errors. The internal timer is always
142
+ * cleared afterward, whether the function succeeds, fails, or is aborted,
143
+ * so nothing is left running in the background.
141
144
  */
142
145
  async function withTimeout(fn, timeoutMs, externalSignal) {
143
146
  const controller = new AbortController();
@@ -147,6 +150,9 @@ async function withTimeout(fn, timeoutMs, externalSignal) {
147
150
  const signal = externalSignal ? AbortSignal.any([externalSignal, controller.signal]) : controller.signal;
148
151
  try {
149
152
  return await fn(signal);
153
+ } catch (err) {
154
+ if (controller.signal.aborted && !externalSignal?.aborted && err instanceof DOMException && err.name === "AbortError") throw new LLMError("Request timed out", "timeout");
155
+ throw err;
150
156
  } finally {
151
157
  clearTimeout(timer);
152
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["message: string","type: LLMErrorType","status?: number","issues?: unknown","err: unknown","key: string","value: T","ttl: number","options: CircuitBreakerOptions","content: string","err: unknown","fn: (signal: AbortSignal) => Promise<T>","timeoutMs: number","externalSignal?: AbortSignal","baseDelayMs: number","attempt: number","delay: number","signal?: AbortSignal","debugEnabled: boolean","message: string","meta?: Record<string, unknown>","options: VernLLMOptions","params: CallParams<T>","fn: () => Promise<T>","requestId: string","signal?: AbortSignal","lastError: unknown","error: unknown","jsonSchema: CallParams<unknown>['jsonSchema']","useJson: boolean","response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>","model: string","content: string","schema?: CallParams<T>['schema']","parsed: unknown","attempt: number","params: CachedCallParams<T>","params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> }","anthropicClient: AnthropicClient","jsonInstruction: string | undefined","geminiClient: GeminiClient","generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n >","bedrockClient: BedrockConverseClient","jsonInstruction: string | undefined","text","config: FetchAdapterConfig","client: unknown"],"sources":["../src/types.ts","../src/circuitBreaker.ts","../src/internal/vernLLM.utilts.ts","../src/logger.ts","../src/vernLLM.ts","../src/adapters/anthropic.ts","../src/adapters/gemini.ts","../src/adapters/bedrock.ts","../src/adapters/fetch.ts","../src/adapters/openaiCompatible.ts"],"sourcesContent":["export type LLMErrorType =\n | 'timeout'\n | 'api'\n | 'parse'\n | 'validation'\n | 'circuit_open'\n | 'unknown'\n | 'aborted';\n\nexport class LLMError extends Error {\n constructor(\n message: string,\n public type: LLMErrorType,\n public status?: number,\n public issues?: unknown,\n ) {\n super(message);\n this.name = 'LLMError';\n }\n}\n\nexport function isLLMError(err: unknown): err is LLMError {\n return err instanceof LLMError;\n}\n\nexport interface CacheAdapter<T = unknown> {\n get(key: string): Promise<T | null>;\n set(key: string, value: T, ttl: number): Promise<void>;\n}\n\n/**\n * Trivial default so the package works out of the box with no external deps\n * Not shared across processes, swap in Redis/Upstash/etc for production\n */\nexport class InMemoryCacheAdapter<T = unknown> implements CacheAdapter<T> {\n private store = new Map<string, { value: T; expiresAt: number }>();\n\n async get(key: string): Promise<T | null> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n return entry.value;\n }\n\n async set(key: string, value: T, ttl: number): Promise<void> {\n this.store.set(key, { value, expiresAt: Date.now() + ttl * 1000 });\n }\n}\n\n/**\n * Minimal shape compatible with the OpenAI SDKs chat.completions.create,\n * so consumers can pass an OpenAI client directly\n * `response_format.json_schema` and `reasoning_effort` are optional on the wire\n * providers that don't support them will just ignore fields they don't recognize,\n * but not every SDKs TS types accept them, hence this being a structural type\n * rather than importing the SDKs own params type\n */\nexport interface LLMClient {\n chat: {\n completions: {\n create(\n params: {\n model: string;\n temperature: number;\n max_tokens: number;\n response_format?:\n | { type: 'json_object' }\n | {\n type: 'json_schema';\n json_schema: {\n name: string;\n schema: Record<string, unknown>;\n strict?: boolean;\n description?: string;\n };\n };\n /** OpenAI reasoning-model param (o-series, gpt-5), ignored by providers that don't support it */\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n messages: Array<{ role: 'system' | 'user'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n choices?: Array<{ message?: { content?: string | null } }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n }>;\n };\n };\n}\n\nexport type ReserveUsage = () => Promise<void>;\nexport type RefundUsage = () => Promise<void>;\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n requestId: string;\n model: string;\n}\n\nexport type OnUsage = (usage: TokenUsage) => void;\n\n/**\n * Minimal structural type for a Zod-like schema, so this package doesnt need\n * a hard dependency on a specific Zod major version. Any object exposing\n * `safeParse` (Zod v3/v4, and most Zod-compatible validators) should satisfy this\n */\nexport interface SchemaLike<T> {\n safeParse(data: unknown): { success: true; data: T } | { success: false; error: unknown };\n}\n\nexport interface VernLLMOptions {\n client: LLMClient;\n model: string;\n /** Max retries after the first attempt. Default 1 (2 attempts total) */\n maxRetries?: number;\n /** Per-attempt timeout in ms. Default 25000 */\n timeoutMs?: number;\n /** Base delay for exponential backoff in ms. Default 500 */\n baseDelayMs?: number;\n /** Default max_tokens for calls that don't override it. Default 1000 */\n defaultMaxTokens?: number;\n /** Enables debug logging of raw model output. Default: NODE_ENV !== 'production' */\n debug?: boolean;\n /** Cache adapter for cachedCall. Defaults to an in-memory adapter */\n cache?: CacheAdapter;\n /** HTTP status codes that should fail fast without retrying. Default [400, 401, 403] */\n nonRetryableStatus?: number[];\n /** Custom JSON parser. Must return undefined/null on failure. Default: JSON.parse wrapped in try/catch */\n parseJson?: (content: string) => unknown;\n /** Called after every successful call with token usage, if the provider reports it */\n onUsage?: OnUsage;\n /** Injectable logger. Defaults to a console-based logger gated by `debug` */\n logger?: import('./logger.js').Logger;\n /**\n * Enables a circuit breaker that short-circuits calls after repeated\n * consecutive failures, instead of continuing to hammer a down provider\n * Pass `true` for defaults, or an options object to tune threshold/cooldown\n */\n circuitBreaker?: boolean | import('./circuitBreaker.js').CircuitBreakerOptions;\n}\n\n/**\n * A provider-native JSON Schema for structured outputs (OpenAI/Groq\n * `response_format: { type: 'json_schema' }`) This is the wire-format\n * schema the model is constrained to generate against, distinct from\n * `schema`, which is a client-side Zod validator run on the parsed result\n * You can use one, both, or neither; using both gets you provider-level\n * constraint plus client-side type inference/validation as a safety net\n */\nexport interface JsonSchemaSpec {\n name: string;\n schema: Record<string, unknown>;\n /** Enforces the schema strictly (OpenAI-specific), default true when supported */\n strict?: boolean;\n description?: string;\n}\n\nexport interface CallParams<T = unknown> {\n systemPrompt: string;\n userContent: string;\n temperature?: number;\n jsonMode?: boolean;\n maxTokens?: number;\n requestId?: string;\n signal?: AbortSignal;\n /** Overrides the model set on the VernLLM instance for this call only */\n model?: string;\n /**\n * OpenAI-style reasoning effort for reasoning models (o-series, gpt-5, etc).\n * Passed through as-is, providers/models that don't support it ignore it\n */\n reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high';\n /**\n * Provider-native JSON Schema structured-output mode. When set, this is sent\n * as `response_format: { type: 'json_schema', json_schema: ... }` instead of\n * the looser `json_object` mode, constraining the models output shape at\n * generation time (not just validating it after the fact). Implies jsonMode: true.\n */\n jsonSchema?: JsonSchemaSpec;\n /**\n * Optional Zod (or Zod-compatible) schema. When provided, the parsed JSON\n * is validated against it; on failure an LLMError('validation') is thrown\n * with `.issues` set to the schema's error object. Implies jsonMode: true.\n * Can be combined with `jsonSchema` for provider-level constraint + client-side typing.\n */\n schema?: SchemaLike<T>;\n}\n\nexport interface CachedCallParams<T> {\n cacheKey: string;\n ttl: number;\n fn: () => Promise<T>;\n reserveUsage?: ReserveUsage;\n refundUsage?: RefundUsage;\n}\n","import { LLMError } from './types.js';\n\nexport interface CircuitBreakerOptions {\n /** Consecutive failures before the circuit opens, default 5 */\n threshold?: number;\n /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */\n cooldownMs?: number;\n}\n\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\n/**\n * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across\n * calls. Once the threshold is hit, short-circuits new calls with an\n * LLMError('circuit_open') instead of hitting the provider, until the\n * cooldown elapses and a single trial call is allowed through\n */\nexport class CircuitBreaker {\n private state: CircuitState = 'closed';\n private consecutiveFailures = 0;\n private openedAt = 0;\n private threshold: number;\n private cooldownMs: number;\n\n constructor(options: CircuitBreakerOptions = {}) {\n this.threshold = options.threshold ?? 5;\n this.cooldownMs = options.cooldownMs ?? 30_000;\n }\n\n /** Throws if the circuit is open and the cooldown hasnt elapsed */\n assertClosed(): void {\n if (this.state !== 'open') return;\n\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n return;\n }\n\n throw new LLMError(\n `Circuit open — provider has failed ${this.consecutiveFailures} times in a row. Retry in ${Math.ceil((this.cooldownMs - elapsed) / 1000)}s.`,\n 'circuit_open',\n );\n }\n\n recordSuccess(): void {\n this.consecutiveFailures = 0;\n this.state = 'closed';\n }\n\n recordFailure(): void {\n this.consecutiveFailures += 1;\n\n if (this.state === 'half-open') {\n // Trial call failed: reopen and reset the cooldown window.\n this.state = 'open';\n this.openedAt = Date.now();\n return;\n }\n\n if (this.consecutiveFailures >= this.threshold) {\n this.state = 'open';\n this.openedAt = Date.now();\n }\n }\n\n getState(): CircuitState {\n return this.state;\n }\n}\n","import { LLMError } from '../types.js';\n\nexport function defaultParseJson(content: string): unknown {\n try {\n return JSON.parse(content);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Looks inside an unknown error value and pulls out an http status code\n * if one is present. Checks the status field first then the status code\n * field since different client libraries use different names for this.\n * Returns undefined when the error is not an object or carries no status\n */\nexport function extractStatus(err: unknown): number | undefined {\n if (!err || typeof err !== 'object') return undefined;\n\n const error = err as {\n status?: unknown;\n statusCode?: unknown;\n };\n\n if (typeof error.status === 'number') return error.status;\n if (typeof error.statusCode === 'number') return error.statusCode;\n\n return undefined;\n}\n\n/**\n * Runs an async function and cancels it if it takes longer than the given\n * timeout. Creates an internal abort controller that fires after the\n * timeout elapses, and combines it with any external signal the caller\n * passed in so either one can cancel the underlying call. The internal\n * timer is always cleared afterward, whether the function succeeds,\n * fails, or is aborted, so nothing is left running in the background\n */\nexport async function withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs: number,\n externalSignal?: AbortSignal,\n): Promise<T> {\n const controller = new AbortController();\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n const signal = externalSignal\n ? AbortSignal.any([externalSignal, controller.signal])\n : controller.signal;\n\n try {\n return await fn(signal);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Exponential backoff with jitter, capped at maxDelayMs.\n * Jitter avoids thundering-herd retries when many callers back off in lockstep,\n * the cap prevents unbounded delays when maxRetries is high\n */\nexport function getBackoffDelay(baseDelayMs: number, attempt: number, maxDelayMs = 10_000): number {\n const exp = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n return exp / 2 + Math.random() * (exp / 2);\n}\n\n/**\n * Pauses execution for the given delay before a retry attempt. If an\n * abort signal is provided and it fires while waiting, the pending\n * timer is cancelled immediately and the wait rejects right away with\n * an aborted error instead of continuing to sit idle until the delay\n * would have finished on its own\n */\nexport async function waitForRetry(delay: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new LLMError('Operation aborted', 'aborted'));\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export interface Logger {\n debug(message: string): void;\n warn(message: string): void;\n error(message: string, meta?: Record<string, unknown>): void;\n}\n\n/**\n * Default logger. `debug` is gated by the `debug` option on VernLLM\n * warn/error always fire since they indicate real problems (retries, cache failures)\n */\nexport class ConsoleLogger implements Logger {\n constructor(private debugEnabled: boolean) {}\n\n debug(message: string): void {\n if (this.debugEnabled) console.debug(message);\n }\n\n warn(message: string): void {\n console.warn(message);\n }\n\n error(message: string, meta?: Record<string, unknown>): void {\n console.error(message, meta ?? '');\n }\n}\n","import { randomUUID } from 'crypto';\n\nimport { CircuitBreaker } from './circuitBreaker.js';\nimport {\n defaultParseJson,\n extractStatus,\n withTimeout,\n getBackoffDelay,\n waitForRetry,\n} from './internal/vernLLM.utilts.js';\nimport { ConsoleLogger, type Logger } from './logger.js';\nimport {\n LLMError,\n InMemoryCacheAdapter,\n type VernLLMOptions,\n type CallParams,\n type CachedCallParams,\n type CacheAdapter,\n type LLMClient,\n} from './types.js';\n\n/**\n * A resilient wrapper around an LLM chat completions client, this is VernLLM!\n *\n * Adds retry with exponential backoff and jitter, per-attempt timeouts,\n * an optional circuit breaker, JSON parsing with optional schema\n * validation, usage tracking, and an optional response cache, all\n * configurable, all opt-in beyond sensible defaults\n */\nexport class VernLLM {\n private readonly client: LLMClient;\n private readonly model: string;\n\n private readonly maxRetries: number;\n private readonly timeoutMs: number;\n private readonly baseDelayMs: number;\n private readonly defaultMaxTokens: number;\n\n private readonly cache: CacheAdapter<unknown>;\n private readonly nonRetryableStatus: number[];\n\n private readonly parseJson: (content: string) => unknown;\n private readonly onUsage?: VernLLMOptions['onUsage'];\n\n private readonly logger: Logger;\n private readonly breaker?: CircuitBreaker;\n\n /**\n * @param options: Client, model, and all tunables (retries, timeout,\n * backoff, cache, circuit breaker, logger, etc). See VernLLMOptions in `types.ts`\n * for individual defaults\n */\n constructor(options: VernLLMOptions) {\n this.client = options.client;\n this.model = options.model;\n\n const retryConfig = this.resolveRetryConfig(options);\n this.maxRetries = retryConfig.maxRetries;\n this.timeoutMs = retryConfig.timeoutMs;\n this.baseDelayMs = retryConfig.baseDelayMs;\n this.defaultMaxTokens = retryConfig.defaultMaxTokens;\n\n this.cache = options.cache ?? new InMemoryCacheAdapter();\n this.nonRetryableStatus = options.nonRetryableStatus ?? [400, 401, 403];\n\n this.parseJson = options.parseJson ?? defaultParseJson;\n this.onUsage = options.onUsage;\n\n this.logger = this.resolveLogger(options);\n this.breaker = this.resolveCircuitBreaker(options);\n }\n\n /**\n * Resolves retry/timeout/token defaults from the given options,\n * falling back to the librarys built-in defaults for anything unset\n */\n private resolveRetryConfig(options: VernLLMOptions) {\n return {\n maxRetries: options.maxRetries ?? 1,\n timeoutMs: options.timeoutMs ?? 25_000,\n baseDelayMs: options.baseDelayMs ?? 500,\n defaultMaxTokens: options.defaultMaxTokens ?? 1000,\n };\n }\n\n /**\n * Returns the caller supplied logger, or a console-based logger whose\n * debug output is gated by the `debug` option (defaulting to on\n * outside production)\n */\n private resolveLogger(options: VernLLMOptions): Logger {\n return (\n options.logger ?? new ConsoleLogger(options.debug ?? process.env.NODE_ENV !== 'production')\n );\n }\n\n /**\n * Builds a circuit breaker if `circuitBreaker` is truthy on the\n * options. Passing `true` uses default thresholds, passing an options\n * object tunes them. Returns undefined when the breaker is disabled\n */\n private resolveCircuitBreaker(options: VernLLMOptions): CircuitBreaker | undefined {\n if (!options.circuitBreaker) return undefined;\n\n return new CircuitBreaker(options.circuitBreaker === true ? undefined : options.circuitBreaker);\n }\n\n /**\n * Makes a single logical LLM call, transparently retrying on failure\n * according to the configured retry policy\n *\n * Fails fast if the circuit breaker is open or the signal is already\n * aborted, before any request is dispatched. On exhausting all\n * retries, records a circuit breaker failure and rejects with a\n * normalized LLMError\n *\n * @param params : System/user content plus per call overrides\n * (model, temperature, jsonMode, schema, signal, etc)\n * @returns The parsed and optionally schema-validated response, or\n * the raw string content when jsonMode is disabled\n */\n async call<T = unknown>(params: CallParams<T>): Promise<T> {\n this.breaker?.assertClosed();\n\n if (params.signal?.aborted) {\n throw new LLMError('LLM request aborted', 'aborted');\n }\n\n const requestId = params.requestId ?? randomUUID();\n\n try {\n const result = await this.retryWithBackoff(\n () => this.executeCall(params, requestId),\n requestId,\n params.signal,\n );\n\n return result;\n } catch (error) {\n this.breaker?.recordFailure();\n throw this.normalizeError(error, params.signal);\n }\n }\n\n /**\n * Runs `fn`, retrying with backoff according to `shouldRetry` policy\n * Purely mechanical: knows nothing about LLM specifics beyond the retry\n * predicate, so its testable independent of request/response shaping\n */\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n if (attempt > 0) {\n await this.recoverDelay(requestId, attempt, signal);\n }\n\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!this.shouldRetry(error, signal)) {\n break;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Converts any thrown value into a well-typed LLMError for the public\n * API surface. Preserves an existing LLMError as is, reports aborted\n * signals as such, classifies errors carrying an http status as\n * type api, and otherwise falls back to a generic unknown error.\n */\n private normalizeError(error: unknown, signal?: AbortSignal): LLMError {\n if (signal?.aborted) {\n return new LLMError('LLM request aborted', 'aborted');\n }\n\n if (error instanceof LLMError) {\n return error;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined) {\n return new LLMError('LLM request failed', 'api', status);\n }\n\n return new LLMError('LLM request failed', 'unknown');\n }\n\n /**\n * Performs a single attempt: builds the request, dispatches it with a\n * timeout, and shapes the response. Throws on an empty response so\n * the retry loop treats it like any other transient failure. Records\n * usage and a circuit breaker success before returning\n */\n private async executeCall<T>(params: CallParams<T>, requestId: string): Promise<T> {\n const { useJson, model, request } = this.buildRequestPayload(params);\n\n const response = await withTimeout(\n (attemptSignal) => this.client.chat.completions.create(request, { signal: attemptSignal }),\n this.timeoutMs,\n params.signal,\n );\n\n const content = response.choices?.[0]?.message?.content?.trim();\n\n if (!content) {\n throw new LLMError('Empty LLM response', 'api');\n }\n\n this.logger.debug(`[vern:${requestId}] output:\\n${content.slice(0, 800)}`);\n\n this.recordUsage(response, requestId, model);\n\n this.breaker?.recordSuccess();\n\n if (!useJson) {\n return content as T;\n }\n\n return this.parseAndValidate(content, params.schema);\n }\n\n /**\n * Applies per call defaults and shapes the params into the request\n * object expected by the underlying client, including the resolved\n * response format. Also returns whether JSON parsing should be\n * applied to the response and which model was ultimately used\n */\n private buildRequestPayload<T>(params: CallParams<T>) {\n const {\n systemPrompt,\n userContent,\n temperature = 0.2,\n jsonMode = true,\n maxTokens = this.defaultMaxTokens,\n model = this.model,\n reasoningEffort,\n jsonSchema,\n } = params;\n\n const useJson = jsonMode || Boolean(jsonSchema);\n const responseFormat = this.buildResponseFormat(jsonSchema, useJson);\n\n const request = {\n model,\n temperature,\n max_tokens: maxTokens,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),\n messages: [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userContent },\n ],\n };\n\n return { useJson, model, request };\n }\n\n /**\n * Chooses the response format to send to the provider. A provider\n * native json schema takes priority when supplied, constraining\n * generation directly, otherwise falls back to the looser json\n * object mode when JSON output is requested, or no format at all\n * for plain text responses\n */\n private buildResponseFormat(jsonSchema: CallParams<unknown>['jsonSchema'], useJson: boolean) {\n if (jsonSchema) {\n return {\n type: 'json_schema' as const,\n json_schema: {\n name: jsonSchema.name,\n schema: jsonSchema.schema,\n strict: jsonSchema.strict ?? true,\n description: jsonSchema.description,\n },\n };\n }\n\n return useJson ? { type: 'json_object' as const } : undefined;\n }\n\n /**\n * Reports token usage to the caller supplied onUsage callback, when\n * both a callback was configured and the provider actually returned\n * usage data on this response. A no op otherwise.\n */\n private recordUsage(\n response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>,\n requestId: string,\n model: string,\n ): void {\n if (!response.usage || !this.onUsage) return;\n\n this.onUsage({\n promptTokens: response.usage.prompt_tokens ?? 0,\n completionTokens: response.usage.completion_tokens ?? 0,\n totalTokens: response.usage.total_tokens ?? 0,\n requestId,\n model,\n });\n }\n\n /**\n * Parses the raw response content as JSON and, when a schema is\n * supplied, validates the parsed value against it. Throws a parse\n * type LLMError on malformed JSON and a validation type LLMError,\n * carrying the schemas issues, on a failed validation\n */\n private parseAndValidate<T>(content: string, schema?: CallParams<T>['schema']): T {\n let parsed: unknown;\n\n try {\n parsed = this.parseJson(content);\n } catch {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (parsed === null || parsed === undefined) {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (!schema) {\n return parsed as T;\n }\n\n const result = schema.safeParse(parsed);\n\n if (!result.success) {\n throw new LLMError('Schema validation failed', 'validation', undefined, result.error);\n }\n\n return result.data;\n }\n\n /**\n * Waits out the backoff delay for a given retry attempt, logging the\n * attempt for observability before the wait begins. Rejects early if\n * the signal aborts during the wait\n */\n private async recoverDelay(requestId: string, attempt: number, signal?: AbortSignal) {\n const delay = getBackoffDelay(this.baseDelayMs, attempt);\n\n this.logger.warn(\n `[vern:${requestId}] recovery attempt ${attempt}/${this.maxRetries}, waiting ${delay}ms`,\n );\n\n await waitForRetry(delay, signal);\n }\n\n /**\n * Decides whether a failed attempt is worth retrying. Never retries\n * once the signal has aborted, never retries a parse or validation\n * failure since those stem from the response content rather than a\n * transient fault, and never retries a status code the caller has\n * marked as non retryable. Retries everything else\n */\n private shouldRetry(error: unknown, signal?: AbortSignal): boolean {\n if (signal?.aborted) {\n return false;\n }\n\n if (error instanceof LLMError && (error.type === 'parse' || error.type === 'validation')) {\n return false;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined && this.nonRetryableStatus.includes(status)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Thin cache wrapper around caller supplied logic. `params.fn` is expected\n * to be a call that itself invokes `this.call(...)` (see `cachedLLMCall`\n * below for a convenience wrapper that wires this up automatically),\n * `cachedCall` does not itself apply retry/timeout policy.\n */\n async cachedCall<T>(params: CachedCallParams<T>): Promise<T> {\n const cached = await this.cache.get(params.cacheKey);\n\n if (cached !== null) {\n return cached as T;\n }\n\n try {\n await params.reserveUsage?.();\n\n const result = await params.fn();\n\n try {\n await this.cache.set(params.cacheKey, result, params.ttl);\n } catch (error) {\n this.logger.error('[VernLLM] cache write failed', {\n message: error instanceof Error ? error.message : 'unknown',\n });\n }\n\n return result;\n } catch (error) {\n try {\n await params.refundUsage?.();\n } catch (refundError) {\n this.logger.error('[VernLLM] refundUsage failed', {\n message: refundError instanceof Error ? refundError.message : 'unknown',\n });\n }\n\n throw error;\n }\n }\n\n /**\n * Convenience wrapper composing `call` + `cachedCall`, so cached LLM calls\n * automatically get retry/timeout/circuit-breaker behavior without callers\n * having to remember to wire `fn: () => this.call(...)` themselves\n */\n async cachedLLMCall<T>(\n params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> },\n ): Promise<T> {\n const { call: callParams, ...cacheParams } = params;\n\n return this.cachedCall({\n ...cacheParams,\n fn: () => this.call(callParams),\n });\n }\n\n /**\n * Returns the current circuit breaker state, or undefined when no\n * circuit breaker was configured on this instance\n */\n getCircuitState() {\n return this.breaker?.getState();\n }\n}\n","import type { LLMClient } from '../types.js';\n\n/** Minimal structural type for the Anthropic SDKs `messages.create` */\nexport interface AnthropicClient {\n messages: {\n create(\n params: {\n model: string;\n max_tokens: number;\n temperature?: number;\n system?: string;\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n content: Array<{ type: string; text?: string }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n }>;\n };\n}\n\n/**\n * Wraps an Anthropic SDK client so it satisfies the same `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. Anthropics Messages API has no\n * `response_format: json_object` equivalent, so when the caller requests\n * JSON mode, this adapter appends an instruction to the system prompt\n * asking the model to respond with JSON only\n */\nexport function fromAnthropic(anthropicClient: AnthropicClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n // Anthropics Messages API has no `response_format: json_object` or\n // `json_schema` equivalent, so both are emulated via system prompt\n // instructions. For json_schema, the schema itself is embedded so the\n // model has something concrete to conform to (not provider-enforced,\n // unlike OpenAIs native structured outputs)\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n // `reasoning_effort` (OpenAI o-series/gpt-5 style) has no direct Anthropic\n // equivalent, Claudes extended thinking uses a token budget, not a tier\n // string, so its intentionally dropped here rather than guessed at.\n\n const system = [systemMessage?.content, jsonInstruction].filter(Boolean).join('\\n\\n');\n\n const response = await anthropicClient.messages.create(\n {\n model: params.model,\n max_tokens: params.max_tokens,\n temperature: params.temperature,\n system: system || undefined,\n messages: userMessages.map((m) => ({ role: 'user' as const, content: m.content })),\n },\n options,\n );\n\n const text = response.content.find((block) => block.type === 'text')?.text ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.input_tokens,\n completion_tokens: response.usage?.output_tokens,\n total_tokens:\n (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type for Geminis `generateContent`, matching both the\n * legacy `@google/generative-ai` SDKs `model.generateContent(...)` and the\n * newer `@google/genai` SDKs `ai.models.generateContent({ model, ... })`\n * closely enough to adapt either — pass whichever `.generateContent` you have.\n */\nexport interface GeminiClient {\n generateContent(\n params: {\n model?: string;\n contents: Array<{ role: 'user'; parts: Array<{ text: string }> }>;\n systemInstruction?: { parts: Array<{ text: string }> };\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n }>;\n}\n\n/**\n * Wraps a Gemini client so it satisfies the `LLMClient` interface VernLLM\n * uses for OpenAI/Groq. Geminis shape differs on nearly every axis: a\n * `contents` array instead of `messages`, a separate `systemInstruction`\n * field instead of a `system` role message, `generationConfig` instead of\n * top-level `temperature`/`max_tokens`, and native JSON Schema support via\n * `responseMimeType: 'application/json'` + `responseSchema` (so `jsonSchema`\n * is provider-enforced here, unlike the Anthropic adapters prompt-embedding\n * fallback). `reasoning_effort` has no equivalent. Geminis thinking models\n * use a token budget, not an effort tier, so its dropped, same as Anthropic.\n */\nexport function fromGemini(geminiClient: GeminiClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n const wantsJson = Boolean(params.response_format);\n const generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n > = {\n temperature: params.temperature,\n maxOutputTokens: params.max_tokens,\n };\n\n if (wantsJson) {\n generationConfig.responseMimeType = 'application/json';\n }\n if (params.response_format?.type === 'json_schema') {\n generationConfig.responseSchema = params.response_format.json_schema.schema;\n }\n\n const response = await geminiClient.generateContent(\n {\n model: params.model,\n contents: userMessages.map((m) => ({\n role: 'user' as const,\n parts: [{ text: m.content }],\n })),\n systemInstruction: systemMessage\n ? { parts: [{ text: systemMessage.content }] }\n : undefined,\n generationConfig,\n },\n options,\n );\n\n const text =\n response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usageMetadata?.promptTokenCount,\n completion_tokens: response.usageMetadata?.candidatesTokenCount,\n total_tokens: response.usageMetadata?.totalTokenCount,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type matching AWS Bedrocks Converse API. This is\n * intentionally NOT `BedrockRuntimeClient` itself, the AWS SDK v3 client\n * exposes `.send(command)`, not a direct `.converse()` method, and pulling\n * in `@aws-sdk/client-bedrock-runtime` as a dependency just for its types\n * isn't worth it for a structural adapter. Wrap your client, e.g:\n *\n * ```ts\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = new BedrockRuntimeClient({ region: 'us-east-1' });\n * const converseClient = {\n * converse: (params, options) =>\n * client.send(new ConverseCommand(params), { abortSignal: options.signal }),\n * };\n * ```\n */\nexport interface BedrockConverseClient {\n converse(\n params: {\n modelId: string;\n messages: Array<{ role: 'user'; content: Array<{ text: string }> }>;\n system?: Array<{ text: string }>;\n inferenceConfig?: { temperature?: number; maxTokens?: number };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n output?: { message?: { content?: Array<{ text?: string }> } };\n usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };\n }>;\n}\n\n/**\n * Wraps a Bedrock Converse-API client so it satisfies the `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. The Converse API is unified\n * across Bedrocks model families (Anthropic, Titan, Llama, Mistral, etc.),\n * so unlike raw per-model Bedrock invocation, this one adapter works\n * regardless of which underlying model `modelId` points at, as long as\n * that model supports Converse (most current-generation ones do)\n *\n * Theres no uniform native JSON Schema enforcement across families here\n * (some support it via forced tool-use, which varies per model), so\n * `jsonSchema`/`jsonMode` are emulated via a system-prompt instruction, same\n * approach as the Anthropic adapter. `reasoning_effort` has no Converse\n * equivalent and is dropped\n */\nexport function fromBedrock(bedrockClient: BedrockConverseClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n const systemParts = [systemMessage?.content, jsonInstruction].filter((s): s is string =>\n Boolean(s),\n );\n\n const response = await bedrockClient.converse(\n {\n modelId: params.model,\n messages: userMessages.map((m) => ({\n role: 'user' as const,\n content: [{ text: m.content }],\n })),\n system: systemParts.length ? systemParts.map((text) => ({ text })) : undefined,\n inferenceConfig: {\n temperature: params.temperature,\n maxTokens: params.max_tokens,\n },\n },\n options,\n );\n\n const text = response.output?.message?.content?.map((c) => c.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.inputTokens,\n completion_tokens: response.usage?.outputTokens,\n total_tokens: response.usage?.totalTokens,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/** The chat-completion-shaped request VernLLM builds internally */\ntype ChatRequest = Parameters<LLMClient['chat']['completions']['create']>[0];\n\nexport interface FetchAdapterConfig {\n /** Endpoint URL, or a function of the request in case it depends on model/params */\n url: string | ((params: ChatRequest) => string);\n /** Static headers, or a function (sync or async) for things like refreshed auth tokens */\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n /** HTTP method. Default 'POST' */\n method?: string;\n /** Maps VernLLMs internal chat-completion request into the providers raw request body */\n mapRequest: (params: ChatRequest) => unknown;\n /**\n * Maps the providers raw JSON response into `{ content, usage? }`\n * `content` is the assistants text (JSON string when JSON mode was requested)\n */\n mapResponse: (json: unknown) => {\n content: string;\n usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number };\n };\n}\n\n/**\n * A fetch-based escape hatch for providers with no SDK, or where pulling one\n * in isnt worth it. You supply the URL, headers, and two small mapping\n * functions; this handles the HTTP call and slots the result into the same\n * `LLMClient` shape every other adapter produces, so retries, timeouts,\n * the circuit breaker, and JSON/schema handling all still work unmodified\n *\n * Non-2xx responses throw an error with `.status` set to the HTTP status\n * code, so VernLLMs `nonRetryableStatus` handling (e.g. failing fast on\n * 401/403) applies here too\n */\nexport function fromFetch(config: FetchAdapterConfig): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const url = typeof config.url === 'function' ? config.url(params) : config.url;\n const headers =\n typeof config.headers === 'function' ? await config.headers() : config.headers;\n\n const res = await fetch(url, {\n method: config.method ?? 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(config.mapRequest(params)),\n signal: options.signal,\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n const err = new Error(\n `Fetch adapter request failed (${res.status}): ${body.slice(0, 500)}`,\n ) as Error & { status?: number };\n err.status = res.status;\n throw err;\n }\n\n const json = await res.json();\n const { content, usage } = config.mapResponse(json);\n\n return {\n choices: [{ message: { content } }],\n usage: usage\n ? {\n prompt_tokens: usage.promptTokens,\n completion_tokens: usage.completionTokens,\n total_tokens: usage.totalTokens,\n }\n : undefined,\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Passthrough adapter for any SDK/client whose `chat.completions.create`\n * already matches the OpenAI wire format 1:1 : this covers most hosted\n * inference providers, since \"OpenAI-compatible\" is a de facto standard for\n * chat completion APIs. No transformation happens here, this exists purely\n * so call sites read clearly (`fromMistral(client)` vs handing a Mistral\n * client to something typed for OpenAI) and so a real transformation could\n * be added later, per-provider, without a breaking change.\n *\n * Not every SDKs own TypeScript types line up exactly with `LLMClient`\n * (extra fields, stricter unions, etc.), so this takes `unknown` and casts:\n * the actual compatibility contract is the JSON each provider sends and\n * receives over the wire, not the SDKs TS types.\n */\nexport function fromOpenAICompatible(client: unknown): LLMClient {\n return client as LLMClient;\n}\n\n/** Groqs SDK matches the OpenAI wire format */\nexport const fromGroq = fromOpenAICompatible;\n\n/** Mistrals `chat.completions`-shaped client (or their OpenAI-compat endpoint) */\nexport const fromMistral = fromOpenAICompatible;\n\n/** DeepSeeks API is OpenAI-compatible */\nexport const fromDeepSeek = fromOpenAICompatible;\n\n/** Cerebras inference API is OpenAI-compatible */\nexport const fromCerebras = fromOpenAICompatible;\n\n/** Together AIs API is OpenAI-compatible */\nexport const fromTogether = fromOpenAICompatible;\n\n/** Fireworks AIs API is OpenAI-compatible */\nexport const fromFireworks = fromOpenAICompatible;\n\n/**\n * Ollama exposes an OpenAI-compatible endpoint at `/v1/chat/completions`\n * (as opposed to its native `/api/chat` format, which differs). Point an\n * OpenAI SDK instances `baseURL` at your Ollama server and pass it here:\n * this does not talk to Ollamas native API directly.\n */\nexport const fromOllama = fromOpenAICompatible;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["message: string","type: LLMErrorType","status?: number","issues?: unknown","err: unknown","key: string","value: T","ttl: number","options: CircuitBreakerOptions","content: string","err: unknown","fn: (signal: AbortSignal) => Promise<T>","timeoutMs: number","externalSignal?: AbortSignal","baseDelayMs: number","attempt: number","delay: number","signal?: AbortSignal","debugEnabled: boolean","message: string","meta?: Record<string, unknown>","options: VernLLMOptions","params: CallParams<T>","fn: () => Promise<T>","requestId: string","signal?: AbortSignal","lastError: unknown","error: unknown","jsonSchema: CallParams<unknown>['jsonSchema']","useJson: boolean","response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>","model: string","content: string","schema?: CallParams<T>['schema']","parsed: unknown","attempt: number","params: CachedCallParams<T>","params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> }","anthropicClient: AnthropicClient","jsonInstruction: string | undefined","geminiClient: GeminiClient","generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n >","bedrockClient: BedrockConverseClient","jsonInstruction: string | undefined","text","config: FetchAdapterConfig","client: unknown"],"sources":["../src/types.ts","../src/circuitBreaker.ts","../src/internal/vernLLM.utilts.ts","../src/logger.ts","../src/vernLLM.ts","../src/adapters/anthropic.ts","../src/adapters/gemini.ts","../src/adapters/bedrock.ts","../src/adapters/fetch.ts","../src/adapters/openaiCompatible.ts"],"sourcesContent":["export type LLMErrorType =\n | 'timeout'\n | 'api'\n | 'parse'\n | 'validation'\n | 'circuit_open'\n | 'unknown'\n | 'aborted';\n\nexport class LLMError extends Error {\n constructor(\n message: string,\n public type: LLMErrorType,\n public status?: number,\n public issues?: unknown,\n ) {\n super(message);\n this.name = 'LLMError';\n }\n}\n\nexport function isLLMError(err: unknown): err is LLMError {\n return err instanceof LLMError;\n}\n\nexport interface CacheAdapter<T = unknown> {\n get(key: string): Promise<T | null>;\n set(key: string, value: T, ttl: number): Promise<void>;\n}\n\n/**\n * Trivial default so the package works out of the box with no external deps\n * Not shared across processes, swap in Redis/Upstash/etc for production\n */\nexport class InMemoryCacheAdapter<T = unknown> implements CacheAdapter<T> {\n private store = new Map<string, { value: T; expiresAt: number }>();\n\n async get(key: string): Promise<T | null> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n return entry.value;\n }\n\n async set(key: string, value: T, ttl: number): Promise<void> {\n this.store.set(key, { value, expiresAt: Date.now() + ttl * 1000 });\n }\n}\n\n/**\n * Minimal shape compatible with the OpenAI SDKs chat.completions.create,\n * so consumers can pass an OpenAI client directly\n * `response_format.json_schema` and `reasoning_effort` are optional on the wire\n * providers that don't support them will just ignore fields they don't recognize,\n * but not every SDKs TS types accept them, hence this being a structural type\n * rather than importing the SDKs own params type\n */\nexport interface LLMClient {\n chat: {\n completions: {\n create(\n params: {\n model: string;\n temperature: number;\n max_tokens: number;\n response_format?:\n | { type: 'json_object' }\n | {\n type: 'json_schema';\n json_schema: {\n name: string;\n schema: Record<string, unknown>;\n strict?: boolean;\n description?: string;\n };\n };\n /** OpenAI reasoning-model param (o-series, gpt-5), ignored by providers that don't support it */\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n messages: Array<{ role: 'system' | 'user'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n choices?: Array<{ message?: { content?: string | null } }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n }>;\n };\n };\n}\n\nexport type ReserveUsage = () => Promise<void>;\nexport type RefundUsage = () => Promise<void>;\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n requestId: string;\n model: string;\n}\n\nexport type OnUsage = (usage: TokenUsage) => void;\n\n/**\n * Minimal structural type for a Zod-like schema, so this package doesnt need\n * a hard dependency on a specific Zod major version. Any object exposing\n * `safeParse` (Zod v3/v4, and most Zod-compatible validators) should satisfy this\n */\nexport interface SchemaLike<T> {\n safeParse(data: unknown): { success: true; data: T } | { success: false; error: unknown };\n}\n\nexport interface VernLLMOptions {\n client: LLMClient;\n model: string;\n /** Max retries after the first attempt. Default 1 (2 attempts total) */\n maxRetries?: number;\n /** Per-attempt timeout in ms. Default 25000 */\n timeoutMs?: number;\n /** Base delay for exponential backoff in ms. Default 500 */\n baseDelayMs?: number;\n /** Default max_tokens for calls that don't override it. Default 1000 */\n defaultMaxTokens?: number;\n /** Enables debug logging of raw model output. Default: NODE_ENV !== 'production' */\n debug?: boolean;\n /** Cache adapter for cachedCall. Defaults to an in-memory adapter */\n cache?: CacheAdapter;\n /** HTTP status codes that should fail fast without retrying. Default [400, 401, 403] */\n nonRetryableStatus?: number[];\n /** Custom JSON parser. Must return undefined/null on failure. Default: JSON.parse wrapped in try/catch */\n parseJson?: (content: string) => unknown;\n /** Called after every successful call with token usage, if the provider reports it */\n onUsage?: OnUsage;\n /** Injectable logger. Defaults to a console-based logger gated by `debug` */\n logger?: import('./logger.js').Logger;\n /**\n * Enables a circuit breaker that short-circuits calls after repeated\n * consecutive failures, instead of continuing to hammer a down provider\n * Pass `true` for defaults, or an options object to tune threshold/cooldown\n */\n circuitBreaker?: boolean | import('./circuitBreaker.js').CircuitBreakerOptions;\n}\n\n/**\n * A provider-native JSON Schema for structured outputs (OpenAI/Groq\n * `response_format: { type: 'json_schema' }`) This is the wire-format\n * schema the model is constrained to generate against, distinct from\n * `schema`, which is a client-side Zod validator run on the parsed result\n * You can use one, both, or neither; using both gets you provider-level\n * constraint plus client-side type inference/validation as a safety net\n */\nexport interface JsonSchemaSpec {\n name: string;\n schema: Record<string, unknown>;\n /** Enforces the schema strictly (OpenAI-specific), default true when supported */\n strict?: boolean;\n description?: string;\n}\n\nexport interface CallParams<T = unknown> {\n systemPrompt: string;\n userContent: string;\n temperature?: number;\n jsonMode?: boolean;\n maxTokens?: number;\n requestId?: string;\n signal?: AbortSignal;\n /** Overrides the model set on the VernLLM instance for this call only */\n model?: string;\n /**\n * OpenAI-style reasoning effort for reasoning models (o-series, gpt-5, etc).\n * Passed through as-is, providers/models that don't support it ignore it\n */\n reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high';\n /**\n * Provider-native JSON Schema structured-output mode. When set, this is sent\n * as `response_format: { type: 'json_schema', json_schema: ... }` instead of\n * the looser `json_object` mode, constraining the models output shape at\n * generation time (not just validating it after the fact). Implies jsonMode: true.\n */\n jsonSchema?: JsonSchemaSpec;\n /**\n * Optional Zod (or Zod-compatible) schema. When provided, the parsed JSON\n * is validated against it; on failure an LLMError('validation') is thrown\n * with `.issues` set to the schema's error object. Implies jsonMode: true.\n * Can be combined with `jsonSchema` for provider-level constraint + client-side typing.\n */\n schema?: SchemaLike<T>;\n}\n\nexport interface CachedCallParams<T> {\n cacheKey: string;\n ttl: number;\n fn: () => Promise<T>;\n reserveUsage?: ReserveUsage;\n refundUsage?: RefundUsage;\n}\n","import { LLMError } from './types.js';\n\nexport interface CircuitBreakerOptions {\n /** Consecutive failures before the circuit opens, default 5 */\n threshold?: number;\n /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */\n cooldownMs?: number;\n}\n\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\n/**\n * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across\n * calls. Once the threshold is hit, short-circuits new calls with an\n * LLMError('circuit_open') instead of hitting the provider, until the\n * cooldown elapses and a single trial call is allowed through\n */\nexport class CircuitBreaker {\n private state: CircuitState = 'closed';\n private consecutiveFailures = 0;\n private openedAt = 0;\n private threshold: number;\n private cooldownMs: number;\n\n constructor(options: CircuitBreakerOptions = {}) {\n this.threshold = options.threshold ?? 5;\n this.cooldownMs = options.cooldownMs ?? 30_000;\n }\n\n /** Throws if the circuit is open and the cooldown hasnt elapsed */\n assertClosed(): void {\n if (this.state !== 'open') return;\n\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n return;\n }\n\n throw new LLMError(\n `Circuit open — provider has failed ${this.consecutiveFailures} times in a row. Retry in ${Math.ceil((this.cooldownMs - elapsed) / 1000)}s.`,\n 'circuit_open',\n );\n }\n\n recordSuccess(): void {\n this.consecutiveFailures = 0;\n this.state = 'closed';\n }\n\n recordFailure(): void {\n this.consecutiveFailures += 1;\n\n if (this.state === 'half-open') {\n // Trial call failed: reopen and reset the cooldown window.\n this.state = 'open';\n this.openedAt = Date.now();\n return;\n }\n\n if (this.consecutiveFailures >= this.threshold) {\n this.state = 'open';\n this.openedAt = Date.now();\n }\n }\n\n getState(): CircuitState {\n return this.state;\n }\n}\n","import { LLMError } from '../types.js';\n\nexport function defaultParseJson(content: string): unknown {\n try {\n return JSON.parse(content);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Looks inside an unknown error value and pulls out an http status code\n * if one is present. Checks the status field first then the status code\n * field since different client libraries use different names for this.\n * Returns undefined when the error is not an object or carries no status\n */\nexport function extractStatus(err: unknown): number | undefined {\n if (!err || typeof err !== 'object') return undefined;\n\n const error = err as {\n status?: unknown;\n statusCode?: unknown;\n };\n\n if (typeof error.status === 'number') return error.status;\n if (typeof error.statusCode === 'number') return error.statusCode;\n\n return undefined;\n}\n\n/**\n * Runs an async function and cancels it if it takes longer than the given\n * timeout. Creates an internal abort controller that fires after the\n * timeout elapses, and combines it with any external signal the caller\n * passed in so either one can cancel the underlying call. If the internal\n * timeout triggers and the underlying operation aborts, the error is\n * converted into an LLMError with type \"timeout\". External cancellations\n * continue to propagate as aborted errors. The internal timer is always\n * cleared afterward, whether the function succeeds, fails, or is aborted,\n * so nothing is left running in the background.\n */\nexport async function withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs: number,\n externalSignal?: AbortSignal,\n): Promise<T> {\n const controller = new AbortController();\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n const signal = externalSignal\n ? AbortSignal.any([externalSignal, controller.signal])\n : controller.signal;\n\n try {\n return await fn(signal);\n } catch (err) {\n if (\n controller.signal.aborted &&\n !externalSignal?.aborted &&\n err instanceof DOMException &&\n err.name === 'AbortError'\n ) {\n throw new LLMError('Request timed out', 'timeout');\n }\n\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Exponential backoff with jitter, capped at maxDelayMs.\n * Jitter avoids thundering-herd retries when many callers back off in lockstep,\n * the cap prevents unbounded delays when maxRetries is high\n */\nexport function getBackoffDelay(baseDelayMs: number, attempt: number, maxDelayMs = 10_000): number {\n const exp = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n return exp / 2 + Math.random() * (exp / 2);\n}\n\n/**\n * Pauses execution for the given delay before a retry attempt. If an\n * abort signal is provided and it fires while waiting, the pending\n * timer is cancelled immediately and the wait rejects right away with\n * an aborted error instead of continuing to sit idle until the delay\n * would have finished on its own\n */\nexport async function waitForRetry(delay: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new LLMError('Operation aborted', 'aborted'));\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export interface Logger {\n debug(message: string): void;\n warn(message: string): void;\n error(message: string, meta?: Record<string, unknown>): void;\n}\n\n/**\n * Default logger. `debug` is gated by the `debug` option on VernLLM\n * warn/error always fire since they indicate real problems (retries, cache failures)\n */\nexport class ConsoleLogger implements Logger {\n constructor(private debugEnabled: boolean) {}\n\n debug(message: string): void {\n if (this.debugEnabled) console.debug(message);\n }\n\n warn(message: string): void {\n console.warn(message);\n }\n\n error(message: string, meta?: Record<string, unknown>): void {\n console.error(message, meta ?? '');\n }\n}\n","import { randomUUID } from 'crypto';\n\nimport { CircuitBreaker } from './circuitBreaker.js';\nimport {\n defaultParseJson,\n extractStatus,\n withTimeout,\n getBackoffDelay,\n waitForRetry,\n} from './internal/vernLLM.utilts.js';\nimport { ConsoleLogger, type Logger } from './logger.js';\nimport {\n LLMError,\n InMemoryCacheAdapter,\n type VernLLMOptions,\n type CallParams,\n type CachedCallParams,\n type CacheAdapter,\n type LLMClient,\n} from './types.js';\n\n/**\n * A resilient wrapper around an LLM chat completions client, this is VernLLM!\n *\n * Adds retry with exponential backoff and jitter, per-attempt timeouts,\n * an optional circuit breaker, JSON parsing with optional schema\n * validation, usage tracking, and an optional response cache, all\n * configurable, all opt-in beyond sensible defaults\n */\nexport class VernLLM {\n private readonly client: LLMClient;\n private readonly model: string;\n\n private readonly maxRetries: number;\n private readonly timeoutMs: number;\n private readonly baseDelayMs: number;\n private readonly defaultMaxTokens: number;\n\n private readonly cache: CacheAdapter<unknown>;\n private readonly nonRetryableStatus: number[];\n\n private readonly parseJson: (content: string) => unknown;\n private readonly onUsage?: VernLLMOptions['onUsage'];\n\n private readonly logger: Logger;\n private readonly breaker?: CircuitBreaker;\n\n /**\n * @param options: Client, model, and all tunables (retries, timeout,\n * backoff, cache, circuit breaker, logger, etc). See VernLLMOptions in `types.ts`\n * for individual defaults\n */\n constructor(options: VernLLMOptions) {\n this.client = options.client;\n this.model = options.model;\n\n const retryConfig = this.resolveRetryConfig(options);\n this.maxRetries = retryConfig.maxRetries;\n this.timeoutMs = retryConfig.timeoutMs;\n this.baseDelayMs = retryConfig.baseDelayMs;\n this.defaultMaxTokens = retryConfig.defaultMaxTokens;\n\n this.cache = options.cache ?? new InMemoryCacheAdapter();\n this.nonRetryableStatus = options.nonRetryableStatus ?? [400, 401, 403];\n\n this.parseJson = options.parseJson ?? defaultParseJson;\n this.onUsage = options.onUsage;\n\n this.logger = this.resolveLogger(options);\n this.breaker = this.resolveCircuitBreaker(options);\n }\n\n /**\n * Resolves retry/timeout/token defaults from the given options,\n * falling back to the librarys built-in defaults for anything unset\n */\n private resolveRetryConfig(options: VernLLMOptions) {\n return {\n maxRetries: options.maxRetries ?? 1,\n timeoutMs: options.timeoutMs ?? 25_000,\n baseDelayMs: options.baseDelayMs ?? 500,\n defaultMaxTokens: options.defaultMaxTokens ?? 1000,\n };\n }\n\n /**\n * Returns the caller supplied logger, or a console-based logger whose\n * debug output is gated by the `debug` option (defaulting to on\n * outside production)\n */\n private resolveLogger(options: VernLLMOptions): Logger {\n return (\n options.logger ?? new ConsoleLogger(options.debug ?? process.env.NODE_ENV !== 'production')\n );\n }\n\n /**\n * Builds a circuit breaker if `circuitBreaker` is truthy on the\n * options. Passing `true` uses default thresholds, passing an options\n * object tunes them. Returns undefined when the breaker is disabled\n */\n private resolveCircuitBreaker(options: VernLLMOptions): CircuitBreaker | undefined {\n if (!options.circuitBreaker) return undefined;\n\n return new CircuitBreaker(options.circuitBreaker === true ? undefined : options.circuitBreaker);\n }\n\n /**\n * Makes a single logical LLM call, transparently retrying on failure\n * according to the configured retry policy\n *\n * Fails fast if the circuit breaker is open or the signal is already\n * aborted, before any request is dispatched. On exhausting all\n * retries, records a circuit breaker failure and rejects with a\n * normalized LLMError\n *\n * @param params : System/user content plus per call overrides\n * (model, temperature, jsonMode, schema, signal, etc)\n * @returns The parsed and optionally schema-validated response, or\n * the raw string content when jsonMode is disabled\n */\n async call<T = unknown>(params: CallParams<T>): Promise<T> {\n this.breaker?.assertClosed();\n\n if (params.signal?.aborted) {\n throw new LLMError('LLM request aborted', 'aborted');\n }\n\n const requestId = params.requestId ?? randomUUID();\n\n try {\n const result = await this.retryWithBackoff(\n () => this.executeCall(params, requestId),\n requestId,\n params.signal,\n );\n\n return result;\n } catch (error) {\n this.breaker?.recordFailure();\n throw this.normalizeError(error, params.signal);\n }\n }\n\n /**\n * Runs `fn`, retrying with backoff according to `shouldRetry` policy\n * Purely mechanical: knows nothing about LLM specifics beyond the retry\n * predicate, so its testable independent of request/response shaping\n */\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n if (attempt > 0) {\n await this.recoverDelay(requestId, attempt, signal);\n }\n\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!this.shouldRetry(error, signal)) {\n break;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Converts any thrown value into a well-typed LLMError for the public\n * API surface. Preserves an existing LLMError as is, reports aborted\n * signals as such, classifies errors carrying an http status as\n * type api, and otherwise falls back to a generic unknown error.\n */\n private normalizeError(error: unknown, signal?: AbortSignal): LLMError {\n if (signal?.aborted) {\n return new LLMError('LLM request aborted', 'aborted');\n }\n\n if (error instanceof LLMError) {\n return error;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined) {\n return new LLMError('LLM request failed', 'api', status);\n }\n\n return new LLMError('LLM request failed', 'unknown');\n }\n\n /**\n * Performs a single attempt: builds the request, dispatches it with a\n * timeout, and shapes the response. Throws on an empty response so\n * the retry loop treats it like any other transient failure. Records\n * usage and a circuit breaker success before returning\n */\n private async executeCall<T>(params: CallParams<T>, requestId: string): Promise<T> {\n const { useJson, model, request } = this.buildRequestPayload(params);\n\n const response = await withTimeout(\n (attemptSignal) => this.client.chat.completions.create(request, { signal: attemptSignal }),\n this.timeoutMs,\n params.signal,\n );\n\n const content = response.choices?.[0]?.message?.content?.trim();\n\n if (!content) {\n throw new LLMError('Empty LLM response', 'api');\n }\n\n this.logger.debug(`[vern:${requestId}] output:\\n${content.slice(0, 800)}`);\n\n this.recordUsage(response, requestId, model);\n\n this.breaker?.recordSuccess();\n\n if (!useJson) {\n return content as T;\n }\n\n return this.parseAndValidate(content, params.schema);\n }\n\n /**\n * Applies per call defaults and shapes the params into the request\n * object expected by the underlying client, including the resolved\n * response format. Also returns whether JSON parsing should be\n * applied to the response and which model was ultimately used\n */\n private buildRequestPayload<T>(params: CallParams<T>) {\n const {\n systemPrompt,\n userContent,\n temperature = 0.2,\n jsonMode = true,\n maxTokens = this.defaultMaxTokens,\n model = this.model,\n reasoningEffort,\n jsonSchema,\n } = params;\n\n const useJson = jsonMode || Boolean(jsonSchema);\n const responseFormat = this.buildResponseFormat(jsonSchema, useJson);\n\n const request = {\n model,\n temperature,\n max_tokens: maxTokens,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),\n messages: [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userContent },\n ],\n };\n\n return { useJson, model, request };\n }\n\n /**\n * Chooses the response format to send to the provider. A provider\n * native json schema takes priority when supplied, constraining\n * generation directly, otherwise falls back to the looser json\n * object mode when JSON output is requested, or no format at all\n * for plain text responses\n */\n private buildResponseFormat(jsonSchema: CallParams<unknown>['jsonSchema'], useJson: boolean) {\n if (jsonSchema) {\n return {\n type: 'json_schema' as const,\n json_schema: {\n name: jsonSchema.name,\n schema: jsonSchema.schema,\n strict: jsonSchema.strict ?? true,\n description: jsonSchema.description,\n },\n };\n }\n\n return useJson ? { type: 'json_object' as const } : undefined;\n }\n\n /**\n * Reports token usage to the caller supplied onUsage callback, when\n * both a callback was configured and the provider actually returned\n * usage data on this response. A no op otherwise.\n */\n private recordUsage(\n response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>,\n requestId: string,\n model: string,\n ): void {\n if (!response.usage || !this.onUsage) return;\n\n this.onUsage({\n promptTokens: response.usage.prompt_tokens ?? 0,\n completionTokens: response.usage.completion_tokens ?? 0,\n totalTokens: response.usage.total_tokens ?? 0,\n requestId,\n model,\n });\n }\n\n /**\n * Parses the raw response content as JSON and, when a schema is\n * supplied, validates the parsed value against it. Throws a parse\n * type LLMError on malformed JSON and a validation type LLMError,\n * carrying the schemas issues, on a failed validation\n */\n private parseAndValidate<T>(content: string, schema?: CallParams<T>['schema']): T {\n let parsed: unknown;\n\n try {\n parsed = this.parseJson(content);\n } catch {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (parsed === null || parsed === undefined) {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (!schema) {\n return parsed as T;\n }\n\n const result = schema.safeParse(parsed);\n\n if (!result.success) {\n throw new LLMError('Schema validation failed', 'validation', undefined, result.error);\n }\n\n return result.data;\n }\n\n /**\n * Waits out the backoff delay for a given retry attempt, logging the\n * attempt for observability before the wait begins. Rejects early if\n * the signal aborts during the wait\n */\n private async recoverDelay(requestId: string, attempt: number, signal?: AbortSignal) {\n const delay = getBackoffDelay(this.baseDelayMs, attempt);\n\n this.logger.warn(\n `[vern:${requestId}] recovery attempt ${attempt}/${this.maxRetries}, waiting ${delay}ms`,\n );\n\n await waitForRetry(delay, signal);\n }\n\n /**\n * Decides whether a failed attempt is worth retrying. Never retries\n * once the signal has aborted, never retries a parse or validation\n * failure since those stem from the response content rather than a\n * transient fault, and never retries a status code the caller has\n * marked as non retryable. Retries everything else\n */\n private shouldRetry(error: unknown, signal?: AbortSignal): boolean {\n if (signal?.aborted) {\n return false;\n }\n\n if (error instanceof LLMError && (error.type === 'parse' || error.type === 'validation')) {\n return false;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined && this.nonRetryableStatus.includes(status)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Thin cache wrapper around caller supplied logic. `params.fn` is expected\n * to be a call that itself invokes `this.call(...)` (see `cachedLLMCall`\n * below for a convenience wrapper that wires this up automatically),\n * `cachedCall` does not itself apply retry/timeout policy.\n */\n async cachedCall<T>(params: CachedCallParams<T>): Promise<T> {\n const cached = await this.cache.get(params.cacheKey);\n\n if (cached !== null) {\n return cached as T;\n }\n\n try {\n await params.reserveUsage?.();\n\n const result = await params.fn();\n\n try {\n await this.cache.set(params.cacheKey, result, params.ttl);\n } catch (error) {\n this.logger.error('[VernLLM] cache write failed', {\n message: error instanceof Error ? error.message : 'unknown',\n });\n }\n\n return result;\n } catch (error) {\n try {\n await params.refundUsage?.();\n } catch (refundError) {\n this.logger.error('[VernLLM] refundUsage failed', {\n message: refundError instanceof Error ? refundError.message : 'unknown',\n });\n }\n\n throw error;\n }\n }\n\n /**\n * Convenience wrapper composing `call` + `cachedCall`, so cached LLM calls\n * automatically get retry/timeout/circuit-breaker behavior without callers\n * having to remember to wire `fn: () => this.call(...)` themselves\n */\n async cachedLLMCall<T>(\n params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> },\n ): Promise<T> {\n const { call: callParams, ...cacheParams } = params;\n\n return this.cachedCall({\n ...cacheParams,\n fn: () => this.call(callParams),\n });\n }\n\n /**\n * Returns the current circuit breaker state, or undefined when no\n * circuit breaker was configured on this instance\n */\n getCircuitState() {\n return this.breaker?.getState();\n }\n}\n","import type { LLMClient } from '../types.js';\n\n/** Minimal structural type for the Anthropic SDKs `messages.create` */\nexport interface AnthropicClient {\n messages: {\n create(\n params: {\n model: string;\n max_tokens: number;\n temperature?: number;\n system?: string;\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n content: Array<{ type: string; text?: string }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n }>;\n };\n}\n\n/**\n * Wraps an Anthropic SDK client so it satisfies the same `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. Anthropics Messages API has no\n * `response_format: json_object` equivalent, so when the caller requests\n * JSON mode, this adapter appends an instruction to the system prompt\n * asking the model to respond with JSON only\n */\nexport function fromAnthropic(anthropicClient: AnthropicClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n // Anthropics Messages API has no `response_format: json_object` or\n // `json_schema` equivalent, so both are emulated via system prompt\n // instructions. For json_schema, the schema itself is embedded so the\n // model has something concrete to conform to (not provider-enforced,\n // unlike OpenAIs native structured outputs)\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n // `reasoning_effort` (OpenAI o-series/gpt-5 style) has no direct Anthropic\n // equivalent, Claudes extended thinking uses a token budget, not a tier\n // string, so its intentionally dropped here rather than guessed at.\n\n const system = [systemMessage?.content, jsonInstruction].filter(Boolean).join('\\n\\n');\n\n const response = await anthropicClient.messages.create(\n {\n model: params.model,\n max_tokens: params.max_tokens,\n temperature: params.temperature,\n system: system || undefined,\n messages: userMessages.map((m) => ({ role: 'user' as const, content: m.content })),\n },\n options,\n );\n\n const text = response.content.find((block) => block.type === 'text')?.text ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.input_tokens,\n completion_tokens: response.usage?.output_tokens,\n total_tokens:\n (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type for Geminis `generateContent`, matching both the\n * legacy `@google/generative-ai` SDKs `model.generateContent(...)` and the\n * newer `@google/genai` SDKs `ai.models.generateContent({ model, ... })`\n * closely enough to adapt either — pass whichever `.generateContent` you have.\n */\nexport interface GeminiClient {\n generateContent(\n params: {\n model?: string;\n contents: Array<{ role: 'user'; parts: Array<{ text: string }> }>;\n systemInstruction?: { parts: Array<{ text: string }> };\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n }>;\n}\n\n/**\n * Wraps a Gemini client so it satisfies the `LLMClient` interface VernLLM\n * uses for OpenAI/Groq. Geminis shape differs on nearly every axis: a\n * `contents` array instead of `messages`, a separate `systemInstruction`\n * field instead of a `system` role message, `generationConfig` instead of\n * top-level `temperature`/`max_tokens`, and native JSON Schema support via\n * `responseMimeType: 'application/json'` + `responseSchema` (so `jsonSchema`\n * is provider-enforced here, unlike the Anthropic adapters prompt-embedding\n * fallback). `reasoning_effort` has no equivalent. Geminis thinking models\n * use a token budget, not an effort tier, so its dropped, same as Anthropic.\n */\nexport function fromGemini(geminiClient: GeminiClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n const wantsJson = Boolean(params.response_format);\n const generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n > = {\n temperature: params.temperature,\n maxOutputTokens: params.max_tokens,\n };\n\n if (wantsJson) {\n generationConfig.responseMimeType = 'application/json';\n }\n if (params.response_format?.type === 'json_schema') {\n generationConfig.responseSchema = params.response_format.json_schema.schema;\n }\n\n const response = await geminiClient.generateContent(\n {\n model: params.model,\n contents: userMessages.map((m) => ({\n role: 'user' as const,\n parts: [{ text: m.content }],\n })),\n systemInstruction: systemMessage\n ? { parts: [{ text: systemMessage.content }] }\n : undefined,\n generationConfig,\n },\n options,\n );\n\n const text =\n response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usageMetadata?.promptTokenCount,\n completion_tokens: response.usageMetadata?.candidatesTokenCount,\n total_tokens: response.usageMetadata?.totalTokenCount,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type matching AWS Bedrocks Converse API. This is\n * intentionally NOT `BedrockRuntimeClient` itself, the AWS SDK v3 client\n * exposes `.send(command)`, not a direct `.converse()` method, and pulling\n * in `@aws-sdk/client-bedrock-runtime` as a dependency just for its types\n * isn't worth it for a structural adapter. Wrap your client, e.g:\n *\n * ```ts\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = new BedrockRuntimeClient({ region: 'us-east-1' });\n * const converseClient = {\n * converse: (params, options) =>\n * client.send(new ConverseCommand(params), { abortSignal: options.signal }),\n * };\n * ```\n */\nexport interface BedrockConverseClient {\n converse(\n params: {\n modelId: string;\n messages: Array<{ role: 'user'; content: Array<{ text: string }> }>;\n system?: Array<{ text: string }>;\n inferenceConfig?: { temperature?: number; maxTokens?: number };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n output?: { message?: { content?: Array<{ text?: string }> } };\n usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };\n }>;\n}\n\n/**\n * Wraps a Bedrock Converse-API client so it satisfies the `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. The Converse API is unified\n * across Bedrocks model families (Anthropic, Titan, Llama, Mistral, etc.),\n * so unlike raw per-model Bedrock invocation, this one adapter works\n * regardless of which underlying model `modelId` points at, as long as\n * that model supports Converse (most current-generation ones do)\n *\n * Theres no uniform native JSON Schema enforcement across families here\n * (some support it via forced tool-use, which varies per model), so\n * `jsonSchema`/`jsonMode` are emulated via a system-prompt instruction, same\n * approach as the Anthropic adapter. `reasoning_effort` has no Converse\n * equivalent and is dropped\n */\nexport function fromBedrock(bedrockClient: BedrockConverseClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n const systemParts = [systemMessage?.content, jsonInstruction].filter((s): s is string =>\n Boolean(s),\n );\n\n const response = await bedrockClient.converse(\n {\n modelId: params.model,\n messages: userMessages.map((m) => ({\n role: 'user' as const,\n content: [{ text: m.content }],\n })),\n system: systemParts.length ? systemParts.map((text) => ({ text })) : undefined,\n inferenceConfig: {\n temperature: params.temperature,\n maxTokens: params.max_tokens,\n },\n },\n options,\n );\n\n const text = response.output?.message?.content?.map((c) => c.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.inputTokens,\n completion_tokens: response.usage?.outputTokens,\n total_tokens: response.usage?.totalTokens,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/** The chat-completion-shaped request VernLLM builds internally */\ntype ChatRequest = Parameters<LLMClient['chat']['completions']['create']>[0];\n\nexport interface FetchAdapterConfig {\n /** Endpoint URL, or a function of the request in case it depends on model/params */\n url: string | ((params: ChatRequest) => string);\n /** Static headers, or a function (sync or async) for things like refreshed auth tokens */\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n /** HTTP method. Default 'POST' */\n method?: string;\n /** Maps VernLLMs internal chat-completion request into the providers raw request body */\n mapRequest: (params: ChatRequest) => unknown;\n /**\n * Maps the providers raw JSON response into `{ content, usage? }`\n * `content` is the assistants text (JSON string when JSON mode was requested)\n */\n mapResponse: (json: unknown) => {\n content: string;\n usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number };\n };\n}\n\n/**\n * A fetch-based escape hatch for providers with no SDK, or where pulling one\n * in isnt worth it. You supply the URL, headers, and two small mapping\n * functions; this handles the HTTP call and slots the result into the same\n * `LLMClient` shape every other adapter produces, so retries, timeouts,\n * the circuit breaker, and JSON/schema handling all still work unmodified\n *\n * Non-2xx responses throw an error with `.status` set to the HTTP status\n * code, so VernLLMs `nonRetryableStatus` handling (e.g. failing fast on\n * 401/403) applies here too\n */\nexport function fromFetch(config: FetchAdapterConfig): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const url = typeof config.url === 'function' ? config.url(params) : config.url;\n const headers =\n typeof config.headers === 'function' ? await config.headers() : config.headers;\n\n const res = await fetch(url, {\n method: config.method ?? 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(config.mapRequest(params)),\n signal: options.signal,\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n const err = new Error(\n `Fetch adapter request failed (${res.status}): ${body.slice(0, 500)}`,\n ) as Error & { status?: number };\n err.status = res.status;\n throw err;\n }\n\n const json = await res.json();\n const { content, usage } = config.mapResponse(json);\n\n return {\n choices: [{ message: { content } }],\n usage: usage\n ? {\n prompt_tokens: usage.promptTokens,\n completion_tokens: usage.completionTokens,\n total_tokens: usage.totalTokens,\n }\n : undefined,\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Passthrough adapter for any SDK/client whose `chat.completions.create`\n * already matches the OpenAI wire format 1:1 : this covers most hosted\n * inference providers, since \"OpenAI-compatible\" is a de facto standard for\n * chat completion APIs. No transformation happens here, this exists purely\n * so call sites read clearly (`fromMistral(client)` vs handing a Mistral\n * client to something typed for OpenAI) and so a real transformation could\n * be added later, per-provider, without a breaking change.\n *\n * Not every SDKs own TypeScript types line up exactly with `LLMClient`\n * (extra fields, stricter unions, etc.), so this takes `unknown` and casts:\n * the actual compatibility contract is the JSON each provider sends and\n * receives over the wire, not the SDKs TS types.\n */\nexport function fromOpenAICompatible(client: unknown): LLMClient {\n return client as LLMClient;\n}\n\n/** Groqs SDK matches the OpenAI wire format */\nexport const fromGroq = fromOpenAICompatible;\n\n/** Mistrals `chat.completions`-shaped client (or their OpenAI-compat endpoint) */\nexport const fromMistral = fromOpenAICompatible;\n\n/** DeepSeeks API is OpenAI-compatible */\nexport const fromDeepSeek = fromOpenAICompatible;\n\n/** Cerebras inference API is OpenAI-compatible */\nexport const fromCerebras = fromOpenAICompatible;\n\n/** Together AIs API is OpenAI-compatible */\nexport const fromTogether = fromOpenAICompatible;\n\n/** Fireworks AIs API is OpenAI-compatible */\nexport const fromFireworks = fromOpenAICompatible;\n\n/**\n * Ollama exposes an OpenAI-compatible endpoint at `/v1/chat/completions`\n * (as opposed to its native `/api/chat` format, which differs). Point an\n * OpenAI SDK instances `baseURL` at your Ollama server and pass it here:\n * this does not talk to Ollamas native API directly.\n */\nexport const fromOllama = fromOpenAICompatible;\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -111,9 +111,12 @@ function extractStatus(err) {
111
111
  * Runs an async function and cancels it if it takes longer than the given
112
112
  * timeout. Creates an internal abort controller that fires after the
113
113
  * timeout elapses, and combines it with any external signal the caller
114
- * passed in so either one can cancel the underlying call. The internal
115
- * timer is always cleared afterward, whether the function succeeds,
116
- * fails, or is aborted, so nothing is left running in the background
114
+ * passed in so either one can cancel the underlying call. If the internal
115
+ * timeout triggers and the underlying operation aborts, the error is
116
+ * converted into an LLMError with type "timeout". External cancellations
117
+ * continue to propagate as aborted errors. The internal timer is always
118
+ * cleared afterward, whether the function succeeds, fails, or is aborted,
119
+ * so nothing is left running in the background.
117
120
  */
118
121
  async function withTimeout(fn, timeoutMs, externalSignal) {
119
122
  const controller = new AbortController();
@@ -123,6 +126,9 @@ async function withTimeout(fn, timeoutMs, externalSignal) {
123
126
  const signal = externalSignal ? AbortSignal.any([externalSignal, controller.signal]) : controller.signal;
124
127
  try {
125
128
  return await fn(signal);
129
+ } catch (err) {
130
+ if (controller.signal.aborted && !externalSignal?.aborted && err instanceof DOMException && err.name === "AbortError") throw new LLMError("Request timed out", "timeout");
131
+ throw err;
126
132
  } finally {
127
133
  clearTimeout(timer);
128
134
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["message: string","type: LLMErrorType","status?: number","issues?: unknown","err: unknown","key: string","value: T","ttl: number","options: CircuitBreakerOptions","content: string","err: unknown","fn: (signal: AbortSignal) => Promise<T>","timeoutMs: number","externalSignal?: AbortSignal","baseDelayMs: number","attempt: number","delay: number","signal?: AbortSignal","debugEnabled: boolean","message: string","meta?: Record<string, unknown>","options: VernLLMOptions","params: CallParams<T>","fn: () => Promise<T>","requestId: string","signal?: AbortSignal","lastError: unknown","error: unknown","jsonSchema: CallParams<unknown>['jsonSchema']","useJson: boolean","response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>","model: string","content: string","schema?: CallParams<T>['schema']","parsed: unknown","attempt: number","params: CachedCallParams<T>","params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> }","anthropicClient: AnthropicClient","jsonInstruction: string | undefined","geminiClient: GeminiClient","generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n >","bedrockClient: BedrockConverseClient","jsonInstruction: string | undefined","text","config: FetchAdapterConfig","client: unknown"],"sources":["../src/types.ts","../src/circuitBreaker.ts","../src/internal/vernLLM.utilts.ts","../src/logger.ts","../src/vernLLM.ts","../src/adapters/anthropic.ts","../src/adapters/gemini.ts","../src/adapters/bedrock.ts","../src/adapters/fetch.ts","../src/adapters/openaiCompatible.ts"],"sourcesContent":["export type LLMErrorType =\n | 'timeout'\n | 'api'\n | 'parse'\n | 'validation'\n | 'circuit_open'\n | 'unknown'\n | 'aborted';\n\nexport class LLMError extends Error {\n constructor(\n message: string,\n public type: LLMErrorType,\n public status?: number,\n public issues?: unknown,\n ) {\n super(message);\n this.name = 'LLMError';\n }\n}\n\nexport function isLLMError(err: unknown): err is LLMError {\n return err instanceof LLMError;\n}\n\nexport interface CacheAdapter<T = unknown> {\n get(key: string): Promise<T | null>;\n set(key: string, value: T, ttl: number): Promise<void>;\n}\n\n/**\n * Trivial default so the package works out of the box with no external deps\n * Not shared across processes, swap in Redis/Upstash/etc for production\n */\nexport class InMemoryCacheAdapter<T = unknown> implements CacheAdapter<T> {\n private store = new Map<string, { value: T; expiresAt: number }>();\n\n async get(key: string): Promise<T | null> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n return entry.value;\n }\n\n async set(key: string, value: T, ttl: number): Promise<void> {\n this.store.set(key, { value, expiresAt: Date.now() + ttl * 1000 });\n }\n}\n\n/**\n * Minimal shape compatible with the OpenAI SDKs chat.completions.create,\n * so consumers can pass an OpenAI client directly\n * `response_format.json_schema` and `reasoning_effort` are optional on the wire\n * providers that don't support them will just ignore fields they don't recognize,\n * but not every SDKs TS types accept them, hence this being a structural type\n * rather than importing the SDKs own params type\n */\nexport interface LLMClient {\n chat: {\n completions: {\n create(\n params: {\n model: string;\n temperature: number;\n max_tokens: number;\n response_format?:\n | { type: 'json_object' }\n | {\n type: 'json_schema';\n json_schema: {\n name: string;\n schema: Record<string, unknown>;\n strict?: boolean;\n description?: string;\n };\n };\n /** OpenAI reasoning-model param (o-series, gpt-5), ignored by providers that don't support it */\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n messages: Array<{ role: 'system' | 'user'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n choices?: Array<{ message?: { content?: string | null } }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n }>;\n };\n };\n}\n\nexport type ReserveUsage = () => Promise<void>;\nexport type RefundUsage = () => Promise<void>;\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n requestId: string;\n model: string;\n}\n\nexport type OnUsage = (usage: TokenUsage) => void;\n\n/**\n * Minimal structural type for a Zod-like schema, so this package doesnt need\n * a hard dependency on a specific Zod major version. Any object exposing\n * `safeParse` (Zod v3/v4, and most Zod-compatible validators) should satisfy this\n */\nexport interface SchemaLike<T> {\n safeParse(data: unknown): { success: true; data: T } | { success: false; error: unknown };\n}\n\nexport interface VernLLMOptions {\n client: LLMClient;\n model: string;\n /** Max retries after the first attempt. Default 1 (2 attempts total) */\n maxRetries?: number;\n /** Per-attempt timeout in ms. Default 25000 */\n timeoutMs?: number;\n /** Base delay for exponential backoff in ms. Default 500 */\n baseDelayMs?: number;\n /** Default max_tokens for calls that don't override it. Default 1000 */\n defaultMaxTokens?: number;\n /** Enables debug logging of raw model output. Default: NODE_ENV !== 'production' */\n debug?: boolean;\n /** Cache adapter for cachedCall. Defaults to an in-memory adapter */\n cache?: CacheAdapter;\n /** HTTP status codes that should fail fast without retrying. Default [400, 401, 403] */\n nonRetryableStatus?: number[];\n /** Custom JSON parser. Must return undefined/null on failure. Default: JSON.parse wrapped in try/catch */\n parseJson?: (content: string) => unknown;\n /** Called after every successful call with token usage, if the provider reports it */\n onUsage?: OnUsage;\n /** Injectable logger. Defaults to a console-based logger gated by `debug` */\n logger?: import('./logger.js').Logger;\n /**\n * Enables a circuit breaker that short-circuits calls after repeated\n * consecutive failures, instead of continuing to hammer a down provider\n * Pass `true` for defaults, or an options object to tune threshold/cooldown\n */\n circuitBreaker?: boolean | import('./circuitBreaker.js').CircuitBreakerOptions;\n}\n\n/**\n * A provider-native JSON Schema for structured outputs (OpenAI/Groq\n * `response_format: { type: 'json_schema' }`) This is the wire-format\n * schema the model is constrained to generate against, distinct from\n * `schema`, which is a client-side Zod validator run on the parsed result\n * You can use one, both, or neither; using both gets you provider-level\n * constraint plus client-side type inference/validation as a safety net\n */\nexport interface JsonSchemaSpec {\n name: string;\n schema: Record<string, unknown>;\n /** Enforces the schema strictly (OpenAI-specific), default true when supported */\n strict?: boolean;\n description?: string;\n}\n\nexport interface CallParams<T = unknown> {\n systemPrompt: string;\n userContent: string;\n temperature?: number;\n jsonMode?: boolean;\n maxTokens?: number;\n requestId?: string;\n signal?: AbortSignal;\n /** Overrides the model set on the VernLLM instance for this call only */\n model?: string;\n /**\n * OpenAI-style reasoning effort for reasoning models (o-series, gpt-5, etc).\n * Passed through as-is, providers/models that don't support it ignore it\n */\n reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high';\n /**\n * Provider-native JSON Schema structured-output mode. When set, this is sent\n * as `response_format: { type: 'json_schema', json_schema: ... }` instead of\n * the looser `json_object` mode, constraining the models output shape at\n * generation time (not just validating it after the fact). Implies jsonMode: true.\n */\n jsonSchema?: JsonSchemaSpec;\n /**\n * Optional Zod (or Zod-compatible) schema. When provided, the parsed JSON\n * is validated against it; on failure an LLMError('validation') is thrown\n * with `.issues` set to the schema's error object. Implies jsonMode: true.\n * Can be combined with `jsonSchema` for provider-level constraint + client-side typing.\n */\n schema?: SchemaLike<T>;\n}\n\nexport interface CachedCallParams<T> {\n cacheKey: string;\n ttl: number;\n fn: () => Promise<T>;\n reserveUsage?: ReserveUsage;\n refundUsage?: RefundUsage;\n}\n","import { LLMError } from './types.js';\n\nexport interface CircuitBreakerOptions {\n /** Consecutive failures before the circuit opens, default 5 */\n threshold?: number;\n /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */\n cooldownMs?: number;\n}\n\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\n/**\n * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across\n * calls. Once the threshold is hit, short-circuits new calls with an\n * LLMError('circuit_open') instead of hitting the provider, until the\n * cooldown elapses and a single trial call is allowed through\n */\nexport class CircuitBreaker {\n private state: CircuitState = 'closed';\n private consecutiveFailures = 0;\n private openedAt = 0;\n private threshold: number;\n private cooldownMs: number;\n\n constructor(options: CircuitBreakerOptions = {}) {\n this.threshold = options.threshold ?? 5;\n this.cooldownMs = options.cooldownMs ?? 30_000;\n }\n\n /** Throws if the circuit is open and the cooldown hasnt elapsed */\n assertClosed(): void {\n if (this.state !== 'open') return;\n\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n return;\n }\n\n throw new LLMError(\n `Circuit open — provider has failed ${this.consecutiveFailures} times in a row. Retry in ${Math.ceil((this.cooldownMs - elapsed) / 1000)}s.`,\n 'circuit_open',\n );\n }\n\n recordSuccess(): void {\n this.consecutiveFailures = 0;\n this.state = 'closed';\n }\n\n recordFailure(): void {\n this.consecutiveFailures += 1;\n\n if (this.state === 'half-open') {\n // Trial call failed: reopen and reset the cooldown window.\n this.state = 'open';\n this.openedAt = Date.now();\n return;\n }\n\n if (this.consecutiveFailures >= this.threshold) {\n this.state = 'open';\n this.openedAt = Date.now();\n }\n }\n\n getState(): CircuitState {\n return this.state;\n }\n}\n","import { LLMError } from '../types.js';\n\nexport function defaultParseJson(content: string): unknown {\n try {\n return JSON.parse(content);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Looks inside an unknown error value and pulls out an http status code\n * if one is present. Checks the status field first then the status code\n * field since different client libraries use different names for this.\n * Returns undefined when the error is not an object or carries no status\n */\nexport function extractStatus(err: unknown): number | undefined {\n if (!err || typeof err !== 'object') return undefined;\n\n const error = err as {\n status?: unknown;\n statusCode?: unknown;\n };\n\n if (typeof error.status === 'number') return error.status;\n if (typeof error.statusCode === 'number') return error.statusCode;\n\n return undefined;\n}\n\n/**\n * Runs an async function and cancels it if it takes longer than the given\n * timeout. Creates an internal abort controller that fires after the\n * timeout elapses, and combines it with any external signal the caller\n * passed in so either one can cancel the underlying call. The internal\n * timer is always cleared afterward, whether the function succeeds,\n * fails, or is aborted, so nothing is left running in the background\n */\nexport async function withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs: number,\n externalSignal?: AbortSignal,\n): Promise<T> {\n const controller = new AbortController();\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n const signal = externalSignal\n ? AbortSignal.any([externalSignal, controller.signal])\n : controller.signal;\n\n try {\n return await fn(signal);\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Exponential backoff with jitter, capped at maxDelayMs.\n * Jitter avoids thundering-herd retries when many callers back off in lockstep,\n * the cap prevents unbounded delays when maxRetries is high\n */\nexport function getBackoffDelay(baseDelayMs: number, attempt: number, maxDelayMs = 10_000): number {\n const exp = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n return exp / 2 + Math.random() * (exp / 2);\n}\n\n/**\n * Pauses execution for the given delay before a retry attempt. If an\n * abort signal is provided and it fires while waiting, the pending\n * timer is cancelled immediately and the wait rejects right away with\n * an aborted error instead of continuing to sit idle until the delay\n * would have finished on its own\n */\nexport async function waitForRetry(delay: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new LLMError('Operation aborted', 'aborted'));\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export interface Logger {\n debug(message: string): void;\n warn(message: string): void;\n error(message: string, meta?: Record<string, unknown>): void;\n}\n\n/**\n * Default logger. `debug` is gated by the `debug` option on VernLLM\n * warn/error always fire since they indicate real problems (retries, cache failures)\n */\nexport class ConsoleLogger implements Logger {\n constructor(private debugEnabled: boolean) {}\n\n debug(message: string): void {\n if (this.debugEnabled) console.debug(message);\n }\n\n warn(message: string): void {\n console.warn(message);\n }\n\n error(message: string, meta?: Record<string, unknown>): void {\n console.error(message, meta ?? '');\n }\n}\n","import { randomUUID } from 'crypto';\n\nimport { CircuitBreaker } from './circuitBreaker.js';\nimport {\n defaultParseJson,\n extractStatus,\n withTimeout,\n getBackoffDelay,\n waitForRetry,\n} from './internal/vernLLM.utilts.js';\nimport { ConsoleLogger, type Logger } from './logger.js';\nimport {\n LLMError,\n InMemoryCacheAdapter,\n type VernLLMOptions,\n type CallParams,\n type CachedCallParams,\n type CacheAdapter,\n type LLMClient,\n} from './types.js';\n\n/**\n * A resilient wrapper around an LLM chat completions client, this is VernLLM!\n *\n * Adds retry with exponential backoff and jitter, per-attempt timeouts,\n * an optional circuit breaker, JSON parsing with optional schema\n * validation, usage tracking, and an optional response cache, all\n * configurable, all opt-in beyond sensible defaults\n */\nexport class VernLLM {\n private readonly client: LLMClient;\n private readonly model: string;\n\n private readonly maxRetries: number;\n private readonly timeoutMs: number;\n private readonly baseDelayMs: number;\n private readonly defaultMaxTokens: number;\n\n private readonly cache: CacheAdapter<unknown>;\n private readonly nonRetryableStatus: number[];\n\n private readonly parseJson: (content: string) => unknown;\n private readonly onUsage?: VernLLMOptions['onUsage'];\n\n private readonly logger: Logger;\n private readonly breaker?: CircuitBreaker;\n\n /**\n * @param options: Client, model, and all tunables (retries, timeout,\n * backoff, cache, circuit breaker, logger, etc). See VernLLMOptions in `types.ts`\n * for individual defaults\n */\n constructor(options: VernLLMOptions) {\n this.client = options.client;\n this.model = options.model;\n\n const retryConfig = this.resolveRetryConfig(options);\n this.maxRetries = retryConfig.maxRetries;\n this.timeoutMs = retryConfig.timeoutMs;\n this.baseDelayMs = retryConfig.baseDelayMs;\n this.defaultMaxTokens = retryConfig.defaultMaxTokens;\n\n this.cache = options.cache ?? new InMemoryCacheAdapter();\n this.nonRetryableStatus = options.nonRetryableStatus ?? [400, 401, 403];\n\n this.parseJson = options.parseJson ?? defaultParseJson;\n this.onUsage = options.onUsage;\n\n this.logger = this.resolveLogger(options);\n this.breaker = this.resolveCircuitBreaker(options);\n }\n\n /**\n * Resolves retry/timeout/token defaults from the given options,\n * falling back to the librarys built-in defaults for anything unset\n */\n private resolveRetryConfig(options: VernLLMOptions) {\n return {\n maxRetries: options.maxRetries ?? 1,\n timeoutMs: options.timeoutMs ?? 25_000,\n baseDelayMs: options.baseDelayMs ?? 500,\n defaultMaxTokens: options.defaultMaxTokens ?? 1000,\n };\n }\n\n /**\n * Returns the caller supplied logger, or a console-based logger whose\n * debug output is gated by the `debug` option (defaulting to on\n * outside production)\n */\n private resolveLogger(options: VernLLMOptions): Logger {\n return (\n options.logger ?? new ConsoleLogger(options.debug ?? process.env.NODE_ENV !== 'production')\n );\n }\n\n /**\n * Builds a circuit breaker if `circuitBreaker` is truthy on the\n * options. Passing `true` uses default thresholds, passing an options\n * object tunes them. Returns undefined when the breaker is disabled\n */\n private resolveCircuitBreaker(options: VernLLMOptions): CircuitBreaker | undefined {\n if (!options.circuitBreaker) return undefined;\n\n return new CircuitBreaker(options.circuitBreaker === true ? undefined : options.circuitBreaker);\n }\n\n /**\n * Makes a single logical LLM call, transparently retrying on failure\n * according to the configured retry policy\n *\n * Fails fast if the circuit breaker is open or the signal is already\n * aborted, before any request is dispatched. On exhausting all\n * retries, records a circuit breaker failure and rejects with a\n * normalized LLMError\n *\n * @param params : System/user content plus per call overrides\n * (model, temperature, jsonMode, schema, signal, etc)\n * @returns The parsed and optionally schema-validated response, or\n * the raw string content when jsonMode is disabled\n */\n async call<T = unknown>(params: CallParams<T>): Promise<T> {\n this.breaker?.assertClosed();\n\n if (params.signal?.aborted) {\n throw new LLMError('LLM request aborted', 'aborted');\n }\n\n const requestId = params.requestId ?? randomUUID();\n\n try {\n const result = await this.retryWithBackoff(\n () => this.executeCall(params, requestId),\n requestId,\n params.signal,\n );\n\n return result;\n } catch (error) {\n this.breaker?.recordFailure();\n throw this.normalizeError(error, params.signal);\n }\n }\n\n /**\n * Runs `fn`, retrying with backoff according to `shouldRetry` policy\n * Purely mechanical: knows nothing about LLM specifics beyond the retry\n * predicate, so its testable independent of request/response shaping\n */\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n if (attempt > 0) {\n await this.recoverDelay(requestId, attempt, signal);\n }\n\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!this.shouldRetry(error, signal)) {\n break;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Converts any thrown value into a well-typed LLMError for the public\n * API surface. Preserves an existing LLMError as is, reports aborted\n * signals as such, classifies errors carrying an http status as\n * type api, and otherwise falls back to a generic unknown error.\n */\n private normalizeError(error: unknown, signal?: AbortSignal): LLMError {\n if (signal?.aborted) {\n return new LLMError('LLM request aborted', 'aborted');\n }\n\n if (error instanceof LLMError) {\n return error;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined) {\n return new LLMError('LLM request failed', 'api', status);\n }\n\n return new LLMError('LLM request failed', 'unknown');\n }\n\n /**\n * Performs a single attempt: builds the request, dispatches it with a\n * timeout, and shapes the response. Throws on an empty response so\n * the retry loop treats it like any other transient failure. Records\n * usage and a circuit breaker success before returning\n */\n private async executeCall<T>(params: CallParams<T>, requestId: string): Promise<T> {\n const { useJson, model, request } = this.buildRequestPayload(params);\n\n const response = await withTimeout(\n (attemptSignal) => this.client.chat.completions.create(request, { signal: attemptSignal }),\n this.timeoutMs,\n params.signal,\n );\n\n const content = response.choices?.[0]?.message?.content?.trim();\n\n if (!content) {\n throw new LLMError('Empty LLM response', 'api');\n }\n\n this.logger.debug(`[vern:${requestId}] output:\\n${content.slice(0, 800)}`);\n\n this.recordUsage(response, requestId, model);\n\n this.breaker?.recordSuccess();\n\n if (!useJson) {\n return content as T;\n }\n\n return this.parseAndValidate(content, params.schema);\n }\n\n /**\n * Applies per call defaults and shapes the params into the request\n * object expected by the underlying client, including the resolved\n * response format. Also returns whether JSON parsing should be\n * applied to the response and which model was ultimately used\n */\n private buildRequestPayload<T>(params: CallParams<T>) {\n const {\n systemPrompt,\n userContent,\n temperature = 0.2,\n jsonMode = true,\n maxTokens = this.defaultMaxTokens,\n model = this.model,\n reasoningEffort,\n jsonSchema,\n } = params;\n\n const useJson = jsonMode || Boolean(jsonSchema);\n const responseFormat = this.buildResponseFormat(jsonSchema, useJson);\n\n const request = {\n model,\n temperature,\n max_tokens: maxTokens,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),\n messages: [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userContent },\n ],\n };\n\n return { useJson, model, request };\n }\n\n /**\n * Chooses the response format to send to the provider. A provider\n * native json schema takes priority when supplied, constraining\n * generation directly, otherwise falls back to the looser json\n * object mode when JSON output is requested, or no format at all\n * for plain text responses\n */\n private buildResponseFormat(jsonSchema: CallParams<unknown>['jsonSchema'], useJson: boolean) {\n if (jsonSchema) {\n return {\n type: 'json_schema' as const,\n json_schema: {\n name: jsonSchema.name,\n schema: jsonSchema.schema,\n strict: jsonSchema.strict ?? true,\n description: jsonSchema.description,\n },\n };\n }\n\n return useJson ? { type: 'json_object' as const } : undefined;\n }\n\n /**\n * Reports token usage to the caller supplied onUsage callback, when\n * both a callback was configured and the provider actually returned\n * usage data on this response. A no op otherwise.\n */\n private recordUsage(\n response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>,\n requestId: string,\n model: string,\n ): void {\n if (!response.usage || !this.onUsage) return;\n\n this.onUsage({\n promptTokens: response.usage.prompt_tokens ?? 0,\n completionTokens: response.usage.completion_tokens ?? 0,\n totalTokens: response.usage.total_tokens ?? 0,\n requestId,\n model,\n });\n }\n\n /**\n * Parses the raw response content as JSON and, when a schema is\n * supplied, validates the parsed value against it. Throws a parse\n * type LLMError on malformed JSON and a validation type LLMError,\n * carrying the schemas issues, on a failed validation\n */\n private parseAndValidate<T>(content: string, schema?: CallParams<T>['schema']): T {\n let parsed: unknown;\n\n try {\n parsed = this.parseJson(content);\n } catch {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (parsed === null || parsed === undefined) {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (!schema) {\n return parsed as T;\n }\n\n const result = schema.safeParse(parsed);\n\n if (!result.success) {\n throw new LLMError('Schema validation failed', 'validation', undefined, result.error);\n }\n\n return result.data;\n }\n\n /**\n * Waits out the backoff delay for a given retry attempt, logging the\n * attempt for observability before the wait begins. Rejects early if\n * the signal aborts during the wait\n */\n private async recoverDelay(requestId: string, attempt: number, signal?: AbortSignal) {\n const delay = getBackoffDelay(this.baseDelayMs, attempt);\n\n this.logger.warn(\n `[vern:${requestId}] recovery attempt ${attempt}/${this.maxRetries}, waiting ${delay}ms`,\n );\n\n await waitForRetry(delay, signal);\n }\n\n /**\n * Decides whether a failed attempt is worth retrying. Never retries\n * once the signal has aborted, never retries a parse or validation\n * failure since those stem from the response content rather than a\n * transient fault, and never retries a status code the caller has\n * marked as non retryable. Retries everything else\n */\n private shouldRetry(error: unknown, signal?: AbortSignal): boolean {\n if (signal?.aborted) {\n return false;\n }\n\n if (error instanceof LLMError && (error.type === 'parse' || error.type === 'validation')) {\n return false;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined && this.nonRetryableStatus.includes(status)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Thin cache wrapper around caller supplied logic. `params.fn` is expected\n * to be a call that itself invokes `this.call(...)` (see `cachedLLMCall`\n * below for a convenience wrapper that wires this up automatically),\n * `cachedCall` does not itself apply retry/timeout policy.\n */\n async cachedCall<T>(params: CachedCallParams<T>): Promise<T> {\n const cached = await this.cache.get(params.cacheKey);\n\n if (cached !== null) {\n return cached as T;\n }\n\n try {\n await params.reserveUsage?.();\n\n const result = await params.fn();\n\n try {\n await this.cache.set(params.cacheKey, result, params.ttl);\n } catch (error) {\n this.logger.error('[VernLLM] cache write failed', {\n message: error instanceof Error ? error.message : 'unknown',\n });\n }\n\n return result;\n } catch (error) {\n try {\n await params.refundUsage?.();\n } catch (refundError) {\n this.logger.error('[VernLLM] refundUsage failed', {\n message: refundError instanceof Error ? refundError.message : 'unknown',\n });\n }\n\n throw error;\n }\n }\n\n /**\n * Convenience wrapper composing `call` + `cachedCall`, so cached LLM calls\n * automatically get retry/timeout/circuit-breaker behavior without callers\n * having to remember to wire `fn: () => this.call(...)` themselves\n */\n async cachedLLMCall<T>(\n params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> },\n ): Promise<T> {\n const { call: callParams, ...cacheParams } = params;\n\n return this.cachedCall({\n ...cacheParams,\n fn: () => this.call(callParams),\n });\n }\n\n /**\n * Returns the current circuit breaker state, or undefined when no\n * circuit breaker was configured on this instance\n */\n getCircuitState() {\n return this.breaker?.getState();\n }\n}\n","import type { LLMClient } from '../types.js';\n\n/** Minimal structural type for the Anthropic SDKs `messages.create` */\nexport interface AnthropicClient {\n messages: {\n create(\n params: {\n model: string;\n max_tokens: number;\n temperature?: number;\n system?: string;\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n content: Array<{ type: string; text?: string }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n }>;\n };\n}\n\n/**\n * Wraps an Anthropic SDK client so it satisfies the same `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. Anthropics Messages API has no\n * `response_format: json_object` equivalent, so when the caller requests\n * JSON mode, this adapter appends an instruction to the system prompt\n * asking the model to respond with JSON only\n */\nexport function fromAnthropic(anthropicClient: AnthropicClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n // Anthropics Messages API has no `response_format: json_object` or\n // `json_schema` equivalent, so both are emulated via system prompt\n // instructions. For json_schema, the schema itself is embedded so the\n // model has something concrete to conform to (not provider-enforced,\n // unlike OpenAIs native structured outputs)\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n // `reasoning_effort` (OpenAI o-series/gpt-5 style) has no direct Anthropic\n // equivalent, Claudes extended thinking uses a token budget, not a tier\n // string, so its intentionally dropped here rather than guessed at.\n\n const system = [systemMessage?.content, jsonInstruction].filter(Boolean).join('\\n\\n');\n\n const response = await anthropicClient.messages.create(\n {\n model: params.model,\n max_tokens: params.max_tokens,\n temperature: params.temperature,\n system: system || undefined,\n messages: userMessages.map((m) => ({ role: 'user' as const, content: m.content })),\n },\n options,\n );\n\n const text = response.content.find((block) => block.type === 'text')?.text ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.input_tokens,\n completion_tokens: response.usage?.output_tokens,\n total_tokens:\n (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type for Geminis `generateContent`, matching both the\n * legacy `@google/generative-ai` SDKs `model.generateContent(...)` and the\n * newer `@google/genai` SDKs `ai.models.generateContent({ model, ... })`\n * closely enough to adapt either — pass whichever `.generateContent` you have.\n */\nexport interface GeminiClient {\n generateContent(\n params: {\n model?: string;\n contents: Array<{ role: 'user'; parts: Array<{ text: string }> }>;\n systemInstruction?: { parts: Array<{ text: string }> };\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n }>;\n}\n\n/**\n * Wraps a Gemini client so it satisfies the `LLMClient` interface VernLLM\n * uses for OpenAI/Groq. Geminis shape differs on nearly every axis: a\n * `contents` array instead of `messages`, a separate `systemInstruction`\n * field instead of a `system` role message, `generationConfig` instead of\n * top-level `temperature`/`max_tokens`, and native JSON Schema support via\n * `responseMimeType: 'application/json'` + `responseSchema` (so `jsonSchema`\n * is provider-enforced here, unlike the Anthropic adapters prompt-embedding\n * fallback). `reasoning_effort` has no equivalent. Geminis thinking models\n * use a token budget, not an effort tier, so its dropped, same as Anthropic.\n */\nexport function fromGemini(geminiClient: GeminiClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n const wantsJson = Boolean(params.response_format);\n const generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n > = {\n temperature: params.temperature,\n maxOutputTokens: params.max_tokens,\n };\n\n if (wantsJson) {\n generationConfig.responseMimeType = 'application/json';\n }\n if (params.response_format?.type === 'json_schema') {\n generationConfig.responseSchema = params.response_format.json_schema.schema;\n }\n\n const response = await geminiClient.generateContent(\n {\n model: params.model,\n contents: userMessages.map((m) => ({\n role: 'user' as const,\n parts: [{ text: m.content }],\n })),\n systemInstruction: systemMessage\n ? { parts: [{ text: systemMessage.content }] }\n : undefined,\n generationConfig,\n },\n options,\n );\n\n const text =\n response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usageMetadata?.promptTokenCount,\n completion_tokens: response.usageMetadata?.candidatesTokenCount,\n total_tokens: response.usageMetadata?.totalTokenCount,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type matching AWS Bedrocks Converse API. This is\n * intentionally NOT `BedrockRuntimeClient` itself, the AWS SDK v3 client\n * exposes `.send(command)`, not a direct `.converse()` method, and pulling\n * in `@aws-sdk/client-bedrock-runtime` as a dependency just for its types\n * isn't worth it for a structural adapter. Wrap your client, e.g:\n *\n * ```ts\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = new BedrockRuntimeClient({ region: 'us-east-1' });\n * const converseClient = {\n * converse: (params, options) =>\n * client.send(new ConverseCommand(params), { abortSignal: options.signal }),\n * };\n * ```\n */\nexport interface BedrockConverseClient {\n converse(\n params: {\n modelId: string;\n messages: Array<{ role: 'user'; content: Array<{ text: string }> }>;\n system?: Array<{ text: string }>;\n inferenceConfig?: { temperature?: number; maxTokens?: number };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n output?: { message?: { content?: Array<{ text?: string }> } };\n usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };\n }>;\n}\n\n/**\n * Wraps a Bedrock Converse-API client so it satisfies the `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. The Converse API is unified\n * across Bedrocks model families (Anthropic, Titan, Llama, Mistral, etc.),\n * so unlike raw per-model Bedrock invocation, this one adapter works\n * regardless of which underlying model `modelId` points at, as long as\n * that model supports Converse (most current-generation ones do)\n *\n * Theres no uniform native JSON Schema enforcement across families here\n * (some support it via forced tool-use, which varies per model), so\n * `jsonSchema`/`jsonMode` are emulated via a system-prompt instruction, same\n * approach as the Anthropic adapter. `reasoning_effort` has no Converse\n * equivalent and is dropped\n */\nexport function fromBedrock(bedrockClient: BedrockConverseClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n const systemParts = [systemMessage?.content, jsonInstruction].filter((s): s is string =>\n Boolean(s),\n );\n\n const response = await bedrockClient.converse(\n {\n modelId: params.model,\n messages: userMessages.map((m) => ({\n role: 'user' as const,\n content: [{ text: m.content }],\n })),\n system: systemParts.length ? systemParts.map((text) => ({ text })) : undefined,\n inferenceConfig: {\n temperature: params.temperature,\n maxTokens: params.max_tokens,\n },\n },\n options,\n );\n\n const text = response.output?.message?.content?.map((c) => c.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.inputTokens,\n completion_tokens: response.usage?.outputTokens,\n total_tokens: response.usage?.totalTokens,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/** The chat-completion-shaped request VernLLM builds internally */\ntype ChatRequest = Parameters<LLMClient['chat']['completions']['create']>[0];\n\nexport interface FetchAdapterConfig {\n /** Endpoint URL, or a function of the request in case it depends on model/params */\n url: string | ((params: ChatRequest) => string);\n /** Static headers, or a function (sync or async) for things like refreshed auth tokens */\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n /** HTTP method. Default 'POST' */\n method?: string;\n /** Maps VernLLMs internal chat-completion request into the providers raw request body */\n mapRequest: (params: ChatRequest) => unknown;\n /**\n * Maps the providers raw JSON response into `{ content, usage? }`\n * `content` is the assistants text (JSON string when JSON mode was requested)\n */\n mapResponse: (json: unknown) => {\n content: string;\n usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number };\n };\n}\n\n/**\n * A fetch-based escape hatch for providers with no SDK, or where pulling one\n * in isnt worth it. You supply the URL, headers, and two small mapping\n * functions; this handles the HTTP call and slots the result into the same\n * `LLMClient` shape every other adapter produces, so retries, timeouts,\n * the circuit breaker, and JSON/schema handling all still work unmodified\n *\n * Non-2xx responses throw an error with `.status` set to the HTTP status\n * code, so VernLLMs `nonRetryableStatus` handling (e.g. failing fast on\n * 401/403) applies here too\n */\nexport function fromFetch(config: FetchAdapterConfig): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const url = typeof config.url === 'function' ? config.url(params) : config.url;\n const headers =\n typeof config.headers === 'function' ? await config.headers() : config.headers;\n\n const res = await fetch(url, {\n method: config.method ?? 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(config.mapRequest(params)),\n signal: options.signal,\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n const err = new Error(\n `Fetch adapter request failed (${res.status}): ${body.slice(0, 500)}`,\n ) as Error & { status?: number };\n err.status = res.status;\n throw err;\n }\n\n const json = await res.json();\n const { content, usage } = config.mapResponse(json);\n\n return {\n choices: [{ message: { content } }],\n usage: usage\n ? {\n prompt_tokens: usage.promptTokens,\n completion_tokens: usage.completionTokens,\n total_tokens: usage.totalTokens,\n }\n : undefined,\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Passthrough adapter for any SDK/client whose `chat.completions.create`\n * already matches the OpenAI wire format 1:1 : this covers most hosted\n * inference providers, since \"OpenAI-compatible\" is a de facto standard for\n * chat completion APIs. No transformation happens here, this exists purely\n * so call sites read clearly (`fromMistral(client)` vs handing a Mistral\n * client to something typed for OpenAI) and so a real transformation could\n * be added later, per-provider, without a breaking change.\n *\n * Not every SDKs own TypeScript types line up exactly with `LLMClient`\n * (extra fields, stricter unions, etc.), so this takes `unknown` and casts:\n * the actual compatibility contract is the JSON each provider sends and\n * receives over the wire, not the SDKs TS types.\n */\nexport function fromOpenAICompatible(client: unknown): LLMClient {\n return client as LLMClient;\n}\n\n/** Groqs SDK matches the OpenAI wire format */\nexport const fromGroq = fromOpenAICompatible;\n\n/** Mistrals `chat.completions`-shaped client (or their OpenAI-compat endpoint) */\nexport const fromMistral = fromOpenAICompatible;\n\n/** DeepSeeks API is OpenAI-compatible */\nexport const fromDeepSeek = fromOpenAICompatible;\n\n/** Cerebras inference API is OpenAI-compatible */\nexport const fromCerebras = fromOpenAICompatible;\n\n/** Together AIs API is OpenAI-compatible */\nexport const fromTogether = fromOpenAICompatible;\n\n/** Fireworks AIs API is OpenAI-compatible */\nexport const fromFireworks = fromOpenAICompatible;\n\n/**\n * Ollama exposes an OpenAI-compatible endpoint at `/v1/chat/completions`\n * (as opposed to its native `/api/chat` format, which differs). Point an\n * OpenAI SDK instances `baseURL` at your Ollama server and pass it here:\n * this does not talk to Ollamas native API directly.\n */\nexport const fromOllama = fromOpenAICompatible;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["message: string","type: LLMErrorType","status?: number","issues?: unknown","err: unknown","key: string","value: T","ttl: number","options: CircuitBreakerOptions","content: string","err: unknown","fn: (signal: AbortSignal) => Promise<T>","timeoutMs: number","externalSignal?: AbortSignal","baseDelayMs: number","attempt: number","delay: number","signal?: AbortSignal","debugEnabled: boolean","message: string","meta?: Record<string, unknown>","options: VernLLMOptions","params: CallParams<T>","fn: () => Promise<T>","requestId: string","signal?: AbortSignal","lastError: unknown","error: unknown","jsonSchema: CallParams<unknown>['jsonSchema']","useJson: boolean","response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>","model: string","content: string","schema?: CallParams<T>['schema']","parsed: unknown","attempt: number","params: CachedCallParams<T>","params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> }","anthropicClient: AnthropicClient","jsonInstruction: string | undefined","geminiClient: GeminiClient","generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n >","bedrockClient: BedrockConverseClient","jsonInstruction: string | undefined","text","config: FetchAdapterConfig","client: unknown"],"sources":["../src/types.ts","../src/circuitBreaker.ts","../src/internal/vernLLM.utilts.ts","../src/logger.ts","../src/vernLLM.ts","../src/adapters/anthropic.ts","../src/adapters/gemini.ts","../src/adapters/bedrock.ts","../src/adapters/fetch.ts","../src/adapters/openaiCompatible.ts"],"sourcesContent":["export type LLMErrorType =\n | 'timeout'\n | 'api'\n | 'parse'\n | 'validation'\n | 'circuit_open'\n | 'unknown'\n | 'aborted';\n\nexport class LLMError extends Error {\n constructor(\n message: string,\n public type: LLMErrorType,\n public status?: number,\n public issues?: unknown,\n ) {\n super(message);\n this.name = 'LLMError';\n }\n}\n\nexport function isLLMError(err: unknown): err is LLMError {\n return err instanceof LLMError;\n}\n\nexport interface CacheAdapter<T = unknown> {\n get(key: string): Promise<T | null>;\n set(key: string, value: T, ttl: number): Promise<void>;\n}\n\n/**\n * Trivial default so the package works out of the box with no external deps\n * Not shared across processes, swap in Redis/Upstash/etc for production\n */\nexport class InMemoryCacheAdapter<T = unknown> implements CacheAdapter<T> {\n private store = new Map<string, { value: T; expiresAt: number }>();\n\n async get(key: string): Promise<T | null> {\n const entry = this.store.get(key);\n if (!entry) return null;\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n return null;\n }\n return entry.value;\n }\n\n async set(key: string, value: T, ttl: number): Promise<void> {\n this.store.set(key, { value, expiresAt: Date.now() + ttl * 1000 });\n }\n}\n\n/**\n * Minimal shape compatible with the OpenAI SDKs chat.completions.create,\n * so consumers can pass an OpenAI client directly\n * `response_format.json_schema` and `reasoning_effort` are optional on the wire\n * providers that don't support them will just ignore fields they don't recognize,\n * but not every SDKs TS types accept them, hence this being a structural type\n * rather than importing the SDKs own params type\n */\nexport interface LLMClient {\n chat: {\n completions: {\n create(\n params: {\n model: string;\n temperature: number;\n max_tokens: number;\n response_format?:\n | { type: 'json_object' }\n | {\n type: 'json_schema';\n json_schema: {\n name: string;\n schema: Record<string, unknown>;\n strict?: boolean;\n description?: string;\n };\n };\n /** OpenAI reasoning-model param (o-series, gpt-5), ignored by providers that don't support it */\n reasoning_effort?: 'minimal' | 'low' | 'medium' | 'high';\n messages: Array<{ role: 'system' | 'user'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n choices?: Array<{ message?: { content?: string | null } }>;\n usage?: {\n prompt_tokens?: number;\n completion_tokens?: number;\n total_tokens?: number;\n };\n }>;\n };\n };\n}\n\nexport type ReserveUsage = () => Promise<void>;\nexport type RefundUsage = () => Promise<void>;\n\nexport interface TokenUsage {\n promptTokens: number;\n completionTokens: number;\n totalTokens: number;\n requestId: string;\n model: string;\n}\n\nexport type OnUsage = (usage: TokenUsage) => void;\n\n/**\n * Minimal structural type for a Zod-like schema, so this package doesnt need\n * a hard dependency on a specific Zod major version. Any object exposing\n * `safeParse` (Zod v3/v4, and most Zod-compatible validators) should satisfy this\n */\nexport interface SchemaLike<T> {\n safeParse(data: unknown): { success: true; data: T } | { success: false; error: unknown };\n}\n\nexport interface VernLLMOptions {\n client: LLMClient;\n model: string;\n /** Max retries after the first attempt. Default 1 (2 attempts total) */\n maxRetries?: number;\n /** Per-attempt timeout in ms. Default 25000 */\n timeoutMs?: number;\n /** Base delay for exponential backoff in ms. Default 500 */\n baseDelayMs?: number;\n /** Default max_tokens for calls that don't override it. Default 1000 */\n defaultMaxTokens?: number;\n /** Enables debug logging of raw model output. Default: NODE_ENV !== 'production' */\n debug?: boolean;\n /** Cache adapter for cachedCall. Defaults to an in-memory adapter */\n cache?: CacheAdapter;\n /** HTTP status codes that should fail fast without retrying. Default [400, 401, 403] */\n nonRetryableStatus?: number[];\n /** Custom JSON parser. Must return undefined/null on failure. Default: JSON.parse wrapped in try/catch */\n parseJson?: (content: string) => unknown;\n /** Called after every successful call with token usage, if the provider reports it */\n onUsage?: OnUsage;\n /** Injectable logger. Defaults to a console-based logger gated by `debug` */\n logger?: import('./logger.js').Logger;\n /**\n * Enables a circuit breaker that short-circuits calls after repeated\n * consecutive failures, instead of continuing to hammer a down provider\n * Pass `true` for defaults, or an options object to tune threshold/cooldown\n */\n circuitBreaker?: boolean | import('./circuitBreaker.js').CircuitBreakerOptions;\n}\n\n/**\n * A provider-native JSON Schema for structured outputs (OpenAI/Groq\n * `response_format: { type: 'json_schema' }`) This is the wire-format\n * schema the model is constrained to generate against, distinct from\n * `schema`, which is a client-side Zod validator run on the parsed result\n * You can use one, both, or neither; using both gets you provider-level\n * constraint plus client-side type inference/validation as a safety net\n */\nexport interface JsonSchemaSpec {\n name: string;\n schema: Record<string, unknown>;\n /** Enforces the schema strictly (OpenAI-specific), default true when supported */\n strict?: boolean;\n description?: string;\n}\n\nexport interface CallParams<T = unknown> {\n systemPrompt: string;\n userContent: string;\n temperature?: number;\n jsonMode?: boolean;\n maxTokens?: number;\n requestId?: string;\n signal?: AbortSignal;\n /** Overrides the model set on the VernLLM instance for this call only */\n model?: string;\n /**\n * OpenAI-style reasoning effort for reasoning models (o-series, gpt-5, etc).\n * Passed through as-is, providers/models that don't support it ignore it\n */\n reasoningEffort?: 'minimal' | 'low' | 'medium' | 'high';\n /**\n * Provider-native JSON Schema structured-output mode. When set, this is sent\n * as `response_format: { type: 'json_schema', json_schema: ... }` instead of\n * the looser `json_object` mode, constraining the models output shape at\n * generation time (not just validating it after the fact). Implies jsonMode: true.\n */\n jsonSchema?: JsonSchemaSpec;\n /**\n * Optional Zod (or Zod-compatible) schema. When provided, the parsed JSON\n * is validated against it; on failure an LLMError('validation') is thrown\n * with `.issues` set to the schema's error object. Implies jsonMode: true.\n * Can be combined with `jsonSchema` for provider-level constraint + client-side typing.\n */\n schema?: SchemaLike<T>;\n}\n\nexport interface CachedCallParams<T> {\n cacheKey: string;\n ttl: number;\n fn: () => Promise<T>;\n reserveUsage?: ReserveUsage;\n refundUsage?: RefundUsage;\n}\n","import { LLMError } from './types.js';\n\nexport interface CircuitBreakerOptions {\n /** Consecutive failures before the circuit opens, default 5 */\n threshold?: number;\n /** How long the circuit stays open before allowing a trial request, in ms. Default 30000 */\n cooldownMs?: number;\n}\n\ntype CircuitState = 'closed' | 'open' | 'half-open';\n\n/**\n * Per retry VernLLM-instance circuit breaker. Tracks consecutive failures across\n * calls. Once the threshold is hit, short-circuits new calls with an\n * LLMError('circuit_open') instead of hitting the provider, until the\n * cooldown elapses and a single trial call is allowed through\n */\nexport class CircuitBreaker {\n private state: CircuitState = 'closed';\n private consecutiveFailures = 0;\n private openedAt = 0;\n private threshold: number;\n private cooldownMs: number;\n\n constructor(options: CircuitBreakerOptions = {}) {\n this.threshold = options.threshold ?? 5;\n this.cooldownMs = options.cooldownMs ?? 30_000;\n }\n\n /** Throws if the circuit is open and the cooldown hasnt elapsed */\n assertClosed(): void {\n if (this.state !== 'open') return;\n\n const elapsed = Date.now() - this.openedAt;\n if (elapsed >= this.cooldownMs) {\n this.state = 'half-open';\n return;\n }\n\n throw new LLMError(\n `Circuit open — provider has failed ${this.consecutiveFailures} times in a row. Retry in ${Math.ceil((this.cooldownMs - elapsed) / 1000)}s.`,\n 'circuit_open',\n );\n }\n\n recordSuccess(): void {\n this.consecutiveFailures = 0;\n this.state = 'closed';\n }\n\n recordFailure(): void {\n this.consecutiveFailures += 1;\n\n if (this.state === 'half-open') {\n // Trial call failed: reopen and reset the cooldown window.\n this.state = 'open';\n this.openedAt = Date.now();\n return;\n }\n\n if (this.consecutiveFailures >= this.threshold) {\n this.state = 'open';\n this.openedAt = Date.now();\n }\n }\n\n getState(): CircuitState {\n return this.state;\n }\n}\n","import { LLMError } from '../types.js';\n\nexport function defaultParseJson(content: string): unknown {\n try {\n return JSON.parse(content);\n } catch {\n return undefined;\n }\n}\n\n/**\n * Looks inside an unknown error value and pulls out an http status code\n * if one is present. Checks the status field first then the status code\n * field since different client libraries use different names for this.\n * Returns undefined when the error is not an object or carries no status\n */\nexport function extractStatus(err: unknown): number | undefined {\n if (!err || typeof err !== 'object') return undefined;\n\n const error = err as {\n status?: unknown;\n statusCode?: unknown;\n };\n\n if (typeof error.status === 'number') return error.status;\n if (typeof error.statusCode === 'number') return error.statusCode;\n\n return undefined;\n}\n\n/**\n * Runs an async function and cancels it if it takes longer than the given\n * timeout. Creates an internal abort controller that fires after the\n * timeout elapses, and combines it with any external signal the caller\n * passed in so either one can cancel the underlying call. If the internal\n * timeout triggers and the underlying operation aborts, the error is\n * converted into an LLMError with type \"timeout\". External cancellations\n * continue to propagate as aborted errors. The internal timer is always\n * cleared afterward, whether the function succeeds, fails, or is aborted,\n * so nothing is left running in the background.\n */\nexport async function withTimeout<T>(\n fn: (signal: AbortSignal) => Promise<T>,\n timeoutMs: number,\n externalSignal?: AbortSignal,\n): Promise<T> {\n const controller = new AbortController();\n\n const timer = setTimeout(() => {\n controller.abort();\n }, timeoutMs);\n\n const signal = externalSignal\n ? AbortSignal.any([externalSignal, controller.signal])\n : controller.signal;\n\n try {\n return await fn(signal);\n } catch (err) {\n if (\n controller.signal.aborted &&\n !externalSignal?.aborted &&\n err instanceof DOMException &&\n err.name === 'AbortError'\n ) {\n throw new LLMError('Request timed out', 'timeout');\n }\n\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Exponential backoff with jitter, capped at maxDelayMs.\n * Jitter avoids thundering-herd retries when many callers back off in lockstep,\n * the cap prevents unbounded delays when maxRetries is high\n */\nexport function getBackoffDelay(baseDelayMs: number, attempt: number, maxDelayMs = 10_000): number {\n const exp = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);\n return exp / 2 + Math.random() * (exp / 2);\n}\n\n/**\n * Pauses execution for the given delay before a retry attempt. If an\n * abort signal is provided and it fires while waiting, the pending\n * timer is cancelled immediately and the wait rejects right away with\n * an aborted error instead of continuing to sit idle until the delay\n * would have finished on its own\n */\nexport async function waitForRetry(delay: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new LLMError('Operation aborted', 'aborted'));\n };\n\n const timer = setTimeout(() => {\n signal?.removeEventListener('abort', onAbort);\n resolve();\n }, delay);\n\n signal?.addEventListener('abort', onAbort, { once: true });\n });\n}\n","export interface Logger {\n debug(message: string): void;\n warn(message: string): void;\n error(message: string, meta?: Record<string, unknown>): void;\n}\n\n/**\n * Default logger. `debug` is gated by the `debug` option on VernLLM\n * warn/error always fire since they indicate real problems (retries, cache failures)\n */\nexport class ConsoleLogger implements Logger {\n constructor(private debugEnabled: boolean) {}\n\n debug(message: string): void {\n if (this.debugEnabled) console.debug(message);\n }\n\n warn(message: string): void {\n console.warn(message);\n }\n\n error(message: string, meta?: Record<string, unknown>): void {\n console.error(message, meta ?? '');\n }\n}\n","import { randomUUID } from 'crypto';\n\nimport { CircuitBreaker } from './circuitBreaker.js';\nimport {\n defaultParseJson,\n extractStatus,\n withTimeout,\n getBackoffDelay,\n waitForRetry,\n} from './internal/vernLLM.utilts.js';\nimport { ConsoleLogger, type Logger } from './logger.js';\nimport {\n LLMError,\n InMemoryCacheAdapter,\n type VernLLMOptions,\n type CallParams,\n type CachedCallParams,\n type CacheAdapter,\n type LLMClient,\n} from './types.js';\n\n/**\n * A resilient wrapper around an LLM chat completions client, this is VernLLM!\n *\n * Adds retry with exponential backoff and jitter, per-attempt timeouts,\n * an optional circuit breaker, JSON parsing with optional schema\n * validation, usage tracking, and an optional response cache, all\n * configurable, all opt-in beyond sensible defaults\n */\nexport class VernLLM {\n private readonly client: LLMClient;\n private readonly model: string;\n\n private readonly maxRetries: number;\n private readonly timeoutMs: number;\n private readonly baseDelayMs: number;\n private readonly defaultMaxTokens: number;\n\n private readonly cache: CacheAdapter<unknown>;\n private readonly nonRetryableStatus: number[];\n\n private readonly parseJson: (content: string) => unknown;\n private readonly onUsage?: VernLLMOptions['onUsage'];\n\n private readonly logger: Logger;\n private readonly breaker?: CircuitBreaker;\n\n /**\n * @param options: Client, model, and all tunables (retries, timeout,\n * backoff, cache, circuit breaker, logger, etc). See VernLLMOptions in `types.ts`\n * for individual defaults\n */\n constructor(options: VernLLMOptions) {\n this.client = options.client;\n this.model = options.model;\n\n const retryConfig = this.resolveRetryConfig(options);\n this.maxRetries = retryConfig.maxRetries;\n this.timeoutMs = retryConfig.timeoutMs;\n this.baseDelayMs = retryConfig.baseDelayMs;\n this.defaultMaxTokens = retryConfig.defaultMaxTokens;\n\n this.cache = options.cache ?? new InMemoryCacheAdapter();\n this.nonRetryableStatus = options.nonRetryableStatus ?? [400, 401, 403];\n\n this.parseJson = options.parseJson ?? defaultParseJson;\n this.onUsage = options.onUsage;\n\n this.logger = this.resolveLogger(options);\n this.breaker = this.resolveCircuitBreaker(options);\n }\n\n /**\n * Resolves retry/timeout/token defaults from the given options,\n * falling back to the librarys built-in defaults for anything unset\n */\n private resolveRetryConfig(options: VernLLMOptions) {\n return {\n maxRetries: options.maxRetries ?? 1,\n timeoutMs: options.timeoutMs ?? 25_000,\n baseDelayMs: options.baseDelayMs ?? 500,\n defaultMaxTokens: options.defaultMaxTokens ?? 1000,\n };\n }\n\n /**\n * Returns the caller supplied logger, or a console-based logger whose\n * debug output is gated by the `debug` option (defaulting to on\n * outside production)\n */\n private resolveLogger(options: VernLLMOptions): Logger {\n return (\n options.logger ?? new ConsoleLogger(options.debug ?? process.env.NODE_ENV !== 'production')\n );\n }\n\n /**\n * Builds a circuit breaker if `circuitBreaker` is truthy on the\n * options. Passing `true` uses default thresholds, passing an options\n * object tunes them. Returns undefined when the breaker is disabled\n */\n private resolveCircuitBreaker(options: VernLLMOptions): CircuitBreaker | undefined {\n if (!options.circuitBreaker) return undefined;\n\n return new CircuitBreaker(options.circuitBreaker === true ? undefined : options.circuitBreaker);\n }\n\n /**\n * Makes a single logical LLM call, transparently retrying on failure\n * according to the configured retry policy\n *\n * Fails fast if the circuit breaker is open or the signal is already\n * aborted, before any request is dispatched. On exhausting all\n * retries, records a circuit breaker failure and rejects with a\n * normalized LLMError\n *\n * @param params : System/user content plus per call overrides\n * (model, temperature, jsonMode, schema, signal, etc)\n * @returns The parsed and optionally schema-validated response, or\n * the raw string content when jsonMode is disabled\n */\n async call<T = unknown>(params: CallParams<T>): Promise<T> {\n this.breaker?.assertClosed();\n\n if (params.signal?.aborted) {\n throw new LLMError('LLM request aborted', 'aborted');\n }\n\n const requestId = params.requestId ?? randomUUID();\n\n try {\n const result = await this.retryWithBackoff(\n () => this.executeCall(params, requestId),\n requestId,\n params.signal,\n );\n\n return result;\n } catch (error) {\n this.breaker?.recordFailure();\n throw this.normalizeError(error, params.signal);\n }\n }\n\n /**\n * Runs `fn`, retrying with backoff according to `shouldRetry` policy\n * Purely mechanical: knows nothing about LLM specifics beyond the retry\n * predicate, so its testable independent of request/response shaping\n */\n private async retryWithBackoff<T>(\n fn: () => Promise<T>,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<T> {\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n if (attempt > 0) {\n await this.recoverDelay(requestId, attempt, signal);\n }\n\n return await fn();\n } catch (error) {\n lastError = error;\n\n if (!this.shouldRetry(error, signal)) {\n break;\n }\n }\n }\n\n throw lastError;\n }\n\n /**\n * Converts any thrown value into a well-typed LLMError for the public\n * API surface. Preserves an existing LLMError as is, reports aborted\n * signals as such, classifies errors carrying an http status as\n * type api, and otherwise falls back to a generic unknown error.\n */\n private normalizeError(error: unknown, signal?: AbortSignal): LLMError {\n if (signal?.aborted) {\n return new LLMError('LLM request aborted', 'aborted');\n }\n\n if (error instanceof LLMError) {\n return error;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined) {\n return new LLMError('LLM request failed', 'api', status);\n }\n\n return new LLMError('LLM request failed', 'unknown');\n }\n\n /**\n * Performs a single attempt: builds the request, dispatches it with a\n * timeout, and shapes the response. Throws on an empty response so\n * the retry loop treats it like any other transient failure. Records\n * usage and a circuit breaker success before returning\n */\n private async executeCall<T>(params: CallParams<T>, requestId: string): Promise<T> {\n const { useJson, model, request } = this.buildRequestPayload(params);\n\n const response = await withTimeout(\n (attemptSignal) => this.client.chat.completions.create(request, { signal: attemptSignal }),\n this.timeoutMs,\n params.signal,\n );\n\n const content = response.choices?.[0]?.message?.content?.trim();\n\n if (!content) {\n throw new LLMError('Empty LLM response', 'api');\n }\n\n this.logger.debug(`[vern:${requestId}] output:\\n${content.slice(0, 800)}`);\n\n this.recordUsage(response, requestId, model);\n\n this.breaker?.recordSuccess();\n\n if (!useJson) {\n return content as T;\n }\n\n return this.parseAndValidate(content, params.schema);\n }\n\n /**\n * Applies per call defaults and shapes the params into the request\n * object expected by the underlying client, including the resolved\n * response format. Also returns whether JSON parsing should be\n * applied to the response and which model was ultimately used\n */\n private buildRequestPayload<T>(params: CallParams<T>) {\n const {\n systemPrompt,\n userContent,\n temperature = 0.2,\n jsonMode = true,\n maxTokens = this.defaultMaxTokens,\n model = this.model,\n reasoningEffort,\n jsonSchema,\n } = params;\n\n const useJson = jsonMode || Boolean(jsonSchema);\n const responseFormat = this.buildResponseFormat(jsonSchema, useJson);\n\n const request = {\n model,\n temperature,\n max_tokens: maxTokens,\n ...(responseFormat ? { response_format: responseFormat } : {}),\n ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),\n messages: [\n { role: 'system' as const, content: systemPrompt },\n { role: 'user' as const, content: userContent },\n ],\n };\n\n return { useJson, model, request };\n }\n\n /**\n * Chooses the response format to send to the provider. A provider\n * native json schema takes priority when supplied, constraining\n * generation directly, otherwise falls back to the looser json\n * object mode when JSON output is requested, or no format at all\n * for plain text responses\n */\n private buildResponseFormat(jsonSchema: CallParams<unknown>['jsonSchema'], useJson: boolean) {\n if (jsonSchema) {\n return {\n type: 'json_schema' as const,\n json_schema: {\n name: jsonSchema.name,\n schema: jsonSchema.schema,\n strict: jsonSchema.strict ?? true,\n description: jsonSchema.description,\n },\n };\n }\n\n return useJson ? { type: 'json_object' as const } : undefined;\n }\n\n /**\n * Reports token usage to the caller supplied onUsage callback, when\n * both a callback was configured and the provider actually returned\n * usage data on this response. A no op otherwise.\n */\n private recordUsage(\n response: Awaited<ReturnType<LLMClient['chat']['completions']['create']>>,\n requestId: string,\n model: string,\n ): void {\n if (!response.usage || !this.onUsage) return;\n\n this.onUsage({\n promptTokens: response.usage.prompt_tokens ?? 0,\n completionTokens: response.usage.completion_tokens ?? 0,\n totalTokens: response.usage.total_tokens ?? 0,\n requestId,\n model,\n });\n }\n\n /**\n * Parses the raw response content as JSON and, when a schema is\n * supplied, validates the parsed value against it. Throws a parse\n * type LLMError on malformed JSON and a validation type LLMError,\n * carrying the schemas issues, on a failed validation\n */\n private parseAndValidate<T>(content: string, schema?: CallParams<T>['schema']): T {\n let parsed: unknown;\n\n try {\n parsed = this.parseJson(content);\n } catch {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (parsed === null || parsed === undefined) {\n throw new LLMError('Invalid JSON response', 'parse');\n }\n\n if (!schema) {\n return parsed as T;\n }\n\n const result = schema.safeParse(parsed);\n\n if (!result.success) {\n throw new LLMError('Schema validation failed', 'validation', undefined, result.error);\n }\n\n return result.data;\n }\n\n /**\n * Waits out the backoff delay for a given retry attempt, logging the\n * attempt for observability before the wait begins. Rejects early if\n * the signal aborts during the wait\n */\n private async recoverDelay(requestId: string, attempt: number, signal?: AbortSignal) {\n const delay = getBackoffDelay(this.baseDelayMs, attempt);\n\n this.logger.warn(\n `[vern:${requestId}] recovery attempt ${attempt}/${this.maxRetries}, waiting ${delay}ms`,\n );\n\n await waitForRetry(delay, signal);\n }\n\n /**\n * Decides whether a failed attempt is worth retrying. Never retries\n * once the signal has aborted, never retries a parse or validation\n * failure since those stem from the response content rather than a\n * transient fault, and never retries a status code the caller has\n * marked as non retryable. Retries everything else\n */\n private shouldRetry(error: unknown, signal?: AbortSignal): boolean {\n if (signal?.aborted) {\n return false;\n }\n\n if (error instanceof LLMError && (error.type === 'parse' || error.type === 'validation')) {\n return false;\n }\n\n const status = extractStatus(error);\n\n if (status !== undefined && this.nonRetryableStatus.includes(status)) {\n return false;\n }\n\n return true;\n }\n\n /**\n * Thin cache wrapper around caller supplied logic. `params.fn` is expected\n * to be a call that itself invokes `this.call(...)` (see `cachedLLMCall`\n * below for a convenience wrapper that wires this up automatically),\n * `cachedCall` does not itself apply retry/timeout policy.\n */\n async cachedCall<T>(params: CachedCallParams<T>): Promise<T> {\n const cached = await this.cache.get(params.cacheKey);\n\n if (cached !== null) {\n return cached as T;\n }\n\n try {\n await params.reserveUsage?.();\n\n const result = await params.fn();\n\n try {\n await this.cache.set(params.cacheKey, result, params.ttl);\n } catch (error) {\n this.logger.error('[VernLLM] cache write failed', {\n message: error instanceof Error ? error.message : 'unknown',\n });\n }\n\n return result;\n } catch (error) {\n try {\n await params.refundUsage?.();\n } catch (refundError) {\n this.logger.error('[VernLLM] refundUsage failed', {\n message: refundError instanceof Error ? refundError.message : 'unknown',\n });\n }\n\n throw error;\n }\n }\n\n /**\n * Convenience wrapper composing `call` + `cachedCall`, so cached LLM calls\n * automatically get retry/timeout/circuit-breaker behavior without callers\n * having to remember to wire `fn: () => this.call(...)` themselves\n */\n async cachedLLMCall<T>(\n params: Omit<CachedCallParams<T>, 'fn'> & { call: CallParams<T> },\n ): Promise<T> {\n const { call: callParams, ...cacheParams } = params;\n\n return this.cachedCall({\n ...cacheParams,\n fn: () => this.call(callParams),\n });\n }\n\n /**\n * Returns the current circuit breaker state, or undefined when no\n * circuit breaker was configured on this instance\n */\n getCircuitState() {\n return this.breaker?.getState();\n }\n}\n","import type { LLMClient } from '../types.js';\n\n/** Minimal structural type for the Anthropic SDKs `messages.create` */\nexport interface AnthropicClient {\n messages: {\n create(\n params: {\n model: string;\n max_tokens: number;\n temperature?: number;\n system?: string;\n messages: Array<{ role: 'user' | 'assistant'; content: string }>;\n },\n options: { signal: AbortSignal },\n ): Promise<{\n content: Array<{ type: string; text?: string }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n }>;\n };\n}\n\n/**\n * Wraps an Anthropic SDK client so it satisfies the same `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. Anthropics Messages API has no\n * `response_format: json_object` equivalent, so when the caller requests\n * JSON mode, this adapter appends an instruction to the system prompt\n * asking the model to respond with JSON only\n */\nexport function fromAnthropic(anthropicClient: AnthropicClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n // Anthropics Messages API has no `response_format: json_object` or\n // `json_schema` equivalent, so both are emulated via system prompt\n // instructions. For json_schema, the schema itself is embedded so the\n // model has something concrete to conform to (not provider-enforced,\n // unlike OpenAIs native structured outputs)\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n // `reasoning_effort` (OpenAI o-series/gpt-5 style) has no direct Anthropic\n // equivalent, Claudes extended thinking uses a token budget, not a tier\n // string, so its intentionally dropped here rather than guessed at.\n\n const system = [systemMessage?.content, jsonInstruction].filter(Boolean).join('\\n\\n');\n\n const response = await anthropicClient.messages.create(\n {\n model: params.model,\n max_tokens: params.max_tokens,\n temperature: params.temperature,\n system: system || undefined,\n messages: userMessages.map((m) => ({ role: 'user' as const, content: m.content })),\n },\n options,\n );\n\n const text = response.content.find((block) => block.type === 'text')?.text ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.input_tokens,\n completion_tokens: response.usage?.output_tokens,\n total_tokens:\n (response.usage?.input_tokens ?? 0) + (response.usage?.output_tokens ?? 0),\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type for Geminis `generateContent`, matching both the\n * legacy `@google/generative-ai` SDKs `model.generateContent(...)` and the\n * newer `@google/genai` SDKs `ai.models.generateContent({ model, ... })`\n * closely enough to adapt either — pass whichever `.generateContent` you have.\n */\nexport interface GeminiClient {\n generateContent(\n params: {\n model?: string;\n contents: Array<{ role: 'user'; parts: Array<{ text: string }> }>;\n systemInstruction?: { parts: Array<{ text: string }> };\n generationConfig?: {\n temperature?: number;\n maxOutputTokens?: number;\n responseMimeType?: string;\n responseSchema?: Record<string, unknown>;\n };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n candidates?: Array<{ content?: { parts?: Array<{ text?: string }> } }>;\n usageMetadata?: {\n promptTokenCount?: number;\n candidatesTokenCount?: number;\n totalTokenCount?: number;\n };\n }>;\n}\n\n/**\n * Wraps a Gemini client so it satisfies the `LLMClient` interface VernLLM\n * uses for OpenAI/Groq. Geminis shape differs on nearly every axis: a\n * `contents` array instead of `messages`, a separate `systemInstruction`\n * field instead of a `system` role message, `generationConfig` instead of\n * top-level `temperature`/`max_tokens`, and native JSON Schema support via\n * `responseMimeType: 'application/json'` + `responseSchema` (so `jsonSchema`\n * is provider-enforced here, unlike the Anthropic adapters prompt-embedding\n * fallback). `reasoning_effort` has no equivalent. Geminis thinking models\n * use a token budget, not an effort tier, so its dropped, same as Anthropic.\n */\nexport function fromGemini(geminiClient: GeminiClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n const wantsJson = Boolean(params.response_format);\n const generationConfig: NonNullable<\n Parameters<GeminiClient['generateContent']>[0]['generationConfig']\n > = {\n temperature: params.temperature,\n maxOutputTokens: params.max_tokens,\n };\n\n if (wantsJson) {\n generationConfig.responseMimeType = 'application/json';\n }\n if (params.response_format?.type === 'json_schema') {\n generationConfig.responseSchema = params.response_format.json_schema.schema;\n }\n\n const response = await geminiClient.generateContent(\n {\n model: params.model,\n contents: userMessages.map((m) => ({\n role: 'user' as const,\n parts: [{ text: m.content }],\n })),\n systemInstruction: systemMessage\n ? { parts: [{ text: systemMessage.content }] }\n : undefined,\n generationConfig,\n },\n options,\n );\n\n const text =\n response.candidates?.[0]?.content?.parts?.map((p) => p.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usageMetadata?.promptTokenCount,\n completion_tokens: response.usageMetadata?.candidatesTokenCount,\n total_tokens: response.usageMetadata?.totalTokenCount,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Minimal structural type matching AWS Bedrocks Converse API. This is\n * intentionally NOT `BedrockRuntimeClient` itself, the AWS SDK v3 client\n * exposes `.send(command)`, not a direct `.converse()` method, and pulling\n * in `@aws-sdk/client-bedrock-runtime` as a dependency just for its types\n * isn't worth it for a structural adapter. Wrap your client, e.g:\n *\n * ```ts\n * import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';\n * const client = new BedrockRuntimeClient({ region: 'us-east-1' });\n * const converseClient = {\n * converse: (params, options) =>\n * client.send(new ConverseCommand(params), { abortSignal: options.signal }),\n * };\n * ```\n */\nexport interface BedrockConverseClient {\n converse(\n params: {\n modelId: string;\n messages: Array<{ role: 'user'; content: Array<{ text: string }> }>;\n system?: Array<{ text: string }>;\n inferenceConfig?: { temperature?: number; maxTokens?: number };\n },\n options: { signal: AbortSignal },\n ): Promise<{\n output?: { message?: { content?: Array<{ text?: string }> } };\n usage?: { inputTokens?: number; outputTokens?: number; totalTokens?: number };\n }>;\n}\n\n/**\n * Wraps a Bedrock Converse-API client so it satisfies the `LLMClient`\n * interface VernLLM uses for OpenAI/Groq. The Converse API is unified\n * across Bedrocks model families (Anthropic, Titan, Llama, Mistral, etc.),\n * so unlike raw per-model Bedrock invocation, this one adapter works\n * regardless of which underlying model `modelId` points at, as long as\n * that model supports Converse (most current-generation ones do)\n *\n * Theres no uniform native JSON Schema enforcement across families here\n * (some support it via forced tool-use, which varies per model), so\n * `jsonSchema`/`jsonMode` are emulated via a system-prompt instruction, same\n * approach as the Anthropic adapter. `reasoning_effort` has no Converse\n * equivalent and is dropped\n */\nexport function fromBedrock(bedrockClient: BedrockConverseClient): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const systemMessage = params.messages.find((m) => m.role === 'system');\n const userMessages = params.messages.filter((m) => m.role === 'user');\n\n let jsonInstruction: string | undefined;\n if (params.response_format?.type === 'json_schema') {\n const { name, schema } = params.response_format.json_schema;\n jsonInstruction = `Respond with valid JSON only, no prose or markdown fences. The JSON must conform to this schema (name: \"${name}\"):\\n${JSON.stringify(schema)}`;\n } else if (params.response_format?.type === 'json_object') {\n jsonInstruction = 'Respond with valid JSON only, no prose or markdown fences.';\n }\n\n const systemParts = [systemMessage?.content, jsonInstruction].filter((s): s is string =>\n Boolean(s),\n );\n\n const response = await bedrockClient.converse(\n {\n modelId: params.model,\n messages: userMessages.map((m) => ({\n role: 'user' as const,\n content: [{ text: m.content }],\n })),\n system: systemParts.length ? systemParts.map((text) => ({ text })) : undefined,\n inferenceConfig: {\n temperature: params.temperature,\n maxTokens: params.max_tokens,\n },\n },\n options,\n );\n\n const text = response.output?.message?.content?.map((c) => c.text ?? '').join('') ?? '';\n\n return {\n choices: [{ message: { content: text } }],\n usage: {\n prompt_tokens: response.usage?.inputTokens,\n completion_tokens: response.usage?.outputTokens,\n total_tokens: response.usage?.totalTokens,\n },\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/** The chat-completion-shaped request VernLLM builds internally */\ntype ChatRequest = Parameters<LLMClient['chat']['completions']['create']>[0];\n\nexport interface FetchAdapterConfig {\n /** Endpoint URL, or a function of the request in case it depends on model/params */\n url: string | ((params: ChatRequest) => string);\n /** Static headers, or a function (sync or async) for things like refreshed auth tokens */\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n /** HTTP method. Default 'POST' */\n method?: string;\n /** Maps VernLLMs internal chat-completion request into the providers raw request body */\n mapRequest: (params: ChatRequest) => unknown;\n /**\n * Maps the providers raw JSON response into `{ content, usage? }`\n * `content` is the assistants text (JSON string when JSON mode was requested)\n */\n mapResponse: (json: unknown) => {\n content: string;\n usage?: { promptTokens?: number; completionTokens?: number; totalTokens?: number };\n };\n}\n\n/**\n * A fetch-based escape hatch for providers with no SDK, or where pulling one\n * in isnt worth it. You supply the URL, headers, and two small mapping\n * functions; this handles the HTTP call and slots the result into the same\n * `LLMClient` shape every other adapter produces, so retries, timeouts,\n * the circuit breaker, and JSON/schema handling all still work unmodified\n *\n * Non-2xx responses throw an error with `.status` set to the HTTP status\n * code, so VernLLMs `nonRetryableStatus` handling (e.g. failing fast on\n * 401/403) applies here too\n */\nexport function fromFetch(config: FetchAdapterConfig): LLMClient {\n return {\n chat: {\n completions: {\n async create(params, options) {\n const url = typeof config.url === 'function' ? config.url(params) : config.url;\n const headers =\n typeof config.headers === 'function' ? await config.headers() : config.headers;\n\n const res = await fetch(url, {\n method: config.method ?? 'POST',\n headers: { 'Content-Type': 'application/json', ...headers },\n body: JSON.stringify(config.mapRequest(params)),\n signal: options.signal,\n });\n\n if (!res.ok) {\n const body = await res.text().catch(() => '');\n const err = new Error(\n `Fetch adapter request failed (${res.status}): ${body.slice(0, 500)}`,\n ) as Error & { status?: number };\n err.status = res.status;\n throw err;\n }\n\n const json = await res.json();\n const { content, usage } = config.mapResponse(json);\n\n return {\n choices: [{ message: { content } }],\n usage: usage\n ? {\n prompt_tokens: usage.promptTokens,\n completion_tokens: usage.completionTokens,\n total_tokens: usage.totalTokens,\n }\n : undefined,\n };\n },\n },\n },\n };\n}\n","import type { LLMClient } from '../types.js';\n\n/**\n * Passthrough adapter for any SDK/client whose `chat.completions.create`\n * already matches the OpenAI wire format 1:1 : this covers most hosted\n * inference providers, since \"OpenAI-compatible\" is a de facto standard for\n * chat completion APIs. No transformation happens here, this exists purely\n * so call sites read clearly (`fromMistral(client)` vs handing a Mistral\n * client to something typed for OpenAI) and so a real transformation could\n * be added later, per-provider, without a breaking change.\n *\n * Not every SDKs own TypeScript types line up exactly with `LLMClient`\n * (extra fields, stricter unions, etc.), so this takes `unknown` and casts:\n * the actual compatibility contract is the JSON each provider sends and\n * receives over the wire, not the SDKs TS types.\n */\nexport function fromOpenAICompatible(client: unknown): LLMClient {\n return client as LLMClient;\n}\n\n/** Groqs SDK matches the OpenAI wire format */\nexport const fromGroq = fromOpenAICompatible;\n\n/** Mistrals `chat.completions`-shaped client (or their OpenAI-compat endpoint) */\nexport const fromMistral = fromOpenAICompatible;\n\n/** DeepSeeks API is OpenAI-compatible */\nexport const fromDeepSeek = fromOpenAICompatible;\n\n/** Cerebras inference API is OpenAI-compatible */\nexport const fromCerebras = fromOpenAICompatible;\n\n/** Together AIs API is OpenAI-compatible */\nexport const fromTogether = fromOpenAICompatible;\n\n/** Fireworks AIs API is OpenAI-compatible */\nexport const fromFireworks = fromOpenAICompatible;\n\n/**\n * Ollama exposes an OpenAI-compatible endpoint at `/v1/chat/completions`\n * (as opposed to its native `/api/chat` format, which differs). Point an\n * OpenAI SDK instances `baseURL` at your Ollama server and pass it here:\n * this does not talk to Ollamas native API directly.\n */\nexport const fromOllama = fromOpenAICompatible;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "vern-llm",
3
- "version": "0.2.1",
4
- "description": "Retry + timeout + cache wrapper for OpenAI-compatible LLM chat completion calls (OpenAI, Groq, etc).",
3
+ "version": "0.3.0",
4
+ "description": "Retry, timeout, caching, and circuit breaker for OpenAI-compatible LLM calls, Groq, Anthropic, Gemini, Bedrock, and more.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -39,7 +39,8 @@
39
39
  },
40
40
  "repository": {
41
41
  "type": "git",
42
- "url": "https://github.com/LakBud/vernLLM"
42
+ "url": "https://github.com/LakBud/vernLLM",
43
+ "directory": "packages/vern-llm"
43
44
  },
44
45
  "license": "MIT",
45
46
  "publishConfig": {