@usagetap/sdk 1.1.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +200 -201
- package/dist/adapters/anthropic.cjs +1676 -6
- package/dist/adapters/anthropic.cjs.map +1 -1
- package/dist/adapters/anthropic.d.cts +39 -2
- package/dist/adapters/anthropic.d.ts +39 -2
- package/dist/adapters/anthropic.mjs +1675 -7
- package/dist/adapters/anthropic.mjs.map +1 -1
- package/dist/adapters/openai.cjs +1726 -6
- package/dist/adapters/openai.cjs.map +1 -1
- package/dist/adapters/openai.d.cts +46 -2
- package/dist/adapters/openai.d.ts +46 -2
- package/dist/adapters/openai.mjs +1725 -7
- package/dist/adapters/openai.mjs.map +1 -1
- package/dist/adapters/openrouter.cjs.map +1 -1
- package/dist/adapters/openrouter.d.cts +1 -1
- package/dist/adapters/openrouter.d.ts +1 -1
- package/dist/adapters/openrouter.mjs.map +1 -1
- package/dist/anthropic/index.cjs +1676 -6
- package/dist/anthropic/index.cjs.map +1 -1
- package/dist/anthropic/index.d.cts +2 -2
- package/dist/anthropic/index.d.ts +2 -2
- package/dist/anthropic/index.mjs +1675 -7
- package/dist/anthropic/index.mjs.map +1 -1
- package/dist/{client-BA-QlnRq.d.cts → client-BD8O2J8Z.d.cts} +106 -11
- package/dist/{client-BA-QlnRq.d.ts → client-BD8O2J8Z.d.ts} +106 -11
- package/dist/express/index.cjs +86 -1
- package/dist/express/index.cjs.map +1 -1
- package/dist/express/index.d.cts +1 -1
- package/dist/express/index.d.ts +1 -1
- package/dist/express/index.mjs +86 -1
- package/dist/express/index.mjs.map +1 -1
- package/dist/index.cjs +263 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +262 -40
- package/dist/index.mjs.map +1 -1
- package/dist/openai/index.cjs +1726 -6
- package/dist/openai/index.cjs.map +1 -1
- package/dist/openai/index.d.cts +2 -2
- package/dist/openai/index.d.ts +2 -2
- package/dist/openai/index.mjs +1725 -7
- package/dist/openai/index.mjs.map +1 -1
- package/dist/openrouter/index.cjs +3024 -0
- package/dist/openrouter/index.cjs.map +1 -0
- package/dist/openrouter/index.d.cts +4 -0
- package/dist/openrouter/index.d.ts +4 -0
- package/dist/openrouter/index.mjs +3019 -0
- package/dist/openrouter/index.mjs.map +1 -0
- package/package.json +102 -44
package/dist/openai/index.mjs
CHANGED
|
@@ -26,16 +26,1546 @@ var UsageTapError = class extends Error {
|
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
|
+
function isUsageTapError(error) {
|
|
30
|
+
return error instanceof UsageTapError;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/idempotency.ts
|
|
34
|
+
function createIdempotencyKey() {
|
|
35
|
+
if (typeof globalThis.crypto?.randomUUID === "function") {
|
|
36
|
+
return globalThis.crypto.randomUUID();
|
|
37
|
+
}
|
|
38
|
+
const random = () => Math.random().toString(16).slice(2, 10);
|
|
39
|
+
return `${random()}-${random()}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/retry.ts
|
|
43
|
+
var DEFAULTS = {
|
|
44
|
+
maxAttempts: 3,
|
|
45
|
+
baseDelayMs: 250,
|
|
46
|
+
maxDelayMs: 5e3,
|
|
47
|
+
jitterRatio: 0.2
|
|
48
|
+
};
|
|
49
|
+
function resolveRetryOptions(base, override) {
|
|
50
|
+
const merged = { ...DEFAULTS, ...base, ...override };
|
|
51
|
+
return {
|
|
52
|
+
maxAttempts: Math.max(1, Math.floor(merged.maxAttempts)),
|
|
53
|
+
baseDelayMs: Math.max(0, merged.baseDelayMs),
|
|
54
|
+
maxDelayMs: Math.max(merged.baseDelayMs, merged.maxDelayMs),
|
|
55
|
+
jitterRatio: Math.min(Math.max(merged.jitterRatio, 0), 1)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async function sleep(delayMs, signal) {
|
|
59
|
+
if (delayMs <= 0) {
|
|
60
|
+
signal?.throwIfAborted?.();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
await new Promise((resolve, reject) => {
|
|
64
|
+
const timer = setTimeout(() => {
|
|
65
|
+
cleanup();
|
|
66
|
+
resolve();
|
|
67
|
+
}, delayMs);
|
|
68
|
+
const cleanup = () => {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
signal?.removeEventListener("abort", onAbort);
|
|
71
|
+
};
|
|
72
|
+
const onAbort = () => {
|
|
73
|
+
cleanup();
|
|
74
|
+
const abortError = new Error("Aborted");
|
|
75
|
+
abortError.name = "AbortError";
|
|
76
|
+
reject(abortError);
|
|
77
|
+
};
|
|
78
|
+
if (signal) {
|
|
79
|
+
if (signal.aborted) {
|
|
80
|
+
onAbort();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
function computeDelay(attempt, options) {
|
|
88
|
+
const exp = options.baseDelayMs * Math.pow(2, attempt - 1);
|
|
89
|
+
const capped = Math.min(exp, options.maxDelayMs);
|
|
90
|
+
const jitter = capped * options.jitterRatio;
|
|
91
|
+
const min = capped - jitter;
|
|
92
|
+
const max = capped + jitter;
|
|
93
|
+
return Math.max(0, Math.random() * (max - min) + min);
|
|
94
|
+
}
|
|
95
|
+
async function runWithRetry(operation, options, shouldRetry, onSchedule, signal) {
|
|
96
|
+
let attempt = 0;
|
|
97
|
+
let lastError;
|
|
98
|
+
while (attempt < options.maxAttempts) {
|
|
99
|
+
attempt += 1;
|
|
100
|
+
signal?.throwIfAborted?.();
|
|
101
|
+
try {
|
|
102
|
+
return await operation(attempt);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
lastError = error;
|
|
105
|
+
if (attempt >= options.maxAttempts || !shouldRetry(error)) {
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
const delayMs = computeDelay(attempt, options);
|
|
109
|
+
onSchedule?.(attempt, delayMs, error);
|
|
110
|
+
await sleep(delayMs, signal);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
114
|
+
}
|
|
29
115
|
|
|
30
116
|
// src/prompt-compression.ts
|
|
117
|
+
var DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS = 1e3;
|
|
118
|
+
var DEFAULT_TTC_ENDPOINT = "https://api.thetokencompany.com/v1/compress";
|
|
119
|
+
var DEFAULT_TTC_MODEL = "bear-2";
|
|
120
|
+
var DEFAULT_TTC_AGGRESSIVENESS = 0.2;
|
|
121
|
+
var DEFAULT_USAGETAP_COMPRESSION_ENDPOINT = "https://compress.usagetap.com/v1/compress";
|
|
122
|
+
var DEFAULT_USAGETAP_MESSAGES_COMPRESSION_ENDPOINT = "https://compress.usagetap.com/v1/messages/compress";
|
|
123
|
+
var PROTECTED_TEXT_PATTERN = /<ttc_safe>[\s\S]*?<\/ttc_safe>|<usagetap_safe>[\s\S]*?<\/usagetap_safe>/g;
|
|
124
|
+
async function compressPrompt(options) {
|
|
125
|
+
const input = resolvePromptCompressionInput(options);
|
|
126
|
+
try {
|
|
127
|
+
if (options.provider === "usagetap") {
|
|
128
|
+
return await compressWithUsageTap(options);
|
|
129
|
+
}
|
|
130
|
+
if (options.provider === "thetokencompany" || options.tokenCompanyApiKey) {
|
|
131
|
+
return await compressWithTheTokenCompany(options);
|
|
132
|
+
}
|
|
133
|
+
if (options.provider === "toon") {
|
|
134
|
+
return compressPromptToon(input);
|
|
135
|
+
}
|
|
136
|
+
return compressPromptHeuristic(input);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (options.failOpen === false) {
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
return createPromptCompressionFallback(
|
|
142
|
+
input,
|
|
143
|
+
options.provider ?? (options.tokenCompanyApiKey ? "thetokencompany" : "heuristic"),
|
|
144
|
+
error
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function compressPromptHeuristic(input) {
|
|
149
|
+
const original = stableStringifyInput(input);
|
|
150
|
+
const techniques = /* @__PURE__ */ new Set();
|
|
151
|
+
const compressedInput = compressValue(input, techniques, { allowToonString: false });
|
|
152
|
+
const compressed = stableStringifyInput(compressedInput);
|
|
153
|
+
const chosenInput = compressed.length <= original.length ? compressedInput : input;
|
|
154
|
+
const chosen = compressed.length <= original.length ? compressed : original;
|
|
155
|
+
if (!techniques.size) {
|
|
156
|
+
techniques.add("no-op");
|
|
157
|
+
}
|
|
158
|
+
return buildResult(
|
|
159
|
+
input,
|
|
160
|
+
chosenInput,
|
|
161
|
+
"heuristic",
|
|
162
|
+
original,
|
|
163
|
+
chosen,
|
|
164
|
+
Array.from(techniques)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
function compressPromptToon(input) {
|
|
168
|
+
const original = stableStringifyInput(input);
|
|
169
|
+
const compressedInput = typeof input === "string" ? compressText(input, /* @__PURE__ */ new Set(), { allowToonString: true }) : encodeToon(input);
|
|
170
|
+
const compressed = stableStringifyInput(compressedInput);
|
|
171
|
+
return buildResult(input, compressedInput, "toon", original, compressed, [
|
|
172
|
+
"toon",
|
|
173
|
+
"json-minify"
|
|
174
|
+
]);
|
|
175
|
+
}
|
|
176
|
+
async function compressPromptMessages(options) {
|
|
177
|
+
try {
|
|
178
|
+
return await compressMessagesWithUsageTap(options);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (options.failOpen === false) {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
return createPromptCompressionFallback(
|
|
184
|
+
options.input,
|
|
185
|
+
options.provider ?? "usagetap",
|
|
186
|
+
error
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function compressWithTheTokenCompany(options) {
|
|
191
|
+
if (!options.tokenCompanyApiKey) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
"tokenCompanyApiKey is required when provider is thetokencompany"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
const fetchCandidate = options.fetchImpl ?? globalThis.fetch;
|
|
197
|
+
if (typeof fetchCandidate !== "function") {
|
|
198
|
+
throw new Error(
|
|
199
|
+
"A fetch implementation is required for The Token Company compression"
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
return compressWithCompatibleRemoteProvider({
|
|
203
|
+
options,
|
|
204
|
+
provider: "thetokencompany",
|
|
205
|
+
endpoint: options.tokenCompanyEndpoint ?? DEFAULT_TTC_ENDPOINT,
|
|
206
|
+
model: options.model ?? options.tokenCompanyModel ?? DEFAULT_TTC_MODEL,
|
|
207
|
+
aggressiveness: options.aggressiveness ?? options.tokenCompanyAggressiveness ?? DEFAULT_TTC_AGGRESSIVENESS,
|
|
208
|
+
apiKey: options.tokenCompanyApiKey,
|
|
209
|
+
appId: options.tokenCompanyAppId,
|
|
210
|
+
providerLabel: "The Token Company"
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function compressWithUsageTap(options) {
|
|
214
|
+
const fetchCandidate = options.fetchImpl ?? globalThis.fetch;
|
|
215
|
+
if (typeof fetchCandidate !== "function") {
|
|
216
|
+
throw new Error(
|
|
217
|
+
"A fetch implementation is required for UsageTap prompt compression"
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return compressWithCompatibleRemoteProvider({
|
|
221
|
+
options,
|
|
222
|
+
provider: "usagetap",
|
|
223
|
+
endpoint: options.usageTapCompressionEndpoint ?? DEFAULT_USAGETAP_COMPRESSION_ENDPOINT,
|
|
224
|
+
model: options.model ?? options.usageTapCompressionModel ?? options.tokenCompanyModel ?? DEFAULT_TTC_MODEL,
|
|
225
|
+
aggressiveness: options.aggressiveness ?? options.usageTapCompressionAggressiveness ?? options.tokenCompanyAggressiveness ?? DEFAULT_TTC_AGGRESSIVENESS,
|
|
226
|
+
apiKey: options.usageTapCompressionApiKey,
|
|
227
|
+
appId: options.tokenCompanyAppId,
|
|
228
|
+
providerLabel: "UsageTap prompt compression"
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
async function compressMessagesWithUsageTap(options) {
|
|
232
|
+
const fetchCandidate = options.fetchImpl ?? globalThis.fetch;
|
|
233
|
+
if (typeof fetchCandidate !== "function") {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"A fetch implementation is required for UsageTap prompt message compression"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
const aggressiveness = options.aggressiveness ?? options.usageTapCompressionAggressiveness ?? DEFAULT_TTC_AGGRESSIVENESS;
|
|
239
|
+
validateAggressiveness(
|
|
240
|
+
aggressiveness,
|
|
241
|
+
"UsageTap prompt message compression"
|
|
242
|
+
);
|
|
243
|
+
const original = stableStringifyInput(options.input);
|
|
244
|
+
const headers = {
|
|
245
|
+
"content-type": "application/json"
|
|
246
|
+
};
|
|
247
|
+
if (options.usageTapCompressionApiKey) {
|
|
248
|
+
headers.authorization = `Bearer ${options.usageTapCompressionApiKey}`;
|
|
249
|
+
}
|
|
250
|
+
const response = await fetchCandidate(
|
|
251
|
+
options.usageTapCompressionMessagesEndpoint ?? DEFAULT_USAGETAP_MESSAGES_COMPRESSION_ENDPOINT,
|
|
252
|
+
{
|
|
253
|
+
method: "POST",
|
|
254
|
+
headers,
|
|
255
|
+
body: JSON.stringify({
|
|
256
|
+
...cloneInputRecord(options.input),
|
|
257
|
+
compression_settings: { aggressiveness }
|
|
258
|
+
}),
|
|
259
|
+
signal: options.signal
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
if (!response.ok) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`UsageTap prompt message compression failed with HTTP ${response.status}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const payload = await response.json();
|
|
268
|
+
const compressedInput = payload.compressed_request ?? payload.compressedInput ?? payload.compressed ?? (payload.messages !== void 0 ? { ...cloneInputRecord(options.input), messages: payload.messages } : void 0);
|
|
269
|
+
if (compressedInput === void 0) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
"UsageTap prompt message compression response did not include compressed content"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const compressed = stableStringifyInput(compressedInput);
|
|
275
|
+
const tokenCounts = normalizeCompatibleTokenCounts(payload);
|
|
276
|
+
return buildResult(
|
|
277
|
+
options.input,
|
|
278
|
+
compressedInput,
|
|
279
|
+
"usagetap",
|
|
280
|
+
original,
|
|
281
|
+
compressed,
|
|
282
|
+
["usagetap", "messages-endpoint"],
|
|
283
|
+
tokenCounts
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
async function compressWithCompatibleRemoteProvider(args) {
|
|
287
|
+
const { options, provider, endpoint, model, aggressiveness, apiKey, appId, providerLabel } = args;
|
|
288
|
+
const fetchCandidate = options.fetchImpl ?? globalThis.fetch;
|
|
289
|
+
const sourceInput = resolvePromptCompressionInput(options);
|
|
290
|
+
const original = stableStringifyInput(sourceInput);
|
|
291
|
+
const heuristic = compressPromptHeuristic(sourceInput);
|
|
292
|
+
const input = typeof heuristic.compressedInput === "string" ? heuristic.compressedInput : stableStringifyInput(heuristic.compressedInput);
|
|
293
|
+
if (!isValidAggressiveness(aggressiveness)) {
|
|
294
|
+
throw new Error(`${providerLabel} aggressiveness must be between 0.0 and 1.0`);
|
|
295
|
+
}
|
|
296
|
+
const headers = {
|
|
297
|
+
"content-type": "application/json"
|
|
298
|
+
};
|
|
299
|
+
if (apiKey) {
|
|
300
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
301
|
+
}
|
|
302
|
+
const response = await fetchCandidate(
|
|
303
|
+
endpoint,
|
|
304
|
+
{
|
|
305
|
+
method: "POST",
|
|
306
|
+
headers,
|
|
307
|
+
body: JSON.stringify({
|
|
308
|
+
model,
|
|
309
|
+
input,
|
|
310
|
+
...provider === "usagetap" ? { text: input } : {},
|
|
311
|
+
compression_settings: { aggressiveness },
|
|
312
|
+
...appId ? { app_id: appId } : {}
|
|
313
|
+
}),
|
|
314
|
+
signal: options.signal
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
if (!response.ok) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`${providerLabel} failed with HTTP ${response.status}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
const payload = await response.json();
|
|
323
|
+
const tokenCompanyResult = normalizeTheTokenCompanyCompressResponse(payload);
|
|
324
|
+
const compressedInput = payload.compressedInput ?? payload.compressed ?? tokenCompanyResult?.output ?? payload.output ?? payload.text;
|
|
325
|
+
if (compressedInput === void 0) {
|
|
326
|
+
throw new Error(`${providerLabel} response did not include compressed content`);
|
|
327
|
+
}
|
|
328
|
+
const compressed = stableStringifyInput(compressedInput);
|
|
329
|
+
const tokenCounts = tokenCompanyResult ? {
|
|
330
|
+
originalTokens: tokenCompanyResult.input_tokens,
|
|
331
|
+
compressedTokens: tokenCompanyResult.output_tokens,
|
|
332
|
+
savedTokens: tokenCompanyResult.tokens_saved
|
|
333
|
+
} : void 0;
|
|
334
|
+
return buildResult(
|
|
335
|
+
sourceInput,
|
|
336
|
+
compressedInput,
|
|
337
|
+
provider,
|
|
338
|
+
original,
|
|
339
|
+
compressed,
|
|
340
|
+
[...heuristic.techniques, provider],
|
|
341
|
+
tokenCounts
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
function resolvePromptCompressionInput(options) {
|
|
345
|
+
if (options.input !== void 0) {
|
|
346
|
+
return options.input;
|
|
347
|
+
}
|
|
348
|
+
if (options.text !== void 0) {
|
|
349
|
+
return options.text;
|
|
350
|
+
}
|
|
351
|
+
throw new Error("Prompt compression requires input or text");
|
|
352
|
+
}
|
|
353
|
+
function validateAggressiveness(value, label) {
|
|
354
|
+
if (typeof value === "number") {
|
|
355
|
+
if (!isValidAggressiveness(value)) {
|
|
356
|
+
throw new Error(`${label} aggressiveness must be between 0.0 and 1.0`);
|
|
357
|
+
}
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
for (const aggressiveness of Object.values(value)) {
|
|
361
|
+
if (aggressiveness !== void 0 && !isValidAggressiveness(aggressiveness)) {
|
|
362
|
+
throw new Error(`${label} aggressiveness must be between 0.0 and 1.0`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function isValidAggressiveness(value) {
|
|
367
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
368
|
+
}
|
|
369
|
+
function cloneInputRecord(input) {
|
|
370
|
+
return input && typeof input === "object" && !Array.isArray(input) ? { ...input } : { input };
|
|
371
|
+
}
|
|
372
|
+
function normalizeCompatibleTokenCounts(data) {
|
|
373
|
+
const inputTokens = typeof data.input_tokens === "number" ? data.input_tokens : data.original_input_tokens;
|
|
374
|
+
const outputTokens = data.output_tokens;
|
|
375
|
+
if (typeof inputTokens !== "number" || typeof outputTokens !== "number") {
|
|
376
|
+
return void 0;
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
originalTokens: inputTokens,
|
|
380
|
+
compressedTokens: outputTokens,
|
|
381
|
+
savedTokens: typeof data.tokens_saved === "number" ? data.tokens_saved : inputTokens - outputTokens
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function normalizeTheTokenCompanyCompressResponse(data) {
|
|
385
|
+
if (typeof data.output !== "string" || typeof data.output_tokens !== "number") {
|
|
386
|
+
return void 0;
|
|
387
|
+
}
|
|
388
|
+
const inputTokens = typeof data.input_tokens === "number" ? data.input_tokens : data.original_input_tokens;
|
|
389
|
+
if (typeof inputTokens !== "number") {
|
|
390
|
+
return void 0;
|
|
391
|
+
}
|
|
392
|
+
const tokensSaved = typeof data.tokens_saved === "number" ? data.tokens_saved : inputTokens - data.output_tokens;
|
|
393
|
+
const compressionRatio = typeof data.compression_ratio === "number" ? data.compression_ratio : data.output_tokens === 0 ? 0 : inputTokens / data.output_tokens;
|
|
394
|
+
return {
|
|
395
|
+
output: data.output,
|
|
396
|
+
output_tokens: data.output_tokens,
|
|
397
|
+
input_tokens: inputTokens,
|
|
398
|
+
tokens_saved: tokensSaved,
|
|
399
|
+
compression_ratio: compressionRatio
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function createPromptCompressionFallback(input, provider = "heuristic", error) {
|
|
403
|
+
const original = stableStringifyInput(input);
|
|
404
|
+
const techniques = ["fallback-original"];
|
|
405
|
+
if (error) {
|
|
406
|
+
techniques.push("compression-error");
|
|
407
|
+
}
|
|
408
|
+
return buildResult(input, input, provider, original, original, techniques);
|
|
409
|
+
}
|
|
410
|
+
function buildResult(input, compressedInput, provider, original, compressed, techniques, tokenCounts) {
|
|
411
|
+
const originalCharacters = original.length;
|
|
412
|
+
const compressedCharacters = compressed.length;
|
|
413
|
+
const savedCharacters = Math.max(
|
|
414
|
+
0,
|
|
415
|
+
originalCharacters - compressedCharacters
|
|
416
|
+
);
|
|
417
|
+
const originalTokens = tokenCounts?.originalTokens ?? estimatePromptTokens(original);
|
|
418
|
+
const compressedTokens = tokenCounts?.compressedTokens ?? estimatePromptTokens(compressed);
|
|
419
|
+
const savedTokens = Math.max(
|
|
420
|
+
0,
|
|
421
|
+
tokenCounts?.savedTokens ?? originalTokens - compressedTokens
|
|
422
|
+
);
|
|
423
|
+
return {
|
|
424
|
+
input,
|
|
425
|
+
compressedInput,
|
|
426
|
+
provider,
|
|
427
|
+
originalCharacters,
|
|
428
|
+
compressedCharacters,
|
|
429
|
+
savedCharacters,
|
|
430
|
+
originalTokens,
|
|
431
|
+
compressedTokens,
|
|
432
|
+
savedTokens,
|
|
433
|
+
tokenSavingsRatio: originalTokens > 0 ? savedTokens / originalTokens : 0,
|
|
434
|
+
savingsRatio: originalCharacters > 0 ? savedCharacters / originalCharacters : 0,
|
|
435
|
+
techniques
|
|
436
|
+
};
|
|
437
|
+
}
|
|
31
438
|
function estimatePromptTokens(input) {
|
|
32
439
|
const text = typeof input === "string" ? input : stableStringifyInput(input);
|
|
33
440
|
return text.match(/[\p{L}\p{N}]+|[^\s]/gu)?.length ?? 0;
|
|
34
441
|
}
|
|
442
|
+
function compressValue(value, techniques, options) {
|
|
443
|
+
if (typeof value === "string") return compressText(value, techniques, options);
|
|
444
|
+
if (Array.isArray(value)) {
|
|
445
|
+
techniques.add("json-minify");
|
|
446
|
+
return value.map((item) => compressValue(item, techniques, options));
|
|
447
|
+
}
|
|
448
|
+
if (value && typeof value === "object") {
|
|
449
|
+
techniques.add("json-minify");
|
|
450
|
+
return Object.keys(value).reduce((acc, key) => {
|
|
451
|
+
const child = value[key];
|
|
452
|
+
if (child !== void 0) {
|
|
453
|
+
acc[key] = compressValue(child, techniques, options);
|
|
454
|
+
}
|
|
455
|
+
return acc;
|
|
456
|
+
}, {});
|
|
457
|
+
}
|
|
458
|
+
return value;
|
|
459
|
+
}
|
|
460
|
+
function compressText(value, techniques, options) {
|
|
461
|
+
const protectedSpans = [];
|
|
462
|
+
const text = value.replace(PROTECTED_TEXT_PATTERN, (match) => {
|
|
463
|
+
const placeholder = `__USAGETAP_PROTECTED_${protectedSpans.length}__`;
|
|
464
|
+
protectedSpans.push(match);
|
|
465
|
+
techniques.add("protected-text");
|
|
466
|
+
return placeholder;
|
|
467
|
+
});
|
|
468
|
+
const compressed = compressTextWithoutProtection(text, techniques, options);
|
|
469
|
+
return protectedSpans.reduce(
|
|
470
|
+
(output, span, index) => output.replace(`__USAGETAP_PROTECTED_${index}__`, span),
|
|
471
|
+
compressed
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
function compressTextWithoutProtection(value, techniques, options) {
|
|
475
|
+
const fencePattern = /```([\w-]+)?\n([\s\S]*?)```/g;
|
|
476
|
+
const parts = [];
|
|
477
|
+
let cursor = 0;
|
|
478
|
+
let match;
|
|
479
|
+
while ((match = fencePattern.exec(value)) !== null) {
|
|
480
|
+
const before = value.slice(cursor, match.index);
|
|
481
|
+
const compressedBefore = compressPlainTextAndEmbeddedJson(
|
|
482
|
+
before,
|
|
483
|
+
techniques,
|
|
484
|
+
options
|
|
485
|
+
);
|
|
486
|
+
if (compressedBefore) parts.push(compressedBefore);
|
|
487
|
+
const lang = match[1]?.toLowerCase();
|
|
488
|
+
const code = cleanCodeBlock(match[2] ?? "");
|
|
489
|
+
const compressedCode = lang === "json" ? compressJsonText(code, techniques, options) : void 0;
|
|
490
|
+
if (compressedCode?.format === "toon") {
|
|
491
|
+
parts.push(`\`\`\`toon
|
|
492
|
+
${compressedCode.text}
|
|
493
|
+
\`\`\``);
|
|
494
|
+
} else if (compressedCode?.format === "json") {
|
|
495
|
+
parts.push(`\`\`\`json
|
|
496
|
+
${compressedCode.text}
|
|
497
|
+
\`\`\``);
|
|
498
|
+
} else {
|
|
499
|
+
if (code !== match[2]) {
|
|
500
|
+
techniques.add("code-whitespace");
|
|
501
|
+
}
|
|
502
|
+
parts.push(lang ? `\`\`\`${lang}
|
|
503
|
+
${code}
|
|
504
|
+
\`\`\`` : `\`\`\`
|
|
505
|
+
${code}
|
|
506
|
+
\`\`\``);
|
|
507
|
+
}
|
|
508
|
+
cursor = match.index + match[0].length;
|
|
509
|
+
}
|
|
510
|
+
const after = compressPlainTextAndEmbeddedJson(value.slice(cursor), techniques, options);
|
|
511
|
+
if (after) parts.push(after);
|
|
512
|
+
return parts.join("\n").trim();
|
|
513
|
+
}
|
|
514
|
+
function compressPlainText(value, techniques) {
|
|
515
|
+
const compressed = value.split("\n").map((line) => line.trim()).filter((line) => line).join("\n").replace(/[ \t]{2,}/g, " ").trim();
|
|
516
|
+
if (compressed !== value.trim()) {
|
|
517
|
+
techniques.add("text-whitespace");
|
|
518
|
+
}
|
|
519
|
+
return compressed;
|
|
520
|
+
}
|
|
521
|
+
function compressPlainTextAndEmbeddedJson(value, techniques, options) {
|
|
522
|
+
const normalized = compressPlainText(value, techniques);
|
|
523
|
+
return compressEmbeddedJson(normalized, techniques, options);
|
|
524
|
+
}
|
|
525
|
+
function cleanCodeBlock(code) {
|
|
526
|
+
const lines = code.replace(/\r\n/g, "\n").split("\n");
|
|
527
|
+
while (lines.length && lines[0].trim() === "") lines.shift();
|
|
528
|
+
while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
|
|
529
|
+
const commonIndent = lines.filter((line) => line.trim()).reduce((min, line) => {
|
|
530
|
+
const indent = /^[ \t]*/.exec(line)?.[0].length ?? 0;
|
|
531
|
+
return min === void 0 ? indent : Math.min(min, indent);
|
|
532
|
+
}, void 0);
|
|
533
|
+
return lines.map((line) => commonIndent ? line.slice(commonIndent) : line).join("\n").replace(/[ \t]+$/gm, "");
|
|
534
|
+
}
|
|
35
535
|
function stableStringifyInput(input) {
|
|
36
536
|
if (typeof input === "string") return input;
|
|
37
537
|
return JSON.stringify(input) ?? String(input);
|
|
38
538
|
}
|
|
539
|
+
function compressJsonText(text, techniques, options) {
|
|
540
|
+
const parsed = safeParseJson(text);
|
|
541
|
+
if (parsed === void 0) {
|
|
542
|
+
return void 0;
|
|
543
|
+
}
|
|
544
|
+
const compactJson = JSON.stringify(parsed);
|
|
545
|
+
const candidates = [
|
|
546
|
+
{ format: "json", text: compactJson }
|
|
547
|
+
];
|
|
548
|
+
if (options.allowToonString || shouldUseToonForJson(parsed)) {
|
|
549
|
+
candidates.push({ format: "toon", text: encodeToon(parsed) });
|
|
550
|
+
}
|
|
551
|
+
const originalLength = text.trim().length;
|
|
552
|
+
const best = candidates.reduce(
|
|
553
|
+
(winner, candidate) => candidate.text.length < winner.text.length ? candidate : winner
|
|
554
|
+
);
|
|
555
|
+
if (best.text.length >= originalLength) {
|
|
556
|
+
return void 0;
|
|
557
|
+
}
|
|
558
|
+
techniques.add(best.format === "toon" ? "embedded-json-toon" : "embedded-json-minify");
|
|
559
|
+
return best;
|
|
560
|
+
}
|
|
561
|
+
function compressEmbeddedJson(text, techniques, options) {
|
|
562
|
+
let result = "";
|
|
563
|
+
let cursor = 0;
|
|
564
|
+
while (cursor < text.length) {
|
|
565
|
+
const start = findNextJsonStart(text, cursor);
|
|
566
|
+
if (start < 0) {
|
|
567
|
+
result += text.slice(cursor);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
result += text.slice(cursor, start);
|
|
571
|
+
const span = findBalancedJsonSpan(text, start);
|
|
572
|
+
if (!span) {
|
|
573
|
+
result += text[start];
|
|
574
|
+
cursor = start + 1;
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const candidate = compressJsonText(span.text, techniques, options);
|
|
578
|
+
if (candidate) {
|
|
579
|
+
result += candidate.text;
|
|
580
|
+
} else {
|
|
581
|
+
result += span.text;
|
|
582
|
+
}
|
|
583
|
+
cursor = span.end;
|
|
584
|
+
}
|
|
585
|
+
return result;
|
|
586
|
+
}
|
|
587
|
+
function findNextJsonStart(text, from) {
|
|
588
|
+
const objectStart = text.indexOf("{", from);
|
|
589
|
+
const arrayStart = text.indexOf("[", from);
|
|
590
|
+
if (objectStart < 0) return arrayStart;
|
|
591
|
+
if (arrayStart < 0) return objectStart;
|
|
592
|
+
return Math.min(objectStart, arrayStart);
|
|
593
|
+
}
|
|
594
|
+
function findBalancedJsonSpan(text, start) {
|
|
595
|
+
const opener = text[start];
|
|
596
|
+
const closer = opener === "{" ? "}" : opener === "[" ? "]" : void 0;
|
|
597
|
+
if (!closer) return void 0;
|
|
598
|
+
const stack = [closer];
|
|
599
|
+
let inString = false;
|
|
600
|
+
let escaped = false;
|
|
601
|
+
for (let index = start + 1; index < text.length; index += 1) {
|
|
602
|
+
const char = text[index];
|
|
603
|
+
if (inString) {
|
|
604
|
+
if (escaped) {
|
|
605
|
+
escaped = false;
|
|
606
|
+
} else if (char === "\\") {
|
|
607
|
+
escaped = true;
|
|
608
|
+
} else if (char === '"') {
|
|
609
|
+
inString = false;
|
|
610
|
+
}
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (char === '"') {
|
|
614
|
+
inString = true;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
if (char === "{" || char === "[") {
|
|
618
|
+
stack.push(char === "{" ? "}" : "]");
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
if (char === stack[stack.length - 1]) {
|
|
622
|
+
stack.pop();
|
|
623
|
+
if (!stack.length) {
|
|
624
|
+
const end = index + 1;
|
|
625
|
+
return { text: text.slice(start, end), end };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return void 0;
|
|
630
|
+
}
|
|
631
|
+
function safeParseJson(text) {
|
|
632
|
+
try {
|
|
633
|
+
return JSON.parse(text);
|
|
634
|
+
} catch {
|
|
635
|
+
return void 0;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function shouldUseToonForJson(value) {
|
|
639
|
+
if (Array.isArray(value)) {
|
|
640
|
+
return isUniformObjectArray(value) || value.some(shouldUseToonForJson);
|
|
641
|
+
}
|
|
642
|
+
if (isPlainObject(value)) {
|
|
643
|
+
return Object.values(value).some(shouldUseToonForJson);
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
function encodeToon(value, indent = 0) {
|
|
648
|
+
if (isPrimitive(value)) {
|
|
649
|
+
return scalarToToon(value);
|
|
650
|
+
}
|
|
651
|
+
if (Array.isArray(value)) {
|
|
652
|
+
return encodeArrayToon(value, indent);
|
|
653
|
+
}
|
|
654
|
+
if (isPlainObject(value)) {
|
|
655
|
+
const lines = [];
|
|
656
|
+
for (const [key, child] of Object.entries(value)) {
|
|
657
|
+
lines.push(...encodePropertyToon(key, child, indent));
|
|
658
|
+
}
|
|
659
|
+
return lines.join("\n");
|
|
660
|
+
}
|
|
661
|
+
return scalarToToon(String(value));
|
|
662
|
+
}
|
|
663
|
+
function encodePropertyToon(key, value, indent) {
|
|
664
|
+
const prefix = " ".repeat(indent);
|
|
665
|
+
const toonKey = keyToToon(key);
|
|
666
|
+
if (isPrimitive(value)) {
|
|
667
|
+
return [`${prefix}${toonKey}: ${scalarToToon(value)}`];
|
|
668
|
+
}
|
|
669
|
+
if (Array.isArray(value)) {
|
|
670
|
+
if (value.every(isPrimitive)) {
|
|
671
|
+
return [`${prefix}${toonKey}[${value.length}]: ${value.map(scalarToToon).join(",")}`];
|
|
672
|
+
}
|
|
673
|
+
if (isUniformObjectArray(value)) {
|
|
674
|
+
const fields = Object.keys(value[0]);
|
|
675
|
+
const header = `${prefix}${toonKey}[${value.length}]{${fields.map(keyToToon).join(",")}}:`;
|
|
676
|
+
const rows = value.map(
|
|
677
|
+
(item) => `${" ".repeat(indent + 2)}${fields.map(
|
|
678
|
+
(field) => scalarToToon(item[field])
|
|
679
|
+
).join(",")}`
|
|
680
|
+
);
|
|
681
|
+
return [header, ...rows];
|
|
682
|
+
}
|
|
683
|
+
return [
|
|
684
|
+
`${prefix}${toonKey}[${value.length}]:`,
|
|
685
|
+
...value.flatMap((item, index) => {
|
|
686
|
+
if (isPrimitive(item)) {
|
|
687
|
+
return [`${" ".repeat(indent + 2)}- ${scalarToToon(item)}`];
|
|
688
|
+
}
|
|
689
|
+
return [
|
|
690
|
+
`${" ".repeat(indent + 2)}- item${index}:`,
|
|
691
|
+
...encodeToon(item, indent + 4).split("\n")
|
|
692
|
+
];
|
|
693
|
+
})
|
|
694
|
+
];
|
|
695
|
+
}
|
|
696
|
+
return [`${prefix}${toonKey}:`, ...encodeToon(value, indent + 2).split("\n")];
|
|
697
|
+
}
|
|
698
|
+
function encodeArrayToon(value, indent) {
|
|
699
|
+
if (value.every(isPrimitive)) {
|
|
700
|
+
return `[${value.length}]: ${value.map(scalarToToon).join(",")}`;
|
|
701
|
+
}
|
|
702
|
+
if (isUniformObjectArray(value)) {
|
|
703
|
+
const fields = Object.keys(value[0]);
|
|
704
|
+
return [
|
|
705
|
+
`[${value.length}]{${fields.map(keyToToon).join(",")}}:`,
|
|
706
|
+
...value.map(
|
|
707
|
+
(item) => `${" ".repeat(indent + 2)}${fields.map(
|
|
708
|
+
(field) => scalarToToon(item[field])
|
|
709
|
+
).join(",")}`
|
|
710
|
+
)
|
|
711
|
+
].join("\n");
|
|
712
|
+
}
|
|
713
|
+
return value.flatMap((item, index) => [
|
|
714
|
+
`${" ".repeat(indent)}- item${index}:`,
|
|
715
|
+
...encodeToon(item, indent + 2).split("\n")
|
|
716
|
+
]).join("\n");
|
|
717
|
+
}
|
|
718
|
+
function isUniformObjectArray(value) {
|
|
719
|
+
if (!value.length || !value.every(isPlainObject)) {
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
const fields = Object.keys(value[0]);
|
|
723
|
+
if (!fields.length) {
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
return value.every((item) => {
|
|
727
|
+
const record = item;
|
|
728
|
+
const itemFields = Object.keys(record);
|
|
729
|
+
return itemFields.length === fields.length && fields.every((field) => itemFields.includes(field) && isPrimitive(record[field]));
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
function isPlainObject(value) {
|
|
733
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
734
|
+
}
|
|
735
|
+
function isPrimitive(value) {
|
|
736
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
737
|
+
}
|
|
738
|
+
function keyToToon(key) {
|
|
739
|
+
return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key) ? key : JSON.stringify(key);
|
|
740
|
+
}
|
|
741
|
+
function scalarToToon(value) {
|
|
742
|
+
if (value === null) return "null";
|
|
743
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
744
|
+
return String(value);
|
|
745
|
+
}
|
|
746
|
+
const text = String(value);
|
|
747
|
+
if (text && !/^(true|false|null|-?\d+(?:\.\d+)?)$/i.test(text) && /^[A-Za-z0-9_./@-]+(?: [A-Za-z0-9_./@-]+)*$/.test(text)) {
|
|
748
|
+
return text;
|
|
749
|
+
}
|
|
750
|
+
return JSON.stringify(text);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/client.ts
|
|
754
|
+
var CALL_BEGIN_PATH = "call_begin";
|
|
755
|
+
var CALL_END_PATH = "call_end";
|
|
756
|
+
var COMPRESS_PROMPT_PATH = "compress_prompt";
|
|
757
|
+
var CHECK_USAGE_PATH = "customers/{customerId}/usage";
|
|
758
|
+
var CREATE_CUSTOMER_PATH = "customers";
|
|
759
|
+
var CHANGE_PLAN_PATH = "customers/{customerId}/change_plan";
|
|
760
|
+
var INCREMENT_CUSTOM_METER_PATH = "custom_meter";
|
|
761
|
+
var AUTH_HEADER = "authorization";
|
|
762
|
+
var API_KEY_HEADER = "x-api-key";
|
|
763
|
+
var CORRELATION_HEADER = "x-usage-correlation-id";
|
|
764
|
+
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
765
|
+
var SDK_HEADER = "x-usage-sdk";
|
|
766
|
+
var USER_AGENT = "UsageTapClient";
|
|
767
|
+
var CANONICAL_MEDIA_TYPE = "application/vnd.usagetap.v1+json";
|
|
768
|
+
var DEFAULT_BASE_URL = "https://api.usagetap.com";
|
|
769
|
+
var SDK_VERSION = "1.3.1" ;
|
|
770
|
+
var HAS_WINDOW = typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
771
|
+
var UsageTapClient = class {
|
|
772
|
+
apiKey;
|
|
773
|
+
baseUrl;
|
|
774
|
+
fetchImpl;
|
|
775
|
+
defaultFeature;
|
|
776
|
+
defaultTags;
|
|
777
|
+
defaultHeaders;
|
|
778
|
+
retryDefaults;
|
|
779
|
+
idempotencyGenerator;
|
|
780
|
+
logFn;
|
|
781
|
+
metricFn;
|
|
782
|
+
authHeader;
|
|
783
|
+
autoIdempotency;
|
|
784
|
+
tokenCompanyApiKey;
|
|
785
|
+
tokenCompanyEndpoint;
|
|
786
|
+
model;
|
|
787
|
+
tokenCompanyModel;
|
|
788
|
+
aggressiveness;
|
|
789
|
+
tokenCompanyAggressiveness;
|
|
790
|
+
tokenCompanyAppId;
|
|
791
|
+
usageTapCompressionApiKey;
|
|
792
|
+
usageTapCompressionEndpoint;
|
|
793
|
+
usageTapCompressionMessagesEndpoint;
|
|
794
|
+
usageTapCompressionModel;
|
|
795
|
+
usageTapCompressionAggressiveness;
|
|
796
|
+
constructor(options = {}) {
|
|
797
|
+
const apiKey = options.apiKey?.trim() || readEnvironmentVariable("USAGETAP_API_KEY");
|
|
798
|
+
const baseUrl = options.baseUrl?.trim() || readEnvironmentVariable("USAGETAP_BASE_URL") || DEFAULT_BASE_URL;
|
|
799
|
+
if (!apiKey) {
|
|
800
|
+
throw new UsageTapError(
|
|
801
|
+
"USAGETAP_BAD_REQUEST",
|
|
802
|
+
"UsageTapClient requires an apiKey or the USAGETAP_API_KEY environment variable"
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
if (HAS_WINDOW && !options.allowBrowser) {
|
|
806
|
+
throw new UsageTapError(
|
|
807
|
+
"USAGETAP_BROWSER_RUNTIME",
|
|
808
|
+
"UsageTapClient is designed for server-side environments. Pass allowBrowser=true only for testing."
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
const fetchCandidate = options.fetchImpl ?? globalThis.fetch;
|
|
812
|
+
if (typeof fetchCandidate !== "function") {
|
|
813
|
+
throw new UsageTapError(
|
|
814
|
+
"USAGETAP_NETWORK_ERROR",
|
|
815
|
+
"A global fetch implementation was not found. Pass fetchImpl in UsageTapClientOptions."
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
|
819
|
+
this.baseUrl = new URL(normalizedBaseUrl);
|
|
820
|
+
this.apiKey = apiKey;
|
|
821
|
+
this.fetchImpl = wrapFetchImplementation(fetchCandidate, !options.fetchImpl);
|
|
822
|
+
this.defaultFeature = options.defaultFeature;
|
|
823
|
+
this.defaultTags = options.defaultTags?.length ? dedupeStrings(options.defaultTags) : void 0;
|
|
824
|
+
this.defaultHeaders = options.headers ? normalizeHeaderDictionary(options.headers) : {};
|
|
825
|
+
this.retryDefaults = resolveRetryOptions(options.retries);
|
|
826
|
+
this.idempotencyGenerator = options.idempotencyGenerator ?? createIdempotencyKey;
|
|
827
|
+
this.logFn = options.onLog;
|
|
828
|
+
this.metricFn = options.onUsageMetric;
|
|
829
|
+
this.authHeader = options.useApiKeyHeader ? API_KEY_HEADER : AUTH_HEADER;
|
|
830
|
+
this.autoIdempotency = options.autoIdempotency ?? true;
|
|
831
|
+
this.tokenCompanyApiKey = options.tokenCompanyApiKey;
|
|
832
|
+
this.tokenCompanyEndpoint = options.tokenCompanyEndpoint;
|
|
833
|
+
this.model = options.model;
|
|
834
|
+
this.tokenCompanyModel = options.tokenCompanyModel;
|
|
835
|
+
this.aggressiveness = options.aggressiveness;
|
|
836
|
+
this.tokenCompanyAggressiveness = options.tokenCompanyAggressiveness;
|
|
837
|
+
this.tokenCompanyAppId = options.tokenCompanyAppId;
|
|
838
|
+
this.usageTapCompressionApiKey = options.usageTapCompressionApiKey ?? apiKey;
|
|
839
|
+
this.usageTapCompressionEndpoint = options.usageTapCompressionEndpoint;
|
|
840
|
+
this.usageTapCompressionMessagesEndpoint = options.usageTapCompressionMessagesEndpoint;
|
|
841
|
+
this.usageTapCompressionModel = options.usageTapCompressionModel;
|
|
842
|
+
this.usageTapCompressionAggressiveness = options.usageTapCompressionAggressiveness;
|
|
843
|
+
}
|
|
844
|
+
async beginCall(request, options = {}) {
|
|
845
|
+
const idempotencyKey = request.idempotencyKey ?? request.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
846
|
+
const payload = {
|
|
847
|
+
...request,
|
|
848
|
+
feature: request.feature ?? this.defaultFeature,
|
|
849
|
+
tags: this.mergeTags(request.tags)
|
|
850
|
+
};
|
|
851
|
+
if (idempotencyKey) {
|
|
852
|
+
payload.idempotencyKey = idempotencyKey;
|
|
853
|
+
payload.idempotency = idempotencyKey;
|
|
854
|
+
}
|
|
855
|
+
const response = await this.request(
|
|
856
|
+
CALL_BEGIN_PATH,
|
|
857
|
+
payload,
|
|
858
|
+
{
|
|
859
|
+
...options,
|
|
860
|
+
idempotencyKey
|
|
861
|
+
}
|
|
862
|
+
);
|
|
863
|
+
return response;
|
|
864
|
+
}
|
|
865
|
+
async promptCompress(request, options = {}) {
|
|
866
|
+
if (!request?.callId) {
|
|
867
|
+
throw new UsageTapError(
|
|
868
|
+
"USAGETAP_BAD_REQUEST",
|
|
869
|
+
"promptCompress requires callId"
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
const requestInput = request.input ?? request.text;
|
|
873
|
+
if (requestInput === void 0) {
|
|
874
|
+
throw new UsageTapError(
|
|
875
|
+
"USAGETAP_BAD_REQUEST",
|
|
876
|
+
"promptCompress requires input or text"
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
const result = await this.compressPromptInput(requestInput, {
|
|
880
|
+
provider: request.provider,
|
|
881
|
+
model: request.model,
|
|
882
|
+
tokenCompanyModel: request.tokenCompanyModel,
|
|
883
|
+
aggressiveness: request.aggressiveness,
|
|
884
|
+
tokenCompanyAggressiveness: request.tokenCompanyAggressiveness,
|
|
885
|
+
tokenCompanyAppId: request.tokenCompanyAppId,
|
|
886
|
+
usageTapCompressionModel: request.usageTapCompressionModel,
|
|
887
|
+
usageTapCompressionAggressiveness: request.usageTapCompressionAggressiveness,
|
|
888
|
+
signal: options.signal
|
|
889
|
+
});
|
|
890
|
+
try {
|
|
891
|
+
await this.recordPromptCompression(
|
|
892
|
+
{
|
|
893
|
+
callId: request.callId,
|
|
894
|
+
promptCompression: this.toPromptCompressionTelemetry(result)
|
|
895
|
+
},
|
|
896
|
+
options
|
|
897
|
+
);
|
|
898
|
+
return { ...result, callId: request.callId };
|
|
899
|
+
} catch (error) {
|
|
900
|
+
return {
|
|
901
|
+
...createPromptCompressionFallback(
|
|
902
|
+
requestInput,
|
|
903
|
+
request.provider ?? result.provider,
|
|
904
|
+
error
|
|
905
|
+
),
|
|
906
|
+
callId: request.callId
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async compressPromptInput(input, options = {}) {
|
|
911
|
+
return compressPrompt({
|
|
912
|
+
input,
|
|
913
|
+
provider: options.provider,
|
|
914
|
+
tokenCompanyApiKey: this.tokenCompanyApiKey,
|
|
915
|
+
tokenCompanyEndpoint: this.tokenCompanyEndpoint,
|
|
916
|
+
model: options.model ?? this.model,
|
|
917
|
+
tokenCompanyModel: options.tokenCompanyModel ?? this.tokenCompanyModel,
|
|
918
|
+
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
919
|
+
tokenCompanyAggressiveness: options.tokenCompanyAggressiveness ?? this.tokenCompanyAggressiveness,
|
|
920
|
+
tokenCompanyAppId: options.tokenCompanyAppId ?? this.tokenCompanyAppId,
|
|
921
|
+
usageTapCompressionApiKey: this.usageTapCompressionApiKey,
|
|
922
|
+
usageTapCompressionEndpoint: this.usageTapCompressionEndpoint,
|
|
923
|
+
usageTapCompressionModel: options.usageTapCompressionModel ?? this.usageTapCompressionModel,
|
|
924
|
+
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
925
|
+
fetchImpl: this.fetchImpl,
|
|
926
|
+
signal: options.signal,
|
|
927
|
+
failOpen: options.failOpen
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Compress text with UsageTap's hosted compression service.
|
|
932
|
+
*
|
|
933
|
+
* This is the short, standalone path. It does not create a metered call and
|
|
934
|
+
* fails open to the original text unless failOpen is explicitly disabled.
|
|
935
|
+
*/
|
|
936
|
+
async compress(text, options = {}) {
|
|
937
|
+
if (typeof text !== "string") {
|
|
938
|
+
throw new UsageTapError(
|
|
939
|
+
"USAGETAP_BAD_REQUEST",
|
|
940
|
+
"compress requires text"
|
|
941
|
+
);
|
|
942
|
+
}
|
|
943
|
+
const result = await this.compressPromptInput(text, {
|
|
944
|
+
...options,
|
|
945
|
+
provider: "usagetap"
|
|
946
|
+
});
|
|
947
|
+
const output = typeof result.compressedInput === "string" ? result.compressedInput : text;
|
|
948
|
+
return {
|
|
949
|
+
...result,
|
|
950
|
+
compressedInput: output,
|
|
951
|
+
output
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
async compressPromptMessages(input, options = {}) {
|
|
955
|
+
return compressPromptMessages({
|
|
956
|
+
input,
|
|
957
|
+
provider: options.provider ?? "usagetap",
|
|
958
|
+
usageTapCompressionApiKey: this.usageTapCompressionApiKey,
|
|
959
|
+
usageTapCompressionMessagesEndpoint: this.usageTapCompressionMessagesEndpoint,
|
|
960
|
+
aggressiveness: options.aggressiveness ?? this.aggressiveness,
|
|
961
|
+
usageTapCompressionAggressiveness: options.usageTapCompressionAggressiveness ?? this.usageTapCompressionAggressiveness,
|
|
962
|
+
fetchImpl: this.fetchImpl,
|
|
963
|
+
signal: options.signal,
|
|
964
|
+
failOpen: options.failOpen
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
async recordPromptCompression(request, options = {}) {
|
|
968
|
+
if (!request?.callId) {
|
|
969
|
+
throw new UsageTapError(
|
|
970
|
+
"USAGETAP_BAD_REQUEST",
|
|
971
|
+
"recordPromptCompression requires callId"
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
return this.request(
|
|
975
|
+
COMPRESS_PROMPT_PATH,
|
|
976
|
+
{
|
|
977
|
+
callId: request.callId,
|
|
978
|
+
promptCompression: request.promptCompression
|
|
979
|
+
},
|
|
980
|
+
options
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
async endCall(request, options = {}) {
|
|
984
|
+
if (!request?.callId) {
|
|
985
|
+
throw new UsageTapError(
|
|
986
|
+
"USAGETAP_BAD_REQUEST",
|
|
987
|
+
"endCall requires callId"
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
const { customerId, feature, tags, ...apiPayload } = request;
|
|
991
|
+
const response = await this.request(
|
|
992
|
+
CALL_END_PATH,
|
|
993
|
+
apiPayload,
|
|
994
|
+
options
|
|
995
|
+
);
|
|
996
|
+
this.emitUsageMetric({
|
|
997
|
+
type: "call_end",
|
|
998
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
999
|
+
customerId: customerId ?? "unknown",
|
|
1000
|
+
callId: request.callId,
|
|
1001
|
+
feature: feature ?? this.defaultFeature,
|
|
1002
|
+
tags: tags ?? this.defaultTags,
|
|
1003
|
+
modelUsed: request.modelUsed,
|
|
1004
|
+
metrics: {
|
|
1005
|
+
inputTokens: request.inputTokens,
|
|
1006
|
+
responseTokens: request.responseTokens,
|
|
1007
|
+
cachedInputTokens: request.cachedInputTokens,
|
|
1008
|
+
reasoningTokens: request.reasoningTokens,
|
|
1009
|
+
searches: request.searches,
|
|
1010
|
+
audioSeconds: request.audioSeconds,
|
|
1011
|
+
costUsd: response.data.costUSD
|
|
1012
|
+
},
|
|
1013
|
+
correlationId: response.correlationId
|
|
1014
|
+
});
|
|
1015
|
+
return response;
|
|
1016
|
+
}
|
|
1017
|
+
async checkUsage(request, options = {}) {
|
|
1018
|
+
if (!request?.customerId) {
|
|
1019
|
+
throw new UsageTapError(
|
|
1020
|
+
"USAGETAP_BAD_REQUEST",
|
|
1021
|
+
"checkUsage requires customerId"
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
const path = CHECK_USAGE_PATH.replace(
|
|
1025
|
+
"{customerId}",
|
|
1026
|
+
encodeURIComponent(request.customerId)
|
|
1027
|
+
);
|
|
1028
|
+
const response = await this.requestGet(
|
|
1029
|
+
path,
|
|
1030
|
+
options
|
|
1031
|
+
);
|
|
1032
|
+
return response;
|
|
1033
|
+
}
|
|
1034
|
+
async createCustomer(request, options = {}) {
|
|
1035
|
+
if (!request?.customerId) {
|
|
1036
|
+
throw new UsageTapError(
|
|
1037
|
+
"USAGETAP_BAD_REQUEST",
|
|
1038
|
+
"createCustomer requires customerId"
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
const idempotencyKey = options.idempotencyKey ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1042
|
+
const response = await this.request(
|
|
1043
|
+
CREATE_CUSTOMER_PATH,
|
|
1044
|
+
{ ...request },
|
|
1045
|
+
{
|
|
1046
|
+
...options,
|
|
1047
|
+
idempotencyKey
|
|
1048
|
+
}
|
|
1049
|
+
);
|
|
1050
|
+
return response;
|
|
1051
|
+
}
|
|
1052
|
+
async changePlan(request, options = {}) {
|
|
1053
|
+
if (!request?.customerId) {
|
|
1054
|
+
throw new UsageTapError(
|
|
1055
|
+
"USAGETAP_BAD_REQUEST",
|
|
1056
|
+
"changePlan requires customerId"
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
if (!request?.planId) {
|
|
1060
|
+
throw new UsageTapError(
|
|
1061
|
+
"USAGETAP_BAD_REQUEST",
|
|
1062
|
+
"changePlan requires planId"
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
const path = CHANGE_PLAN_PATH.replace(
|
|
1066
|
+
"{customerId}",
|
|
1067
|
+
encodeURIComponent(request.customerId)
|
|
1068
|
+
);
|
|
1069
|
+
const idempotencyKey = options.idempotencyKey ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1070
|
+
const payload = {
|
|
1071
|
+
planId: request.planId,
|
|
1072
|
+
strategy: request.strategy ?? "IMMEDIATE_RESET"
|
|
1073
|
+
};
|
|
1074
|
+
const response = await this.request(
|
|
1075
|
+
path,
|
|
1076
|
+
payload,
|
|
1077
|
+
{
|
|
1078
|
+
...options,
|
|
1079
|
+
idempotencyKey
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
return response;
|
|
1083
|
+
}
|
|
1084
|
+
async incrementCustomMeter(request, options = {}) {
|
|
1085
|
+
if (!request?.customerId) {
|
|
1086
|
+
throw new UsageTapError(
|
|
1087
|
+
"USAGETAP_BAD_REQUEST",
|
|
1088
|
+
"incrementCustomMeter requires customerId"
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
if (!request?.meterSlot) {
|
|
1092
|
+
throw new UsageTapError(
|
|
1093
|
+
"USAGETAP_BAD_REQUEST",
|
|
1094
|
+
"incrementCustomMeter requires meterSlot"
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (!["CUSTOM1", "CUSTOM2"].includes(request.meterSlot)) {
|
|
1098
|
+
throw new UsageTapError(
|
|
1099
|
+
"USAGETAP_BAD_REQUEST",
|
|
1100
|
+
"meterSlot must be CUSTOM1 or CUSTOM2"
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
if (typeof request.amount !== "number" || !Number.isFinite(request.amount) || request.amount <= 0) {
|
|
1104
|
+
throw new UsageTapError(
|
|
1105
|
+
"USAGETAP_BAD_REQUEST",
|
|
1106
|
+
"incrementCustomMeter requires a positive numeric amount"
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
const idempotencyKey = options.idempotencyKey ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1110
|
+
const payload = {
|
|
1111
|
+
customerId: request.customerId,
|
|
1112
|
+
meterSlot: request.meterSlot,
|
|
1113
|
+
amount: request.amount
|
|
1114
|
+
};
|
|
1115
|
+
if (request.feature) {
|
|
1116
|
+
payload.feature = request.feature;
|
|
1117
|
+
}
|
|
1118
|
+
if (request.tags && request.tags.length > 0) {
|
|
1119
|
+
payload.tags = request.tags;
|
|
1120
|
+
}
|
|
1121
|
+
if (request.metadata) {
|
|
1122
|
+
payload.metadata = request.metadata;
|
|
1123
|
+
}
|
|
1124
|
+
const response = await this.request(
|
|
1125
|
+
INCREMENT_CUSTOM_METER_PATH,
|
|
1126
|
+
payload,
|
|
1127
|
+
{
|
|
1128
|
+
...options,
|
|
1129
|
+
idempotencyKey
|
|
1130
|
+
}
|
|
1131
|
+
);
|
|
1132
|
+
this.emitUsageMetric({
|
|
1133
|
+
type: "custom_meter",
|
|
1134
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1135
|
+
customerId: request.customerId,
|
|
1136
|
+
feature: request.feature ?? this.defaultFeature,
|
|
1137
|
+
tags: request.tags ?? this.defaultTags,
|
|
1138
|
+
metrics: {
|
|
1139
|
+
customMeterSlot: request.meterSlot,
|
|
1140
|
+
customMeterAmount: request.amount
|
|
1141
|
+
},
|
|
1142
|
+
correlationId: response.correlationId
|
|
1143
|
+
});
|
|
1144
|
+
return response;
|
|
1145
|
+
}
|
|
1146
|
+
async withUsage(beginRequest, handler, options = {}) {
|
|
1147
|
+
const idempotencyKey = beginRequest.idempotencyKey ?? beginRequest.idempotency ?? (this.autoIdempotency ? this.idempotencyGenerator() : void 0);
|
|
1148
|
+
const beginPayload = idempotencyKey ? { ...beginRequest, idempotencyKey, idempotency: idempotencyKey } : { ...beginRequest };
|
|
1149
|
+
const beginResponse = await this.beginCall(beginPayload, options);
|
|
1150
|
+
let usage = {};
|
|
1151
|
+
const initialStripeCustomerId = typeof beginResponse.data.stripeCustomerId === "string" ? beginResponse.data.stripeCustomerId : typeof beginRequest.stripeCustomerId === "string" ? beginRequest.stripeCustomerId : void 0;
|
|
1152
|
+
if (initialStripeCustomerId) {
|
|
1153
|
+
usage = { ...usage, stripeCustomerId: initialStripeCustomerId };
|
|
1154
|
+
}
|
|
1155
|
+
let errorPayload;
|
|
1156
|
+
let handlerResult;
|
|
1157
|
+
let handlerError;
|
|
1158
|
+
let endCallError;
|
|
1159
|
+
const context = {
|
|
1160
|
+
begin: beginResponse,
|
|
1161
|
+
setUsage: (u) => {
|
|
1162
|
+
usage = { ...usage, ...u };
|
|
1163
|
+
},
|
|
1164
|
+
setError: (err) => {
|
|
1165
|
+
errorPayload = err;
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
try {
|
|
1169
|
+
handlerResult = await handler(context);
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
handlerError = error;
|
|
1172
|
+
if (!errorPayload) {
|
|
1173
|
+
errorPayload = {
|
|
1174
|
+
code: options.defaultErrorCode ?? "VENDOR_ERROR",
|
|
1175
|
+
message: error instanceof Error ? error.message : String(error)
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
} finally {
|
|
1179
|
+
try {
|
|
1180
|
+
await this.endCall(
|
|
1181
|
+
{
|
|
1182
|
+
callId: beginResponse.data.callId,
|
|
1183
|
+
// Pass context for metric tracking
|
|
1184
|
+
customerId: beginRequest.customerId,
|
|
1185
|
+
feature: beginRequest.feature ?? this.defaultFeature,
|
|
1186
|
+
tags: beginRequest.tags ?? this.defaultTags,
|
|
1187
|
+
...usage,
|
|
1188
|
+
error: errorPayload
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
...options,
|
|
1192
|
+
correlationId: beginResponse.correlationId
|
|
1193
|
+
}
|
|
1194
|
+
);
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
endCallError = error;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
if (handlerError) {
|
|
1200
|
+
throw handlerError;
|
|
1201
|
+
}
|
|
1202
|
+
if (endCallError) {
|
|
1203
|
+
throw wrapEndCallError(endCallError, beginResponse.correlationId);
|
|
1204
|
+
}
|
|
1205
|
+
return handlerResult;
|
|
1206
|
+
}
|
|
1207
|
+
/**
|
|
1208
|
+
* Meter one operation. Pass only a customer ID for the common path, or the
|
|
1209
|
+
* existing begin-call request object when feature, tags, or entitlements are needed.
|
|
1210
|
+
*/
|
|
1211
|
+
async meter(request, handler, options = {}) {
|
|
1212
|
+
const beginRequest = typeof request === "string" ? { customerId: request } : request;
|
|
1213
|
+
return this.withUsage(beginRequest, handler, options);
|
|
1214
|
+
}
|
|
1215
|
+
toPromptCompressionTelemetry(result) {
|
|
1216
|
+
return {
|
|
1217
|
+
provider: result.provider,
|
|
1218
|
+
originalCharacters: result.originalCharacters,
|
|
1219
|
+
compressedCharacters: result.compressedCharacters,
|
|
1220
|
+
savedCharacters: result.savedCharacters,
|
|
1221
|
+
originalTokens: result.originalTokens,
|
|
1222
|
+
compressedTokens: result.compressedTokens,
|
|
1223
|
+
savedTokens: result.savedTokens,
|
|
1224
|
+
tokenSavingsRatio: result.tokenSavingsRatio,
|
|
1225
|
+
savingsRatio: result.savingsRatio,
|
|
1226
|
+
techniques: result.techniques
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
async request(path, payload, options) {
|
|
1230
|
+
const url = new URL(path, this.baseUrl).toString();
|
|
1231
|
+
const body = payload !== void 0 ? JSON.stringify(payload) : void 0;
|
|
1232
|
+
const headers = this.composeHeaders(body, options);
|
|
1233
|
+
const resolvedRetry = resolveRetryOptions(
|
|
1234
|
+
this.retryDefaults,
|
|
1235
|
+
options.retries
|
|
1236
|
+
);
|
|
1237
|
+
const startTime = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1238
|
+
return runWithRetry(
|
|
1239
|
+
async (attempt) => {
|
|
1240
|
+
const startedAt = startTime();
|
|
1241
|
+
this.log({
|
|
1242
|
+
event: "request:start",
|
|
1243
|
+
path,
|
|
1244
|
+
attempt,
|
|
1245
|
+
idempotencyKey: options.idempotencyKey,
|
|
1246
|
+
correlationId: options.correlationId
|
|
1247
|
+
});
|
|
1248
|
+
const response = await this.performFetch({
|
|
1249
|
+
url,
|
|
1250
|
+
method: "POST",
|
|
1251
|
+
headers,
|
|
1252
|
+
body,
|
|
1253
|
+
signal: options.signal
|
|
1254
|
+
});
|
|
1255
|
+
this.log({
|
|
1256
|
+
event: "request:success",
|
|
1257
|
+
path,
|
|
1258
|
+
attempt,
|
|
1259
|
+
idempotencyKey: options.idempotencyKey,
|
|
1260
|
+
correlationId: response.correlationId,
|
|
1261
|
+
elapsedMs: startTime() - startedAt
|
|
1262
|
+
});
|
|
1263
|
+
return response;
|
|
1264
|
+
},
|
|
1265
|
+
resolvedRetry,
|
|
1266
|
+
(error) => this.shouldRetry(error),
|
|
1267
|
+
(attempt, delayMs, error) => {
|
|
1268
|
+
this.log({
|
|
1269
|
+
event: "retry:scheduled",
|
|
1270
|
+
path,
|
|
1271
|
+
attempt,
|
|
1272
|
+
idempotencyKey: options.idempotencyKey,
|
|
1273
|
+
correlationId: options.correlationId,
|
|
1274
|
+
error,
|
|
1275
|
+
elapsedMs: delayMs
|
|
1276
|
+
});
|
|
1277
|
+
},
|
|
1278
|
+
options.signal
|
|
1279
|
+
).catch((error) => {
|
|
1280
|
+
this.log({
|
|
1281
|
+
event: "retry:exhausted",
|
|
1282
|
+
path,
|
|
1283
|
+
attempt: resolvedRetry.maxAttempts,
|
|
1284
|
+
idempotencyKey: options.idempotencyKey,
|
|
1285
|
+
correlationId: options.correlationId,
|
|
1286
|
+
error
|
|
1287
|
+
});
|
|
1288
|
+
throw error;
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
async requestGet(path, options) {
|
|
1292
|
+
const url = new URL(path, this.baseUrl).toString();
|
|
1293
|
+
const headers = this.composeHeaders(void 0, options);
|
|
1294
|
+
const resolvedRetry = resolveRetryOptions(
|
|
1295
|
+
this.retryDefaults,
|
|
1296
|
+
options.retries
|
|
1297
|
+
);
|
|
1298
|
+
const startTime = () => typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
1299
|
+
return runWithRetry(
|
|
1300
|
+
async (attempt) => {
|
|
1301
|
+
const startedAt = startTime();
|
|
1302
|
+
this.log({
|
|
1303
|
+
event: "request:start",
|
|
1304
|
+
path,
|
|
1305
|
+
attempt,
|
|
1306
|
+
correlationId: options.correlationId
|
|
1307
|
+
});
|
|
1308
|
+
const response = await this.performFetch({
|
|
1309
|
+
url,
|
|
1310
|
+
method: "GET",
|
|
1311
|
+
headers,
|
|
1312
|
+
signal: options.signal
|
|
1313
|
+
});
|
|
1314
|
+
this.log({
|
|
1315
|
+
event: "request:success",
|
|
1316
|
+
path,
|
|
1317
|
+
attempt,
|
|
1318
|
+
correlationId: response.correlationId,
|
|
1319
|
+
elapsedMs: startTime() - startedAt
|
|
1320
|
+
});
|
|
1321
|
+
return response;
|
|
1322
|
+
},
|
|
1323
|
+
resolvedRetry,
|
|
1324
|
+
(error) => this.shouldRetry(error),
|
|
1325
|
+
(attempt, delayMs, error) => {
|
|
1326
|
+
this.log({
|
|
1327
|
+
event: "retry:scheduled",
|
|
1328
|
+
path,
|
|
1329
|
+
attempt,
|
|
1330
|
+
correlationId: options.correlationId,
|
|
1331
|
+
error,
|
|
1332
|
+
elapsedMs: delayMs
|
|
1333
|
+
});
|
|
1334
|
+
},
|
|
1335
|
+
options.signal
|
|
1336
|
+
).catch((error) => {
|
|
1337
|
+
this.log({
|
|
1338
|
+
event: "retry:exhausted",
|
|
1339
|
+
path,
|
|
1340
|
+
attempt: resolvedRetry.maxAttempts,
|
|
1341
|
+
correlationId: options.correlationId,
|
|
1342
|
+
error
|
|
1343
|
+
});
|
|
1344
|
+
throw error;
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
async performFetch(init) {
|
|
1348
|
+
let response;
|
|
1349
|
+
try {
|
|
1350
|
+
response = await this.fetchImpl(init.url, {
|
|
1351
|
+
method: init.method,
|
|
1352
|
+
headers: init.headers,
|
|
1353
|
+
body: init.body,
|
|
1354
|
+
signal: init.signal
|
|
1355
|
+
});
|
|
1356
|
+
} catch (error) {
|
|
1357
|
+
throw new UsageTapError(
|
|
1358
|
+
"USAGETAP_NETWORK_ERROR",
|
|
1359
|
+
"Failed to reach UsageTap",
|
|
1360
|
+
{
|
|
1361
|
+
retryable: true,
|
|
1362
|
+
cause: error
|
|
1363
|
+
}
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
const correlationId = response.headers.get(CORRELATION_HEADER) ?? void 0;
|
|
1367
|
+
const text = await response.text();
|
|
1368
|
+
let payload;
|
|
1369
|
+
if (text) {
|
|
1370
|
+
try {
|
|
1371
|
+
payload = JSON.parse(text);
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
throw new UsageTapError(
|
|
1374
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
1375
|
+
"UsageTap returned invalid JSON",
|
|
1376
|
+
{
|
|
1377
|
+
retryable: false,
|
|
1378
|
+
correlationId,
|
|
1379
|
+
cause: error
|
|
1380
|
+
}
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
if (!response.ok) {
|
|
1385
|
+
throw this.toHttpError(response.status, payload, correlationId);
|
|
1386
|
+
}
|
|
1387
|
+
if (!payload?.result || payload.result.status !== "ACCEPTED") {
|
|
1388
|
+
throw this.toApiError(payload, correlationId);
|
|
1389
|
+
}
|
|
1390
|
+
const resolvedCorrelation = payload.correlationId ?? correlationId;
|
|
1391
|
+
if (payload.data === void 0 || payload.data === null || !resolvedCorrelation) {
|
|
1392
|
+
throw new UsageTapError(
|
|
1393
|
+
"USAGETAP_INVALID_RESPONSE",
|
|
1394
|
+
"UsageTap response missing data or correlationId",
|
|
1395
|
+
{
|
|
1396
|
+
correlationId: resolvedCorrelation ?? correlationId
|
|
1397
|
+
}
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
return {
|
|
1401
|
+
result: {
|
|
1402
|
+
status: payload.result.status,
|
|
1403
|
+
code: payload.result.code,
|
|
1404
|
+
message: payload.result.message,
|
|
1405
|
+
timestamp: payload.result.timestamp
|
|
1406
|
+
},
|
|
1407
|
+
data: payload.data,
|
|
1408
|
+
correlationId: resolvedCorrelation
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1411
|
+
composeHeaders(body, options) {
|
|
1412
|
+
const headers = {
|
|
1413
|
+
...this.defaultHeaders,
|
|
1414
|
+
[SDK_HEADER]: `js/${SDK_VERSION}`,
|
|
1415
|
+
"content-type": "application/json",
|
|
1416
|
+
accept: CANONICAL_MEDIA_TYPE
|
|
1417
|
+
};
|
|
1418
|
+
if (!HAS_WINDOW) {
|
|
1419
|
+
headers["user-agent"] = `${USER_AGENT}/${SDK_VERSION}`;
|
|
1420
|
+
}
|
|
1421
|
+
if (this.authHeader === API_KEY_HEADER) {
|
|
1422
|
+
headers[API_KEY_HEADER] = this.apiKey;
|
|
1423
|
+
} else {
|
|
1424
|
+
headers[AUTH_HEADER] = `Bearer ${this.apiKey}`;
|
|
1425
|
+
}
|
|
1426
|
+
if (options.idempotencyKey) {
|
|
1427
|
+
headers[IDEMPOTENCY_HEADER] = options.idempotencyKey;
|
|
1428
|
+
}
|
|
1429
|
+
if (options.correlationId) {
|
|
1430
|
+
headers[CORRELATION_HEADER] = options.correlationId;
|
|
1431
|
+
}
|
|
1432
|
+
if (!body) {
|
|
1433
|
+
delete headers["content-type"];
|
|
1434
|
+
}
|
|
1435
|
+
if (options.headers) {
|
|
1436
|
+
Object.assign(headers, normalizeHeaderDictionary(options.headers));
|
|
1437
|
+
}
|
|
1438
|
+
return headers;
|
|
1439
|
+
}
|
|
1440
|
+
log(entry) {
|
|
1441
|
+
this.logFn?.(entry);
|
|
1442
|
+
}
|
|
1443
|
+
emitUsageMetric(event) {
|
|
1444
|
+
try {
|
|
1445
|
+
this.metricFn?.(event);
|
|
1446
|
+
} catch {
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
mergeTags(tags) {
|
|
1450
|
+
if (!tags && !this.defaultTags) {
|
|
1451
|
+
return void 0;
|
|
1452
|
+
}
|
|
1453
|
+
const combined = [...this.defaultTags ?? [], ...tags ?? []].filter(
|
|
1454
|
+
Boolean
|
|
1455
|
+
);
|
|
1456
|
+
return combined.length ? dedupeStrings(combined) : void 0;
|
|
1457
|
+
}
|
|
1458
|
+
shouldRetry(error) {
|
|
1459
|
+
if (isUsageTapError(error)) {
|
|
1460
|
+
return Boolean(error.retryable);
|
|
1461
|
+
}
|
|
1462
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1463
|
+
return false;
|
|
1464
|
+
}
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
toHttpError(status, payload, correlationId) {
|
|
1468
|
+
const code = mapStatusToErrorCode(status);
|
|
1469
|
+
const retryable = isRetryableStatus(status);
|
|
1470
|
+
const message = payload?.error?.message ?? payload?.result?.message ?? `UsageTap responded with HTTP ${status}`;
|
|
1471
|
+
return new UsageTapError(code, message, {
|
|
1472
|
+
status,
|
|
1473
|
+
retryable,
|
|
1474
|
+
correlationId: payload?.correlationId ?? correlationId,
|
|
1475
|
+
details: sanitizeDetails(payload)
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
toApiError(payload, correlationId) {
|
|
1479
|
+
const normalizedCode = payload?.error?.code ?? payload?.result?.code ?? "UNKNOWN";
|
|
1480
|
+
const retryable = isRetryableApiCode(normalizedCode);
|
|
1481
|
+
const message = payload?.error?.message ?? payload?.result?.message ?? "UsageTap reported an error";
|
|
1482
|
+
return new UsageTapError(mapApiCodeToError(normalizedCode), message, {
|
|
1483
|
+
retryable,
|
|
1484
|
+
correlationId: payload?.correlationId ?? correlationId,
|
|
1485
|
+
details: sanitizeDetails(payload)
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
function mapStatusToErrorCode(status) {
|
|
1490
|
+
if (status === 401 || status === 403) return "USAGETAP_AUTH_ERROR";
|
|
1491
|
+
if (status === 400 || status === 404 || status === 409)
|
|
1492
|
+
return "USAGETAP_BAD_REQUEST";
|
|
1493
|
+
if (status === 429) return "USAGETAP_RATE_LIMITED";
|
|
1494
|
+
if (status >= 500) return "USAGETAP_SERVER_ERROR";
|
|
1495
|
+
return "USAGETAP_INVALID_RESPONSE";
|
|
1496
|
+
}
|
|
1497
|
+
function isRetryableStatus(status) {
|
|
1498
|
+
return status === 408 || status === 425 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
1499
|
+
}
|
|
1500
|
+
function isRetryableApiCode(code) {
|
|
1501
|
+
const normalized = code.toUpperCase();
|
|
1502
|
+
return normalized.includes("TRANSIENT") || normalized.includes("RETRY") || normalized.includes("TIMEOUT") || normalized.includes("THROTTLE") || normalized.includes("RATE_LIMIT");
|
|
1503
|
+
}
|
|
1504
|
+
function mapApiCodeToError(code) {
|
|
1505
|
+
const normalized = code.toUpperCase();
|
|
1506
|
+
if (normalized.includes("AUTH") || normalized.includes("TOKEN")) {
|
|
1507
|
+
return "USAGETAP_AUTH_ERROR";
|
|
1508
|
+
}
|
|
1509
|
+
if (normalized.includes("RATE") || normalized.includes("THROTTLE")) {
|
|
1510
|
+
return "USAGETAP_RATE_LIMITED";
|
|
1511
|
+
}
|
|
1512
|
+
if (normalized.includes("SERVER") || normalized.includes("TRANSIENT")) {
|
|
1513
|
+
return "USAGETAP_SERVER_ERROR";
|
|
1514
|
+
}
|
|
1515
|
+
if (normalized.includes("IDEMPOTENCY") || normalized.includes("VALIDATION") || normalized.includes("REQUEST")) {
|
|
1516
|
+
return "USAGETAP_BAD_REQUEST";
|
|
1517
|
+
}
|
|
1518
|
+
return "USAGETAP_INVALID_RESPONSE";
|
|
1519
|
+
}
|
|
1520
|
+
function sanitizeDetails(payload) {
|
|
1521
|
+
if (!payload) return void 0;
|
|
1522
|
+
const details = {};
|
|
1523
|
+
if (payload.result) details.result = payload.result;
|
|
1524
|
+
if (payload.error) details.error = payload.error;
|
|
1525
|
+
return Object.keys(details).length ? details : void 0;
|
|
1526
|
+
}
|
|
1527
|
+
function readEnvironmentVariable(name) {
|
|
1528
|
+
const runtime = globalThis;
|
|
1529
|
+
const value = runtime.process?.env?.[name]?.trim();
|
|
1530
|
+
return value || void 0;
|
|
1531
|
+
}
|
|
1532
|
+
function normalizeBaseUrl(baseUrl) {
|
|
1533
|
+
const trimmed = baseUrl.trim();
|
|
1534
|
+
if (!trimmed) return trimmed;
|
|
1535
|
+
return trimmed.endsWith("/") ? trimmed : `${trimmed}/`;
|
|
1536
|
+
}
|
|
1537
|
+
function normalizeHeaderDictionary(dict) {
|
|
1538
|
+
return Object.keys(dict).reduce((acc, key) => {
|
|
1539
|
+
acc[key.toLowerCase()] = dict[key];
|
|
1540
|
+
return acc;
|
|
1541
|
+
}, {});
|
|
1542
|
+
}
|
|
1543
|
+
function dedupeStrings(values) {
|
|
1544
|
+
return Array.from(
|
|
1545
|
+
new Set(values.map((value) => value.trim()).filter(Boolean))
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
function wrapFetchImplementation(fetchCandidate, preferGlobalContext) {
|
|
1549
|
+
const target = preferGlobalContext ? globalThis : void 0;
|
|
1550
|
+
return ((...args) => target ? Reflect.apply(fetchCandidate, target, args) : fetchCandidate(...args));
|
|
1551
|
+
}
|
|
1552
|
+
function wrapEndCallError(error, correlationId) {
|
|
1553
|
+
if (isUsageTapError(error)) {
|
|
1554
|
+
return new UsageTapError("USAGETAP_END_CALL_ERROR", error.message, {
|
|
1555
|
+
correlationId: error.correlationId ?? correlationId,
|
|
1556
|
+
details: error.details,
|
|
1557
|
+
cause: error
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
return new UsageTapError(
|
|
1561
|
+
"USAGETAP_END_CALL_ERROR",
|
|
1562
|
+
"Failed to finalize UsageTap call",
|
|
1563
|
+
{
|
|
1564
|
+
correlationId,
|
|
1565
|
+
cause: error
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
}
|
|
39
1569
|
|
|
40
1570
|
// src/adapters/openai.ts
|
|
41
1571
|
var OpenAIPromptCompressionStats = class {
|
|
@@ -233,6 +1763,68 @@ async function pipeToResponse(stream, res, options = {}) {
|
|
|
233
1763
|
}
|
|
234
1764
|
}
|
|
235
1765
|
var USAGETAP_CORRELATION_HEADER = "x-usage-correlation-id";
|
|
1766
|
+
function withCompression(client, options = {}) {
|
|
1767
|
+
if (!client) {
|
|
1768
|
+
throw new UsageTapError(
|
|
1769
|
+
"USAGETAP_BAD_REQUEST",
|
|
1770
|
+
"withCompression requires an OpenAI-compatible client instance"
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
const {
|
|
1774
|
+
apiKey,
|
|
1775
|
+
usageTapClient,
|
|
1776
|
+
...compressionOverrides
|
|
1777
|
+
} = options;
|
|
1778
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1779
|
+
const compression = {
|
|
1780
|
+
provider: "usagetap",
|
|
1781
|
+
minContextTokens: DEFAULT_PROMPT_COMPRESSION_MIN_CONTEXT_TOKENS,
|
|
1782
|
+
...compressionOverrides
|
|
1783
|
+
};
|
|
1784
|
+
const proxiedChat = client.chat ? createCompressionOnlyChatProxy(client.chat, usageTap, compression) : void 0;
|
|
1785
|
+
const proxiedResponses = typeof client.responses !== "undefined" ? createCompressionOnlyResponsesProxy(
|
|
1786
|
+
client.responses,
|
|
1787
|
+
usageTap,
|
|
1788
|
+
compression
|
|
1789
|
+
) : void 0;
|
|
1790
|
+
return new Proxy(client, {
|
|
1791
|
+
get(target, prop, receiver) {
|
|
1792
|
+
if (prop === "chat" && proxiedChat) {
|
|
1793
|
+
return proxiedChat;
|
|
1794
|
+
}
|
|
1795
|
+
if (prop === "responses" && typeof target.responses !== "undefined") {
|
|
1796
|
+
return proxiedResponses ?? Reflect.get(target, prop, receiver);
|
|
1797
|
+
}
|
|
1798
|
+
if (prop === "unwrap") {
|
|
1799
|
+
return () => target;
|
|
1800
|
+
}
|
|
1801
|
+
return Reflect.get(target, prop, receiver);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
function withMetering(client, customer) {
|
|
1806
|
+
const config = typeof customer === "string" ? { customerId: customer } : customer;
|
|
1807
|
+
if (!config?.customerId) {
|
|
1808
|
+
throw new UsageTapError(
|
|
1809
|
+
"USAGETAP_BAD_REQUEST",
|
|
1810
|
+
"withMetering requires a customerId"
|
|
1811
|
+
);
|
|
1812
|
+
}
|
|
1813
|
+
const {
|
|
1814
|
+
apiKey,
|
|
1815
|
+
usageTapClient,
|
|
1816
|
+
applyVendorHints,
|
|
1817
|
+
promptCompression,
|
|
1818
|
+
...defaultContext
|
|
1819
|
+
} = config;
|
|
1820
|
+
const usageTap = usageTapClient ?? new UsageTapClient({ apiKey });
|
|
1821
|
+
const normalizedCompression = promptCompression === true ? { provider: "usagetap" } : promptCompression ? { provider: "usagetap", ...promptCompression } : void 0;
|
|
1822
|
+
return wrapOpenAI(client, usageTap, {
|
|
1823
|
+
defaultContext,
|
|
1824
|
+
applyVendorHints,
|
|
1825
|
+
promptCompression: normalizedCompression
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
236
1828
|
function wrapOpenAI(client, usageTap, options = {}) {
|
|
237
1829
|
if (!client) {
|
|
238
1830
|
throw new UsageTapError("USAGETAP_BAD_REQUEST", "wrapOpenAI requires an OpenAI client instance");
|
|
@@ -282,6 +1874,47 @@ function wrapOpenAI(client, usageTap, options = {}) {
|
|
|
282
1874
|
};
|
|
283
1875
|
return new Proxy(client, handler);
|
|
284
1876
|
}
|
|
1877
|
+
function createCompressionOnlyChatProxy(resource, usageTap, compression) {
|
|
1878
|
+
const completions = resource.completions;
|
|
1879
|
+
const originalCreate = completions.create.bind(completions);
|
|
1880
|
+
const streamCandidate = completions.stream;
|
|
1881
|
+
const originalStream = typeof streamCandidate === "function" ? streamCandidate.bind(completions) : void 0;
|
|
1882
|
+
const wrappedCreate = (params, options) => {
|
|
1883
|
+
return compressChatParams(params, usageTap, compression).then((outcome) => originalCreate(outcome.params, options));
|
|
1884
|
+
};
|
|
1885
|
+
const wrappedStream = originalStream ? (params, options) => compressChatParams(params, usageTap, compression).then((outcome) => originalStream(outcome.params, options)) : void 0;
|
|
1886
|
+
const proxiedCompletions = new Proxy(completions, {
|
|
1887
|
+
get(target, prop, receiver) {
|
|
1888
|
+
if (prop === "create") return wrappedCreate;
|
|
1889
|
+
if (prop === "stream" && wrappedStream) return wrappedStream;
|
|
1890
|
+
return Reflect.get(target, prop, receiver);
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1893
|
+
return new Proxy(resource, {
|
|
1894
|
+
get(target, prop, receiver) {
|
|
1895
|
+
if (prop === "completions") return proxiedCompletions;
|
|
1896
|
+
return Reflect.get(target, prop, receiver);
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
function createCompressionOnlyResponsesProxy(resource, usageTap, compression) {
|
|
1901
|
+
if (!resource || typeof resource !== "object") {
|
|
1902
|
+
return resource;
|
|
1903
|
+
}
|
|
1904
|
+
if (!("create" in resource) || typeof resource.create !== "function") {
|
|
1905
|
+
return resource;
|
|
1906
|
+
}
|
|
1907
|
+
const originalCreate = resource.create.bind(resource);
|
|
1908
|
+
const wrappedCreate = (params, options) => {
|
|
1909
|
+
return compressResponsesParams(params, usageTap, compression).then((outcome) => originalCreate(outcome.params, options));
|
|
1910
|
+
};
|
|
1911
|
+
return new Proxy(resource, {
|
|
1912
|
+
get(target, prop, receiver) {
|
|
1913
|
+
if (prop === "create") return wrappedCreate;
|
|
1914
|
+
return Reflect.get(target, prop, receiver);
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
285
1918
|
function streamOpenAIRoute(usageTap, openai, options) {
|
|
286
1919
|
if (!options?.getRequest) {
|
|
287
1920
|
throw new UsageTapError("USAGETAP_BAD_REQUEST", "streamOpenAIRoute requires a getRequest function");
|
|
@@ -603,6 +2236,24 @@ async function compressChatParams(params, usageTap, compression, signal) {
|
|
|
603
2236
|
if (!messages) {
|
|
604
2237
|
return { params, segments: [] };
|
|
605
2238
|
}
|
|
2239
|
+
if (isBelowMinContextTokens(
|
|
2240
|
+
{ messages, tools: source.tools },
|
|
2241
|
+
compression.minContextTokens
|
|
2242
|
+
)) {
|
|
2243
|
+
return { params, segments: [] };
|
|
2244
|
+
}
|
|
2245
|
+
if (shouldUseUsageTapMessageEndpoint(compression)) {
|
|
2246
|
+
const result = await usageTap.compressPromptMessages(source, {
|
|
2247
|
+
provider: "usagetap",
|
|
2248
|
+
failOpen: compression.failOpen,
|
|
2249
|
+
aggressiveness: resolveMessageEndpointAggressiveness(compression),
|
|
2250
|
+
signal
|
|
2251
|
+
});
|
|
2252
|
+
return {
|
|
2253
|
+
params: result.compressedInput,
|
|
2254
|
+
segments: [{ role: "user", result }]
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
606
2257
|
const messageResults = await Promise.all(
|
|
607
2258
|
messages.map(
|
|
608
2259
|
(message) => compressOpenAIMessage(message, usageTap, compression, signal)
|
|
@@ -622,6 +2273,16 @@ async function compressResponsesParams(params, usageTap, compression, signal) {
|
|
|
622
2273
|
}
|
|
623
2274
|
const source = cloneRecord(params);
|
|
624
2275
|
const segments = [];
|
|
2276
|
+
if (isBelowMinContextTokens(
|
|
2277
|
+
{
|
|
2278
|
+
instructions: source.instructions,
|
|
2279
|
+
input: source.input,
|
|
2280
|
+
tools: source.tools
|
|
2281
|
+
},
|
|
2282
|
+
compression.minContextTokens
|
|
2283
|
+
)) {
|
|
2284
|
+
return { params, segments };
|
|
2285
|
+
}
|
|
625
2286
|
if (typeof source.instructions === "string") {
|
|
626
2287
|
const compressed = await compressTextForRole(
|
|
627
2288
|
source.instructions,
|
|
@@ -826,8 +2487,11 @@ async function compressTextForRole(text, role, usageTap, compression, signal) {
|
|
|
826
2487
|
provider: roleOptions.provider,
|
|
827
2488
|
failOpen: roleOptions.failOpen,
|
|
828
2489
|
tokenCompanyModel: roleOptions.tokenCompanyModel,
|
|
2490
|
+
aggressiveness: roleOptions.aggressiveness,
|
|
829
2491
|
tokenCompanyAggressiveness: roleOptions.tokenCompanyAggressiveness,
|
|
830
2492
|
tokenCompanyAppId: roleOptions.tokenCompanyAppId,
|
|
2493
|
+
usageTapCompressionModel: roleOptions.usageTapCompressionModel,
|
|
2494
|
+
usageTapCompressionAggressiveness: roleOptions.usageTapCompressionAggressiveness,
|
|
831
2495
|
signal
|
|
832
2496
|
});
|
|
833
2497
|
const compressedText = typeof result.compressedInput === "string" ? result.compressedInput : String(result.compressedInput);
|
|
@@ -848,6 +2512,9 @@ function normalizePromptCompressionOptions(options) {
|
|
|
848
2512
|
}
|
|
849
2513
|
return options;
|
|
850
2514
|
}
|
|
2515
|
+
function isBelowMinContextTokens(context, minContextTokens) {
|
|
2516
|
+
return typeof minContextTokens === "number" && estimatePromptTokens(context) < Math.max(0, minContextTokens);
|
|
2517
|
+
}
|
|
851
2518
|
function resolveEffectivePromptCompressionOptions(defaults, override) {
|
|
852
2519
|
if (override === false) {
|
|
853
2520
|
return void 0;
|
|
@@ -886,16 +2553,67 @@ function resolveRoleCompressionOptions(compression, role) {
|
|
|
886
2553
|
minTokens: roleOptions?.minTokens ?? compression.minTokens,
|
|
887
2554
|
failOpen: compression.failOpen,
|
|
888
2555
|
tokenCompanyModel: compression.tokenCompanyModel,
|
|
2556
|
+
aggressiveness: roleOptions?.aggressiveness ?? resolveAggressiveness(compression, role),
|
|
889
2557
|
tokenCompanyAggressiveness: roleOptions?.tokenCompanyAggressiveness ?? resolveTokenCompanyAggressiveness(compression, role),
|
|
890
|
-
tokenCompanyAppId: compression.tokenCompanyAppId
|
|
2558
|
+
tokenCompanyAppId: compression.tokenCompanyAppId,
|
|
2559
|
+
usageTapCompressionModel: compression.usageTapCompressionModel,
|
|
2560
|
+
usageTapCompressionAggressiveness: roleOptions?.usageTapCompressionAggressiveness ?? resolveUsageTapCompressionAggressiveness(compression, role)
|
|
891
2561
|
};
|
|
892
2562
|
}
|
|
2563
|
+
function resolveAggressiveness(compression, role) {
|
|
2564
|
+
if (typeof compression.aggressiveness === "number") {
|
|
2565
|
+
return compression.aggressiveness;
|
|
2566
|
+
}
|
|
2567
|
+
return compression.aggressiveness?.[role];
|
|
2568
|
+
}
|
|
893
2569
|
function resolveTokenCompanyAggressiveness(compression, role) {
|
|
894
2570
|
if (typeof compression.tokenCompanyAggressiveness === "number") {
|
|
895
2571
|
return compression.tokenCompanyAggressiveness;
|
|
896
2572
|
}
|
|
897
2573
|
return compression.tokenCompanyAggressiveness?.[role];
|
|
898
2574
|
}
|
|
2575
|
+
function resolveUsageTapCompressionAggressiveness(compression, role) {
|
|
2576
|
+
if (typeof compression.usageTapCompressionAggressiveness === "number") {
|
|
2577
|
+
return compression.usageTapCompressionAggressiveness;
|
|
2578
|
+
}
|
|
2579
|
+
return compression.usageTapCompressionAggressiveness?.[role];
|
|
2580
|
+
}
|
|
2581
|
+
function shouldUseUsageTapMessageEndpoint(compression) {
|
|
2582
|
+
if (compression.provider !== "usagetap") {
|
|
2583
|
+
return false;
|
|
2584
|
+
}
|
|
2585
|
+
return Object.values(compression.roles ?? {}).every((setting) => {
|
|
2586
|
+
if (typeof setting !== "object" || setting === null) {
|
|
2587
|
+
return true;
|
|
2588
|
+
}
|
|
2589
|
+
return setting.provider === void 0 || setting.provider === "usagetap";
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
function resolveMessageEndpointAggressiveness(compression) {
|
|
2593
|
+
const base = compression.aggressiveness ?? compression.usageTapCompressionAggressiveness ?? compression.tokenCompanyAggressiveness;
|
|
2594
|
+
if (typeof base === "number" || base === void 0) {
|
|
2595
|
+
return hasExplicitEnabledRoles(compression) ? buildRoleAggressiveness(compression, base) : base;
|
|
2596
|
+
}
|
|
2597
|
+
return buildRoleAggressiveness(compression, void 0, base);
|
|
2598
|
+
}
|
|
2599
|
+
function hasExplicitEnabledRoles(compression) {
|
|
2600
|
+
return Object.values(compression.roles ?? {}).some((setting) => setting !== false);
|
|
2601
|
+
}
|
|
2602
|
+
function buildRoleAggressiveness(compression, fallback, base = {}) {
|
|
2603
|
+
const roles = ["system", "user", "tool", "assistant"];
|
|
2604
|
+
const result = {};
|
|
2605
|
+
for (const role of roles) {
|
|
2606
|
+
const roleOptions = resolveRoleCompressionOptions(compression, role);
|
|
2607
|
+
if (!roleOptions) {
|
|
2608
|
+
continue;
|
|
2609
|
+
}
|
|
2610
|
+
const roleAggressiveness = roleOptions.aggressiveness ?? roleOptions.usageTapCompressionAggressiveness ?? roleOptions.tokenCompanyAggressiveness ?? base[role] ?? fallback;
|
|
2611
|
+
if (roleAggressiveness !== void 0) {
|
|
2612
|
+
result[role] = roleAggressiveness;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
return result;
|
|
2616
|
+
}
|
|
899
2617
|
function buildPromptCompressionTelemetry(segments) {
|
|
900
2618
|
if (!segments.length) {
|
|
901
2619
|
return void 0;
|
|
@@ -918,9 +2636,9 @@ function buildPromptCompressionTelemetry(segments) {
|
|
|
918
2636
|
);
|
|
919
2637
|
const savedCharacters = Math.max(0, originalCharacters - compressedCharacters);
|
|
920
2638
|
const savedTokens = Math.max(0, originalTokens - compressedTokens);
|
|
921
|
-
const providers =
|
|
922
|
-
const roles =
|
|
923
|
-
const techniques =
|
|
2639
|
+
const providers = dedupeStrings2(segments.map((segment) => segment.result.provider));
|
|
2640
|
+
const roles = dedupeStrings2(segments.map((segment) => `role:${segment.role}`));
|
|
2641
|
+
const techniques = dedupeStrings2([
|
|
924
2642
|
"openai-wrapper",
|
|
925
2643
|
...roles,
|
|
926
2644
|
...segments.flatMap((segment) => segment.result.techniques),
|
|
@@ -1100,9 +2818,9 @@ function mergeTags(a, b) {
|
|
|
1100
2818
|
if (!values.length) {
|
|
1101
2819
|
return void 0;
|
|
1102
2820
|
}
|
|
1103
|
-
return
|
|
2821
|
+
return dedupeStrings2(values);
|
|
1104
2822
|
}
|
|
1105
|
-
function
|
|
2823
|
+
function dedupeStrings2(values) {
|
|
1106
2824
|
return Array.from(new Set(values));
|
|
1107
2825
|
}
|
|
1108
2826
|
function isStreamingRequest(params) {
|
|
@@ -1347,6 +3065,6 @@ function createOpenRouterAdapter(init) {
|
|
|
1347
3065
|
return createOpenAIAdapter(init);
|
|
1348
3066
|
}
|
|
1349
3067
|
|
|
1350
|
-
export { OpenAIPromptCompressionStats, createOpenAIAdapter, createOpenRouterAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, wrapOpenAI };
|
|
3068
|
+
export { OpenAIPromptCompressionStats, createOpenAIAdapter, createOpenRouterAdapter, pipeToResponse, streamOpenAIRoute, toNextResponse, withCompression, withMetering, wrapOpenAI };
|
|
1351
3069
|
//# sourceMappingURL=index.mjs.map
|
|
1352
3070
|
//# sourceMappingURL=index.mjs.map
|