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