@usagetap/sdk 0.10.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -22
  3. package/dist/adapters/anthropic.cjs +943 -0
  4. package/dist/adapters/anthropic.cjs.map +1 -0
  5. package/dist/adapters/anthropic.d.cts +81 -0
  6. package/dist/adapters/anthropic.d.ts +81 -0
  7. package/dist/adapters/anthropic.mjs +940 -0
  8. package/dist/adapters/anthropic.mjs.map +1 -0
  9. package/dist/adapters/openai.cjs +601 -17
  10. package/dist/adapters/openai.cjs.map +1 -1
  11. package/dist/adapters/openai.d.cts +57 -2
  12. package/dist/adapters/openai.d.ts +57 -2
  13. package/dist/adapters/openai.mjs +601 -18
  14. package/dist/adapters/openai.mjs.map +1 -1
  15. package/dist/adapters/openrouter.cjs.map +1 -1
  16. package/dist/adapters/openrouter.d.cts +1 -1
  17. package/dist/adapters/openrouter.d.ts +1 -1
  18. package/dist/adapters/openrouter.mjs.map +1 -1
  19. package/dist/anthropic/index.cjs +943 -0
  20. package/dist/anthropic/index.cjs.map +1 -0
  21. package/dist/anthropic/index.d.cts +2 -0
  22. package/dist/anthropic/index.d.ts +2 -0
  23. package/dist/anthropic/index.mjs +940 -0
  24. package/dist/anthropic/index.mjs.map +1 -0
  25. package/dist/{client-DEbk0Q2l.d.cts → client-BA-QlnRq.d.cts} +95 -1
  26. package/dist/{client-DEbk0Q2l.d.ts → client-BA-QlnRq.d.ts} +95 -1
  27. package/dist/express/index.cjs +597 -17
  28. package/dist/express/index.cjs.map +1 -1
  29. package/dist/express/index.d.cts +1 -1
  30. package/dist/express/index.d.ts +1 -1
  31. package/dist/express/index.mjs +597 -17
  32. package/dist/express/index.mjs.map +1 -1
  33. package/dist/index.cjs +586 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +2 -2
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.mjs +581 -2
  38. package/dist/index.mjs.map +1 -1
  39. package/dist/openai/index.cjs +601 -17
  40. package/dist/openai/index.cjs.map +1 -1
  41. package/dist/openai/index.d.cts +2 -2
  42. package/dist/openai/index.d.ts +2 -2
  43. package/dist/openai/index.mjs +601 -18
  44. package/dist/openai/index.mjs.map +1 -1
  45. package/package.json +22 -2
@@ -0,0 +1,943 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var UsageTapError = class extends Error {
5
+ code;
6
+ status;
7
+ retryable;
8
+ correlationId;
9
+ details;
10
+ constructor(code, message, init = {}) {
11
+ super(message, init.cause ? { cause: init.cause } : void 0);
12
+ this.name = "UsageTapError";
13
+ this.code = code;
14
+ this.status = init.status;
15
+ this.retryable = init.retryable ?? false;
16
+ this.correlationId = init.correlationId;
17
+ this.details = init.details;
18
+ }
19
+ toJSON() {
20
+ return {
21
+ name: this.name,
22
+ message: this.message,
23
+ code: this.code,
24
+ status: this.status,
25
+ retryable: this.retryable,
26
+ correlationId: this.correlationId,
27
+ details: this.details
28
+ };
29
+ }
30
+ };
31
+
32
+ // src/prompt-compression.ts
33
+ function estimatePromptTokens(input) {
34
+ const text = typeof input === "string" ? input : stableStringifyInput(input);
35
+ return text.match(/[\p{L}\p{N}]+|[^\s]/gu)?.length ?? 0;
36
+ }
37
+ function stableStringifyInput(input) {
38
+ if (typeof input === "string") return input;
39
+ return JSON.stringify(input) ?? String(input);
40
+ }
41
+
42
+ // src/adapters/anthropic.ts
43
+ var AnthropicPromptCompressionStats = class {
44
+ history = [];
45
+ failures = [];
46
+ _record(turn) {
47
+ this.history.push(turn);
48
+ }
49
+ _recordFailure(failure) {
50
+ this.failures.push(failure);
51
+ }
52
+ get totalOriginalTokens() {
53
+ return this.history.reduce((sum, turn) => sum + (turn.originalTokens ?? 0), 0);
54
+ }
55
+ get totalCompressedTokens() {
56
+ return this.history.reduce((sum, turn) => sum + (turn.compressedTokens ?? 0), 0);
57
+ }
58
+ get totalTokensSaved() {
59
+ return this.history.reduce((sum, turn) => sum + (turn.savedTokens ?? 0), 0);
60
+ }
61
+ get totalOriginalCharacters() {
62
+ return this.history.reduce((sum, turn) => sum + turn.originalCharacters, 0);
63
+ }
64
+ get totalCompressedCharacters() {
65
+ return this.history.reduce((sum, turn) => sum + turn.compressedCharacters, 0);
66
+ }
67
+ get totalCharactersSaved() {
68
+ return this.history.reduce((sum, turn) => sum + turn.savedCharacters, 0);
69
+ }
70
+ get calls() {
71
+ return this.history.length;
72
+ }
73
+ get telemetryFailures() {
74
+ return this.failures.length;
75
+ }
76
+ get failOpenEvents() {
77
+ return this.history.filter(
78
+ (turn) => turn.techniques.includes("compression-error") || turn.techniques.includes("fallback-original")
79
+ ).length;
80
+ }
81
+ get tokenSavingsRatio() {
82
+ return this.totalOriginalTokens > 0 ? this.totalTokensSaved / this.totalOriginalTokens : 0;
83
+ }
84
+ get savingsRatio() {
85
+ return this.totalOriginalCharacters > 0 ? this.totalCharactersSaved / this.totalOriginalCharacters : 0;
86
+ }
87
+ };
88
+ var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
89
+ function wrapAnthropic(client, usageTap, options = {}) {
90
+ if (!client) {
91
+ throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires an Anthropic client instance");
92
+ }
93
+ if (!client.messages || !isObjectRecord(client.messages)) {
94
+ throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages");
95
+ }
96
+ const defaultContext = options.defaultContext;
97
+ const applyVendorHints = options.applyVendorHints !== false;
98
+ const defaultPromptCompression = normalizePromptCompressionOptions(options.promptCompression);
99
+ const promptCompressionStats = new AnthropicPromptCompressionStats();
100
+ const proxiedMessages = createMessagesProxy(
101
+ client.messages,
102
+ usageTap,
103
+ defaultContext,
104
+ applyVendorHints,
105
+ defaultPromptCompression,
106
+ promptCompressionStats
107
+ );
108
+ const handler = {
109
+ get(target, prop, receiver) {
110
+ if (prop === "messages") {
111
+ return proxiedMessages;
112
+ }
113
+ if (prop === "promptCompression") {
114
+ return promptCompressionStats;
115
+ }
116
+ if (prop === "unwrap") {
117
+ return () => target;
118
+ }
119
+ return Reflect.get(target, prop, receiver);
120
+ }
121
+ };
122
+ return new Proxy(client, handler);
123
+ }
124
+ function createMessagesProxy(resource, usageTap, defaultContext, applyVendorHints, defaultPromptCompression, promptCompressionStats) {
125
+ if (typeof resource.create !== "function") {
126
+ throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapAnthropic requires client.messages.create");
127
+ }
128
+ const originalCreate = resource.create.bind(resource);
129
+ const wrappedCreate = ((params, options) => {
130
+ const {
131
+ requestOptions,
132
+ usageContext,
133
+ withUsage,
134
+ promptCompression
135
+ } = splitUsageOptions(options);
136
+ return invokeMessagesCreate({
137
+ params,
138
+ requestOptions,
139
+ usageContext,
140
+ withUsage,
141
+ promptCompression,
142
+ originalCreate,
143
+ usageTap,
144
+ defaultContext,
145
+ applyVendorHints,
146
+ defaultPromptCompression,
147
+ promptCompressionStats
148
+ });
149
+ });
150
+ const handler = {
151
+ get(target, prop, receiver) {
152
+ if (prop === "create") {
153
+ return wrappedCreate;
154
+ }
155
+ return Reflect.get(target, prop, receiver);
156
+ }
157
+ };
158
+ return new Proxy(resource, handler);
159
+ }
160
+ async function invokeMessagesCreate(args) {
161
+ const beginRequest = resolveBeginRequest(args.defaultContext, args.usageContext);
162
+ const begin = await args.usageTap.beginCall(
163
+ beginRequest,
164
+ beginCallOptions(args.withUsage)
165
+ );
166
+ const state = createCallState(beginRequest, begin);
167
+ const ctx = createUsageContext(state);
168
+ try {
169
+ const hintedParams = args.applyVendorHints ? applyAnthropicVendorHints(args.params, ctx.begin.data.vendorHints) : args.params;
170
+ const finalParams = await compressAnthropicParamsForCall({
171
+ params: hintedParams,
172
+ usageTap: args.usageTap,
173
+ ctx,
174
+ defaultPromptCompression: args.defaultPromptCompression,
175
+ callPromptCompression: args.promptCompression,
176
+ stats: args.promptCompressionStats,
177
+ withUsage: args.withUsage,
178
+ operation: "messages.create"
179
+ });
180
+ const request = attachCorrelationHeader(args.requestOptions, ctx.begin.correlationId);
181
+ const response = await args.originalCreate(finalParams, request);
182
+ if (isStreamingRequest(finalParams)) {
183
+ ensureAsyncIterable(response, "messages.create");
184
+ return wrapAnthropicStreamForUsageTap(
185
+ response,
186
+ state,
187
+ args.usageTap,
188
+ args.withUsage,
189
+ readString(finalParams.model)
190
+ );
191
+ }
192
+ inferAnthropicUsage(response, readString(finalParams.model), ctx);
193
+ await finalizeCall(state, args.usageTap, args.withUsage);
194
+ return response;
195
+ } catch (error) {
196
+ if (!state.error) {
197
+ state.error = {
198
+ code: args.withUsage?.defaultErrorCode ?? "VENDOR_ERROR",
199
+ message: error instanceof Error ? error.message : String(error)
200
+ };
201
+ }
202
+ await finalizeCall(state, args.usageTap, args.withUsage);
203
+ throw error;
204
+ }
205
+ }
206
+ async function compressAnthropicParamsForCall(args) {
207
+ const compression = resolveEffectivePromptCompressionOptions(
208
+ args.defaultPromptCompression,
209
+ args.callPromptCompression
210
+ );
211
+ if (!compression) {
212
+ return args.params;
213
+ }
214
+ const outcome = await compressAnthropicParams(
215
+ args.params,
216
+ args.usageTap,
217
+ compression,
218
+ args.withUsage?.signal
219
+ );
220
+ await recordCompressionOutcome({
221
+ outcome,
222
+ compression,
223
+ usageTap: args.usageTap,
224
+ ctx: args.ctx,
225
+ stats: args.stats,
226
+ withUsage: args.withUsage,
227
+ operation: args.operation
228
+ });
229
+ return outcome.params;
230
+ }
231
+ async function recordCompressionOutcome(args) {
232
+ const telemetry = buildPromptCompressionTelemetry(args.outcome.segments);
233
+ if (!telemetry) {
234
+ return;
235
+ }
236
+ const turn = {
237
+ ...telemetry,
238
+ callId: args.ctx.begin.data.callId,
239
+ operation: args.operation,
240
+ messagesCompressed: args.outcome.segments.length,
241
+ timestamp: Date.now()
242
+ };
243
+ args.stats._record(turn);
244
+ try {
245
+ await args.usageTap.recordPromptCompression(
246
+ {
247
+ callId: args.ctx.begin.data.callId,
248
+ promptCompression: telemetry
249
+ },
250
+ promptCompressionRequestOptions(args.withUsage, args.ctx.begin.correlationId)
251
+ );
252
+ } catch (error) {
253
+ args.stats._recordFailure({
254
+ callId: args.ctx.begin.data.callId,
255
+ operation: args.operation,
256
+ stage: "telemetry",
257
+ message: error instanceof Error ? error.message : String(error),
258
+ timestamp: Date.now()
259
+ });
260
+ if (args.compression.failOpen === false) {
261
+ throw error;
262
+ }
263
+ }
264
+ }
265
+ async function compressAnthropicParams(params, usageTap, compression, signal) {
266
+ if (!params || typeof params !== "object") {
267
+ return { params, segments: [] };
268
+ }
269
+ const source = cloneRecord(params);
270
+ const segments = [];
271
+ if (typeof source.system === "string") {
272
+ const compressed = await compressTextForRole(
273
+ source.system,
274
+ "system",
275
+ usageTap,
276
+ compression,
277
+ signal
278
+ );
279
+ if (compressed) {
280
+ source.system = compressed.text;
281
+ segments.push(compressed.segment);
282
+ }
283
+ } else if (Array.isArray(source.system)) {
284
+ const systemResults = await Promise.all(
285
+ source.system.map(
286
+ (block) => compressAnthropicTextBlock(block, "system", usageTap, compression, signal)
287
+ )
288
+ );
289
+ const systemSegments = systemResults.flatMap(
290
+ (result) => result.segment ? [result.segment] : []
291
+ );
292
+ if (systemSegments.length) {
293
+ source.system = systemResults.map((result) => result.value);
294
+ segments.push(...systemSegments);
295
+ }
296
+ }
297
+ if (Array.isArray(source.messages)) {
298
+ const messageResults = await Promise.all(
299
+ source.messages.map(
300
+ (message) => compressAnthropicMessage(message, usageTap, compression, signal)
301
+ )
302
+ );
303
+ source.messages = messageResults.map((result) => result.value);
304
+ segments.push(...messageResults.flatMap((result) => result.segments));
305
+ }
306
+ return {
307
+ params: source,
308
+ segments
309
+ };
310
+ }
311
+ async function compressAnthropicMessage(message, usageTap, compression, signal) {
312
+ if (!isObjectRecord(message)) {
313
+ return { value: message, segments: [] };
314
+ }
315
+ const role = mapAnthropicMessageRole(message.role);
316
+ if (!role) {
317
+ return { value: message, segments: [] };
318
+ }
319
+ const content = message.content;
320
+ if (typeof content === "string") {
321
+ const compressed = await compressTextForRole(
322
+ content,
323
+ role,
324
+ usageTap,
325
+ compression,
326
+ signal
327
+ );
328
+ if (!compressed) {
329
+ return { value: message, segments: [] };
330
+ }
331
+ return {
332
+ value: { ...message, content: compressed.text },
333
+ segments: [compressed.segment]
334
+ };
335
+ }
336
+ if (Array.isArray(content)) {
337
+ const blockResults = await Promise.all(
338
+ content.map(
339
+ (block) => compressAnthropicContentBlock(block, role, usageTap, compression, signal)
340
+ )
341
+ );
342
+ const segments = blockResults.flatMap((result) => result.segments);
343
+ return {
344
+ value: segments.length ? { ...message, content: blockResults.map((result) => result.value) } : message,
345
+ segments
346
+ };
347
+ }
348
+ return { value: message, segments: [] };
349
+ }
350
+ async function compressAnthropicContentBlock(block, messageRole, usageTap, compression, signal) {
351
+ if (!isObjectRecord(block)) {
352
+ return { value: block, segments: [] };
353
+ }
354
+ if (block.type === "tool_result") {
355
+ return compressAnthropicToolResultBlock(block, usageTap, compression, signal);
356
+ }
357
+ const textResult = await compressAnthropicTextBlock(
358
+ block,
359
+ messageRole,
360
+ usageTap,
361
+ compression,
362
+ signal
363
+ );
364
+ return {
365
+ value: textResult.value,
366
+ segments: textResult.segment ? [textResult.segment] : []
367
+ };
368
+ }
369
+ async function compressAnthropicToolResultBlock(block, usageTap, compression, signal) {
370
+ const content = block.content;
371
+ if (typeof content === "string") {
372
+ const compressed = await compressTextForRole(
373
+ content,
374
+ "tool",
375
+ usageTap,
376
+ compression,
377
+ signal
378
+ );
379
+ if (!compressed) {
380
+ return { value: block, segments: [] };
381
+ }
382
+ return {
383
+ value: { ...block, content: compressed.text },
384
+ segments: [compressed.segment]
385
+ };
386
+ }
387
+ if (Array.isArray(content)) {
388
+ const contentResults = await Promise.all(
389
+ content.map(
390
+ (child) => compressAnthropicTextBlock(child, "tool", usageTap, compression, signal)
391
+ )
392
+ );
393
+ const segments = contentResults.flatMap(
394
+ (result) => result.segment ? [result.segment] : []
395
+ );
396
+ if (!segments.length) {
397
+ return { value: block, segments: [] };
398
+ }
399
+ return {
400
+ value: {
401
+ ...block,
402
+ content: contentResults.map((result) => result.value)
403
+ },
404
+ segments
405
+ };
406
+ }
407
+ return { value: block, segments: [] };
408
+ }
409
+ async function compressAnthropicTextBlock(block, role, usageTap, compression, signal) {
410
+ if (!isObjectRecord(block) || block.type !== "text" || typeof block.text !== "string") {
411
+ return { value: block };
412
+ }
413
+ const compressed = await compressTextForRole(
414
+ block.text,
415
+ role,
416
+ usageTap,
417
+ compression,
418
+ signal
419
+ );
420
+ if (!compressed) {
421
+ return { value: block };
422
+ }
423
+ return {
424
+ value: { ...block, text: compressed.text },
425
+ segment: compressed.segment
426
+ };
427
+ }
428
+ async function compressTextForRole(text, role, usageTap, compression, signal) {
429
+ if (!text.trim()) {
430
+ return void 0;
431
+ }
432
+ const roleOptions = resolveRoleCompressionOptions(compression, role);
433
+ if (!roleOptions) {
434
+ return void 0;
435
+ }
436
+ const estimatedTokens = estimatePromptTokens(text);
437
+ if (typeof roleOptions.minTokens === "number" && estimatedTokens < roleOptions.minTokens) {
438
+ return void 0;
439
+ }
440
+ const result = await usageTap.compressPromptInput(text, {
441
+ provider: roleOptions.provider,
442
+ failOpen: roleOptions.failOpen,
443
+ tokenCompanyModel: roleOptions.tokenCompanyModel,
444
+ tokenCompanyAggressiveness: roleOptions.tokenCompanyAggressiveness,
445
+ tokenCompanyAppId: roleOptions.tokenCompanyAppId,
446
+ signal
447
+ });
448
+ const compressedText = typeof result.compressedInput === "string" ? result.compressedInput : String(result.compressedInput);
449
+ return {
450
+ text: compressedText,
451
+ segment: { role, result: { ...result, compressedInput: compressedText } }
452
+ };
453
+ }
454
+ function normalizePromptCompressionOptions(options) {
455
+ if (!options) {
456
+ return void 0;
457
+ }
458
+ if (options === true) {
459
+ return {};
460
+ }
461
+ if (options.enabled === false) {
462
+ return void 0;
463
+ }
464
+ return options;
465
+ }
466
+ function resolveEffectivePromptCompressionOptions(defaults, override) {
467
+ if (override === false) {
468
+ return void 0;
469
+ }
470
+ if (override === void 0) {
471
+ return defaults;
472
+ }
473
+ if (override === true) {
474
+ return defaults ?? {};
475
+ }
476
+ const merged = {
477
+ ...defaults ?? {},
478
+ ...override,
479
+ roles: override.roles ?? defaults?.roles
480
+ };
481
+ return normalizePromptCompressionOptions(merged);
482
+ }
483
+ function resolveRoleCompressionOptions(compression, role) {
484
+ const hasExplicitRoles = compression.roles !== void 0;
485
+ const setting = compression.roles?.[role];
486
+ if (hasExplicitRoles && setting === void 0) {
487
+ return void 0;
488
+ }
489
+ if (!hasExplicitRoles && role === "assistant") {
490
+ return void 0;
491
+ }
492
+ if (setting === false) {
493
+ return void 0;
494
+ }
495
+ const roleOptions = typeof setting === "object" ? setting : void 0;
496
+ if (roleOptions?.enabled === false) {
497
+ return void 0;
498
+ }
499
+ return {
500
+ provider: roleOptions?.provider ?? compression.provider,
501
+ minTokens: roleOptions?.minTokens ?? compression.minTokens,
502
+ failOpen: compression.failOpen,
503
+ tokenCompanyModel: compression.tokenCompanyModel,
504
+ tokenCompanyAggressiveness: roleOptions?.tokenCompanyAggressiveness ?? resolveTokenCompanyAggressiveness(compression, role),
505
+ tokenCompanyAppId: compression.tokenCompanyAppId
506
+ };
507
+ }
508
+ function resolveTokenCompanyAggressiveness(compression, role) {
509
+ if (typeof compression.tokenCompanyAggressiveness === "number") {
510
+ return compression.tokenCompanyAggressiveness;
511
+ }
512
+ return compression.tokenCompanyAggressiveness?.[role];
513
+ }
514
+ function buildPromptCompressionTelemetry(segments) {
515
+ if (!segments.length) {
516
+ return void 0;
517
+ }
518
+ const originalCharacters = segments.reduce(
519
+ (sum, segment) => sum + segment.result.originalCharacters,
520
+ 0
521
+ );
522
+ const compressedCharacters = segments.reduce(
523
+ (sum, segment) => sum + segment.result.compressedCharacters,
524
+ 0
525
+ );
526
+ const originalTokens = segments.reduce(
527
+ (sum, segment) => sum + segment.result.originalTokens,
528
+ 0
529
+ );
530
+ const compressedTokens = segments.reduce(
531
+ (sum, segment) => sum + segment.result.compressedTokens,
532
+ 0
533
+ );
534
+ const savedCharacters = Math.max(0, originalCharacters - compressedCharacters);
535
+ const savedTokens = Math.max(0, originalTokens - compressedTokens);
536
+ const providers = dedupeStrings(segments.map((segment) => segment.result.provider));
537
+ const roles = dedupeStrings(segments.map((segment) => `role:${segment.role}`));
538
+ const techniques = dedupeStrings([
539
+ "anthropic-wrapper",
540
+ ...roles,
541
+ ...segments.flatMap((segment) => segment.result.techniques),
542
+ ...providers.length > 1 ? ["mixed-providers"] : []
543
+ ]);
544
+ return {
545
+ provider: segments[0]?.result.provider ?? "heuristic",
546
+ originalCharacters,
547
+ compressedCharacters,
548
+ savedCharacters,
549
+ originalTokens,
550
+ compressedTokens,
551
+ savedTokens,
552
+ tokenSavingsRatio: originalTokens > 0 ? savedTokens / originalTokens : 0,
553
+ savingsRatio: originalCharacters > 0 ? savedCharacters / originalCharacters : 0,
554
+ techniques
555
+ };
556
+ }
557
+ function promptCompressionRequestOptions(withUsage, correlationId) {
558
+ return {
559
+ signal: withUsage?.signal,
560
+ headers: withUsage?.headers,
561
+ retries: withUsage?.retries,
562
+ correlationId
563
+ };
564
+ }
565
+ function splitUsageOptions(options) {
566
+ if (!options || typeof options !== "object") {
567
+ return {};
568
+ }
569
+ const { usageTap, withUsage, promptCompression, ...rest } = options;
570
+ const requestOptions = Object.keys(rest).length ? cloneRequestOptions(rest) : void 0;
571
+ return {
572
+ requestOptions,
573
+ usageContext: usageTap,
574
+ withUsage,
575
+ promptCompression
576
+ };
577
+ }
578
+ function resolveBeginRequest(defaults, override) {
579
+ const base = defaults ?? {};
580
+ const current = override ?? {};
581
+ const customerId = current.customerId ?? base.customerId;
582
+ if (!customerId) {
583
+ throw new UsageTapError(
584
+ "USAGETAP_BAD_REQUEST",
585
+ "wrapAnthropic requires usageTap.customerId (provide defaultContext or options.usageTap)"
586
+ );
587
+ }
588
+ const tags = mergeTags(base.tags, current.tags);
589
+ const begin = { customerId };
590
+ const requested = current.requested ?? base.requested;
591
+ if (requested) begin.requested = requested;
592
+ const feature = current.feature ?? base.feature;
593
+ if (feature) begin.feature = feature;
594
+ const idempotency = current.idempotency ?? base.idempotency;
595
+ if (idempotency) begin.idempotency = idempotency;
596
+ const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
597
+ if (idempotencyKey) begin.idempotencyKey = idempotencyKey;
598
+ const customerName = current.customerName ?? base.customerName;
599
+ if (customerName) begin.customerName = customerName;
600
+ const customerEmail = current.customerEmail ?? base.customerEmail;
601
+ if (customerEmail) begin.customerEmail = customerEmail;
602
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
603
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
604
+ const holdUsd = current.holdUsd ?? base.holdUsd;
605
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
606
+ const batch = current.batch ?? base.batch;
607
+ if (typeof batch === "boolean") begin.batch = batch;
608
+ const pricingMode = current.pricingMode ?? base.pricingMode;
609
+ if (pricingMode) begin.pricingMode = pricingMode;
610
+ if (tags?.length) {
611
+ begin.tags = tags;
612
+ }
613
+ return begin;
614
+ }
615
+ function createCallState(beginRequest, begin) {
616
+ const usage = {};
617
+ const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
618
+ if (stripeCustomerId) {
619
+ usage.stripeCustomerId = stripeCustomerId;
620
+ }
621
+ return {
622
+ beginRequest,
623
+ begin,
624
+ usage,
625
+ finalized: false
626
+ };
627
+ }
628
+ function createUsageContext(state) {
629
+ return {
630
+ begin: state.begin,
631
+ setUsage: (usage) => {
632
+ state.usage = { ...state.usage, ...usage };
633
+ },
634
+ setError: (error) => {
635
+ state.error = error;
636
+ }
637
+ };
638
+ }
639
+ async function finalizeCall(state, usageTap, options) {
640
+ if (state.finalized) {
641
+ return;
642
+ }
643
+ state.finalized = true;
644
+ await usageTap.endCall(
645
+ {
646
+ callId: state.begin.data.callId,
647
+ customerId: state.beginRequest.customerId,
648
+ feature: state.beginRequest.feature,
649
+ tags: state.beginRequest.tags,
650
+ ...state.usage,
651
+ error: state.error
652
+ },
653
+ endCallOptions(options, state.begin.correlationId)
654
+ );
655
+ }
656
+ function beginCallOptions(options) {
657
+ return {
658
+ signal: options?.signal,
659
+ headers: options?.headers,
660
+ retries: options?.retries,
661
+ correlationId: options?.correlationId
662
+ };
663
+ }
664
+ function endCallOptions(options, correlationId) {
665
+ return {
666
+ signal: options?.signal,
667
+ headers: options?.headers,
668
+ retries: options?.retries,
669
+ correlationId
670
+ };
671
+ }
672
+ function inferAnthropicUsage(response, fallbackModel, ctx) {
673
+ const usage = extractAnthropicUsage(response, fallbackModel);
674
+ if (usage) {
675
+ ctx.setUsage(usage);
676
+ }
677
+ }
678
+ function extractAnthropicUsage(payload, fallbackModel) {
679
+ if (!isObjectRecord(payload)) {
680
+ return fallbackModel ? { modelUsed: fallbackModel } : void 0;
681
+ }
682
+ const usage = findAnthropicUsageRecord(payload);
683
+ const result = {};
684
+ const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
685
+ if (model) {
686
+ result.modelUsed = model;
687
+ }
688
+ if (usage) {
689
+ applyAnthropicUsageRecord(result, usage);
690
+ }
691
+ return Object.keys(result).length ? result : void 0;
692
+ }
693
+ function findAnthropicUsageRecord(payload) {
694
+ if (isObjectRecord(payload.usage)) {
695
+ return payload.usage;
696
+ }
697
+ if (isObjectRecord(payload.message) && isObjectRecord(payload.message.usage)) {
698
+ return payload.message.usage;
699
+ }
700
+ return void 0;
701
+ }
702
+ function applyAnthropicUsageRecord(usage, usageRecord) {
703
+ const inputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
704
+ if (inputTokens !== void 0) {
705
+ usage.inputTokens = inputTokens;
706
+ }
707
+ const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
708
+ if (outputTokens !== void 0) {
709
+ usage.responseTokens = outputTokens;
710
+ }
711
+ const cachedInputTokens = readNumber(
712
+ usageRecord.cache_read_input_tokens ?? usageRecord.prompt_cache_hit_tokens ?? usageRecord.cached_tokens
713
+ );
714
+ if (cachedInputTokens !== void 0) {
715
+ usage.cachedInputTokens = cachedInputTokens;
716
+ }
717
+ }
718
+ function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
719
+ const usage = extractAnthropicUsage(chunk, fallbackModel);
720
+ if (usage) {
721
+ state.usage = { ...state.usage, ...usage };
722
+ }
723
+ }
724
+ function wrapAnthropicStreamForUsageTap(source, state, usageTap, options, fallbackModel) {
725
+ const getIterator = source[Symbol.asyncIterator];
726
+ if (typeof getIterator !== "function") {
727
+ throw new TypeError("Stream is not async iterable");
728
+ }
729
+ const iterator = getIterator.call(source);
730
+ const invokeFinalize = async (error) => {
731
+ if (error && !state.error) {
732
+ state.error = {
733
+ code: options?.defaultErrorCode ?? "VENDOR_ERROR",
734
+ message: error instanceof Error ? error.message : String(error)
735
+ };
736
+ }
737
+ await finalizeCall(state, usageTap, options);
738
+ };
739
+ const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
740
+ const wrapped = Object.create(prototype);
741
+ for (const key of Reflect.ownKeys(source)) {
742
+ try {
743
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
744
+ if (descriptor) {
745
+ Object.defineProperty(wrapped, key, descriptor);
746
+ }
747
+ } catch {
748
+ }
749
+ }
750
+ Object.defineProperty(wrapped, Symbol.asyncIterator, {
751
+ value() {
752
+ return this;
753
+ },
754
+ configurable: true
755
+ });
756
+ Object.defineProperty(wrapped, "next", {
757
+ value: async (...args) => {
758
+ try {
759
+ const result = await iterator.next(...args);
760
+ if (result.value !== void 0) {
761
+ accumulateAnthropicStreamUsage(result.value, fallbackModel, state);
762
+ }
763
+ if (result.done) {
764
+ await invokeFinalize();
765
+ }
766
+ return result;
767
+ } catch (error) {
768
+ await invokeFinalize(error).catch(() => void 0);
769
+ throw error;
770
+ }
771
+ },
772
+ configurable: true,
773
+ writable: true
774
+ });
775
+ Object.defineProperty(wrapped, "return", {
776
+ value: async (value) => {
777
+ if (typeof iterator.return === "function") {
778
+ const rawResult = await iterator.return(value);
779
+ if (!isIteratorResult(rawResult)) {
780
+ throw new TypeError("Iterator.return() returned an invalid result");
781
+ }
782
+ await invokeFinalize();
783
+ return rawResult;
784
+ }
785
+ await invokeFinalize();
786
+ return { done: true, value };
787
+ },
788
+ configurable: true,
789
+ writable: true
790
+ });
791
+ Object.defineProperty(wrapped, "throw", {
792
+ value: async (error) => {
793
+ if (typeof iterator.throw === "function") {
794
+ const rawResult = await iterator.throw(error);
795
+ if (!isIteratorResult(rawResult)) {
796
+ throw new TypeError("Iterator.throw() returned an invalid result");
797
+ }
798
+ await invokeFinalize(error);
799
+ return rawResult;
800
+ }
801
+ await invokeFinalize(error);
802
+ throw error;
803
+ },
804
+ configurable: true,
805
+ writable: true
806
+ });
807
+ Object.defineProperty(wrapped, "__usageTapFinalize", {
808
+ value: async () => {
809
+ await invokeFinalize();
810
+ },
811
+ configurable: true
812
+ });
813
+ return wrapped;
814
+ }
815
+ function applyAnthropicVendorHints(params, hints) {
816
+ if (!hints) {
817
+ return params;
818
+ }
819
+ const next = cloneRecord(params);
820
+ if (hints.preferredModel && (next.model === void 0 || next.model === null)) {
821
+ next.model = hints.preferredModel;
822
+ }
823
+ if (typeof hints.maxResponseTokens === "number" && next.max_tokens == null) {
824
+ next.max_tokens = hints.maxResponseTokens;
825
+ }
826
+ return next;
827
+ }
828
+ function attachCorrelationHeader(options, correlationId) {
829
+ const normalized = normalizeHeaders(options?.headers);
830
+ if (correlationId && !normalized[USAGETAP_CORRELATION_HEADER]) {
831
+ normalized[USAGETAP_CORRELATION_HEADER] = correlationId;
832
+ }
833
+ if (!options) {
834
+ return Object.keys(normalized).length ? { headers: normalized } : void 0;
835
+ }
836
+ const next = { ...options };
837
+ if (Object.keys(normalized).length) {
838
+ next.headers = normalized;
839
+ }
840
+ return next;
841
+ }
842
+ function cloneRequestOptions(source) {
843
+ const clone = { ...source };
844
+ if ("headers" in clone) {
845
+ clone.headers = normalizeHeaders(clone.headers);
846
+ }
847
+ return clone;
848
+ }
849
+ function normalizeHeaders(headers) {
850
+ if (!headers) {
851
+ return {};
852
+ }
853
+ if (headers instanceof Headers) {
854
+ const result = {};
855
+ headers.forEach((value, key) => {
856
+ result[key.toLowerCase()] = value;
857
+ });
858
+ return result;
859
+ }
860
+ if (Array.isArray(headers)) {
861
+ const result = {};
862
+ for (const entry of headers) {
863
+ if (!isStringTuple(entry)) {
864
+ continue;
865
+ }
866
+ const [key, value] = entry;
867
+ result[key.toLowerCase()] = value;
868
+ }
869
+ return result;
870
+ }
871
+ if (isObjectRecord(headers)) {
872
+ const result = {};
873
+ const record = headers;
874
+ for (const key of Object.keys(record)) {
875
+ const value = record[key];
876
+ if (value !== void 0 && value !== null) {
877
+ result[key.toLowerCase()] = String(value);
878
+ }
879
+ }
880
+ return result;
881
+ }
882
+ return {};
883
+ }
884
+ function mapAnthropicMessageRole(role) {
885
+ if (role === "user") {
886
+ return "user";
887
+ }
888
+ if (role === "assistant") {
889
+ return "assistant";
890
+ }
891
+ return void 0;
892
+ }
893
+ function isObjectRecord(value) {
894
+ return typeof value === "object" && value !== null;
895
+ }
896
+ function cloneRecord(value) {
897
+ return isObjectRecord(value) ? { ...value } : {};
898
+ }
899
+ function isStringTuple(value) {
900
+ return Array.isArray(value) && value.length >= 2 && typeof value[0] === "string" && typeof value[1] === "string";
901
+ }
902
+ function readString(value) {
903
+ return typeof value === "string" ? value : void 0;
904
+ }
905
+ function readNumber(value) {
906
+ return typeof value === "number" ? value : void 0;
907
+ }
908
+ function mergeTags(a, b) {
909
+ const values = [...a ?? [], ...b ?? []].map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
910
+ if (!values.length) {
911
+ return void 0;
912
+ }
913
+ return dedupeStrings(values);
914
+ }
915
+ function dedupeStrings(values) {
916
+ return Array.from(new Set(values));
917
+ }
918
+ function isStreamingRequest(params) {
919
+ if (!params || typeof params !== "object") {
920
+ return false;
921
+ }
922
+ const stream = params.stream;
923
+ if (typeof stream === "boolean") {
924
+ return stream;
925
+ }
926
+ return stream != null;
927
+ }
928
+ function ensureAsyncIterable(value, label) {
929
+ if (!value || typeof value !== "object" || typeof value[Symbol.asyncIterator] !== "function") {
930
+ throw new UsageTapError(
931
+ "USAGETAP_BAD_REQUEST",
932
+ `${label} expected an async iterable stream but received ${typeof value}`
933
+ );
934
+ }
935
+ }
936
+ function isIteratorResult(value) {
937
+ return isObjectRecord(value) && "done" in value;
938
+ }
939
+
940
+ exports.AnthropicPromptCompressionStats = AnthropicPromptCompressionStats;
941
+ exports.wrapAnthropic = wrapAnthropic;
942
+ //# sourceMappingURL=anthropic.cjs.map
943
+ //# sourceMappingURL=anthropic.cjs.map