@anvaka/vue-llm 0.3.0 → 0.3.2

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
@@ -7,6 +7,7 @@ Browser-only LLM client + Vue 3 plugin, provider adapters, and lightweight compo
7
7
  - LocalStorage-based config store (custom storage adapter supported)
8
8
  - Streaming + promise requests via `llmClient.stream()`
9
9
  - Normalized usage + USD cost on every response (override built-in rates per app or per model)
10
+ - Automatic prompt caching for Claude (Anthropic + Bedrock) — caches the system+tools prefix, plus the rolling conversation in agent loops; opt out with `promptCache: false`
10
11
  - Vue plugin for dependency injection
11
12
  - `useLLM()` composable with reactive streaming state
12
13
  - Ready-to-use components: `ProviderSelector`, `LLMConfigModal`, `StoredKeysManager`
@@ -157,6 +158,22 @@ const { messages, usage, cost } = await client.runAgentLoop({
157
158
  })
158
159
  ```
159
160
 
161
+ ### Prompt caching (Claude)
162
+
163
+ Anthropic and Bedrock requests are sent with `cache_control` markers by default, so repeated prefixes are read from cache instead of re-billed at full input price. Two prefixes are tagged:
164
+
165
+ - **System + tools** — on every Claude request. The static prefix recurs identically across calls and runs within the cache TTL (~5 min).
166
+ - **Rolling conversation** — added by `runAgentLoop` only, since it re-sends the whole growing transcript each turn. Iteration *N* reads iterations `1..N-1` from cache and only writes the new turn.
167
+
168
+ Cache hits and writes surface in the usual `usage` fields (`cachedInputTokens`, `cacheCreationInputTokens`) and are priced via the `cachedInput` / `cacheCreation` rate keys. No setup is required — prefixes under the model's minimum cacheable length simply aren't cached (no error).
169
+
170
+ Disable it per call or per provider config:
171
+
172
+ ```js
173
+ client.stream({ messages: [...], promptCache: false }) // single request
174
+ configStore.saveConfig('claude', { ...cfg, promptCache: false }) // all requests for this provider
175
+ ```
176
+
160
177
  ### Overriding rates
161
178
 
162
179
  Built-in rates are sourced from public pricing pages and *will* drift. Three ways to override, in priority order:
@@ -120,16 +120,16 @@ class P {
120
120
  temperature: n,
121
121
  model: o,
122
122
  maxTokens: l,
123
- enableThinking: p
123
+ enableThinking: d
124
124
  } = {}) {
125
125
  if (await this.ensureInitialized(), !this.provider.hasCapability("tools"))
126
126
  throw new Error("Configured provider does not support tools");
127
127
  const c = t.slice();
128
- let d = 0, m = null;
128
+ let p = 0, m = null;
129
129
  const f = { inputTokens: 0, outputTokens: 0, totalTokens: 0, cachedInputTokens: 0, cacheCreationInputTokens: 0, reasoningTokens: 0 };
130
130
  let b = !1;
131
- for (; d < e; ) {
132
- d++, r && r({ type: "iter-start", iter: d });
131
+ for (; p < e; ) {
132
+ p++, r && r({ type: "iter-start", iter: p });
133
133
  const x = this.validateCapabilities({
134
134
  messages: c,
135
135
  tools: s,
@@ -137,7 +137,10 @@ class P {
137
137
  model: o || this.config.model,
138
138
  temperature: n ?? this.config.temperature,
139
139
  maxTokens: l ?? this.config.maxTokens ?? 4096,
140
- enableThinking: p ?? !1,
140
+ enableThinking: d ?? !1,
141
+ // Re-sends the whole growing conversation each turn, so cache the
142
+ // rolling transcript prefix (Anthropic-family providers honor this).
143
+ cacheTranscript: !0,
141
144
  requestId: this.generateRequestId()
142
145
  }), h = await this.provider.streamRequest(c, x, (a) => {
143
146
  a.content && r && r({ type: "text-delta", text: a.content }), a.toolCallDelta && r && r({ type: "tool-call-delta", delta: a.toolCallDelta });
@@ -171,7 +174,7 @@ class P {
171
174
  }
172
175
  m || (m = "max-iters");
173
176
  const g = b ? f : null, v = this.costFor(g, { provider: this.config.provider, model: o || this.config.model });
174
- return r && r({ type: "stop", reason: m, iterations: d, usage: g, cost: v }), { messages: c, iterations: d, stopReason: m, usage: g, cost: v };
177
+ return r && r({ type: "stop", reason: m, iterations: p, usage: g, cost: v }), { messages: c, iterations: p, stopReason: m, usage: g, cost: v };
175
178
  }
176
179
  generateRequestId() {
177
180
  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -256,8 +259,8 @@ class $ {
256
259
  };
257
260
  try {
258
261
  if (this.targetNode) {
259
- const p = r({ ...n, stream: !0 });
260
- return await this._streamIntoTarget(e, p, s, i);
262
+ const d = r({ ...n, stream: !0 });
263
+ return await this._streamIntoTarget(e, d, s, i);
261
264
  }
262
265
  const l = r({ ...n, stream: !1 });
263
266
  return await this._promiseResponse(e, l, s, i);
@@ -275,8 +278,8 @@ class $ {
275
278
  throw new Error("Provider presets require a configured ConfigStore instance");
276
279
  const s = (e = (r = this.client).getConfigByName) == null ? void 0 : e.call(r, t);
277
280
  if (!s) {
278
- const l = (((o = (n = this.client.configStore).getEnabledConfigs) == null ? void 0 : o.call(n)) || []).map((c) => c.name || `${c.provider} (${c.baseUrl})`).filter(Boolean), p = l.length ? ` Available presets: ${l.join(", ")}` : " No configured providers found.";
279
- throw new Error(`Provider preset '${t}' not found.${p}`);
281
+ const l = (((o = (n = this.client.configStore).getEnabledConfigs) == null ? void 0 : o.call(n)) || []).map((c) => c.name || `${c.provider} (${c.baseUrl})`).filter(Boolean), d = l.length ? ` Available presets: ${l.join(", ")}` : " No configured providers found.";
282
+ throw new Error(`Provider preset '${t}' not found.${d}`);
280
283
  }
281
284
  const i = U(s.provider, s);
282
285
  return await i.initialize(), { provider: i, config: s, cleanup: () => {
@@ -298,9 +301,9 @@ class $ {
298
301
  }
299
302
  }
300
303
  async _streamIntoTarget(t, s, i, r) {
301
- var d, m, f, b, g, v, x, h, q, y, C, w, k, a, R, T;
304
+ var p, m, f, b, g, v, x, h, q, y, C, w, k, a, R, T;
302
305
  let e, n;
303
- typeof this.targetNode == "string" ? (n = this.targetNode, e = this.contextNode.createChild(n), (d = e.setSelected) == null || d.call(e)) : (e = this.targetNode, n = ((m = e.getText) == null ? void 0 : m.call(e)) || ""), this._applyAttributes(e);
306
+ typeof this.targetNode == "string" ? (n = this.targetNode, e = this.contextNode.createChild(n), (p = e.setSelected) == null || p.call(e)) : (e = this.targetNode, n = ((m = e.getText) == null ? void 0 : m.call(e)) || ""), this._applyAttributes(e);
304
307
  const o = r == null ? void 0 : r.name;
305
308
  o && e.addTag && e.addTag("provider", o), (f = e.addLLMLog) == null || f.call(e, this.operationName || "streaming-request", {
306
309
  provider: r == null ? void 0 : r.provider,
@@ -308,26 +311,26 @@ class $ {
308
311
  messages: t,
309
312
  options: s
310
313
  }), (b = e.setStreamingState) == null || b.call(e, !0), e._activeRequestId = s.requestId;
311
- let l = "", p = null, c = null;
314
+ let l = "", d = null, c = null;
312
315
  try {
313
316
  await i.streamRequest(t, s, (_) => {
314
317
  var I, S;
315
- _.content && (l = _.fullContent, (I = e.setText) == null || I.call(e, l)), _.fullUsage && (c = _.fullUsage), _.finishReason && (p = _.finishReason), _.done && ((S = e.setStreamingState) == null || S.call(e, !1), e._activeRequestId = null);
318
+ _.content && (l = _.fullContent, (I = e.setText) == null || I.call(e, l)), _.fullUsage && (c = _.fullUsage), _.finishReason && (d = _.finishReason), _.done && ((S = e.setStreamingState) == null || S.call(e, !1), e._activeRequestId = null);
316
319
  });
317
320
  const u = (v = (g = this.client).costFor) == null ? void 0 : v.call(g, c, { provider: r == null ? void 0 : r.provider, model: s.model });
318
- return (x = e.addLLMLog) == null || x.call(e, (this.operationName || "streaming-request") + "-response", { requestId: s.requestId }, { content: l, usage: c, cost: u }), p === "length" ? ((h = e.setAttribute) == null || h.call(e, "_truncated", !0), (q = e.addLog) == null || q.call(e, "Response truncated. Increase Max Tokens.", "warn", { finish_reason: "length" })) : (y = e.setAttribute) == null || y.call(e, "_truncated", !1), await ((C = e.persistNow) == null ? void 0 : C.call(e)), e;
321
+ return (x = e.addLLMLog) == null || x.call(e, (this.operationName || "streaming-request") + "-response", { requestId: s.requestId }, { content: l, usage: c, cost: u }), d === "length" ? ((h = e.setAttribute) == null || h.call(e, "_truncated", !0), (q = e.addLog) == null || q.call(e, "Response truncated. Increase Max Tokens.", "warn", { finish_reason: "length" })) : (y = e.setAttribute) == null || y.call(e, "_truncated", !1), await ((C = e.persistNow) == null ? void 0 : C.call(e)), e;
319
322
  } catch (u) {
320
323
  throw (w = e.addLLMLog) == null || w.call(e, (this.operationName || "streaming-request") + "-error", { requestId: s.requestId }, null, u), (k = e.setStreamingState) == null || k.call(e, !1), e._activeRequestId = null, (a = e.setText) == null || a.call(e, `Error: ${u.message}`), (R = e.setAttribute) == null || R.call(e, "_truncated", !1), await ((T = e.persistNow) == null ? void 0 : T.call(e)), u;
321
324
  }
322
325
  }
323
326
  async _promiseResponse(t, s, i, r) {
324
- var p, c, d, m, f, b, g;
327
+ var d, c, p, m, f, b, g;
325
328
  const e = i.prepareRequest(t, s), n = await i.makeRequest(e), o = i.processResponse(n);
326
- if ((o.finishReason || ((c = (p = n == null ? void 0 : n.choices) == null ? void 0 : p[0]) == null ? void 0 : c.finish_reason) || null) === "length")
329
+ if ((o.finishReason || ((c = (d = n == null ? void 0 : n.choices) == null ? void 0 : d[0]) == null ? void 0 : c.finish_reason) || null) === "length")
327
330
  try {
328
331
  this.contextNode.setAttribute("_truncated", !0);
329
332
  } catch (v) {
330
- (m = (d = this.client.logger) == null ? void 0 : d.warn) == null || m.call(d, "Unable to mark node as truncated", { error: v });
333
+ (m = (p = this.client.logger) == null ? void 0 : p.warn) == null || m.call(p, "Unable to mark node as truncated", { error: v });
331
334
  }
332
335
  else
333
336
  try {
@@ -1,69 +1,79 @@
1
1
  const t = {
2
2
  openai: {
3
- "gpt-5": { input: 0.625, output: 5, cachedInput: 0.125 },
4
- "gpt-5-mini": { input: 0.25, output: 2, cachedInput: 0.025 },
5
- "gpt-5-nano": { input: 0.05, output: 0.4, cachedInput: 5e-3 },
3
+ // Flagship + workhorse (April–May 2026 lineup)
4
+ "gpt-5.5-pro": { input: 5, output: 30, cachedInput: 0.5 },
5
+ "gpt-5.5": { input: 5, output: 30, cachedInput: 0.5 },
6
+ "gpt-5.4-pro": { input: 30, output: 180 },
6
7
  "gpt-5.4": { input: 2.5, output: 15, cachedInput: 0.25 },
7
8
  "gpt-5.4-mini": { input: 0.75, output: 4.5, cachedInput: 0.075 },
8
9
  "gpt-5.4-nano": { input: 0.2, output: 1.25, cachedInput: 0.02 },
9
- "gpt-4o": { input: 2.5, output: 10, cachedInput: 1.25 },
10
- "gpt-4o-mini": { input: 0.15, output: 0.6, cachedInput: 0.075 },
11
- "gpt-4.1": { input: 2, output: 8, cachedInput: 0.5 },
12
- "gpt-4.1-mini": { input: 0.2, output: 0.8, cachedInput: 0.1 },
13
- "gpt-4.1-nano": { input: 0.05, output: 0.2, cachedInput: 0.025 },
14
- "gpt-4-turbo": { input: 10, output: 30 },
15
- "gpt-4": { input: 30, output: 60 },
16
- "gpt-3.5-turbo": { input: 0.5, output: 1 },
17
- o1: { input: 15, output: 60, cachedInput: 7.5 },
18
- "o1-mini": { input: 0.55, output: 2.2, cachedInput: 0.55 },
10
+ // GPT-5.x family still widely deployed
11
+ "gpt-5.3": { input: 1.75, output: 14, cachedInput: 0.175 },
12
+ "gpt-5.2-pro": { input: 10.5, output: 84 },
13
+ "gpt-5.2": { input: 1.75, output: 14, cachedInput: 0.175 },
14
+ "gpt-5.1": { input: 0.625, output: 5, cachedInput: 0.125 },
15
+ "gpt-5-chat": { input: 1.25, output: 10, cachedInput: 0.125 },
16
+ "gpt-5": { input: 0.625, output: 5, cachedInput: 0.125 },
17
+ "gpt-5-mini": { input: 0.25, output: 2, cachedInput: 0.025 },
18
+ "gpt-5-nano": { input: 0.05, output: 0.4, cachedInput: 5e-3 },
19
+ // Reasoning models
20
+ "o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 },
19
21
  o3: { input: 2, output: 8, cachedInput: 0.5 },
20
22
  "o3-mini": { input: 1.1, output: 4.4, cachedInput: 0.55 },
21
- "o4-mini": { input: 1.1, output: 4.4, cachedInput: 0.275 }
23
+ // Codex variants
24
+ "gpt-5.1-codex": { input: 1.25, output: 10, cachedInput: 0.125 },
25
+ "gpt-5.1-codex-mini": { input: 0.25, output: 2, cachedInput: 0.025 },
26
+ "gpt-5-codex": { input: 1.25, output: 10, cachedInput: 0.125 }
22
27
  },
23
28
  anthropic: {
29
+ "claude-opus-4-8": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
24
30
  "claude-opus-4-7": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
25
31
  "claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
26
32
  "claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
27
33
  "claude-sonnet-4-5": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
28
- "claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 },
29
- "claude-3-5-sonnet": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
30
- "claude-3-5-haiku": { input: 0.8, output: 4, cachedInput: 0.08, cacheCreation: 1 },
31
- "claude-3-opus": { input: 15, output: 75 },
32
- "claude-3-haiku": { input: 0.25, output: 1.25 }
34
+ "claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
33
35
  },
34
36
  // Bedrock charges the same per-token rates as Anthropic direct for the
35
37
  // Claude inference profiles; the model IDs differ (us.anthropic.* prefix).
36
38
  bedrock: {
39
+ "us.anthropic.claude-opus-4-8": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
37
40
  "us.anthropic.claude-opus-4-7": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
38
41
  "us.anthropic.claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
39
42
  "us.anthropic.claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
40
43
  "us.anthropic.claude-sonnet-4-5": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
41
- "us.anthropic.claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 },
42
- "us.anthropic.claude-opus-4-1": { input: 15, output: 75 }
44
+ "us.anthropic.claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
43
45
  },
44
46
  gemini: {
47
+ // Gemini 3.x — current lineup (May 2026)
48
+ "gemini-3.5-flash": { input: 1.5, output: 9, cachedInput: 0.15 },
49
+ "gemini-3.1-pro-preview": { input: 2, output: 12, cachedInput: 0.2 },
50
+ "gemini-3.1-flash-lite-preview": { input: 0.25, output: 1.5, cachedInput: 0.025 },
51
+ "gemini-3-pro-preview": { input: 2, output: 12 },
52
+ "gemini-3-flash-preview": { input: 0.5, output: 3, cachedInput: 0.05 },
53
+ // Gemini 2.5 — still in active use
45
54
  "gemini-2.5-pro": { input: 1, output: 10, cachedInput: 0.125 },
46
55
  "gemini-2.5-flash": { input: 0.3, output: 2.5, cachedInput: 0.03 },
47
- "gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cachedInput: 0.01 },
48
- "gemini-2.0-flash": { input: 0.1, output: 0.4, cachedInput: 0.025 },
49
- "gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
50
- "gemini-1.5-pro": { input: 1.25, output: 5 },
51
- "gemini-1.5-flash": { input: 0.075, output: 0.3 }
56
+ "gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cachedInput: 0.01 }
52
57
  },
53
58
  grok: {
54
- "grok-4": { input: 3, output: 15 },
59
+ // Grok 4.3 (April 30, 2026 release) — current primary, 1M context
60
+ "grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
61
+ "grok-4.20-0309-reasoning": { input: 1.25, output: 2.5, cachedInput: 0.2 },
62
+ "grok-4.20-0309-non-reasoning": { input: 1.25, output: 2.5, cachedInput: 0.2 },
63
+ "grok-4.20-multi-agent-0309": { input: 1.25, output: 2.5, cachedInput: 0.2 },
64
+ "grok-4.20": { input: 1.25, output: 2.5, cachedInput: 0.2 },
65
+ "grok-build-0.1": { input: 1, output: 2 },
66
+ // Cheap fast tier
55
67
  "grok-4-fast": { input: 0.2, output: 0.5 },
56
- "grok-3": { input: 3, output: 15 },
57
- "grok-3-fast": { input: 5, output: 25 },
58
- "grok-3-mini": { input: 0.25, output: 0.5 },
59
- "grok-code-fast-1": { input: 0.2, output: 1.5 },
60
- "grok-2": { input: 2, output: 10 }
68
+ "grok-code-fast-1": { input: 0.2, output: 1.5 }
61
69
  },
62
70
  deepseek: {
63
- "deepseek-chat": { input: 0.21, output: 0.79, cachedInput: 0.13 },
64
- "deepseek-reasoner": { input: 0.55, output: 2 },
71
+ "deepseek-v4-pro": { input: 0.435, output: 0.87, cachedInput: 36e-4 },
65
72
  "deepseek-v4-flash": { input: 0.1, output: 0.2, cachedInput: 28e-4 },
66
- "deepseek-v4-pro": { input: 0.435, output: 0.87, cachedInput: 36e-4 }
73
+ "deepseek-v3.2": { input: 0.252, output: 0.378, cachedInput: 0.0252 },
74
+ "deepseek-v3.2-thinking": { input: 0.252, output: 0.378, cachedInput: 0.0252 },
75
+ "deepseek-chat": { input: 0.014, output: 0.028 },
76
+ "deepseek-reasoner": { input: 0.55, output: 2 }
67
77
  },
68
78
  // OpenRouter prices vary per upstream model and include their own margin —
69
79
  // ship empty; callers can either register their own rates or use
@@ -1,77 +1,86 @@
1
- import { BaseProvider as p } from "./BaseProvider.js";
2
- class y extends p {
1
+ import { BaseProvider as g } from "./BaseProvider.js";
2
+ class b extends g {
3
3
  async detectCapabilities() {
4
- var e, t, n, o;
5
- ((e = this.config.model) != null && e.includes("claude-3") || (t = this.config.model) != null && t.includes("claude-sonnet") || (n = this.config.model) != null && n.includes("claude-opus") || (o = this.config.model) != null && o.includes("claude-haiku")) && (this.capabilities.add("vision"), this.capabilities.add("tools"));
6
- }
7
- prepareRequest(e, t) {
8
- const n = _(e), o = t.model || this.config.model || "claude-3-sonnet-20240229", i = {
9
- model: o,
10
- max_tokens: t.maxTokens || 1e3,
11
- messages: n,
12
- stream: t.stream || !1
4
+ var t, e, o, s;
5
+ ((t = this.config.model) != null && t.includes("claude-3") || (e = this.config.model) != null && e.includes("claude-sonnet") || (o = this.config.model) != null && o.includes("claude-opus") || (s = this.config.model) != null && s.includes("claude-haiku")) && (this.capabilities.add("vision"), this.capabilities.add("tools"));
6
+ }
7
+ prepareRequest(t, e) {
8
+ const o = m(t), s = e.model || this.config.model || "claude-3-sonnet-20240229", r = {
9
+ model: s,
10
+ max_tokens: e.maxTokens || 1e3,
11
+ messages: o,
12
+ stream: e.stream || !1
13
13
  };
14
- o.includes("claude-opus-4-7") || (i.temperature = t.temperature ?? 0.7);
15
- const c = e.find((r) => r.role === "system");
16
- return c && (i.system = c.content), t.tools && this.capabilities.has("tools") && (i.tools = g(t.tools), t.tool_choice && (i.tool_choice = t.tool_choice)), i;
17
- }
18
- processMessages(e, t) {
19
- return t.images && this.capabilities.has("vision") ? this.addImagesToMessages(e, t.images) : e;
20
- }
21
- addImagesToMessages(e, t) {
22
- const n = e[e.length - 1];
23
- if (n && n.role === "user") {
24
- const o = [{ type: "text", text: n.content }];
25
- t.forEach((i) => {
26
- o.push({
14
+ _(s) || (r.temperature = e.temperature ?? 0.7);
15
+ const c = t.find((i) => i.role === "system");
16
+ return c && (r.system = c.content), e.tools && this.capabilities.has("tools") && (r.tools = y(e.tools), e.tool_choice && (r.tool_choice = e.tool_choice)), this.promptCachingEnabled(e) && k(r, e), r;
17
+ }
18
+ // Prompt caching is on by default for the Claude/Anthropic-family path
19
+ // (Bedrock inherits this method) because it's strictly cheaper and degrades
20
+ // gracefully — a prefix under the model's minimum cacheable length is simply
21
+ // not cached, with no error. Disable per-call with options.promptCache:false
22
+ // or per-config with config.promptCache:false.
23
+ promptCachingEnabled(t) {
24
+ var e;
25
+ return (t == null ? void 0 : t.promptCache) !== !1 && ((e = this.config) == null ? void 0 : e.promptCache) !== !1;
26
+ }
27
+ processMessages(t, e) {
28
+ return e.images && this.capabilities.has("vision") ? this.addImagesToMessages(t, e.images) : t;
29
+ }
30
+ addImagesToMessages(t, e) {
31
+ const o = t[t.length - 1];
32
+ if (o && o.role === "user") {
33
+ const s = [{ type: "text", text: o.content }];
34
+ e.forEach((r) => {
35
+ s.push({
27
36
  type: "image",
28
- source: { type: "base64", media_type: "image/jpeg", data: typeof i == "string" ? i : i.data }
37
+ source: { type: "base64", media_type: "image/jpeg", data: typeof r == "string" ? r : r.data }
29
38
  });
30
- }), n.content = o;
39
+ }), o.content = s;
31
40
  }
32
- return e;
41
+ return t;
33
42
  }
34
- processResponse(e) {
35
- const t = l(e.stop_reason), n = e.content || [], o = n.find((r) => r.type === "text"), c = n.filter((r) => r.type === "tool_use").map((r) => ({ id: r.id, name: r.name, args: r.input || {} }));
43
+ processResponse(t) {
44
+ const e = h(t.stop_reason), o = t.content || [], s = o.find((i) => i.type === "text"), c = o.filter((i) => i.type === "tool_use").map((i) => ({ id: i.id, name: i.name, args: i.input || {} }));
36
45
  return {
37
- content: (o == null ? void 0 : o.text) || "",
38
- usage: d(e.usage),
39
- finishReason: t,
46
+ content: (s == null ? void 0 : s.text) || "",
47
+ usage: f(t.usage),
48
+ finishReason: e,
40
49
  toolCalls: c
41
50
  };
42
51
  }
43
- parseStreamingLine(e) {
44
- if (!e.startsWith("data: ")) return null;
45
- const t = e.slice(6).trim();
46
- if (t === "[DONE]") return { done: !0 };
52
+ parseStreamingLine(t) {
53
+ if (!t.startsWith("data: ")) return null;
54
+ const e = t.slice(6).trim();
55
+ if (e === "[DONE]") return { done: !0 };
47
56
  try {
48
- return JSON.parse(t);
57
+ return JSON.parse(e);
49
58
  } catch {
50
59
  return null;
51
60
  }
52
61
  }
53
- extractStreamingContent(e) {
54
- var t, n, o, i, c, r, h, f;
55
- if (e.done) return { done: !0 };
56
- if (e.type === "error") {
57
- const a = ((t = e.error) == null ? void 0 : t.type) || "anthropic_error", u = new Error(((n = e.error) == null ? void 0 : n.message) || "Anthropic streaming error");
58
- throw u.code = a, e.request_id && (u.requestId = e.request_id), u;
62
+ extractStreamingContent(t) {
63
+ var e, o, s, r, c, i, d, p;
64
+ if (t.done) return { done: !0 };
65
+ if (t.type === "error") {
66
+ const a = ((e = t.error) == null ? void 0 : e.type) || "anthropic_error", u = new Error(((o = t.error) == null ? void 0 : o.message) || "Anthropic streaming error");
67
+ throw u.code = a, t.request_id && (u.requestId = t.request_id), u;
59
68
  }
60
- if (e.type === "message_start")
61
- return { content: "", thinking: "", done: !1, usage: d((o = e.message) == null ? void 0 : o.usage), finishReason: null };
62
- if (e.type === "content_block_start") {
63
- const a = e.content_block;
69
+ if (t.type === "message_start")
70
+ return { content: "", thinking: "", done: !1, usage: f((s = t.message) == null ? void 0 : s.usage), finishReason: null };
71
+ if (t.type === "content_block_start") {
72
+ const a = t.content_block;
64
73
  return (a == null ? void 0 : a.type) === "tool_use" ? {
65
74
  content: "",
66
75
  done: !1,
67
- toolCallDelta: { index: e.index, id: a.id, name: a.name, argsTextDelta: "" }
76
+ toolCallDelta: { index: t.index, id: a.id, name: a.name, argsTextDelta: "" }
68
77
  } : null;
69
78
  }
70
- return e.type === "content_block_delta" ? ((i = e.delta) == null ? void 0 : i.type) === "text_delta" ? { content: ((c = e.delta) == null ? void 0 : c.text) || "", thinking: "", done: !1, usage: null, finishReason: null } : ((r = e.delta) == null ? void 0 : r.type) === "input_json_delta" ? {
79
+ return t.type === "content_block_delta" ? ((r = t.delta) == null ? void 0 : r.type) === "text_delta" ? { content: ((c = t.delta) == null ? void 0 : c.text) || "", thinking: "", done: !1, usage: null, finishReason: null } : ((i = t.delta) == null ? void 0 : i.type) === "input_json_delta" ? {
71
80
  content: "",
72
81
  done: !1,
73
- toolCallDelta: { index: e.index, argsTextDelta: ((h = e.delta) == null ? void 0 : h.partial_json) || "" }
74
- } : null : e.type === "message_delta" ? { content: "", thinking: "", done: !1, usage: d(e.usage), finishReason: l((f = e.delta) == null ? void 0 : f.stop_reason) } : e.type === "message_stop" ? { content: "", thinking: "", done: !0, usage: null, finishReason: l(e.stop_reason) } : null;
82
+ toolCallDelta: { index: t.index, argsTextDelta: ((d = t.delta) == null ? void 0 : d.partial_json) || "" }
83
+ } : null : t.type === "message_delta" ? { content: "", thinking: "", done: !1, usage: f(t.usage), finishReason: h((p = t.delta) == null ? void 0 : p.stop_reason) } : t.type === "message_stop" ? { content: "", thinking: "", done: !0, usage: null, finishReason: h(t.stop_reason) } : null;
75
84
  }
76
85
  getApiPath() {
77
86
  return "/v1/messages";
@@ -86,67 +95,107 @@ class y extends p {
86
95
  return this.config.apiKey;
87
96
  }
88
97
  buildHeaders() {
89
- const e = super.buildHeaders();
90
- return this.requiresAuth() && (e["anthropic-version"] = "2023-06-01", e["anthropic-dangerous-direct-browser-access"] = "true"), e;
98
+ const t = super.buildHeaders();
99
+ return this.requiresAuth() && (t["anthropic-version"] = "2023-06-01", t["anthropic-dangerous-direct-browser-access"] = "true"), t;
91
100
  }
92
101
  getModelsEndpoint() {
93
102
  return `${this.config.baseUrl}/v1/models`;
94
103
  }
95
- parseModelsResponse(e) {
96
- var t;
97
- return ((t = e.data) == null ? void 0 : t.map((n) => n.id)) || [];
104
+ parseModelsResponse(t) {
105
+ var e;
106
+ return ((e = t.data) == null ? void 0 : e.map((o) => o.id)) || [];
98
107
  }
99
108
  }
100
- function l(s) {
101
- if (!s) return null;
102
- const e = String(s).toLowerCase();
103
- return e === "max_tokens" ? "length" : e;
109
+ function _(n) {
110
+ return /claude-opus-4-[789]/.test(n);
111
+ }
112
+ function h(n) {
113
+ if (!n) return null;
114
+ const t = String(n).toLowerCase();
115
+ return t === "max_tokens" ? "length" : t;
104
116
  }
105
- function d(s) {
106
- if (!s) return null;
107
- const e = s.input_tokens ?? 0, t = s.cache_read_input_tokens ?? 0, n = s.cache_creation_input_tokens ?? 0, i = s.input_tokens != null || s.cache_read_input_tokens != null || s.cache_creation_input_tokens != null ? e + t + n : 0, c = s.output_tokens ?? 0, r = { inputTokens: i, outputTokens: c, totalTokens: i + c, raw: s };
108
- return s.cache_read_input_tokens != null && (r.cachedInputTokens = t), s.cache_creation_input_tokens != null && (r.cacheCreationInputTokens = n), r;
117
+ function f(n) {
118
+ if (!n) return null;
119
+ const t = n.input_tokens ?? 0, e = n.cache_read_input_tokens ?? 0, o = n.cache_creation_input_tokens ?? 0, r = n.input_tokens != null || n.cache_read_input_tokens != null || n.cache_creation_input_tokens != null ? t + e + o : 0, c = n.output_tokens ?? 0, i = { inputTokens: r, outputTokens: c, totalTokens: r + c, raw: n };
120
+ return n.cache_read_input_tokens != null && (i.cachedInputTokens = e), n.cache_creation_input_tokens != null && (i.cacheCreationInputTokens = o), i;
109
121
  }
110
- function _(s) {
111
- const e = [];
112
- for (const t of s)
113
- if (t.role !== "system") {
114
- if (t.role === "assistant" && Array.isArray(t.tool_calls) && t.tool_calls.length) {
115
- const n = [];
116
- t.content && n.push({ type: "text", text: t.content });
117
- for (const o of t.tool_calls)
118
- n.push({
122
+ function m(n) {
123
+ const t = [];
124
+ for (const e of n)
125
+ if (e.role !== "system") {
126
+ if (e.role === "assistant" && Array.isArray(e.tool_calls) && e.tool_calls.length) {
127
+ const o = [];
128
+ e.content && o.push({ type: "text", text: e.content });
129
+ for (const s of e.tool_calls)
130
+ o.push({
119
131
  type: "tool_use",
120
- id: o.id,
121
- name: o.name,
122
- input: o.args || {}
132
+ id: s.id,
133
+ name: s.name,
134
+ input: s.args || {}
123
135
  });
124
- e.push({ role: "assistant", content: n });
136
+ t.push({ role: "assistant", content: o });
125
137
  continue;
126
138
  }
127
- if (t.role === "tool") {
128
- e.push({
139
+ if (e.role === "tool") {
140
+ t.push({
129
141
  role: "user",
130
142
  content: [{
131
143
  type: "tool_result",
132
- tool_use_id: t.tool_call_id,
133
- content: String(t.content ?? "")
144
+ tool_use_id: e.tool_call_id,
145
+ content: String(e.content ?? "")
134
146
  }]
135
147
  });
136
148
  continue;
137
149
  }
138
- e.push({ role: t.role, content: t.content });
150
+ t.push({ role: e.role, content: e.content });
151
+ }
152
+ return t;
153
+ }
154
+ function y(n) {
155
+ return n.map((t) => t.type === "function" && t.function ? {
156
+ name: t.function.name,
157
+ description: t.function.description,
158
+ input_schema: t.function.parameters || { type: "object", properties: {} }
159
+ } : t);
160
+ }
161
+ const l = { type: "ephemeral" };
162
+ function k(n, t) {
163
+ if (n.system)
164
+ n.system = x(n.system);
165
+ else if (Array.isArray(n.tools) && n.tools.length) {
166
+ const e = n.tools.length - 1;
167
+ n.tools[e] = { ...n.tools[e], cache_control: l };
168
+ }
169
+ if (t != null && t.cacheTranscript) {
170
+ const e = n.messages;
171
+ if (Array.isArray(e) && e.length) {
172
+ const o = e[e.length - 1];
173
+ o.content = A(o.content);
139
174
  }
140
- return e;
175
+ }
176
+ return n;
177
+ }
178
+ function x(n) {
179
+ let t;
180
+ if (typeof n == "string")
181
+ t = [{ type: "text", text: n }];
182
+ else if (Array.isArray(n) && n.length)
183
+ t = n.map((e) => typeof e == "string" ? { type: "text", text: e } : { ...e });
184
+ else
185
+ return n;
186
+ return t[t.length - 1] = { ...t[t.length - 1], cache_control: l }, t;
141
187
  }
142
- function g(s) {
143
- return s.map((e) => e.type === "function" && e.function ? {
144
- name: e.function.name,
145
- description: e.function.description,
146
- input_schema: e.function.parameters || { type: "object", properties: {} }
147
- } : e);
188
+ function A(n) {
189
+ if (typeof n == "string")
190
+ return [{ type: "text", text: n, cache_control: l }];
191
+ if (Array.isArray(n) && n.length) {
192
+ const t = n.slice(), e = t.length - 1, o = t[e];
193
+ return t[e] = typeof o == "string" ? { type: "text", text: o, cache_control: l } : { ...o, cache_control: l }, t;
194
+ }
195
+ return n;
148
196
  }
149
197
  export {
150
- y as AnthropicProvider,
151
- d as normalizeAnthropicUsage
198
+ b as AnthropicProvider,
199
+ f as normalizeAnthropicUsage,
200
+ _ as samplingParamsRemoved
152
201
  };
@@ -1,6 +1,7 @@
1
1
  import { BaseProvider as y } from "./BaseProvider.js";
2
2
  import { AnthropicProvider as w } from "./AnthropicProvider.js";
3
- const g = [
3
+ const p = [
4
+ "us.anthropic.claude-opus-4-8",
4
5
  "us.anthropic.claude-opus-4-7",
5
6
  "us.anthropic.claude-sonnet-4-6",
6
7
  "us.anthropic.claude-haiku-4-5-20251001-v1:0",
@@ -9,10 +10,10 @@ const g = [
9
10
  "us.anthropic.claude-sonnet-4-5-20250929-v1:0",
10
11
  "us.anthropic.claude-opus-4-1-20250805-v1:0"
11
12
  ];
12
- class R extends w {
13
- prepareRequest(r, n) {
14
- const t = super.prepareRequest(r, n);
15
- return delete t.stream, t.anthropic_version = "bedrock-2023-05-31", t;
13
+ class A extends w {
14
+ prepareRequest(n, t) {
15
+ const r = super.prepareRequest(n, t);
16
+ return delete r.stream, r.anthropic_version = "bedrock-2023-05-31", r;
16
17
  }
17
18
  buildHeaders() {
18
19
  return y.prototype.buildHeaders.call(this);
@@ -26,147 +27,170 @@ class R extends w {
26
27
  requiresAuth() {
27
28
  return !!this.config.apiKey;
28
29
  }
29
- async makeRequest(r, n, t) {
30
- const e = r.model, d = { ...r };
30
+ async makeRequest(n, t, r) {
31
+ const e = n.model, d = { ...n };
31
32
  delete d.model;
32
- const c = `${this.config.baseUrl}/model/${encodeURIComponent(e)}/invoke`, u = n ? { signal: n } : new AbortController();
33
- n || (t = t || this.generateRequestId(), this.activeRequests.set(t, u));
33
+ const i = `${this.config.baseUrl}/model/${encodeURIComponent(e)}/invoke`, l = t ? { signal: t } : new AbortController();
34
+ t || (r = r || this.generateRequestId(), this.activeRequests.set(r, l));
34
35
  try {
35
- const s = await fetch(c, {
36
+ const o = await fetch(i, {
36
37
  method: "POST",
37
38
  headers: this.buildHeaders(),
38
39
  body: JSON.stringify(d),
39
- signal: u.signal || n
40
+ signal: l.signal || t
40
41
  });
41
- if (!s.ok) {
42
- const l = await s.text();
43
- throw new Error(`Bedrock API Error (${s.status}): ${l}`);
42
+ if (!o.ok) {
43
+ const h = await o.text();
44
+ throw new Error(`Bedrock API Error (${o.status}): ${h}`);
44
45
  }
45
- return s.json();
46
+ return o.json();
46
47
  } finally {
47
- !n && t && this.activeRequests.delete(t);
48
+ !t && r && this.activeRequests.delete(r);
48
49
  }
49
50
  }
50
- async makeStreamingRequest(r, n) {
51
- const t = r.model, e = { ...r };
51
+ async makeStreamingRequest(n, t) {
52
+ const r = n.model, e = { ...n };
52
53
  delete e.model;
53
- const d = `${this.config.baseUrl}/model/${encodeURIComponent(t)}/invoke-with-response-stream`, c = await fetch(d, {
54
+ const d = `${this.config.baseUrl}/model/${encodeURIComponent(r)}/invoke-with-response-stream`, i = await fetch(d, {
54
55
  method: "POST",
55
56
  headers: this.buildHeaders(),
56
57
  body: JSON.stringify(e),
57
- signal: n
58
+ signal: t
58
59
  });
59
- if (!c.ok) {
60
- const i = await c.text();
61
- throw new Error(`Bedrock API Error (${c.status}): ${i}`);
60
+ if (!i.ok) {
61
+ const c = await i.text();
62
+ throw new Error(`Bedrock API Error (${i.status}): ${c}`);
62
63
  }
63
- const u = c.body.getReader(), s = new v(), l = new TextEncoder(), f = new ReadableStream({
64
- async pull(i) {
64
+ const l = i.body.getReader(), o = new v(), h = new TextEncoder(), f = new ReadableStream({
65
+ async pull(c) {
65
66
  try {
66
- const { done: a, value: m } = await u.read();
67
+ const { done: a, value: g } = await l.read();
67
68
  if (a) {
68
- i.close();
69
+ c.close();
69
70
  return;
70
71
  }
71
- const p = s.feed(m);
72
- for (const h of p) {
73
- if (h.kind === "error") {
74
- i.error(new Error(h.message));
72
+ const m = o.feed(g);
73
+ for (const u of m) {
74
+ if (u.kind === "error") {
75
+ c.error(new Error(u.message));
75
76
  return;
76
77
  }
77
- i.enqueue(l.encode(`data: ${JSON.stringify(h.payload)}
78
+ c.enqueue(h.encode(`data: ${JSON.stringify(u.payload)}
78
79
  `));
79
80
  }
80
81
  } catch (a) {
81
- i.error(a);
82
+ c.error(a);
82
83
  }
83
84
  },
84
- cancel(i) {
85
+ cancel(c) {
85
86
  try {
86
- u.cancel(i);
87
+ l.cancel(c);
87
88
  } catch {
88
89
  }
89
90
  }
90
91
  });
91
92
  return new Response(f, { status: 200, headers: { "Content-Type": "text/event-stream" } });
92
93
  }
93
- // Bedrock control plane needs SigV4; Bearer auth can't list models.
94
- // Return the hardcoded list so the dropdown still populates.
95
- async discoverModels() {
96
- return g.slice();
94
+ // Live discovery is opt-in via config.liveModelDiscovery. A Bedrock API key
95
+ // authenticates against the control plane with Bearer auth (no SigV4), and
96
+ // the control-plane host returns permissive CORS headers, so a browser fetch
97
+ // works. When disabled (default) or on any failure, fall back to the static
98
+ // list — existing apps keep working with zero surprise network calls.
99
+ async discoverModels(n = 1e4) {
100
+ if (!this.config.liveModelDiscovery || !this.getModelsEndpoint())
101
+ return p.slice();
102
+ try {
103
+ const t = await super.discoverModels(n);
104
+ return t.length ? t : p.slice();
105
+ } catch {
106
+ return p.slice();
107
+ }
97
108
  }
109
+ // The control plane lives at the same host with the `-runtime` segment
110
+ // stripped: bedrock-runtime.{region}.amazonaws.com (invoke) ->
111
+ // bedrock.{region}.amazonaws.com (ListInferenceProfiles). Returns null when
112
+ // no distinct control-plane host can be derived (custom/relative baseUrl),
113
+ // which disables live discovery and leaves the static fallback in place.
98
114
  getModelsEndpoint() {
99
- return null;
115
+ const n = this.config.baseUrl || "", t = n.replace("bedrock-runtime.", "bedrock.");
116
+ return !t || t === n ? null : `${t}/inference-profiles`;
100
117
  }
101
- parseModelsResponse() {
102
- return g.slice();
118
+ // ListInferenceProfiles returns
119
+ // { inferenceProfileSummaries: [{ inferenceProfileId, ... }] }
120
+ // Keep only Anthropic Claude profiles (us.* and global.*) — those ids are
121
+ // exactly what InvokeModel expects in the URL path.
122
+ parseModelsResponse(n) {
123
+ const t = n == null ? void 0 : n.inferenceProfileSummaries;
124
+ if (!Array.isArray(t)) return p.slice();
125
+ const r = t.map((e) => e == null ? void 0 : e.inferenceProfileId).filter((e) => typeof e == "string" && e.includes("anthropic.claude"));
126
+ return r.length ? r : p.slice();
103
127
  }
104
128
  }
105
129
  class v {
106
130
  constructor() {
107
131
  this.buf = new Uint8Array(0), this.textDecoder = new TextDecoder();
108
132
  }
109
- feed(r) {
110
- if (r && r.length) {
111
- const t = new Uint8Array(this.buf.length + r.length);
112
- t.set(this.buf, 0), t.set(r, this.buf.length), this.buf = t;
133
+ feed(n) {
134
+ if (n && n.length) {
135
+ const r = new Uint8Array(this.buf.length + n.length);
136
+ r.set(this.buf, 0), r.set(n, this.buf.length), this.buf = r;
113
137
  }
114
- const n = [];
138
+ const t = [];
115
139
  for (; this.buf.length >= 12; ) {
116
- const t = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength), e = t.getUint32(0, !1), d = t.getUint32(4, !1);
140
+ const r = new DataView(this.buf.buffer, this.buf.byteOffset, this.buf.byteLength), e = r.getUint32(0, !1), d = r.getUint32(4, !1);
117
141
  if (e < 16 || e > 16 * 1024 * 1024)
118
142
  throw new Error(`Bedrock event stream: invalid frame length ${e}`);
119
143
  if (this.buf.length < e) break;
120
- const c = 12, u = c + d, s = u, l = e - 4, f = k(this.buf.subarray(c, u)), i = this.textDecoder.decode(this.buf.subarray(s, l));
144
+ const i = 12, l = i + d, o = l, h = e - 4, f = k(this.buf.subarray(i, l)), c = this.textDecoder.decode(this.buf.subarray(o, h));
121
145
  this.buf = this.buf.subarray(e);
122
146
  let a;
123
147
  try {
124
- a = JSON.parse(i);
148
+ a = JSON.parse(c);
125
149
  } catch {
126
150
  a = null;
127
151
  }
128
- const m = f[":message-type"], p = f[":exception-type"];
129
- if (m === "exception" || p) {
130
- const h = p || f[":event-type"] || "BedrockStreamException", b = (a == null ? void 0 : a.message) || i || "unknown error";
131
- n.push({ kind: "error", message: `${h}: ${b}` });
152
+ const g = f[":message-type"], m = f[":exception-type"];
153
+ if (g === "exception" || m) {
154
+ const u = m || f[":event-type"] || "BedrockStreamException", b = (a == null ? void 0 : a.message) || c || "unknown error";
155
+ t.push({ kind: "error", message: `${u}: ${b}` });
132
156
  continue;
133
157
  }
134
158
  if (a && typeof a.bytes == "string")
135
159
  try {
136
- const h = x(a.bytes), b = JSON.parse(h);
137
- n.push({ kind: "event", payload: b });
138
- } catch (h) {
139
- n.push({ kind: "error", message: `Bedrock event-stream decode failed: ${h.message}` });
160
+ const u = x(a.bytes), b = JSON.parse(u);
161
+ t.push({ kind: "event", payload: b });
162
+ } catch (u) {
163
+ t.push({ kind: "error", message: `Bedrock event-stream decode failed: ${u.message}` });
140
164
  }
141
165
  }
142
- return n;
166
+ return t;
143
167
  }
144
168
  }
145
- function k(o) {
146
- const r = {}, n = new DataView(o.buffer, o.byteOffset, o.byteLength), t = new TextDecoder();
169
+ function k(s) {
170
+ const n = {}, t = new DataView(s.buffer, s.byteOffset, s.byteLength), r = new TextDecoder();
147
171
  let e = 0;
148
- for (; e < o.length; ) {
149
- const d = o[e];
150
- if (e += 1, e + d > o.length) break;
151
- const c = t.decode(o.subarray(e, e + d));
152
- if (e += d, e >= o.length) break;
153
- const u = o[e];
154
- if (e += 1, u === 7) {
155
- if (e + 2 > o.length) break;
156
- const s = n.getUint16(e, !1);
157
- if (e += 2, e + s > o.length) break;
158
- r[c] = t.decode(o.subarray(e, e + s)), e += s;
172
+ for (; e < s.length; ) {
173
+ const d = s[e];
174
+ if (e += 1, e + d > s.length) break;
175
+ const i = r.decode(s.subarray(e, e + d));
176
+ if (e += d, e >= s.length) break;
177
+ const l = s[e];
178
+ if (e += 1, l === 7) {
179
+ if (e + 2 > s.length) break;
180
+ const o = t.getUint16(e, !1);
181
+ if (e += 2, e + o > s.length) break;
182
+ n[i] = r.decode(s.subarray(e, e + o)), e += o;
159
183
  } else
160
184
  break;
161
185
  }
162
- return r;
186
+ return n;
163
187
  }
164
- function x(o) {
165
- const r = atob(o), n = new Uint8Array(r.length);
166
- for (let t = 0; t < r.length; t++) n[t] = r.charCodeAt(t);
167
- return new TextDecoder().decode(n);
188
+ function x(s) {
189
+ const n = atob(s), t = new Uint8Array(n.length);
190
+ for (let r = 0; r < n.length; r++) t[r] = n.charCodeAt(r);
191
+ return new TextDecoder().decode(t);
168
192
  }
169
193
  export {
170
- g as BEDROCK_CLAUDE_MODELS,
171
- R as BedrockProvider
194
+ p as BEDROCK_CLAUDE_MODELS,
195
+ A as BedrockProvider
172
196
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anvaka/vue-llm",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Browser-only LLM client with provider adapters and Vue 3 components",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "build": "vite build",
11
11
  "dev": "vite build --watch",
12
- "test:providers": "node test/providers.mjs"
12
+ "test:providers": "node test/providers.mjs",
13
+ "test:caching": "node test/caching.mjs"
13
14
  },
14
15
  "exports": {
15
16
  ".": {