@anvaka/vue-llm 0.3.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 +17 -0
- package/dist/core/LLMClient.js +21 -18
- package/dist/pricing/rates.js +43 -35
- package/dist/providers/AnthropicProvider.js +136 -91
- package/package.json +3 -2
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:
|
package/dist/core/LLMClient.js
CHANGED
|
@@ -120,16 +120,16 @@ class P {
|
|
|
120
120
|
temperature: n,
|
|
121
121
|
model: o,
|
|
122
122
|
maxTokens: l,
|
|
123
|
-
enableThinking:
|
|
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
|
|
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 (;
|
|
132
|
-
|
|
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:
|
|
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:
|
|
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
|
|
260
|
-
return await this._streamIntoTarget(e,
|
|
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),
|
|
279
|
-
throw new Error(`Provider preset '${t}' not found.${
|
|
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
|
|
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), (
|
|
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 = "",
|
|
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 && (
|
|
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 }),
|
|
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
|
|
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 = (
|
|
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 = (
|
|
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 {
|
package/dist/pricing/rates.js
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
const t = {
|
|
2
2
|
openai: {
|
|
3
|
-
|
|
4
|
-
"gpt-5-
|
|
5
|
-
"gpt-5
|
|
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
|
-
|
|
10
|
-
"gpt-
|
|
11
|
-
"gpt-
|
|
12
|
-
"gpt-
|
|
13
|
-
"gpt-
|
|
14
|
-
"gpt-
|
|
15
|
-
"gpt-
|
|
16
|
-
"gpt-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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: {
|
|
24
29
|
"claude-opus-4-7": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
|
|
25
30
|
"claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
|
|
26
31
|
"claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
|
|
27
32
|
"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 }
|
|
33
|
+
"claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
|
|
33
34
|
},
|
|
34
35
|
// Bedrock charges the same per-token rates as Anthropic direct for the
|
|
35
36
|
// Claude inference profiles; the model IDs differ (us.anthropic.* prefix).
|
|
@@ -38,32 +39,39 @@ const t = {
|
|
|
38
39
|
"us.anthropic.claude-opus-4-6": { input: 5, output: 25, cachedInput: 0.5, cacheCreation: 6.25 },
|
|
39
40
|
"us.anthropic.claude-sonnet-4-6": { input: 3, output: 15, cachedInput: 0.3, cacheCreation: 3.75 },
|
|
40
41
|
"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 }
|
|
42
|
+
"us.anthropic.claude-haiku-4-5": { input: 1, output: 5, cachedInput: 0.1, cacheCreation: 1.25 }
|
|
43
43
|
},
|
|
44
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
|
|
45
52
|
"gemini-2.5-pro": { input: 1, output: 10, cachedInput: 0.125 },
|
|
46
53
|
"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 }
|
|
54
|
+
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4, cachedInput: 0.01 }
|
|
52
55
|
},
|
|
53
56
|
grok: {
|
|
54
|
-
|
|
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
|
|
55
65
|
"grok-4-fast": { input: 0.2, output: 0.5 },
|
|
56
|
-
"grok-
|
|
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 }
|
|
66
|
+
"grok-code-fast-1": { input: 0.2, output: 1.5 }
|
|
61
67
|
},
|
|
62
68
|
deepseek: {
|
|
63
|
-
"deepseek-
|
|
64
|
-
"deepseek-reasoner": { input: 0.55, output: 2 },
|
|
69
|
+
"deepseek-v4-pro": { input: 0.435, output: 0.87, cachedInput: 36e-4 },
|
|
65
70
|
"deepseek-v4-flash": { input: 0.1, output: 0.2, cachedInput: 28e-4 },
|
|
66
|
-
"deepseek-
|
|
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 }
|
|
67
75
|
},
|
|
68
76
|
// OpenRouter prices vary per upstream model and include their own margin —
|
|
69
77
|
// ship empty; callers can either register their own rates or use
|
|
@@ -1,77 +1,86 @@
|
|
|
1
|
-
import { BaseProvider as
|
|
2
|
-
class
|
|
1
|
+
import { BaseProvider as g } from "./BaseProvider.js";
|
|
2
|
+
class C extends g {
|
|
3
3
|
async detectCapabilities() {
|
|
4
|
-
var
|
|
5
|
-
((
|
|
6
|
-
}
|
|
7
|
-
prepareRequest(
|
|
8
|
-
const
|
|
9
|
-
model:
|
|
10
|
-
max_tokens:
|
|
11
|
-
messages:
|
|
12
|
-
stream:
|
|
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 = _(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
|
-
|
|
15
|
-
const c =
|
|
16
|
-
return c && (
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
s.includes("claude-opus-4-7") || (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 = m(e.tools), e.tool_choice && (r.tool_choice = e.tool_choice)), this.promptCachingEnabled(e) && y(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
|
|
37
|
+
source: { type: "base64", media_type: "image/jpeg", data: typeof r == "string" ? r : r.data }
|
|
29
38
|
});
|
|
30
|
-
}),
|
|
39
|
+
}), o.content = s;
|
|
31
40
|
}
|
|
32
|
-
return
|
|
41
|
+
return t;
|
|
33
42
|
}
|
|
34
|
-
processResponse(
|
|
35
|
-
const
|
|
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: (
|
|
38
|
-
usage:
|
|
39
|
-
finishReason:
|
|
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(
|
|
44
|
-
if (!
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
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(
|
|
57
|
+
return JSON.parse(e);
|
|
49
58
|
} catch {
|
|
50
59
|
return null;
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
|
-
extractStreamingContent(
|
|
54
|
-
var
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
const a = ((
|
|
58
|
-
throw u.code = a,
|
|
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 (
|
|
61
|
-
return { content: "", thinking: "", done: !1, usage:
|
|
62
|
-
if (
|
|
63
|
-
const a =
|
|
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:
|
|
76
|
+
toolCallDelta: { index: t.index, id: a.id, name: a.name, argsTextDelta: "" }
|
|
68
77
|
} : null;
|
|
69
78
|
}
|
|
70
|
-
return
|
|
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:
|
|
74
|
-
} : 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,103 @@ class y extends p {
|
|
|
86
95
|
return this.config.apiKey;
|
|
87
96
|
}
|
|
88
97
|
buildHeaders() {
|
|
89
|
-
const
|
|
90
|
-
return this.requiresAuth() && (
|
|
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(
|
|
96
|
-
var
|
|
97
|
-
return ((
|
|
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
|
|
101
|
-
if (!
|
|
102
|
-
const
|
|
103
|
-
return
|
|
109
|
+
function h(n) {
|
|
110
|
+
if (!n) return null;
|
|
111
|
+
const t = String(n).toLowerCase();
|
|
112
|
+
return t === "max_tokens" ? "length" : t;
|
|
104
113
|
}
|
|
105
|
-
function
|
|
106
|
-
if (!
|
|
107
|
-
const
|
|
108
|
-
return
|
|
114
|
+
function f(n) {
|
|
115
|
+
if (!n) return null;
|
|
116
|
+
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 };
|
|
117
|
+
return n.cache_read_input_tokens != null && (i.cachedInputTokens = e), n.cache_creation_input_tokens != null && (i.cacheCreationInputTokens = o), i;
|
|
109
118
|
}
|
|
110
|
-
function _(
|
|
111
|
-
const
|
|
112
|
-
for (const
|
|
113
|
-
if (
|
|
114
|
-
if (
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
for (const
|
|
118
|
-
|
|
119
|
+
function _(n) {
|
|
120
|
+
const t = [];
|
|
121
|
+
for (const e of n)
|
|
122
|
+
if (e.role !== "system") {
|
|
123
|
+
if (e.role === "assistant" && Array.isArray(e.tool_calls) && e.tool_calls.length) {
|
|
124
|
+
const o = [];
|
|
125
|
+
e.content && o.push({ type: "text", text: e.content });
|
|
126
|
+
for (const s of e.tool_calls)
|
|
127
|
+
o.push({
|
|
119
128
|
type: "tool_use",
|
|
120
|
-
id:
|
|
121
|
-
name:
|
|
122
|
-
input:
|
|
129
|
+
id: s.id,
|
|
130
|
+
name: s.name,
|
|
131
|
+
input: s.args || {}
|
|
123
132
|
});
|
|
124
|
-
|
|
133
|
+
t.push({ role: "assistant", content: o });
|
|
125
134
|
continue;
|
|
126
135
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
136
|
+
if (e.role === "tool") {
|
|
137
|
+
t.push({
|
|
129
138
|
role: "user",
|
|
130
139
|
content: [{
|
|
131
140
|
type: "tool_result",
|
|
132
|
-
tool_use_id:
|
|
133
|
-
content: String(
|
|
141
|
+
tool_use_id: e.tool_call_id,
|
|
142
|
+
content: String(e.content ?? "")
|
|
134
143
|
}]
|
|
135
144
|
});
|
|
136
145
|
continue;
|
|
137
146
|
}
|
|
138
|
-
|
|
147
|
+
t.push({ role: e.role, content: e.content });
|
|
139
148
|
}
|
|
140
|
-
return
|
|
149
|
+
return t;
|
|
150
|
+
}
|
|
151
|
+
function m(n) {
|
|
152
|
+
return n.map((t) => t.type === "function" && t.function ? {
|
|
153
|
+
name: t.function.name,
|
|
154
|
+
description: t.function.description,
|
|
155
|
+
input_schema: t.function.parameters || { type: "object", properties: {} }
|
|
156
|
+
} : t);
|
|
141
157
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
const l = { type: "ephemeral" };
|
|
159
|
+
function y(n, t) {
|
|
160
|
+
if (n.system)
|
|
161
|
+
n.system = k(n.system);
|
|
162
|
+
else if (Array.isArray(n.tools) && n.tools.length) {
|
|
163
|
+
const e = n.tools.length - 1;
|
|
164
|
+
n.tools[e] = { ...n.tools[e], cache_control: l };
|
|
165
|
+
}
|
|
166
|
+
if (t != null && t.cacheTranscript) {
|
|
167
|
+
const e = n.messages;
|
|
168
|
+
if (Array.isArray(e) && e.length) {
|
|
169
|
+
const o = e[e.length - 1];
|
|
170
|
+
o.content = x(o.content);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return n;
|
|
174
|
+
}
|
|
175
|
+
function k(n) {
|
|
176
|
+
let t;
|
|
177
|
+
if (typeof n == "string")
|
|
178
|
+
t = [{ type: "text", text: n }];
|
|
179
|
+
else if (Array.isArray(n) && n.length)
|
|
180
|
+
t = n.map((e) => typeof e == "string" ? { type: "text", text: e } : { ...e });
|
|
181
|
+
else
|
|
182
|
+
return n;
|
|
183
|
+
return t[t.length - 1] = { ...t[t.length - 1], cache_control: l }, t;
|
|
184
|
+
}
|
|
185
|
+
function x(n) {
|
|
186
|
+
if (typeof n == "string")
|
|
187
|
+
return [{ type: "text", text: n, cache_control: l }];
|
|
188
|
+
if (Array.isArray(n) && n.length) {
|
|
189
|
+
const t = n.slice(), e = t.length - 1, o = t[e];
|
|
190
|
+
return t[e] = typeof o == "string" ? { type: "text", text: o, cache_control: l } : { ...o, cache_control: l }, t;
|
|
191
|
+
}
|
|
192
|
+
return n;
|
|
148
193
|
}
|
|
149
194
|
export {
|
|
150
|
-
|
|
151
|
-
|
|
195
|
+
C as AnthropicProvider,
|
|
196
|
+
f as normalizeAnthropicUsage
|
|
152
197
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anvaka/vue-llm",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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
|
".": {
|