@anvaka/vue-llm 0.2.0 → 0.3.1

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
@@ -3,9 +3,11 @@
3
3
  Browser-only LLM client + Vue 3 plugin, provider adapters, and lightweight components.
4
4
 
5
5
  ## Features
6
- - Provider factory with 8 built-in providers (OpenAI, Anthropic, Grok, Gemini, Ollama, Llama Server, OpenRouter, Custom) – extend with `registerProvider()`
6
+ - Provider factory with 10 built-in providers (OpenAI, Anthropic, Bedrock, Grok, Gemini, Ollama, Llama Server, OpenRouter, DeepSeek, Custom) – extend with `registerProvider()`
7
7
  - LocalStorage-based config store (custom storage adapter supported)
8
8
  - Streaming + promise requests via `llmClient.stream()`
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`
9
11
  - Vue plugin for dependency injection
10
12
  - `useLLM()` composable with reactive streaming state
11
13
  - Ready-to-use components: `ProviderSelector`, `LLMConfigModal`, `StoredKeysManager`
@@ -121,13 +123,89 @@ For scripts outside Vue components, use the singleton exports:
121
123
  import { llmClient, configStore, keyStore } from '@anvaka/vue-llm'
122
124
 
123
125
  // Stream directly
124
- await llmClient.stream({ messages: [...] }, chunk => console.log(chunk.fullContent))
126
+ const { content, usage, cost } = await llmClient.stream(
127
+ { messages: [...] },
128
+ chunk => console.log(chunk.fullContent)
129
+ )
125
130
 
126
131
  // Manage configs
127
132
  configStore.saveConfig('my-provider', { ... })
128
133
  configStore.setActiveProviderId('my-provider')
129
134
  ```
130
135
 
136
+ ## Usage & Cost
137
+
138
+ Every response carries a normalized `usage` object and a USD `cost` breakdown when the model's rates are known. Stream chunks expose a running `fullUsage` so consumers can render a live cost counter.
139
+
140
+ ```js
141
+ const { content, usage, cost } = await client.stream({ messages: [...] })
142
+
143
+ usage // { inputTokens, outputTokens, totalTokens,
144
+ // cachedInputTokens?, cacheCreationInputTokens?, reasoningTokens?, raw }
145
+ cost // { total, input, cachedInput, cacheCreation, output, currency, rates }
146
+ // or null when the model isn't in the rates table
147
+ ```
148
+
149
+ `runAgentLoop` aggregates usage across all iterations (each tool round-trip is one call) and emits a `usage` event per iteration:
150
+
151
+ ```js
152
+ const { messages, usage, cost } = await client.runAgentLoop({
153
+ messages: [{ role: 'user', content: '7 * 11?' }],
154
+ tools, executors,
155
+ onEvent: ev => {
156
+ if (ev.type === 'usage') console.log(`iter cost: ${ev.cost?.total}`)
157
+ }
158
+ })
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
+
177
+ ### Overriding rates
178
+
179
+ Built-in rates are sourced from public pricing pages and *will* drift. Three ways to override, in priority order:
180
+
181
+ ```js
182
+ // 1. Per-instance — wins over everything
183
+ import { LLMClient } from '@anvaka/vue-llm'
184
+ const client = new LLMClient({
185
+ pricing: {
186
+ openai: { 'gpt-4o': { input: 1.50, output: 6.00, cachedInput: 0.75 } }
187
+ }
188
+ })
189
+
190
+ // 2. Global runtime override
191
+ import { registerPricing } from '@anvaka/vue-llm'
192
+ registerPricing('openai', 'gpt-4o', { input: 1.50, output: 6.00 })
193
+
194
+ // 3. Inline (without LLMClient)
195
+ import { calculateCost } from '@anvaka/vue-llm/pricing'
196
+ const cost = calculateCost(usage, { provider: 'openai', model: 'gpt-4o' })
197
+ ```
198
+
199
+ Rate keys: `input` (uncached prompt, per 1M tokens, USD), `output`, optional `cachedInput` (prompt-cache hits — defaults to `input` if omitted), optional `cacheCreation` (Anthropic-only cache-write premium).
200
+
201
+ Model lookup is exact match first, then longest-prefix match — so registering `claude-haiku-4-5` automatically covers `claude-haiku-4-5-20251001`.
202
+
203
+ ```js
204
+ // Pricing-only import (no Vue):
205
+ import { calculateCost, formatCost, registerPricing, DEFAULT_RATES } from '@anvaka/vue-llm/pricing'
206
+ formatCost(0.00012) // "$0.000120"
207
+ ```
208
+
131
209
  ## Theming
132
210
  Override any `--llm-*` CSS variable globally or per container.
133
211
  ```css
@@ -150,7 +228,7 @@ registerProvider('my-provider', MyProvider)
150
228
  // From '@anvaka/vue-llm/providers'
151
229
  import {
152
230
  BaseProvider,
153
- PROVIDERS, // { OPENAI, ANTHROPIC, GROK, GEMINI, OLLAMA, LLAMA_SERVER, OPENROUTER, CUSTOM }
231
+ PROVIDERS, // { OPENAI, ANTHROPIC, BEDROCK, GROK, GEMINI, OLLAMA, LLAMA_SERVER, OPENROUTER, DEEPSEEK, CUSTOM }
154
232
  DEFAULT_CONFIGS, // Default configs for each provider type
155
233
  createProvider, // (type, config) => Provider
156
234
  registerProvider, // (type, ProviderClass) => void
@@ -1,12 +1,25 @@
1
- import { createProviderFlexible as I } from "../providers/factory.js";
2
- class $ {
3
- constructor({ configStore: t, logger: s } = {}) {
4
- this.configStore = t, this.logger = s || console, this.config = null, this.provider = null, this.usageTracker = null;
1
+ import { createProviderFlexible as U } from "../providers/factory.js";
2
+ import { calculateCost as A } from "../pricing/calculate.js";
3
+ class P {
4
+ constructor({ configStore: t, logger: s, pricing: i } = {}) {
5
+ this.configStore = t, this.logger = s || console, this.config = null, this.provider = null, this.usageTracker = null, this.pricing = i || null;
6
+ }
7
+ // Compute USD cost for a usage object using this client's active provider /
8
+ // model + any per-instance overrides. Returns null when rates are unknown.
9
+ // Exposed so consumers can re-cost historical usage objects without
10
+ // reaching into the pricing module directly.
11
+ costFor(t, { provider: s, model: i } = {}) {
12
+ var r, e;
13
+ return A(t, {
14
+ provider: s || ((r = this.config) == null ? void 0 : r.provider),
15
+ model: i || ((e = this.config) == null ? void 0 : e.model),
16
+ pricing: this.pricing
17
+ });
5
18
  }
6
19
  async initialize(t = null) {
7
20
  if (this.config = t || this.configStore.getActiveConfig(), !this.config)
8
21
  throw new Error("LLM not configured");
9
- this.provider = I(this.config.provider, this.config), await this.provider.initialize(), this.usageTracker = this._createUsageTracker();
22
+ this.provider = U(this.config.provider, this.config), await this.provider.initialize(), this.usageTracker = this._createUsageTracker();
10
23
  }
11
24
  async ensureInitialized() {
12
25
  this.provider || await this.initialize();
@@ -35,17 +48,18 @@ class $ {
35
48
  }
36
49
  }
37
50
  _createUsageTracker() {
38
- return {
51
+ const t = {
39
52
  totalTokens: 0,
40
53
  totalCost: 0,
41
- recordUsage: (t, s) => {
42
- var i;
43
- (i = s.usage) != null && i.tokens && (this.totalTokens += s.usage.tokens);
54
+ recordUsage: (s, i) => {
55
+ var r;
56
+ ((r = i == null ? void 0 : i.usage) == null ? void 0 : r.totalTokens) != null && (t.totalTokens += i.usage.totalTokens);
44
57
  },
45
- recordPartialUsage: (t) => {
46
- t != null && t.tokens && (this.totalTokens += t.tokens);
58
+ recordPartialUsage: (s) => {
59
+ (s == null ? void 0 : s.totalTokens) != null && (t.totalTokens += s.totalTokens);
47
60
  }
48
61
  };
62
+ return t;
49
63
  }
50
64
  validateCapabilities(t) {
51
65
  var i;
@@ -75,10 +89,12 @@ class $ {
75
89
  async stream(t, s) {
76
90
  await this.ensureInitialized();
77
91
  const i = this.validateCapabilities({ ...t, stream: !0, model: t.model || this.config.model, requestId: this.generateRequestId() }), r = t.messages;
78
- let e = "";
79
- return await this.provider.streamRequest(r, i, (n) => {
80
- e = n.fullContent, s && s(n);
81
- }), e;
92
+ let e = "", n = null;
93
+ return await this.provider.streamRequest(r, i, (o) => {
94
+ e = o.fullContent, o.fullUsage && (n = o.fullUsage);
95
+ const l = o.fullUsage ? { ...o, cost: this.costFor(o.fullUsage, { provider: this.config.provider, model: i.model }) } : o;
96
+ s && s(l);
97
+ }), { content: e, usage: n, cost: this.costFor(n, { provider: this.config.provider, model: i.model }) };
82
98
  }
83
99
  // Multi-turn tool-calling loop. Each iteration:
84
100
  // 1) stream a model response (text + accumulated tool_calls)
@@ -104,47 +120,61 @@ class $ {
104
120
  temperature: n,
105
121
  model: o,
106
122
  maxTokens: l,
107
- enableThinking: u
123
+ enableThinking: d
108
124
  } = {}) {
109
125
  if (await this.ensureInitialized(), !this.provider.hasCapability("tools"))
110
126
  throw new Error("Configured provider does not support tools");
111
- const h = t.slice();
112
- let c = 0, m = null;
113
- for (; c < e; ) {
114
- c++, r && r({ type: "iter-start", iter: c });
115
- const b = this.validateCapabilities({
116
- messages: h,
127
+ const c = t.slice();
128
+ let p = 0, m = null;
129
+ const f = { inputTokens: 0, outputTokens: 0, totalTokens: 0, cachedInputTokens: 0, cacheCreationInputTokens: 0, reasoningTokens: 0 };
130
+ let b = !1;
131
+ for (; p < e; ) {
132
+ p++, r && r({ type: "iter-start", iter: p });
133
+ const x = this.validateCapabilities({
134
+ messages: c,
117
135
  tools: s,
118
136
  stream: !0,
119
137
  model: o || this.config.model,
120
138
  temperature: n ?? this.config.temperature,
121
139
  maxTokens: l ?? this.config.maxTokens ?? 4096,
122
- enableThinking: u ?? !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,
123
144
  requestId: this.generateRequestId()
124
- }), p = await this.provider.streamRequest(h, b, (a) => {
145
+ }), h = await this.provider.streamRequest(c, x, (a) => {
125
146
  a.content && r && r({ type: "text-delta", text: a.content }), a.toolCallDelta && r && r({ type: "tool-call-delta", delta: a.toolCallDelta });
126
- }), v = (p == null ? void 0 : p.content) || "", d = (p == null ? void 0 : p.toolCalls) || [], w = { role: "assistant", content: v };
127
- if (d.length && (w.tool_calls = d), h.push(w), r && r({ type: "assistant-message", content: v, toolCalls: d }), !d.length) {
147
+ }), q = (h == null ? void 0 : h.content) || "", y = (h == null ? void 0 : h.toolCalls) || [], C = (h == null ? void 0 : h.thinking) || "", w = (h == null ? void 0 : h.usage) || null;
148
+ if (w) {
149
+ b = !0;
150
+ for (const a of Object.keys(f))
151
+ w[a] != null && (f[a] += w[a]);
152
+ r && r({ type: "usage", usage: w, cost: this.costFor(w, { provider: this.config.provider, model: x.model }) });
153
+ }
154
+ const k = { role: "assistant", content: q };
155
+ if (y.length && (k.tool_calls = y), C && (k.thinking = C), c.push(k), r && r({ type: "assistant-message", content: q, toolCalls: y, thinking: C }), !y.length) {
128
156
  m = "no-tool-calls";
129
157
  break;
130
158
  }
131
- for (const a of d) {
159
+ for (const a of y) {
132
160
  r && r({ type: "tool-call", id: a.id, name: a.name, args: a.args });
133
- const _ = i && i[a.name];
134
- let y;
135
- if (!_)
136
- y = `Error: unknown tool '${a.name}'`;
161
+ const R = i && i[a.name];
162
+ let T;
163
+ if (!R)
164
+ T = `Error: unknown tool '${a.name}'`;
137
165
  else
138
166
  try {
139
- const f = await _(a.args || {});
140
- y = typeof f == "string" ? f : JSON.stringify(f ?? "");
141
- } catch (f) {
142
- y = `Error: ${(f == null ? void 0 : f.message) || String(f)}`;
167
+ const u = await R(a.args || {});
168
+ T = typeof u == "string" ? u : JSON.stringify(u ?? "");
169
+ } catch (u) {
170
+ T = `Error: ${(u == null ? void 0 : u.message) || String(u)}`;
143
171
  }
144
- h.push({ role: "tool", tool_call_id: a.id, content: y }), r && r({ type: "tool-result", id: a.id, name: a.name, content: y });
172
+ c.push({ role: "tool", tool_call_id: a.id, content: T }), r && r({ type: "tool-result", id: a.id, name: a.name, content: T });
145
173
  }
146
174
  }
147
- return m || (m = "max-iters"), r && r({ type: "stop", reason: m, iterations: c }), { messages: h, iterations: c, stopReason: m };
175
+ m || (m = "max-iters");
176
+ const g = b ? f : null, v = this.costFor(g, { provider: this.config.provider, model: o || this.config.model });
177
+ return r && r({ type: "stop", reason: m, iterations: p, usage: g, cost: v }), { messages: c, iterations: p, stopReason: m, usage: g, cost: v };
148
178
  }
149
179
  generateRequestId() {
150
180
  return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
@@ -185,12 +215,12 @@ class $ {
185
215
  createLLMWrapper(t, s = null, i = null) {
186
216
  const r = this;
187
217
  function e(n, o = {}) {
188
- return new S(r, t, n, o, s, i);
218
+ return new $(r, t, n, o, s, i);
189
219
  }
190
220
  return e;
191
221
  }
192
222
  }
193
- class S {
223
+ class $ {
194
224
  constructor(t, s, i, r, e, n) {
195
225
  this.client = t, this.contextNode = s, this.prompt = i, this.options = r || {}, this.originatingCode = e, this.defaultPresetName = n, this.targetNode = null, this.targetAttributes = {}, this.operationName = null, this._executed = !1, this._promise = null;
196
226
  }
@@ -229,8 +259,8 @@ class S {
229
259
  };
230
260
  try {
231
261
  if (this.targetNode) {
232
- const u = r({ ...n, stream: !0 });
233
- return await this._streamIntoTarget(e, u, s, i);
262
+ const d = r({ ...n, stream: !0 });
263
+ return await this._streamIntoTarget(e, d, s, i);
234
264
  }
235
265
  const l = r({ ...n, stream: !1 });
236
266
  return await this._promiseResponse(e, l, s, i);
@@ -248,10 +278,10 @@ class S {
248
278
  throw new Error("Provider presets require a configured ConfigStore instance");
249
279
  const s = (e = (r = this.client).getConfigByName) == null ? void 0 : e.call(r, t);
250
280
  if (!s) {
251
- const l = (((o = (n = this.client.configStore).getEnabledConfigs) == null ? void 0 : o.call(n)) || []).map((h) => h.name || `${h.provider} (${h.baseUrl})`).filter(Boolean), u = l.length ? ` Available presets: ${l.join(", ")}` : " No configured providers found.";
252
- throw new Error(`Provider preset '${t}' not found.${u}`);
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}`);
253
283
  }
254
- const i = I(s.provider, s);
284
+ const i = U(s.provider, s);
255
285
  return await i.initialize(), { provider: i, config: s, cleanup: () => {
256
286
  var l;
257
287
  return (l = i.cancelAllRequests) == null ? void 0 : l.call(i);
@@ -271,44 +301,46 @@ class S {
271
301
  }
272
302
  }
273
303
  async _streamIntoTarget(t, s, i, r) {
274
- var c, m, b, p, v, d, w, a, _, y, f, C, T, k;
304
+ var p, m, f, b, g, v, x, h, q, y, C, w, k, a, R, T;
275
305
  let e, n;
276
- typeof this.targetNode == "string" ? (n = this.targetNode, e = this.contextNode.createChild(n), (c = e.setSelected) == null || c.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);
277
307
  const o = r == null ? void 0 : r.name;
278
- o && e.addTag && e.addTag("provider", o), (b = e.addLLMLog) == null || b.call(e, this.operationName || "streaming-request", {
308
+ o && e.addTag && e.addTag("provider", o), (f = e.addLLMLog) == null || f.call(e, this.operationName || "streaming-request", {
279
309
  provider: r == null ? void 0 : r.provider,
280
310
  model: r == null ? void 0 : r.model,
281
311
  messages: t,
282
312
  options: s
283
- }), (p = e.setStreamingState) == null || p.call(e, !0), e._activeRequestId = s.requestId;
284
- let l = "", u = null, h = 0;
313
+ }), (b = e.setStreamingState) == null || b.call(e, !0), e._activeRequestId = s.requestId;
314
+ let l = "", d = null, c = null;
285
315
  try {
286
- return await i.streamRequest(t, s, (g) => {
287
- var x, q, R;
288
- g.content && (l = g.fullContent, (x = e.setText) == null || x.call(e, l)), (q = g.usage) != null && q.tokens && (h += g.usage.tokens), g.finishReason && (u = g.finishReason), g.done && ((R = e.setStreamingState) == null || R.call(e, !1), e._activeRequestId = null);
289
- }), (v = e.addLLMLog) == null || v.call(e, (this.operationName || "streaming-request") + "-response", { requestId: s.requestId }, { content: l, usage: { tokens: h } }), u === "length" ? ((d = e.setAttribute) == null || d.call(e, "_truncated", !0), (w = e.addLog) == null || w.call(e, "Response truncated. Increase Max Tokens.", "warn", { finish_reason: "length" })) : (a = e.setAttribute) == null || a.call(e, "_truncated", !1), await ((_ = e.persistNow) == null ? void 0 : _.call(e)), e;
290
- } catch (g) {
291
- throw (y = e.addLLMLog) == null || y.call(e, (this.operationName || "streaming-request") + "-error", { requestId: s.requestId }, null, g), (f = e.setStreamingState) == null || f.call(e, !1), e._activeRequestId = null, (C = e.setText) == null || C.call(e, `Error: ${g.message}`), (T = e.setAttribute) == null || T.call(e, "_truncated", !1), await ((k = e.persistNow) == null ? void 0 : k.call(e)), g;
316
+ await i.streamRequest(t, s, (_) => {
317
+ var I, S;
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);
319
+ });
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 });
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;
322
+ } catch (u) {
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;
292
324
  }
293
325
  }
294
326
  async _promiseResponse(t, s, i, r) {
295
- var u, h, c, m, b, p, v;
327
+ var d, c, p, m, f, b, g;
296
328
  const e = i.prepareRequest(t, s), n = await i.makeRequest(e), o = i.processResponse(n);
297
- if ((o.finishReason || ((h = (u = n == null ? void 0 : n.choices) == null ? void 0 : u[0]) == null ? void 0 : h.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")
298
330
  try {
299
331
  this.contextNode.setAttribute("_truncated", !0);
300
- } catch (d) {
301
- (m = (c = this.client.logger) == null ? void 0 : c.warn) == null || m.call(c, "Unable to mark node as truncated", { error: d });
332
+ } catch (v) {
333
+ (m = (p = this.client.logger) == null ? void 0 : p.warn) == null || m.call(p, "Unable to mark node as truncated", { error: v });
302
334
  }
303
335
  else
304
336
  try {
305
337
  this.contextNode.setAttribute("_truncated", !1);
306
- } catch (d) {
307
- (p = (b = this.client.logger) == null ? void 0 : b.warn) == null || p.call(b, "Unable to clear truncated marker on node", { error: d });
338
+ } catch (v) {
339
+ (b = (f = this.client.logger) == null ? void 0 : f.warn) == null || b.call(f, "Unable to clear truncated marker on node", { error: v });
308
340
  }
309
- return ((v = o.content) == null ? void 0 : v.trim()) || "";
341
+ return ((g = o.content) == null ? void 0 : g.trim()) || "";
310
342
  }
311
343
  }
312
344
  export {
313
- $ as LLMClient
345
+ P as LLMClient
314
346
  };
package/dist/index.js CHANGED
@@ -1,38 +1,46 @@
1
1
  import { LLMClient as l } from "./core/LLMClient.js";
2
- import { ConfigStore as S } from "./core/configStore.js";
3
- import { KeyStore as p, maskApiKey as M } from "./core/keyStore.js";
4
- import { LocalStorageAdapter as s, MemoryStorageAdapter as x } from "./core/storageAdapter.js";
5
- import { DEFAULT_CONFIGS as g, PROVIDERS as _, createProvider as C, createProviderFlexible as O, registerProvider as u } from "./providers/factory.js";
2
+ import { ConfigStore as m } from "./core/configStore.js";
3
+ import { KeyStore as p, maskApiKey as s } from "./core/keyStore.js";
4
+ import { LocalStorageAdapter as g, MemoryStorageAdapter as x } from "./core/storageAdapter.js";
5
+ import { DEFAULT_CONFIGS as d, PROVIDERS as C, createProvider as _, createProviderFlexible as u, registerProvider as O } from "./providers/factory.js";
6
+ import { DEFAULT_RATES as E } from "./pricing/rates.js";
7
+ import { calculateCost as A, clearPricingOverrides as v, formatCost as F, lookupRates as R, registerPricing as T } from "./pricing/calculate.js";
6
8
  import { createLLM as r } from "./vue/plugin.js";
7
- import { LLMPlugin as P, LLM_CLIENT_SYMBOL as E, LLM_CONFIG_SYMBOL as v, LLM_KEYSTORE_SYMBOL as A } from "./vue/plugin.js";
8
- import { createDefaultConfig as I, useLLM as K } from "./vue/useLLM.js";
9
- import { default as k } from "./vue/components/ProviderSelector.vue.js";
10
- import { default as D } from "./vue/components/LLMConfigModal.vue.js";
11
- import { default as R } from "./vue/components/StoredKeysManager.vue.js";
12
- const e = r({}), t = e.client, L = e.configStore, a = e.keyStore;
9
+ import { LLMPlugin as D, LLM_CLIENT_SYMBOL as I, LLM_CONFIG_SYMBOL as K, LLM_KEYSTORE_SYMBOL as Y } from "./vue/plugin.js";
10
+ import { createDefaultConfig as N, useLLM as G } from "./vue/useLLM.js";
11
+ import { default as b } from "./vue/components/ProviderSelector.vue.js";
12
+ import { default as h } from "./vue/components/LLMConfigModal.vue.js";
13
+ import { default as q } from "./vue/components/StoredKeysManager.vue.js";
14
+ const e = r({}), t = e.client, a = e.configStore, i = e.keyStore;
13
15
  export {
14
- S as ConfigStore,
15
- g as DEFAULT_CONFIGS,
16
+ m as ConfigStore,
17
+ d as DEFAULT_CONFIGS,
18
+ E as DEFAULT_RATES,
16
19
  p as KeyStore,
17
20
  l as LLMClient,
18
- D as LLMConfigModal,
19
- P as LLMPlugin,
20
- E as LLM_CLIENT_SYMBOL,
21
- v as LLM_CONFIG_SYMBOL,
22
- A as LLM_KEYSTORE_SYMBOL,
23
- s as LocalStorageAdapter,
21
+ h as LLMConfigModal,
22
+ D as LLMPlugin,
23
+ I as LLM_CLIENT_SYMBOL,
24
+ K as LLM_CONFIG_SYMBOL,
25
+ Y as LLM_KEYSTORE_SYMBOL,
26
+ g as LocalStorageAdapter,
24
27
  x as MemoryStorageAdapter,
25
- _ as PROVIDERS,
26
- k as ProviderSelector,
27
- R as StoredKeysManager,
28
- L as configStore,
29
- I as createDefaultConfig,
28
+ C as PROVIDERS,
29
+ b as ProviderSelector,
30
+ q as StoredKeysManager,
31
+ A as calculateCost,
32
+ v as clearPricingOverrides,
33
+ a as configStore,
34
+ N as createDefaultConfig,
30
35
  r as createLLM,
31
- C as createProvider,
32
- O as createProviderFlexible,
33
- a as keyStore,
36
+ _ as createProvider,
37
+ u as createProviderFlexible,
38
+ F as formatCost,
39
+ i as keyStore,
34
40
  t as llmClient,
35
- M as maskApiKey,
36
- u as registerProvider,
37
- K as useLLM
41
+ R as lookupRates,
42
+ s as maskApiKey,
43
+ T as registerPricing,
44
+ O as registerProvider,
45
+ G as useLLM
38
46
  };
@@ -0,0 +1,58 @@
1
+ import { DEFAULT_RATES as R } from "./rates.js";
2
+ const c = {};
3
+ function S(t, e, n) {
4
+ if (typeof t != "string" || typeof e != "string" || !n)
5
+ throw new Error("registerPricing(provider, model, rates) requires strings and a rates object");
6
+ c[t] || (c[t] = {}), c[t][e] = { ...n };
7
+ }
8
+ function U(t, e) {
9
+ if (t == null) {
10
+ for (const n of Object.keys(c)) delete c[n];
11
+ return;
12
+ }
13
+ if (e == null) {
14
+ delete c[t];
15
+ return;
16
+ }
17
+ c[t] && delete c[t][e];
18
+ }
19
+ function b(t, e, n = null) {
20
+ return !t || !e ? null : r(n == null ? void 0 : n[t], e) || r(c[t], e) || r(R[t], e) || null;
21
+ }
22
+ function r(t, e) {
23
+ if (!t) return null;
24
+ if (t[e]) return t[e];
25
+ let n = null;
26
+ for (const u of Object.keys(t))
27
+ e.startsWith(u) && (n == null || u.length > n.length) && (n = u);
28
+ return n ? t[n] : null;
29
+ }
30
+ function $(t, { provider: e, model: n, pricing: u = null } = {}) {
31
+ if (!t) return null;
32
+ const s = b(e, n, u);
33
+ if (!s) return null;
34
+ const T = t.inputTokens ?? 0, p = t.outputTokens ?? 0, i = t.cachedInputTokens ?? 0, f = t.cacheCreationInputTokens ?? 0, _ = Math.max(0, T - i - f), o = s.input ?? 0, y = s.output ?? 0, C = s.cachedInput ?? o, I = s.cacheCreation ?? o, l = _ * o / 1e6, a = i * C / 1e6, h = f * I / 1e6, k = p * y / 1e6;
35
+ return {
36
+ total: l + a + h + k,
37
+ input: l,
38
+ cachedInput: a,
39
+ cacheCreation: h,
40
+ output: k,
41
+ currency: "USD",
42
+ rates: s
43
+ };
44
+ }
45
+ function g(t, { currency: e = "USD" } = {}) {
46
+ if (t == null || Number.isNaN(t)) return "—";
47
+ const n = e === "USD" ? "$" : "";
48
+ if (t === 0) return `${n}0.00`;
49
+ const u = Math.abs(t), s = u >= 1 ? 2 : u >= 0.01 ? 4 : 6;
50
+ return `${n}${t.toFixed(s)}`;
51
+ }
52
+ export {
53
+ $ as calculateCost,
54
+ U as clearPricingOverrides,
55
+ g as formatCost,
56
+ b as lookupRates,
57
+ S as registerPricing
58
+ };
@@ -0,0 +1,10 @@
1
+ import { DEFAULT_RATES as o } from "./rates.js";
2
+ import { calculateCost as i, clearPricingOverrides as a, formatCost as c, lookupRates as s, registerPricing as l } from "./calculate.js";
3
+ export {
4
+ o as DEFAULT_RATES,
5
+ i as calculateCost,
6
+ a as clearPricingOverrides,
7
+ c as formatCost,
8
+ s as lookupRates,
9
+ l as registerPricing
10
+ };
@@ -0,0 +1,88 @@
1
+ const t = {
2
+ openai: {
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 },
7
+ "gpt-5.4": { input: 2.5, output: 15, cachedInput: 0.25 },
8
+ "gpt-5.4-mini": { input: 0.75, output: 4.5, cachedInput: 0.075 },
9
+ "gpt-5.4-nano": { input: 0.2, output: 1.25, cachedInput: 0.02 },
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 },
21
+ o3: { input: 2, output: 8, cachedInput: 0.5 },
22
+ "o3-mini": { input: 1.1, output: 4.4, cachedInput: 0.55 },
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 }
27
+ },
28
+ anthropic: {
29
+ "claude-opus-4-7": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
30
+ "claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
31
+ "claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
32
+ "claude-sonnet-4-5": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
33
+ "claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
34
+ },
35
+ // Bedrock charges the same per-token rates as Anthropic direct for the
36
+ // Claude inference profiles; the model IDs differ (us.anthropic.* prefix).
37
+ bedrock: {
38
+ "us.anthropic.claude-opus-4-7": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
39
+ "us.anthropic.claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
40
+ "us.anthropic.claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
41
+ "us.anthropic.claude-sonnet-4-5": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
42
+ "us.anthropic.claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
43
+ },
44
+ gemini: {
45
+ // Gemini 3.x — current lineup (May 2026)
46
+ "gemini-3.5-flash": { input: 1.5, output: 9, cachedInput: 0.15 },
47
+ "gemini-3.1-pro-preview": { input: 2, output: 12, cachedInput: 0.2 },
48
+ "gemini-3.1-flash-lite-preview": { input: 0.25, output: 1.5, cachedInput: 0.025 },
49
+ "gemini-3-pro-preview": { input: 2, output: 12 },
50
+ "gemini-3-flash-preview": { input: 0.5, output: 3, cachedInput: 0.05 },
51
+ // Gemini 2.5 — still in active use
52
+ "gemini-2.5-pro": { input: 1, output: 10, cachedInput: 0.125 },
53
+ "gemini-2.5-flash": { input: 0.3, output: 2.5, cachedInput: 0.03 },
54
+ "gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cachedInput: 0.01 }
55
+ },
56
+ grok: {
57
+ // Grok 4.3 (April 30, 2026 release) — current primary, 1M context
58
+ "grok-4.3": { input: 1.25, output: 2.5, cachedInput: 0.2 },
59
+ "grok-4.20-0309-reasoning": { input: 1.25, output: 2.5, cachedInput: 0.2 },
60
+ "grok-4.20-0309-non-reasoning": { input: 1.25, output: 2.5, cachedInput: 0.2 },
61
+ "grok-4.20-multi-agent-0309": { input: 1.25, output: 2.5, cachedInput: 0.2 },
62
+ "grok-4.20": { input: 1.25, output: 2.5, cachedInput: 0.2 },
63
+ "grok-build-0.1": { input: 1, output: 2 },
64
+ // Cheap fast tier
65
+ "grok-4-fast": { input: 0.2, output: 0.5 },
66
+ "grok-code-fast-1": { input: 0.2, output: 1.5 }
67
+ },
68
+ deepseek: {
69
+ "deepseek-v4-pro": { input: 0.435, output: 0.87, cachedInput: 36e-4 },
70
+ "deepseek-v4-flash": { input: 0.1, output: 0.2, cachedInput: 28e-4 },
71
+ "deepseek-v3.2": { input: 0.252, output: 0.378, cachedInput: 0.0252 },
72
+ "deepseek-v3.2-thinking": { input: 0.252, output: 0.378, cachedInput: 0.0252 },
73
+ "deepseek-chat": { input: 0.014, output: 0.028 },
74
+ "deepseek-reasoner": { input: 0.55, output: 2 }
75
+ },
76
+ // OpenRouter prices vary per upstream model and include their own margin —
77
+ // ship empty; callers can either register their own rates or use
78
+ // OpenRouterProvider.discoverModelsWithMetadata() (which returns pricing).
79
+ openrouter: {},
80
+ // Local inference — free at the per-token layer.
81
+ ollama: {},
82
+ "llama-server": {},
83
+ // Custom OpenAI-compatible endpoints: unknown by definition.
84
+ custom: {}
85
+ };
86
+ export {
87
+ t as DEFAULT_RATES
88
+ };