@veryfront/ext-llm-anthropic 0.1.985

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.
@@ -0,0 +1,413 @@
1
+ import * as dntShim from "./_dnt.shims.js";
2
+ import { mergeUsage, parseSseChunk, readGatewayBillingMode, readRecord, stringifyJsonValue, } from "veryfront/provider/shared";
3
+ const CLIENT_TOOL_USE_FINISH_REASON = { unified: "tool-calls", raw: "tool_use" };
4
+ const DEFAULT_CLIENT_TOOL_USE_TRAILING_USAGE_GRACE_MS = 100;
5
+ function isEmptyRecord(value) {
6
+ return Boolean(value &&
7
+ typeof value === "object" &&
8
+ !Array.isArray(value) &&
9
+ Object.keys(value).length === 0);
10
+ }
11
+ export function normalizeAnthropicFinishReason(raw) {
12
+ if (typeof raw !== "string") {
13
+ return null;
14
+ }
15
+ switch (raw) {
16
+ case "tool_use":
17
+ return { unified: "tool-calls", raw };
18
+ case "end_turn":
19
+ case "stop_sequence":
20
+ return { unified: "stop", raw };
21
+ case "max_tokens":
22
+ return { unified: "length", raw };
23
+ default:
24
+ return raw;
25
+ }
26
+ }
27
+ export function extractAnthropicUsage(payload) {
28
+ const record = readRecord(payload);
29
+ const usage = readRecord(record?.usage);
30
+ if (!usage) {
31
+ return undefined;
32
+ }
33
+ const inputTokens = usage.input_tokens;
34
+ const outputTokens = usage.output_tokens;
35
+ const cacheCreationInputTokens = usage.cache_creation_input_tokens;
36
+ const cacheReadInputTokens = usage.cache_read_input_tokens;
37
+ const veryfront = readRecord(usage.veryfront);
38
+ const costSource = veryfront?.cost_source;
39
+ const billingMode = readGatewayBillingMode(veryfront?.billing_mode);
40
+ const usageCaptureStatus = veryfront?.usage_capture_status;
41
+ return {
42
+ inputTokens: typeof inputTokens === "number" ? inputTokens : undefined,
43
+ outputTokens: typeof outputTokens === "number" ? outputTokens : undefined,
44
+ totalTokens: typeof inputTokens === "number" || typeof outputTokens === "number"
45
+ ? (typeof inputTokens === "number" ? inputTokens : 0) +
46
+ (typeof outputTokens === "number" ? outputTokens : 0)
47
+ : undefined,
48
+ ...(typeof cacheCreationInputTokens === "number" ? { cacheCreationInputTokens } : {}),
49
+ ...(typeof cacheReadInputTokens === "number" ? { cacheReadInputTokens } : {}),
50
+ ...(typeof veryfront?.billable_input_tokens === "number"
51
+ ? { billableInputTokens: veryfront.billable_input_tokens }
52
+ : {}),
53
+ ...(typeof veryfront?.billable_output_tokens === "number"
54
+ ? { billableOutputTokens: veryfront.billable_output_tokens }
55
+ : {}),
56
+ ...(typeof veryfront?.provider_input_cost_usd === "number"
57
+ ? { providerInputCostUsd: veryfront.provider_input_cost_usd }
58
+ : {}),
59
+ ...(typeof veryfront?.provider_output_cost_usd === "number"
60
+ ? { providerOutputCostUsd: veryfront.provider_output_cost_usd }
61
+ : {}),
62
+ ...(typeof veryfront?.provider_cost_usd === "number"
63
+ ? { providerCostUsd: veryfront.provider_cost_usd }
64
+ : {}),
65
+ ...(typeof veryfront?.veryfront_input_charge_usd === "number"
66
+ ? { veryfrontInputChargeUsd: veryfront.veryfront_input_charge_usd }
67
+ : {}),
68
+ ...(typeof veryfront?.veryfront_output_charge_usd === "number"
69
+ ? { veryfrontOutputChargeUsd: veryfront.veryfront_output_charge_usd }
70
+ : {}),
71
+ ...(typeof veryfront?.veryfront_charge_usd === "number"
72
+ ? { veryfrontChargeUsd: veryfront.veryfront_charge_usd }
73
+ : {}),
74
+ ...(typeof veryfront?.veryfront_billed_usd === "number"
75
+ ? { veryfrontBilledUsd: veryfront.veryfront_billed_usd }
76
+ : {}),
77
+ ...(typeof veryfront?.cost_credits === "number" ? { costCredits: veryfront.cost_credits } : {}),
78
+ ...(costSource === "gateway" || costSource === "missing" || costSource === "partial"
79
+ ? { costSource }
80
+ : {}),
81
+ ...(billingMode !== undefined ? { billingMode } : {}),
82
+ ...(usageCaptureStatus === "complete" ||
83
+ usageCaptureStatus === "missing" ||
84
+ usageCaptureStatus === "partial"
85
+ ? { usageCaptureStatus }
86
+ : {}),
87
+ };
88
+ }
89
+ function isToolCallsFinishReason(finishReason) {
90
+ return finishReason === "tool-calls" ||
91
+ (typeof finishReason === "object" && finishReason?.unified === "tool-calls");
92
+ }
93
+ function hasGatewayUsageMetadata(usage) {
94
+ return usage?.billableInputTokens !== undefined ||
95
+ usage?.billableOutputTokens !== undefined ||
96
+ usage?.providerInputCostUsd !== undefined ||
97
+ usage?.providerOutputCostUsd !== undefined ||
98
+ usage?.providerCostUsd !== undefined ||
99
+ usage?.veryfrontInputChargeUsd !== undefined ||
100
+ usage?.veryfrontOutputChargeUsd !== undefined ||
101
+ usage?.veryfrontChargeUsd !== undefined ||
102
+ usage?.veryfrontBilledUsd !== undefined ||
103
+ usage?.costCredits !== undefined ||
104
+ usage?.costSource !== undefined ||
105
+ usage?.billingMode !== undefined ||
106
+ usage?.usageCaptureStatus !== undefined;
107
+ }
108
+ async function readStreamChunk(reader, timeoutMs) {
109
+ if (timeoutMs === undefined) {
110
+ const read = await reader.read();
111
+ return read.done ? { kind: "done" } : { kind: "chunk", chunk: read.value };
112
+ }
113
+ let timeoutId;
114
+ const readPromise = reader.read().then((read) => read.done ? { kind: "done" } : { kind: "chunk", chunk: read.value });
115
+ const timeoutPromise = new Promise((resolve) => {
116
+ timeoutId = dntShim.setTimeout(() => resolve({ kind: "timeout" }), Math.max(1, timeoutMs));
117
+ });
118
+ try {
119
+ const result = await Promise.race([readPromise, timeoutPromise]);
120
+ if (result.kind === "timeout") {
121
+ await cancelStreamReader(reader, "Timed out waiting for trailing Anthropic tool-use usage metadata");
122
+ }
123
+ return result;
124
+ }
125
+ finally {
126
+ if (timeoutId) {
127
+ clearTimeout(timeoutId);
128
+ }
129
+ }
130
+ }
131
+ async function cancelStreamReader(reader, reason) {
132
+ try {
133
+ await reader.cancel(reason);
134
+ }
135
+ catch {
136
+ // The upstream body may already be closed or canceled by the runtime.
137
+ }
138
+ }
139
+ export async function* streamAnthropicCompatibleParts(stream, options = {}) {
140
+ const decoder = new TextDecoder();
141
+ const reader = stream.getReader();
142
+ const trailingUsageGraceMs = options.clientToolUseTrailingUsageGraceMs ??
143
+ DEFAULT_CLIENT_TOOL_USE_TRAILING_USAGE_GRACE_MS;
144
+ let buffer = "";
145
+ const toolCalls = new Map();
146
+ const reasoningBlocks = new Map();
147
+ let finishReason = null;
148
+ let usage;
149
+ let completedClientToolUseStep = false;
150
+ let clientToolUseIdleDeadlineMs = null;
151
+ let clientToolUseTerminalDeadlineMs = null;
152
+ const mergeTrailingBufferUsage = () => {
153
+ if (buffer.trim().length === 0) {
154
+ return;
155
+ }
156
+ const parsed = parseSseChunk(`${buffer}\n\n`);
157
+ buffer = parsed.remainder;
158
+ for (const event of parsed.events) {
159
+ if (event === "[DONE]") {
160
+ continue;
161
+ }
162
+ const record = readRecord(event);
163
+ usage = mergeUsage(usage, extractAnthropicUsage(record));
164
+ }
165
+ };
166
+ const getClientToolUseReadTimeoutMs = () => {
167
+ if (!completedClientToolUseStep || toolCalls.size > 0) {
168
+ return undefined;
169
+ }
170
+ const deadline = isToolCallsFinishReason(finishReason)
171
+ ? clientToolUseTerminalDeadlineMs
172
+ : clientToolUseIdleDeadlineMs;
173
+ return deadline === null ? undefined : Math.max(1, deadline - Date.now());
174
+ };
175
+ const buildFinishPart = () => ({
176
+ type: "finish",
177
+ finishReason: finishReason ??
178
+ (completedClientToolUseStep ? CLIENT_TOOL_USE_FINISH_REASON : null),
179
+ ...(usage ? { usage } : {}),
180
+ });
181
+ try {
182
+ while (true) {
183
+ const read = await readStreamChunk(reader, getClientToolUseReadTimeoutMs());
184
+ if (read.kind === "timeout") {
185
+ mergeTrailingBufferUsage();
186
+ finishReason ??= CLIENT_TOOL_USE_FINISH_REASON;
187
+ yield buildFinishPart();
188
+ return;
189
+ }
190
+ if (read.kind === "done") {
191
+ break;
192
+ }
193
+ buffer += decoder.decode(read.chunk, { stream: true });
194
+ const parsed = parseSseChunk(buffer);
195
+ buffer = parsed.remainder;
196
+ for (const event of parsed.events) {
197
+ if (event === "[DONE]") {
198
+ continue;
199
+ }
200
+ const record = readRecord(event);
201
+ const eventType = typeof record?.type === "string" ? record.type : undefined;
202
+ usage = mergeUsage(usage, extractAnthropicUsage(record));
203
+ if (eventType === "message_start") {
204
+ usage = mergeUsage(usage, extractAnthropicUsage(record?.message));
205
+ continue;
206
+ }
207
+ if (eventType === "content_block_start") {
208
+ const index = typeof record?.index === "number" ? record.index : 0;
209
+ const contentBlock = readRecord(record?.content_block);
210
+ const blockType = typeof contentBlock?.type === "string" ? contentBlock.type : undefined;
211
+ if (blockType === "text" && typeof contentBlock?.text === "string" &&
212
+ contentBlock.text.length > 0) {
213
+ yield { type: "text-delta", delta: contentBlock.text };
214
+ continue;
215
+ }
216
+ if (blockType === "thinking") {
217
+ const reasoningId = `thinking-${index}`;
218
+ reasoningBlocks.set(index, { id: reasoningId, text: "" });
219
+ yield {
220
+ type: "reasoning-start",
221
+ id: reasoningId,
222
+ };
223
+ if (typeof contentBlock?.thinking === "string" && contentBlock.thinking.length > 0) {
224
+ const current = reasoningBlocks.get(index);
225
+ if (current) {
226
+ current.text += contentBlock.thinking;
227
+ }
228
+ yield {
229
+ type: "reasoning-delta",
230
+ id: reasoningId,
231
+ delta: contentBlock.thinking,
232
+ };
233
+ }
234
+ continue;
235
+ }
236
+ // Redacted thinking blocks arrive as opaque encrypted payloads when
237
+ // Claude's safety classifier flags the reasoning trace. Surface them
238
+ // as a zero-length reasoning block so callers know thinking happened
239
+ // without leaking the (legitimately hidden) contents.
240
+ if (blockType === "redacted_thinking") {
241
+ const reasoningId = `thinking-${index}`;
242
+ reasoningBlocks.set(index, {
243
+ id: reasoningId,
244
+ text: "",
245
+ ...(typeof contentBlock?.data === "string"
246
+ ? { redactedData: contentBlock.data }
247
+ : {}),
248
+ });
249
+ yield {
250
+ type: "reasoning-start",
251
+ id: reasoningId,
252
+ };
253
+ continue;
254
+ }
255
+ if ((blockType === "tool_use" || blockType === "server_tool_use") &&
256
+ typeof contentBlock?.id === "string" &&
257
+ typeof contentBlock?.name === "string") {
258
+ const providerExecuted = blockType === "server_tool_use" ? true : undefined;
259
+ const current = {
260
+ id: contentBlock.id,
261
+ name: contentBlock.name,
262
+ input: "",
263
+ ...(providerExecuted ? { providerExecuted } : {}),
264
+ };
265
+ toolCalls.set(index, current);
266
+ clientToolUseIdleDeadlineMs = null;
267
+ clientToolUseTerminalDeadlineMs = null;
268
+ yield {
269
+ type: "tool-input-start",
270
+ id: current.id,
271
+ toolName: current.name,
272
+ ...(providerExecuted ? { providerExecuted } : {}),
273
+ };
274
+ const initialInput = contentBlock.input;
275
+ if (initialInput !== undefined && !isEmptyRecord(initialInput)) {
276
+ const serializedInput = stringifyJsonValue(initialInput);
277
+ current.input += serializedInput;
278
+ yield {
279
+ type: "tool-input-delta",
280
+ id: current.id,
281
+ delta: serializedInput,
282
+ };
283
+ }
284
+ continue;
285
+ }
286
+ if (blockType === "web_search_tool_result" &&
287
+ typeof contentBlock?.tool_use_id === "string" &&
288
+ Array.isArray(contentBlock?.content)) {
289
+ yield {
290
+ type: "tool-result",
291
+ toolCallId: contentBlock.tool_use_id,
292
+ toolName: "web_search",
293
+ result: contentBlock.content,
294
+ providerExecuted: true,
295
+ };
296
+ }
297
+ if (blockType === "web_fetch_tool_result" &&
298
+ typeof contentBlock?.tool_use_id === "string" &&
299
+ readRecord(contentBlock?.content)) {
300
+ yield {
301
+ type: "tool-result",
302
+ toolCallId: contentBlock.tool_use_id,
303
+ toolName: "web_fetch",
304
+ result: contentBlock.content,
305
+ providerExecuted: true,
306
+ };
307
+ }
308
+ continue;
309
+ }
310
+ if (eventType === "content_block_delta") {
311
+ const index = typeof record?.index === "number" ? record.index : 0;
312
+ const delta = readRecord(record?.delta);
313
+ const deltaType = typeof delta?.type === "string" ? delta.type : undefined;
314
+ if (deltaType === "text_delta" && typeof delta?.text === "string" && delta.text.length > 0) {
315
+ yield { type: "text-delta", delta: delta.text };
316
+ continue;
317
+ }
318
+ if (deltaType === "thinking_delta" && typeof delta?.thinking === "string" &&
319
+ delta.thinking.length > 0) {
320
+ const current = reasoningBlocks.get(index);
321
+ if (!current) {
322
+ continue;
323
+ }
324
+ current.text += delta.thinking;
325
+ yield {
326
+ type: "reasoning-delta",
327
+ id: current.id,
328
+ delta: delta.thinking,
329
+ };
330
+ continue;
331
+ }
332
+ if (deltaType === "signature_delta" && typeof delta?.signature === "string") {
333
+ const current = reasoningBlocks.get(index);
334
+ if (current) {
335
+ current.signature = delta.signature;
336
+ }
337
+ continue;
338
+ }
339
+ if (deltaType === "input_json_delta" && typeof delta?.partial_json === "string") {
340
+ const current = toolCalls.get(index);
341
+ if (!current) {
342
+ continue;
343
+ }
344
+ current.input += delta.partial_json;
345
+ yield {
346
+ type: "tool-input-delta",
347
+ id: current.id,
348
+ delta: delta.partial_json,
349
+ };
350
+ }
351
+ continue;
352
+ }
353
+ if (eventType === "content_block_stop") {
354
+ const index = typeof record?.index === "number" ? record.index : 0;
355
+ const reasoning = reasoningBlocks.get(index);
356
+ if (reasoning) {
357
+ yield {
358
+ type: "reasoning-end",
359
+ id: reasoning.id,
360
+ ...(reasoning.signature ? { signature: reasoning.signature } : {}),
361
+ ...(reasoning.redactedData ? { redactedData: reasoning.redactedData } : {}),
362
+ };
363
+ reasoningBlocks.delete(index);
364
+ continue;
365
+ }
366
+ const current = toolCalls.get(index);
367
+ if (!current) {
368
+ continue;
369
+ }
370
+ yield {
371
+ type: "tool-call",
372
+ toolCallId: current.id,
373
+ toolName: current.name,
374
+ input: current.input.length > 0 ? current.input : "{}",
375
+ ...(current.providerExecuted ? { providerExecuted: true } : {}),
376
+ };
377
+ if (!current.providerExecuted) {
378
+ completedClientToolUseStep = true;
379
+ clientToolUseIdleDeadlineMs = null;
380
+ }
381
+ toolCalls.delete(index);
382
+ continue;
383
+ }
384
+ if (eventType === "message_delta") {
385
+ const delta = readRecord(record?.delta);
386
+ const normalizedFinishReason = normalizeAnthropicFinishReason(delta?.stop_reason);
387
+ if (normalizedFinishReason) {
388
+ finishReason = normalizedFinishReason;
389
+ }
390
+ }
391
+ }
392
+ if (completedClientToolUseStep && toolCalls.size === 0) {
393
+ if (isToolCallsFinishReason(finishReason)) {
394
+ clientToolUseIdleDeadlineMs = null;
395
+ clientToolUseTerminalDeadlineMs ??= Date.now() + trailingUsageGraceMs;
396
+ if (hasGatewayUsageMetadata(usage)) {
397
+ await cancelStreamReader(reader, "Finished Anthropic tool-use turn after gateway usage metadata");
398
+ yield buildFinishPart();
399
+ return;
400
+ }
401
+ }
402
+ else {
403
+ clientToolUseIdleDeadlineMs ??= Date.now() + trailingUsageGraceMs;
404
+ }
405
+ }
406
+ }
407
+ }
408
+ finally {
409
+ reader.releaseLock();
410
+ }
411
+ mergeTrailingBufferUsage();
412
+ yield buildFinishPart();
413
+ }
package/esm/index.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @veryfront/ext-llm-anthropic — registers the Anthropic provider into the
3
+ * core `LLMProviderRegistry`.
4
+ *
5
+ * @module extensions/ext-llm-anthropic
6
+ */
7
+ import "./_dnt.polyfills.js";
8
+ import type { ExtensionFactory } from "veryfront/extensions";
9
+ import { AnthropicProvider } from "./anthropic-provider.js";
10
+ declare const extAnthropic: ExtensionFactory;
11
+ export default extAnthropic;
12
+ export { AnthropicProvider };
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAE5D,QAAA,MAAM,YAAY,EAAE,gBAsBnB,CAAC;AAEF,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
package/esm/index.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @veryfront/ext-llm-anthropic — registers the Anthropic provider into the
3
+ * core `LLMProviderRegistry`.
4
+ *
5
+ * @module extensions/ext-llm-anthropic
6
+ */
7
+ import "./_dnt.polyfills.js";
8
+ import { LLMProviderRegistryName } from "veryfront/extensions/llm";
9
+ import { AnthropicProvider } from "./anthropic-provider.js";
10
+ const extAnthropic = () => {
11
+ const provider = new AnthropicProvider();
12
+ let registryRef;
13
+ return {
14
+ name: "ext-llm-anthropic",
15
+ version: "0.1.0",
16
+ contracts: {
17
+ provides: ["LLMProvider:anthropic"],
18
+ requires: [LLMProviderRegistryName],
19
+ },
20
+ capabilities: [],
21
+ setup(ctx) {
22
+ const registry = ctx.require(LLMProviderRegistryName);
23
+ registry.register(provider);
24
+ registryRef = registry;
25
+ ctx.logger.info("[ext-llm-anthropic] Anthropic provider registered");
26
+ },
27
+ teardown() {
28
+ registryRef?.unregister(provider.id);
29
+ registryRef = undefined;
30
+ },
31
+ };
32
+ };
33
+ export default extAnthropic;
34
+ export { AnthropicProvider };
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@veryfront/ext-llm-anthropic",
3
+ "version": "0.1.985",
4
+ "description": "Veryfront first-party extension package for ext-llm-anthropic",
5
+ "keywords": [
6
+ "veryfront",
7
+ "extension",
8
+ "ext-llm-anthropic"
9
+ ],
10
+ "author": "Veryfront",
11
+ "homepage": "https://github.com/veryfront/veryfront-code/tree/main/extensions/ext-llm-anthropic",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/veryfront/veryfront-code.git",
15
+ "directory": "extensions/ext-llm-anthropic"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "bugs": {
19
+ "url": "https://github.com/veryfront/veryfront-code/issues"
20
+ },
21
+ "module": "./esm/index.js",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./esm/index.js",
25
+ "types": "./esm/index.d.ts"
26
+ }
27
+ },
28
+ "scripts": {},
29
+ "engines": {
30
+ "node": ">=18.0.0"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "veryfront": {
36
+ "extension": true,
37
+ "contracts": {
38
+ "provides": [
39
+ "LLMProvider:anthropic"
40
+ ],
41
+ "requires": [
42
+ "LLMProviderRegistry"
43
+ ]
44
+ },
45
+ "capabilities": []
46
+ },
47
+ "dependencies": {
48
+ "@deno/shim-deno": "~0.18.0",
49
+ "@deno/shim-crypto": "~0.3.1",
50
+ "@deno/shim-timers": "~0.1.0"
51
+ },
52
+ "peerDependencies": {
53
+ "veryfront": "^0.1.985"
54
+ },
55
+ "type": "module",
56
+ "types": "./esm/index.d.ts",
57
+ "files": [
58
+ "esm",
59
+ "LICENSE",
60
+ "NOTICE",
61
+ "README.md"
62
+ ]
63
+ }