@usagetap/sdk 1.0.0 → 1.2.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 +77 -16
  2. package/dist/adapters/anthropic.cjs +1009 -0
  3. package/dist/adapters/anthropic.cjs.map +1 -0
  4. package/dist/adapters/anthropic.d.cts +90 -0
  5. package/dist/adapters/anthropic.d.ts +90 -0
  6. package/dist/adapters/anthropic.mjs +1006 -0
  7. package/dist/adapters/anthropic.mjs.map +1 -0
  8. package/dist/adapters/openai.cjs +667 -17
  9. package/dist/adapters/openai.cjs.map +1 -1
  10. package/dist/adapters/openai.d.cts +66 -2
  11. package/dist/adapters/openai.d.ts +66 -2
  12. package/dist/adapters/openai.mjs +667 -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 +1009 -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 +1006 -0
  23. package/dist/anthropic/index.mjs.map +1 -0
  24. package/dist/{client-BHNMYvlO.d.cts → client-EMXt9_fA.d.cts} +106 -6
  25. package/dist/{client-BHNMYvlO.d.ts → client-EMXt9_fA.d.ts} +106 -6
  26. package/dist/express/index.cjs +663 -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 +663 -17
  31. package/dist/express/index.mjs.map +1 -1
  32. package/dist/index.cjs +287 -24
  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 +285 -25
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/openai/index.cjs +667 -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 +667 -18
  43. package/dist/openai/index.mjs.map +1 -1
  44. package/package.json +21 -1
@@ -0,0 +1,1009 @@
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 (shouldUseUsageTapMessageEndpoint(compression)) {
272
+ const result = await usageTap.compressPromptMessages(source, {
273
+ provider: "usagetap",
274
+ failOpen: compression.failOpen,
275
+ aggressiveness: resolveMessageEndpointAggressiveness(compression),
276
+ signal
277
+ });
278
+ return {
279
+ params: result.compressedInput,
280
+ segments: [{ role: "user", result }]
281
+ };
282
+ }
283
+ if (typeof source.system === "string") {
284
+ const compressed = await compressTextForRole(
285
+ source.system,
286
+ "system",
287
+ usageTap,
288
+ compression,
289
+ signal
290
+ );
291
+ if (compressed) {
292
+ source.system = compressed.text;
293
+ segments.push(compressed.segment);
294
+ }
295
+ } else if (Array.isArray(source.system)) {
296
+ const systemResults = await Promise.all(
297
+ source.system.map(
298
+ (block) => compressAnthropicTextBlock(block, "system", usageTap, compression, signal)
299
+ )
300
+ );
301
+ const systemSegments = systemResults.flatMap(
302
+ (result) => result.segment ? [result.segment] : []
303
+ );
304
+ if (systemSegments.length) {
305
+ source.system = systemResults.map((result) => result.value);
306
+ segments.push(...systemSegments);
307
+ }
308
+ }
309
+ if (Array.isArray(source.messages)) {
310
+ const messageResults = await Promise.all(
311
+ source.messages.map(
312
+ (message) => compressAnthropicMessage(message, usageTap, compression, signal)
313
+ )
314
+ );
315
+ source.messages = messageResults.map((result) => result.value);
316
+ segments.push(...messageResults.flatMap((result) => result.segments));
317
+ }
318
+ return {
319
+ params: source,
320
+ segments
321
+ };
322
+ }
323
+ async function compressAnthropicMessage(message, usageTap, compression, signal) {
324
+ if (!isObjectRecord(message)) {
325
+ return { value: message, segments: [] };
326
+ }
327
+ const role = mapAnthropicMessageRole(message.role);
328
+ if (!role) {
329
+ return { value: message, segments: [] };
330
+ }
331
+ const content = message.content;
332
+ if (typeof content === "string") {
333
+ const compressed = await compressTextForRole(
334
+ content,
335
+ role,
336
+ usageTap,
337
+ compression,
338
+ signal
339
+ );
340
+ if (!compressed) {
341
+ return { value: message, segments: [] };
342
+ }
343
+ return {
344
+ value: { ...message, content: compressed.text },
345
+ segments: [compressed.segment]
346
+ };
347
+ }
348
+ if (Array.isArray(content)) {
349
+ const blockResults = await Promise.all(
350
+ content.map(
351
+ (block) => compressAnthropicContentBlock(block, role, usageTap, compression, signal)
352
+ )
353
+ );
354
+ const segments = blockResults.flatMap((result) => result.segments);
355
+ return {
356
+ value: segments.length ? { ...message, content: blockResults.map((result) => result.value) } : message,
357
+ segments
358
+ };
359
+ }
360
+ return { value: message, segments: [] };
361
+ }
362
+ async function compressAnthropicContentBlock(block, messageRole, usageTap, compression, signal) {
363
+ if (!isObjectRecord(block)) {
364
+ return { value: block, segments: [] };
365
+ }
366
+ if (block.type === "tool_result") {
367
+ return compressAnthropicToolResultBlock(block, usageTap, compression, signal);
368
+ }
369
+ const textResult = await compressAnthropicTextBlock(
370
+ block,
371
+ messageRole,
372
+ usageTap,
373
+ compression,
374
+ signal
375
+ );
376
+ return {
377
+ value: textResult.value,
378
+ segments: textResult.segment ? [textResult.segment] : []
379
+ };
380
+ }
381
+ async function compressAnthropicToolResultBlock(block, usageTap, compression, signal) {
382
+ const content = block.content;
383
+ if (typeof content === "string") {
384
+ const compressed = await compressTextForRole(
385
+ content,
386
+ "tool",
387
+ usageTap,
388
+ compression,
389
+ signal
390
+ );
391
+ if (!compressed) {
392
+ return { value: block, segments: [] };
393
+ }
394
+ return {
395
+ value: { ...block, content: compressed.text },
396
+ segments: [compressed.segment]
397
+ };
398
+ }
399
+ if (Array.isArray(content)) {
400
+ const contentResults = await Promise.all(
401
+ content.map(
402
+ (child) => compressAnthropicTextBlock(child, "tool", usageTap, compression, signal)
403
+ )
404
+ );
405
+ const segments = contentResults.flatMap(
406
+ (result) => result.segment ? [result.segment] : []
407
+ );
408
+ if (!segments.length) {
409
+ return { value: block, segments: [] };
410
+ }
411
+ return {
412
+ value: {
413
+ ...block,
414
+ content: contentResults.map((result) => result.value)
415
+ },
416
+ segments
417
+ };
418
+ }
419
+ return { value: block, segments: [] };
420
+ }
421
+ async function compressAnthropicTextBlock(block, role, usageTap, compression, signal) {
422
+ if (!isObjectRecord(block) || block.type !== "text" || typeof block.text !== "string") {
423
+ return { value: block };
424
+ }
425
+ const compressed = await compressTextForRole(
426
+ block.text,
427
+ role,
428
+ usageTap,
429
+ compression,
430
+ signal
431
+ );
432
+ if (!compressed) {
433
+ return { value: block };
434
+ }
435
+ return {
436
+ value: { ...block, text: compressed.text },
437
+ segment: compressed.segment
438
+ };
439
+ }
440
+ async function compressTextForRole(text, role, usageTap, compression, signal) {
441
+ if (!text.trim()) {
442
+ return void 0;
443
+ }
444
+ const roleOptions = resolveRoleCompressionOptions(compression, role);
445
+ if (!roleOptions) {
446
+ return void 0;
447
+ }
448
+ const estimatedTokens = estimatePromptTokens(text);
449
+ if (typeof roleOptions.minTokens === "number" && estimatedTokens < roleOptions.minTokens) {
450
+ return void 0;
451
+ }
452
+ const result = await usageTap.compressPromptInput(text, {
453
+ provider: roleOptions.provider,
454
+ failOpen: roleOptions.failOpen,
455
+ tokenCompanyModel: roleOptions.tokenCompanyModel,
456
+ aggressiveness: roleOptions.aggressiveness,
457
+ tokenCompanyAggressiveness: roleOptions.tokenCompanyAggressiveness,
458
+ tokenCompanyAppId: roleOptions.tokenCompanyAppId,
459
+ usageTapCompressionModel: roleOptions.usageTapCompressionModel,
460
+ usageTapCompressionAggressiveness: roleOptions.usageTapCompressionAggressiveness,
461
+ signal
462
+ });
463
+ const compressedText = typeof result.compressedInput === "string" ? result.compressedInput : String(result.compressedInput);
464
+ return {
465
+ text: compressedText,
466
+ segment: { role, result: { ...result, compressedInput: compressedText } }
467
+ };
468
+ }
469
+ function normalizePromptCompressionOptions(options) {
470
+ if (!options) {
471
+ return void 0;
472
+ }
473
+ if (options === true) {
474
+ return {};
475
+ }
476
+ if (options.enabled === false) {
477
+ return void 0;
478
+ }
479
+ return options;
480
+ }
481
+ function resolveEffectivePromptCompressionOptions(defaults, override) {
482
+ if (override === false) {
483
+ return void 0;
484
+ }
485
+ if (override === void 0) {
486
+ return defaults;
487
+ }
488
+ if (override === true) {
489
+ return defaults ?? {};
490
+ }
491
+ const merged = {
492
+ ...defaults ?? {},
493
+ ...override,
494
+ roles: override.roles ?? defaults?.roles
495
+ };
496
+ return normalizePromptCompressionOptions(merged);
497
+ }
498
+ function resolveRoleCompressionOptions(compression, role) {
499
+ const hasExplicitRoles = compression.roles !== void 0;
500
+ const setting = compression.roles?.[role];
501
+ if (hasExplicitRoles && setting === void 0) {
502
+ return void 0;
503
+ }
504
+ if (!hasExplicitRoles && role === "assistant") {
505
+ return void 0;
506
+ }
507
+ if (setting === false) {
508
+ return void 0;
509
+ }
510
+ const roleOptions = typeof setting === "object" ? setting : void 0;
511
+ if (roleOptions?.enabled === false) {
512
+ return void 0;
513
+ }
514
+ return {
515
+ provider: roleOptions?.provider ?? compression.provider,
516
+ minTokens: roleOptions?.minTokens ?? compression.minTokens,
517
+ failOpen: compression.failOpen,
518
+ tokenCompanyModel: compression.tokenCompanyModel,
519
+ aggressiveness: roleOptions?.aggressiveness ?? resolveAggressiveness(compression, role),
520
+ tokenCompanyAggressiveness: roleOptions?.tokenCompanyAggressiveness ?? resolveTokenCompanyAggressiveness(compression, role),
521
+ tokenCompanyAppId: compression.tokenCompanyAppId,
522
+ usageTapCompressionModel: compression.usageTapCompressionModel,
523
+ usageTapCompressionAggressiveness: roleOptions?.usageTapCompressionAggressiveness ?? resolveUsageTapCompressionAggressiveness(compression, role)
524
+ };
525
+ }
526
+ function resolveAggressiveness(compression, role) {
527
+ if (typeof compression.aggressiveness === "number") {
528
+ return compression.aggressiveness;
529
+ }
530
+ return compression.aggressiveness?.[role];
531
+ }
532
+ function resolveTokenCompanyAggressiveness(compression, role) {
533
+ if (typeof compression.tokenCompanyAggressiveness === "number") {
534
+ return compression.tokenCompanyAggressiveness;
535
+ }
536
+ return compression.tokenCompanyAggressiveness?.[role];
537
+ }
538
+ function resolveUsageTapCompressionAggressiveness(compression, role) {
539
+ if (typeof compression.usageTapCompressionAggressiveness === "number") {
540
+ return compression.usageTapCompressionAggressiveness;
541
+ }
542
+ return compression.usageTapCompressionAggressiveness?.[role];
543
+ }
544
+ function shouldUseUsageTapMessageEndpoint(compression) {
545
+ if (compression.provider !== "usagetap") {
546
+ return false;
547
+ }
548
+ return Object.values(compression.roles ?? {}).every((setting) => {
549
+ if (typeof setting !== "object" || setting === null) {
550
+ return true;
551
+ }
552
+ return setting.provider === void 0 || setting.provider === "usagetap";
553
+ });
554
+ }
555
+ function resolveMessageEndpointAggressiveness(compression) {
556
+ const base = compression.aggressiveness ?? compression.usageTapCompressionAggressiveness ?? compression.tokenCompanyAggressiveness;
557
+ if (typeof base === "number" || base === void 0) {
558
+ return hasExplicitEnabledRoles(compression) ? buildRoleAggressiveness(compression, base) : base;
559
+ }
560
+ return buildRoleAggressiveness(compression, void 0, base);
561
+ }
562
+ function hasExplicitEnabledRoles(compression) {
563
+ return Object.values(compression.roles ?? {}).some((setting) => setting !== false);
564
+ }
565
+ function buildRoleAggressiveness(compression, fallback, base = {}) {
566
+ const roles = ["system", "user", "tool", "assistant"];
567
+ const result = {};
568
+ for (const role of roles) {
569
+ const roleOptions = resolveRoleCompressionOptions(compression, role);
570
+ if (!roleOptions) {
571
+ continue;
572
+ }
573
+ const roleAggressiveness = roleOptions.aggressiveness ?? roleOptions.usageTapCompressionAggressiveness ?? roleOptions.tokenCompanyAggressiveness ?? base[role] ?? fallback;
574
+ if (roleAggressiveness !== void 0) {
575
+ result[role] = roleAggressiveness;
576
+ }
577
+ }
578
+ return result;
579
+ }
580
+ function buildPromptCompressionTelemetry(segments) {
581
+ if (!segments.length) {
582
+ return void 0;
583
+ }
584
+ const originalCharacters = segments.reduce(
585
+ (sum, segment) => sum + segment.result.originalCharacters,
586
+ 0
587
+ );
588
+ const compressedCharacters = segments.reduce(
589
+ (sum, segment) => sum + segment.result.compressedCharacters,
590
+ 0
591
+ );
592
+ const originalTokens = segments.reduce(
593
+ (sum, segment) => sum + segment.result.originalTokens,
594
+ 0
595
+ );
596
+ const compressedTokens = segments.reduce(
597
+ (sum, segment) => sum + segment.result.compressedTokens,
598
+ 0
599
+ );
600
+ const savedCharacters = Math.max(0, originalCharacters - compressedCharacters);
601
+ const savedTokens = Math.max(0, originalTokens - compressedTokens);
602
+ const providers = dedupeStrings(segments.map((segment) => segment.result.provider));
603
+ const roles = dedupeStrings(segments.map((segment) => `role:${segment.role}`));
604
+ const techniques = dedupeStrings([
605
+ "anthropic-wrapper",
606
+ ...roles,
607
+ ...segments.flatMap((segment) => segment.result.techniques),
608
+ ...providers.length > 1 ? ["mixed-providers"] : []
609
+ ]);
610
+ return {
611
+ provider: segments[0]?.result.provider ?? "heuristic",
612
+ originalCharacters,
613
+ compressedCharacters,
614
+ savedCharacters,
615
+ originalTokens,
616
+ compressedTokens,
617
+ savedTokens,
618
+ tokenSavingsRatio: originalTokens > 0 ? savedTokens / originalTokens : 0,
619
+ savingsRatio: originalCharacters > 0 ? savedCharacters / originalCharacters : 0,
620
+ techniques
621
+ };
622
+ }
623
+ function promptCompressionRequestOptions(withUsage, correlationId) {
624
+ return {
625
+ signal: withUsage?.signal,
626
+ headers: withUsage?.headers,
627
+ retries: withUsage?.retries,
628
+ correlationId
629
+ };
630
+ }
631
+ function splitUsageOptions(options) {
632
+ if (!options || typeof options !== "object") {
633
+ return {};
634
+ }
635
+ const { usageTap, withUsage, promptCompression, ...rest } = options;
636
+ const requestOptions = Object.keys(rest).length ? cloneRequestOptions(rest) : void 0;
637
+ return {
638
+ requestOptions,
639
+ usageContext: usageTap,
640
+ withUsage,
641
+ promptCompression
642
+ };
643
+ }
644
+ function resolveBeginRequest(defaults, override) {
645
+ const base = defaults ?? {};
646
+ const current = override ?? {};
647
+ const customerId = current.customerId ?? base.customerId;
648
+ if (!customerId) {
649
+ throw new UsageTapError(
650
+ "USAGETAP_BAD_REQUEST",
651
+ "wrapAnthropic requires usageTap.customerId (provide defaultContext or options.usageTap)"
652
+ );
653
+ }
654
+ const tags = mergeTags(base.tags, current.tags);
655
+ const begin = { customerId };
656
+ const requested = current.requested ?? base.requested;
657
+ if (requested) begin.requested = requested;
658
+ const feature = current.feature ?? base.feature;
659
+ if (feature) begin.feature = feature;
660
+ const idempotency = current.idempotency ?? base.idempotency;
661
+ if (idempotency) begin.idempotency = idempotency;
662
+ const idempotencyKey = current.idempotencyKey ?? base.idempotencyKey;
663
+ if (idempotencyKey) begin.idempotencyKey = idempotencyKey;
664
+ const customerName = current.customerName ?? base.customerName;
665
+ if (customerName) begin.customerName = customerName;
666
+ const customerEmail = current.customerEmail ?? base.customerEmail;
667
+ if (customerEmail) begin.customerEmail = customerEmail;
668
+ const stripeCustomerId = current.stripeCustomerId ?? base.stripeCustomerId;
669
+ if (stripeCustomerId) begin.stripeCustomerId = stripeCustomerId;
670
+ const holdUsd = current.holdUsd ?? base.holdUsd;
671
+ if (typeof holdUsd === "number") begin.holdUsd = holdUsd;
672
+ const batch = current.batch ?? base.batch;
673
+ if (typeof batch === "boolean") begin.batch = batch;
674
+ const pricingMode = current.pricingMode ?? base.pricingMode;
675
+ if (pricingMode) begin.pricingMode = pricingMode;
676
+ if (tags?.length) {
677
+ begin.tags = tags;
678
+ }
679
+ return begin;
680
+ }
681
+ function createCallState(beginRequest, begin) {
682
+ const usage = {};
683
+ const stripeCustomerId = typeof begin.data.stripeCustomerId === "string" ? begin.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
684
+ if (stripeCustomerId) {
685
+ usage.stripeCustomerId = stripeCustomerId;
686
+ }
687
+ return {
688
+ beginRequest,
689
+ begin,
690
+ usage,
691
+ finalized: false
692
+ };
693
+ }
694
+ function createUsageContext(state) {
695
+ return {
696
+ begin: state.begin,
697
+ setUsage: (usage) => {
698
+ state.usage = { ...state.usage, ...usage };
699
+ },
700
+ setError: (error) => {
701
+ state.error = error;
702
+ }
703
+ };
704
+ }
705
+ async function finalizeCall(state, usageTap, options) {
706
+ if (state.finalized) {
707
+ return;
708
+ }
709
+ state.finalized = true;
710
+ await usageTap.endCall(
711
+ {
712
+ callId: state.begin.data.callId,
713
+ customerId: state.beginRequest.customerId,
714
+ feature: state.beginRequest.feature,
715
+ tags: state.beginRequest.tags,
716
+ ...state.usage,
717
+ error: state.error
718
+ },
719
+ endCallOptions(options, state.begin.correlationId)
720
+ );
721
+ }
722
+ function beginCallOptions(options) {
723
+ return {
724
+ signal: options?.signal,
725
+ headers: options?.headers,
726
+ retries: options?.retries,
727
+ correlationId: options?.correlationId
728
+ };
729
+ }
730
+ function endCallOptions(options, correlationId) {
731
+ return {
732
+ signal: options?.signal,
733
+ headers: options?.headers,
734
+ retries: options?.retries,
735
+ correlationId
736
+ };
737
+ }
738
+ function inferAnthropicUsage(response, fallbackModel, ctx) {
739
+ const usage = extractAnthropicUsage(response, fallbackModel);
740
+ if (usage) {
741
+ ctx.setUsage(usage);
742
+ }
743
+ }
744
+ function extractAnthropicUsage(payload, fallbackModel) {
745
+ if (!isObjectRecord(payload)) {
746
+ return fallbackModel ? { modelUsed: fallbackModel } : void 0;
747
+ }
748
+ const usage = findAnthropicUsageRecord(payload);
749
+ const result = {};
750
+ const model = readString(payload.model) ?? readString(payload.message?.model) ?? fallbackModel;
751
+ if (model) {
752
+ result.modelUsed = model;
753
+ }
754
+ if (usage) {
755
+ applyAnthropicUsageRecord(result, usage);
756
+ }
757
+ return Object.keys(result).length ? result : void 0;
758
+ }
759
+ function findAnthropicUsageRecord(payload) {
760
+ if (isObjectRecord(payload.usage)) {
761
+ return payload.usage;
762
+ }
763
+ if (isObjectRecord(payload.message) && isObjectRecord(payload.message.usage)) {
764
+ return payload.message.usage;
765
+ }
766
+ return void 0;
767
+ }
768
+ function applyAnthropicUsageRecord(usage, usageRecord) {
769
+ const inputTokens = readNumber(usageRecord.input_tokens ?? usageRecord.prompt_tokens);
770
+ if (inputTokens !== void 0) {
771
+ usage.inputTokens = inputTokens;
772
+ }
773
+ const outputTokens = readNumber(usageRecord.output_tokens ?? usageRecord.completion_tokens);
774
+ if (outputTokens !== void 0) {
775
+ usage.responseTokens = outputTokens;
776
+ }
777
+ const cachedInputTokens = readNumber(
778
+ usageRecord.cache_read_input_tokens ?? usageRecord.prompt_cache_hit_tokens ?? usageRecord.cached_tokens
779
+ );
780
+ if (cachedInputTokens !== void 0) {
781
+ usage.cachedInputTokens = cachedInputTokens;
782
+ }
783
+ }
784
+ function accumulateAnthropicStreamUsage(chunk, fallbackModel, state) {
785
+ const usage = extractAnthropicUsage(chunk, fallbackModel);
786
+ if (usage) {
787
+ state.usage = { ...state.usage, ...usage };
788
+ }
789
+ }
790
+ function wrapAnthropicStreamForUsageTap(source, state, usageTap, options, fallbackModel) {
791
+ const getIterator = source[Symbol.asyncIterator];
792
+ if (typeof getIterator !== "function") {
793
+ throw new TypeError("Stream is not async iterable");
794
+ }
795
+ const iterator = getIterator.call(source);
796
+ const invokeFinalize = async (error) => {
797
+ if (error && !state.error) {
798
+ state.error = {
799
+ code: options?.defaultErrorCode ?? "VENDOR_ERROR",
800
+ message: error instanceof Error ? error.message : String(error)
801
+ };
802
+ }
803
+ await finalizeCall(state, usageTap, options);
804
+ };
805
+ const prototype = Object.getPrototypeOf(source) ?? Object.prototype;
806
+ const wrapped = Object.create(prototype);
807
+ for (const key of Reflect.ownKeys(source)) {
808
+ try {
809
+ const descriptor = Object.getOwnPropertyDescriptor(source, key);
810
+ if (descriptor) {
811
+ Object.defineProperty(wrapped, key, descriptor);
812
+ }
813
+ } catch {
814
+ }
815
+ }
816
+ Object.defineProperty(wrapped, Symbol.asyncIterator, {
817
+ value() {
818
+ return this;
819
+ },
820
+ configurable: true
821
+ });
822
+ Object.defineProperty(wrapped, "next", {
823
+ value: async (...args) => {
824
+ try {
825
+ const result = await iterator.next(...args);
826
+ if (result.value !== void 0) {
827
+ accumulateAnthropicStreamUsage(result.value, fallbackModel, state);
828
+ }
829
+ if (result.done) {
830
+ await invokeFinalize();
831
+ }
832
+ return result;
833
+ } catch (error) {
834
+ await invokeFinalize(error).catch(() => void 0);
835
+ throw error;
836
+ }
837
+ },
838
+ configurable: true,
839
+ writable: true
840
+ });
841
+ Object.defineProperty(wrapped, "return", {
842
+ value: async (value) => {
843
+ if (typeof iterator.return === "function") {
844
+ const rawResult = await iterator.return(value);
845
+ if (!isIteratorResult(rawResult)) {
846
+ throw new TypeError("Iterator.return() returned an invalid result");
847
+ }
848
+ await invokeFinalize();
849
+ return rawResult;
850
+ }
851
+ await invokeFinalize();
852
+ return { done: true, value };
853
+ },
854
+ configurable: true,
855
+ writable: true
856
+ });
857
+ Object.defineProperty(wrapped, "throw", {
858
+ value: async (error) => {
859
+ if (typeof iterator.throw === "function") {
860
+ const rawResult = await iterator.throw(error);
861
+ if (!isIteratorResult(rawResult)) {
862
+ throw new TypeError("Iterator.throw() returned an invalid result");
863
+ }
864
+ await invokeFinalize(error);
865
+ return rawResult;
866
+ }
867
+ await invokeFinalize(error);
868
+ throw error;
869
+ },
870
+ configurable: true,
871
+ writable: true
872
+ });
873
+ Object.defineProperty(wrapped, "__usageTapFinalize", {
874
+ value: async () => {
875
+ await invokeFinalize();
876
+ },
877
+ configurable: true
878
+ });
879
+ return wrapped;
880
+ }
881
+ function applyAnthropicVendorHints(params, hints) {
882
+ if (!hints) {
883
+ return params;
884
+ }
885
+ const next = cloneRecord(params);
886
+ if (hints.preferredModel && (next.model === void 0 || next.model === null)) {
887
+ next.model = hints.preferredModel;
888
+ }
889
+ if (typeof hints.maxResponseTokens === "number" && next.max_tokens == null) {
890
+ next.max_tokens = hints.maxResponseTokens;
891
+ }
892
+ return next;
893
+ }
894
+ function attachCorrelationHeader(options, correlationId) {
895
+ const normalized = normalizeHeaders(options?.headers);
896
+ if (correlationId && !normalized[USAGETAP_CORRELATION_HEADER]) {
897
+ normalized[USAGETAP_CORRELATION_HEADER] = correlationId;
898
+ }
899
+ if (!options) {
900
+ return Object.keys(normalized).length ? { headers: normalized } : void 0;
901
+ }
902
+ const next = { ...options };
903
+ if (Object.keys(normalized).length) {
904
+ next.headers = normalized;
905
+ }
906
+ return next;
907
+ }
908
+ function cloneRequestOptions(source) {
909
+ const clone = { ...source };
910
+ if ("headers" in clone) {
911
+ clone.headers = normalizeHeaders(clone.headers);
912
+ }
913
+ return clone;
914
+ }
915
+ function normalizeHeaders(headers) {
916
+ if (!headers) {
917
+ return {};
918
+ }
919
+ if (headers instanceof Headers) {
920
+ const result = {};
921
+ headers.forEach((value, key) => {
922
+ result[key.toLowerCase()] = value;
923
+ });
924
+ return result;
925
+ }
926
+ if (Array.isArray(headers)) {
927
+ const result = {};
928
+ for (const entry of headers) {
929
+ if (!isStringTuple(entry)) {
930
+ continue;
931
+ }
932
+ const [key, value] = entry;
933
+ result[key.toLowerCase()] = value;
934
+ }
935
+ return result;
936
+ }
937
+ if (isObjectRecord(headers)) {
938
+ const result = {};
939
+ const record = headers;
940
+ for (const key of Object.keys(record)) {
941
+ const value = record[key];
942
+ if (value !== void 0 && value !== null) {
943
+ result[key.toLowerCase()] = String(value);
944
+ }
945
+ }
946
+ return result;
947
+ }
948
+ return {};
949
+ }
950
+ function mapAnthropicMessageRole(role) {
951
+ if (role === "user") {
952
+ return "user";
953
+ }
954
+ if (role === "assistant") {
955
+ return "assistant";
956
+ }
957
+ return void 0;
958
+ }
959
+ function isObjectRecord(value) {
960
+ return typeof value === "object" && value !== null;
961
+ }
962
+ function cloneRecord(value) {
963
+ return isObjectRecord(value) ? { ...value } : {};
964
+ }
965
+ function isStringTuple(value) {
966
+ return Array.isArray(value) && value.length >= 2 && typeof value[0] === "string" && typeof value[1] === "string";
967
+ }
968
+ function readString(value) {
969
+ return typeof value === "string" ? value : void 0;
970
+ }
971
+ function readNumber(value) {
972
+ return typeof value === "number" ? value : void 0;
973
+ }
974
+ function mergeTags(a, b) {
975
+ const values = [...a ?? [], ...b ?? []].map((value) => typeof value === "string" ? value.trim() : "").filter(Boolean);
976
+ if (!values.length) {
977
+ return void 0;
978
+ }
979
+ return dedupeStrings(values);
980
+ }
981
+ function dedupeStrings(values) {
982
+ return Array.from(new Set(values));
983
+ }
984
+ function isStreamingRequest(params) {
985
+ if (!params || typeof params !== "object") {
986
+ return false;
987
+ }
988
+ const stream = params.stream;
989
+ if (typeof stream === "boolean") {
990
+ return stream;
991
+ }
992
+ return stream != null;
993
+ }
994
+ function ensureAsyncIterable(value, label) {
995
+ if (!value || typeof value !== "object" || typeof value[Symbol.asyncIterator] !== "function") {
996
+ throw new UsageTapError(
997
+ "USAGETAP_BAD_REQUEST",
998
+ `${label} expected an async iterable stream but received ${typeof value}`
999
+ );
1000
+ }
1001
+ }
1002
+ function isIteratorResult(value) {
1003
+ return isObjectRecord(value) && "done" in value;
1004
+ }
1005
+
1006
+ exports.AnthropicPromptCompressionStats = AnthropicPromptCompressionStats;
1007
+ exports.wrapAnthropic = wrapAnthropic;
1008
+ //# sourceMappingURL=index.cjs.map
1009
+ //# sourceMappingURL=index.cjs.map