@curatedmcp/tokenshield-core 0.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 (61) hide show
  1. package/dist/index.d.ts +15 -0
  2. package/dist/index.js +11 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/ledger.d.ts +33 -0
  5. package/dist/ledger.js +141 -0
  6. package/dist/ledger.js.map +1 -0
  7. package/dist/pricing.d.ts +5 -0
  8. package/dist/pricing.js +83 -0
  9. package/dist/pricing.js.map +1 -0
  10. package/dist/processors/conversation-dedup.d.ts +23 -0
  11. package/dist/processors/conversation-dedup.js +71 -0
  12. package/dist/processors/conversation-dedup.js.map +1 -0
  13. package/dist/processors/pipeline.d.ts +10 -0
  14. package/dist/processors/pipeline.js +89 -0
  15. package/dist/processors/pipeline.js.map +1 -0
  16. package/dist/processors/response-cache.d.ts +53 -0
  17. package/dist/processors/response-cache.js +129 -0
  18. package/dist/processors/response-cache.js.map +1 -0
  19. package/dist/processors/types.d.ts +54 -0
  20. package/dist/processors/types.js +2 -0
  21. package/dist/processors/types.js.map +1 -0
  22. package/dist/providers/anthropic.d.ts +6 -0
  23. package/dist/providers/anthropic.js +216 -0
  24. package/dist/providers/anthropic.js.map +1 -0
  25. package/dist/providers/registry.d.ts +4 -0
  26. package/dist/providers/registry.js +7 -0
  27. package/dist/providers/registry.js.map +1 -0
  28. package/dist/providers/types.d.ts +79 -0
  29. package/dist/providers/types.js +2 -0
  30. package/dist/providers/types.js.map +1 -0
  31. package/dist/proxy/anthropic-passthrough.d.ts +13 -0
  32. package/dist/proxy/anthropic-passthrough.js +363 -0
  33. package/dist/proxy/anthropic-passthrough.js.map +1 -0
  34. package/dist/proxy/sse.d.ts +20 -0
  35. package/dist/proxy/sse.js +59 -0
  36. package/dist/proxy/sse.js.map +1 -0
  37. package/dist/proxy/usage.d.ts +25 -0
  38. package/dist/proxy/usage.js +82 -0
  39. package/dist/proxy/usage.js.map +1 -0
  40. package/dist/server.d.ts +18 -0
  41. package/dist/server.js +130 -0
  42. package/dist/server.js.map +1 -0
  43. package/dist/types.d.ts +36 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +38 -0
  47. package/src/index.ts +31 -0
  48. package/src/ledger.ts +232 -0
  49. package/src/pricing.ts +93 -0
  50. package/src/processors/conversation-dedup.ts +77 -0
  51. package/src/processors/pipeline.ts +104 -0
  52. package/src/processors/response-cache.ts +161 -0
  53. package/src/processors/types.ts +58 -0
  54. package/src/providers/anthropic.ts +236 -0
  55. package/src/providers/registry.ts +10 -0
  56. package/src/providers/types.ts +87 -0
  57. package/src/proxy/anthropic-passthrough.ts +393 -0
  58. package/src/proxy/sse.ts +58 -0
  59. package/src/proxy/usage.ts +98 -0
  60. package/src/server.ts +154 -0
  61. package/src/types.ts +47 -0
@@ -0,0 +1,363 @@
1
+ import { request as httpsRequest } from "node:https";
2
+ import { request as httpRequest } from "node:http";
3
+ import { URL } from "node:url";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { emptyUsage, dollarsFor } from "../pricing.js";
6
+ import { SSEParser } from "./sse.js";
7
+ import { providerForPath } from "../providers/registry.js";
8
+ import { Pipeline } from "../processors/pipeline.js";
9
+ import { conversationDedup } from "../processors/conversation-dedup.js";
10
+ import { ResponseCache } from "../processors/response-cache.js";
11
+ const HOP_BY_HOP = new Set([
12
+ "connection",
13
+ "keep-alive",
14
+ "proxy-authenticate",
15
+ "proxy-authorization",
16
+ "te",
17
+ "trailer",
18
+ "transfer-encoding",
19
+ "upgrade",
20
+ "host",
21
+ ]);
22
+ function copyHeaders(src) {
23
+ const out = {};
24
+ for (const [name, value] of Object.entries(src)) {
25
+ if (value === undefined)
26
+ continue;
27
+ if (HOP_BY_HOP.has(name.toLowerCase()))
28
+ continue;
29
+ out[name] = Array.isArray(value) ? value.join(", ") : value;
30
+ }
31
+ return out;
32
+ }
33
+ async function readJsonBody(req, limitBytes = 64 * 1024 * 1024) {
34
+ const chunks = [];
35
+ let total = 0;
36
+ for await (const chunk of req) {
37
+ const buf = chunk;
38
+ total += buf.length;
39
+ if (total > limitBytes) {
40
+ throw new Error(`Request body exceeds ${limitBytes} bytes`);
41
+ }
42
+ chunks.push(buf);
43
+ }
44
+ const raw = Buffer.concat(chunks);
45
+ let parsed = null;
46
+ if (raw.length > 0) {
47
+ try {
48
+ parsed = JSON.parse(raw.toString("utf8"));
49
+ }
50
+ catch {
51
+ parsed = null;
52
+ }
53
+ }
54
+ return { raw, parsed };
55
+ }
56
+ function conversationFingerprint(parsed) {
57
+ if (!parsed || typeof parsed !== "object")
58
+ return "_";
59
+ const obj = parsed;
60
+ const sys = obj["system"];
61
+ const messages = Array.isArray(obj["messages"]) ? obj["messages"] : [];
62
+ const firstUser = messages.find((m) => typeof m === "object" && m !== null && m["role"] === "user");
63
+ return createHash("sha256")
64
+ .update(JSON.stringify({ sys, firstUser }))
65
+ .digest("hex")
66
+ .slice(0, 16);
67
+ }
68
+ function bodySize(json) {
69
+ return Buffer.byteLength(JSON.stringify(json ?? null), "utf8");
70
+ }
71
+ /**
72
+ * Singleton pipeline + cache. One process = one set of state.
73
+ * Future: per-license configuration once cloud-tier gating ships.
74
+ */
75
+ const PROCESSORS = [conversationDedup];
76
+ const ENABLED = new Set(PROCESSORS.filter((p) => p.enabledByDefault).map((p) => p.id));
77
+ const PIPELINE = new Pipeline({ processors: PROCESSORS, enabled: ENABLED });
78
+ const RESPONSE_CACHE = new ResponseCache();
79
+ export function setProcessorEnabled(id, enabled) {
80
+ if (enabled)
81
+ ENABLED.add(id);
82
+ else
83
+ ENABLED.delete(id);
84
+ }
85
+ export function getProcessorEnabledIds() {
86
+ return Array.from(ENABLED);
87
+ }
88
+ export function getResponseCacheStats() {
89
+ return RESPONSE_CACHE.stats();
90
+ }
91
+ export async function handleAnthropicRequest(req, res, config, sink) {
92
+ const startedAt = Date.now();
93
+ const requestId = randomUUID();
94
+ let body;
95
+ try {
96
+ body = await readJsonBody(req);
97
+ }
98
+ catch (err) {
99
+ res.statusCode = 413;
100
+ res.setHeader("content-type", "text/plain");
101
+ res.end(`tokenshield: ${err.message}`);
102
+ return;
103
+ }
104
+ const upstream = new URL(req.url ?? "/", config.upstreamBaseUrl);
105
+ const provider = providerForPath(upstream.pathname);
106
+ const isHttps = upstream.protocol === "https:";
107
+ const requester = isHttps ? httpsRequest : httpRequest;
108
+ // ── 1. Determine model + stream flag from raw body (before any rewrite) ──
109
+ let model = "unknown";
110
+ let streamed = false;
111
+ if (provider !== null) {
112
+ model = provider.extractModel(body.parsed);
113
+ streamed = provider.isStreaming(body.parsed);
114
+ }
115
+ // ── 2. Run request-side processors (fail-open) ──────────────────────────
116
+ let outboundParsed = body.parsed;
117
+ let outboundBytes = body.raw;
118
+ const effects = [];
119
+ let bytesRaw = body.raw.length;
120
+ let bytesSent = body.raw.length;
121
+ if (provider !== null && body.parsed !== null) {
122
+ const conv = provider.toConversation(body.parsed);
123
+ if (conv !== null) {
124
+ const ctx = {
125
+ providerId: provider.id,
126
+ conversationFingerprint: conversationFingerprint(body.parsed),
127
+ inboundBytes: body.raw.length,
128
+ };
129
+ // sizeOf measures wire-format (after applyConversation), not in-memory shape
130
+ const wireSize = (c) => bodySize(provider.applyConversation(body.parsed, c));
131
+ const result = PIPELINE.run(conv, ctx, wireSize);
132
+ for (const e of result.effects)
133
+ effects.push(e);
134
+ if (result.effects.length > 0) {
135
+ outboundParsed = provider.applyConversation(body.parsed, result.conversation);
136
+ const serialized = Buffer.from(JSON.stringify(outboundParsed), "utf8");
137
+ if (serialized.length < body.raw.length) {
138
+ outboundBytes = serialized;
139
+ bytesSent = serialized.length;
140
+ }
141
+ }
142
+ }
143
+ }
144
+ // ── 3. Response cache: short-circuit if we have a fresh hit ─────────────
145
+ if (provider !== null && body.parsed !== null) {
146
+ const hit = RESPONSE_CACHE.lookup(body.parsed);
147
+ if (hit !== null) {
148
+ res.statusCode = hit.status;
149
+ for (const [k, v] of Object.entries(hit.headers)) {
150
+ if (HOP_BY_HOP.has(k.toLowerCase()))
151
+ continue;
152
+ res.setHeader(k, v);
153
+ }
154
+ res.setHeader("x-tokenshield-cache", "hit");
155
+ res.setHeader("x-tokenshield-cache-age-ms", String(hit.cachedAgoMs));
156
+ res.end(hit.body);
157
+ const dollarsRaw = dollarsFor(hit.model || model, {
158
+ inputTokens: hit.usage.inputTokens,
159
+ outputTokens: hit.usage.outputTokens,
160
+ cacheCreationInputTokens: 0,
161
+ cacheReadInputTokens: 0,
162
+ });
163
+ try {
164
+ sink({
165
+ id: requestId,
166
+ timestamp: startedAt,
167
+ model: hit.model || model,
168
+ endpoint: upstream.pathname,
169
+ streamed: false,
170
+ durationMs: Date.now() - startedAt,
171
+ upstreamStatus: hit.status,
172
+ upstreamError: null,
173
+ usageRaw: {
174
+ inputTokens: hit.usage.inputTokens,
175
+ outputTokens: hit.usage.outputTokens,
176
+ cacheCreationInputTokens: 0,
177
+ cacheReadInputTokens: 0,
178
+ },
179
+ // Cached: zero new tokens billed
180
+ usageSent: emptyUsage(),
181
+ dollarsRaw,
182
+ dollarsSent: 0,
183
+ dollarsSaved: dollarsRaw,
184
+ processorsApplied: ["response-cache:hit", ...effects.map((e) => e.name)],
185
+ });
186
+ }
187
+ catch {
188
+ /* sink errors must never break the request */
189
+ }
190
+ return;
191
+ }
192
+ }
193
+ const headers = copyHeaders(req.headers);
194
+ headers["host"] = upstream.host;
195
+ headers["content-length"] = String(outboundBytes.length);
196
+ let upstreamStatus = 0;
197
+ let upstreamError = null;
198
+ let usage = emptyUsage();
199
+ let modelFromResponse = null;
200
+ const cachedHeaders = {};
201
+ const responseBodyChunks = [];
202
+ const finalize = () => {
203
+ const effectiveModel = modelFromResponse ?? model;
204
+ const dollarsSent = dollarsFor(effectiveModel, usage);
205
+ // Estimate "raw" cost — what the bill would have been without compression.
206
+ // We use actual sent input tokens + the ratio of bytes saved at the
207
+ // request layer. This is honest: it's an estimate, marked as such.
208
+ const totalBytesSavedReq = Math.max(0, bytesRaw - bytesSent);
209
+ const ratio = bytesSent > 0 ? totalBytesSavedReq / bytesSent : 0;
210
+ const estimatedInputTokensRaw = Math.round(usage.inputTokens * (1 + ratio));
211
+ const usageRaw = {
212
+ inputTokens: estimatedInputTokensRaw,
213
+ outputTokens: usage.outputTokens,
214
+ cacheCreationInputTokens: usage.cacheCreationInputTokens,
215
+ cacheReadInputTokens: usage.cacheReadInputTokens,
216
+ };
217
+ const dollarsRaw = dollarsFor(effectiveModel, usageRaw);
218
+ const dollarsSaved = Math.max(0, dollarsRaw - dollarsSent);
219
+ const record = {
220
+ id: requestId,
221
+ timestamp: startedAt,
222
+ model: effectiveModel,
223
+ endpoint: upstream.pathname,
224
+ streamed,
225
+ durationMs: Date.now() - startedAt,
226
+ upstreamStatus,
227
+ upstreamError,
228
+ usageRaw,
229
+ usageSent: usage,
230
+ dollarsRaw,
231
+ dollarsSent,
232
+ dollarsSaved,
233
+ processorsApplied: effects.map((e) => e.name),
234
+ };
235
+ try {
236
+ sink(record);
237
+ }
238
+ catch {
239
+ /* never */
240
+ }
241
+ // Cache the JSON response (no-op if not cacheable)
242
+ if (!streamed && provider !== null && responseBodyChunks.length > 0) {
243
+ const buf = Buffer.concat(responseBodyChunks);
244
+ RESPONSE_CACHE.store(body.parsed, {
245
+ status: upstreamStatus,
246
+ headers: cachedHeaders,
247
+ body: buf,
248
+ usage: { inputTokens: usage.inputTokens, outputTokens: usage.outputTokens },
249
+ model: effectiveModel,
250
+ });
251
+ }
252
+ };
253
+ await new Promise((resolve) => {
254
+ const upstreamReq = requester({
255
+ method: req.method ?? "POST",
256
+ hostname: upstream.hostname,
257
+ port: upstream.port || (isHttps ? 443 : 80),
258
+ path: upstream.pathname + upstream.search,
259
+ headers,
260
+ }, (upstreamRes) => {
261
+ upstreamStatus = upstreamRes.statusCode ?? 0;
262
+ res.statusCode = upstreamStatus;
263
+ for (const [name, value] of Object.entries(upstreamRes.headers)) {
264
+ if (value === undefined)
265
+ continue;
266
+ if (HOP_BY_HOP.has(name.toLowerCase()))
267
+ continue;
268
+ const strVal = Array.isArray(value) ? value.join(", ") : value;
269
+ res.setHeader(name, strVal);
270
+ cachedHeaders[name] = strVal;
271
+ }
272
+ if (effects.length > 0) {
273
+ res.setHeader("x-tokenshield-processors", effects.map((e) => e.name).join(","));
274
+ }
275
+ const contentType = String(upstreamRes.headers["content-type"] ?? "");
276
+ const isSse = contentType.includes("text/event-stream");
277
+ if (isSse) {
278
+ const parser = new SSEParser();
279
+ const accum = provider !== null ? provider.createStreamAccumulator() : null;
280
+ upstreamRes.on("data", (chunk) => {
281
+ res.write(chunk);
282
+ if (accum !== null) {
283
+ try {
284
+ for (const ev of parser.push(chunk.toString("utf8"))) {
285
+ accum.observe(ev);
286
+ }
287
+ }
288
+ catch { /* accounting must never break the data path */ }
289
+ }
290
+ });
291
+ upstreamRes.on("end", () => {
292
+ if (accum !== null) {
293
+ try {
294
+ for (const ev of parser.flush())
295
+ accum.observe(ev);
296
+ usage = accum.total();
297
+ modelFromResponse = accum.model();
298
+ }
299
+ catch { /* ignore */ }
300
+ }
301
+ res.end();
302
+ finalize();
303
+ resolve();
304
+ });
305
+ upstreamRes.on("error", (err) => {
306
+ upstreamError = err.message;
307
+ res.end();
308
+ finalize();
309
+ resolve();
310
+ });
311
+ }
312
+ else {
313
+ upstreamRes.on("data", (chunk) => {
314
+ responseBodyChunks.push(chunk);
315
+ res.write(chunk);
316
+ });
317
+ upstreamRes.on("end", () => {
318
+ try {
319
+ const text = Buffer.concat(responseBodyChunks).toString("utf8");
320
+ if (text.length > 0 && provider !== null) {
321
+ const parsed = JSON.parse(text);
322
+ const u = provider.usageFromResponseJson(parsed);
323
+ usage = u.usage;
324
+ modelFromResponse = u.model;
325
+ }
326
+ }
327
+ catch { /* non-JSON or parse failure — leave usage at zero */ }
328
+ res.end();
329
+ finalize();
330
+ resolve();
331
+ });
332
+ upstreamRes.on("error", (err) => {
333
+ upstreamError = err.message;
334
+ res.end();
335
+ finalize();
336
+ resolve();
337
+ });
338
+ }
339
+ });
340
+ upstreamReq.on("error", (err) => {
341
+ upstreamError = err.message;
342
+ if (!res.headersSent) {
343
+ res.statusCode = 502;
344
+ res.setHeader("content-type", "application/json");
345
+ res.end(JSON.stringify({
346
+ type: "error",
347
+ error: {
348
+ type: "tokenshield_upstream_error",
349
+ message: `Failed to reach Anthropic: ${err.message}`,
350
+ },
351
+ }));
352
+ }
353
+ else {
354
+ res.end();
355
+ }
356
+ finalize();
357
+ resolve();
358
+ });
359
+ upstreamReq.write(outboundBytes);
360
+ upstreamReq.end();
361
+ });
362
+ }
363
+ //# sourceMappingURL=anthropic-passthrough.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic-passthrough.js","sourceRoot":"","sources":["../../src/proxy/anthropic-passthrough.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { SSEEvent } from "../types.js";
2
+ /**
3
+ * Streaming SSE parser. Accepts raw bytes (UTF-8) progressively and yields
4
+ * complete events. Events are buffered until a blank-line terminator is seen.
5
+ *
6
+ * Anthropic's SSE format emits lines of the form:
7
+ * event: message_start
8
+ * data: {"type":"message_start", ...}
9
+ *
10
+ * separated by blank lines. We preserve unrecognized fields and pass raw
11
+ * bytes through unchanged so the downstream client sees a byte-faithful
12
+ * stream — we only parse a copy for accounting.
13
+ */
14
+ export declare class SSEParser {
15
+ private buffer;
16
+ private eventName;
17
+ private dataLines;
18
+ push(chunk: string): SSEEvent[];
19
+ flush(): SSEEvent[];
20
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Streaming SSE parser. Accepts raw bytes (UTF-8) progressively and yields
3
+ * complete events. Events are buffered until a blank-line terminator is seen.
4
+ *
5
+ * Anthropic's SSE format emits lines of the form:
6
+ * event: message_start
7
+ * data: {"type":"message_start", ...}
8
+ *
9
+ * separated by blank lines. We preserve unrecognized fields and pass raw
10
+ * bytes through unchanged so the downstream client sees a byte-faithful
11
+ * stream — we only parse a copy for accounting.
12
+ */
13
+ export class SSEParser {
14
+ buffer = "";
15
+ eventName = "";
16
+ dataLines = [];
17
+ push(chunk) {
18
+ this.buffer += chunk;
19
+ const events = [];
20
+ let idx;
21
+ while ((idx = this.buffer.indexOf("\n")) !== -1) {
22
+ const line = this.buffer.slice(0, idx).replace(/\r$/, "");
23
+ this.buffer = this.buffer.slice(idx + 1);
24
+ if (line === "") {
25
+ if (this.dataLines.length > 0 || this.eventName !== "") {
26
+ events.push({
27
+ event: this.eventName || "message",
28
+ data: this.dataLines.join("\n"),
29
+ });
30
+ }
31
+ this.eventName = "";
32
+ this.dataLines = [];
33
+ }
34
+ else if (line.startsWith(":")) {
35
+ // comment / keep-alive
36
+ }
37
+ else if (line.startsWith("event:")) {
38
+ this.eventName = line.slice(6).trimStart();
39
+ }
40
+ else if (line.startsWith("data:")) {
41
+ this.dataLines.push(line.slice(5).trimStart());
42
+ }
43
+ // ignore other field names (id:, retry:) — not used by Anthropic
44
+ }
45
+ return events;
46
+ }
47
+ flush() {
48
+ if (this.dataLines.length === 0 && this.eventName === "")
49
+ return [];
50
+ const event = {
51
+ event: this.eventName || "message",
52
+ data: this.dataLines.join("\n"),
53
+ };
54
+ this.eventName = "";
55
+ this.dataLines = [];
56
+ return [event];
57
+ }
58
+ }
59
+ //# sourceMappingURL=sse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/proxy/sse.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,SAAS;IACZ,MAAM,GAAG,EAAE,CAAC;IACZ,SAAS,GAAG,EAAE,CAAC;IACf,SAAS,GAAa,EAAE,CAAC;IAEjC,IAAI,CAAC,KAAa;QAChB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;QACrB,MAAM,MAAM,GAAe,EAAE,CAAC;QAC9B,IAAI,GAAW,CAAC;QAChB,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACzC,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;gBAChB,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE,CAAC;oBACvD,MAAM,CAAC,IAAI,CAAC;wBACV,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;wBAClC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;qBAChC,CAAC,CAAC;gBACL,CAAC;gBACD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;YACtB,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAChC,uBAAuB;YACzB,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;YAC7C,CAAC;iBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;YACjD,CAAC;YACD,iEAAiE;QACnE,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,SAAS,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG;YACZ,KAAK,EAAE,IAAI,CAAC,SAAS,IAAI,SAAS;YAClC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;SAChC,CAAC;QACF,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;QACpB,OAAO,CAAC,KAAK,CAAC,CAAC;IACjB,CAAC;CACF"}
@@ -0,0 +1,25 @@
1
+ import type { SSEEvent, UsageCounts } from "../types.js";
2
+ import { addUsage } from "../pricing.js";
3
+ /**
4
+ * Accumulates Anthropic usage from a stream of SSE events.
5
+ *
6
+ * Streaming responses emit:
7
+ * message_start — has usage with input_tokens + initial output_tokens=1
8
+ * message_delta — has usage with cumulative output_tokens
9
+ * message_stop — terminal
10
+ *
11
+ * Non-streaming responses come as a single JSON body with `.usage` at the top
12
+ * level — handled by usageFromJson.
13
+ */
14
+ export declare class StreamUsageAccumulator {
15
+ private current;
16
+ private modelFromEvent;
17
+ observe(event: SSEEvent): void;
18
+ total(): UsageCounts;
19
+ model(): string | null;
20
+ }
21
+ export declare function usageFromJson(body: unknown): {
22
+ usage: UsageCounts;
23
+ model: string | null;
24
+ };
25
+ export { addUsage };
@@ -0,0 +1,82 @@
1
+ import { emptyUsage, addUsage } from "../pricing.js";
2
+ function fromAnthropic(u) {
3
+ if (!u)
4
+ return emptyUsage();
5
+ return {
6
+ inputTokens: u.input_tokens ?? 0,
7
+ outputTokens: u.output_tokens ?? 0,
8
+ cacheCreationInputTokens: u.cache_creation_input_tokens ?? 0,
9
+ cacheReadInputTokens: u.cache_read_input_tokens ?? 0,
10
+ };
11
+ }
12
+ /**
13
+ * Accumulates Anthropic usage from a stream of SSE events.
14
+ *
15
+ * Streaming responses emit:
16
+ * message_start — has usage with input_tokens + initial output_tokens=1
17
+ * message_delta — has usage with cumulative output_tokens
18
+ * message_stop — terminal
19
+ *
20
+ * Non-streaming responses come as a single JSON body with `.usage` at the top
21
+ * level — handled by usageFromJson.
22
+ */
23
+ export class StreamUsageAccumulator {
24
+ current = emptyUsage();
25
+ modelFromEvent = null;
26
+ observe(event) {
27
+ if (event.event !== "message_start" && event.event !== "message_delta") {
28
+ return;
29
+ }
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(event.data);
33
+ }
34
+ catch {
35
+ return;
36
+ }
37
+ if (!parsed || typeof parsed !== "object")
38
+ return;
39
+ const obj = parsed;
40
+ if (event.event === "message_start") {
41
+ const message = obj["message"];
42
+ if (message) {
43
+ if (typeof message["model"] === "string") {
44
+ this.modelFromEvent = message["model"];
45
+ }
46
+ const u = fromAnthropic(message["usage"]);
47
+ this.current = u;
48
+ }
49
+ }
50
+ else if (event.event === "message_delta") {
51
+ const usage = obj["usage"];
52
+ if (usage) {
53
+ // message_delta usage is cumulative for output_tokens; input is fixed
54
+ const u = fromAnthropic(usage);
55
+ this.current = {
56
+ inputTokens: this.current.inputTokens || u.inputTokens,
57
+ outputTokens: u.outputTokens,
58
+ cacheCreationInputTokens: this.current.cacheCreationInputTokens ||
59
+ u.cacheCreationInputTokens,
60
+ cacheReadInputTokens: this.current.cacheReadInputTokens || u.cacheReadInputTokens,
61
+ };
62
+ }
63
+ }
64
+ }
65
+ total() {
66
+ return { ...this.current };
67
+ }
68
+ model() {
69
+ return this.modelFromEvent;
70
+ }
71
+ }
72
+ export function usageFromJson(body) {
73
+ if (!body || typeof body !== "object") {
74
+ return { usage: emptyUsage(), model: null };
75
+ }
76
+ const obj = body;
77
+ const usage = fromAnthropic(obj["usage"]);
78
+ const model = typeof obj["model"] === "string" ? obj["model"] : null;
79
+ return { usage, model };
80
+ }
81
+ export { addUsage };
82
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../../src/proxy/usage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AASrD,SAAS,aAAa,CAAC,CAA6B;IAClD,IAAI,CAAC,CAAC;QAAE,OAAO,UAAU,EAAE,CAAC;IAC5B,OAAO;QACL,WAAW,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC;QAChC,YAAY,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;QAClC,wBAAwB,EAAE,CAAC,CAAC,2BAA2B,IAAI,CAAC;QAC5D,oBAAoB,EAAE,CAAC,CAAC,uBAAuB,IAAI,CAAC;KACrD,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,sBAAsB;IACzB,OAAO,GAAgB,UAAU,EAAE,CAAC;IACpC,cAAc,GAAkB,IAAI,CAAC;IAE7C,OAAO,CAAC,KAAe;QACrB,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;YACvE,OAAO;QACT,CAAC;QACD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO;QAClD,MAAM,GAAG,GAAG,MAAiC,CAAC;QAE9C,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;YACpC,MAAM,OAAO,GAAG,GAAG,CAAC,SAAS,CAAwC,CAAC;YACtE,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,OAAO,OAAO,CAAC,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;oBACzC,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,OAAO,CAAW,CAAC;gBACnD,CAAC;gBACD,MAAM,CAAC,GAAG,aAAa,CAAC,OAAO,CAAC,OAAO,CAA+B,CAAC,CAAC;gBACxE,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;YAC3C,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAA+B,CAAC;YACzD,IAAI,KAAK,EAAE,CAAC;gBACV,sEAAsE;gBACtE,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;gBAC/B,IAAI,CAAC,OAAO,GAAG;oBACb,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW;oBACtD,YAAY,EAAE,CAAC,CAAC,YAAY;oBAC5B,wBAAwB,EACtB,IAAI,CAAC,OAAO,CAAC,wBAAwB;wBACrC,CAAC,CAAC,wBAAwB;oBAC5B,oBAAoB,EAClB,IAAI,CAAC,OAAO,CAAC,oBAAoB,IAAI,CAAC,CAAC,oBAAoB;iBAC9D,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK;QACH,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;IAC7B,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,aAAa,CAAC,IAAa;IAIzC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9C,CAAC;IACD,MAAM,GAAG,GAAG,IAA+B,CAAC;IAC5C,MAAM,KAAK,GAAG,aAAa,CAAC,GAAG,CAAC,OAAO,CAA+B,CAAC,CAAC;IACxE,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,GAAG,CAAC,OAAO,CAAY,CAAC,CAAC,CAAC,IAAI,CAAC;IACjF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC1B,CAAC;AAED,OAAO,EAAE,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { Server } from "node:http";
2
+ import type { ProxyConfig, RequestRecord } from "./types.js";
3
+ import { Ledger } from "./ledger.js";
4
+ export interface ProxyServerHandle {
5
+ proxy: Server;
6
+ dashboard: Server;
7
+ ledger: Ledger;
8
+ close: () => Promise<void>;
9
+ }
10
+ type DashboardRenderer = (ledger: Ledger) => string;
11
+ export interface StartOptions {
12
+ config: ProxyConfig;
13
+ onRecord?: (r: RequestRecord) => void;
14
+ renderDashboard?: DashboardRenderer;
15
+ }
16
+ export declare function defaultConfig(overrides?: Partial<ProxyConfig>): ProxyConfig;
17
+ export declare function start(opts: StartOptions): Promise<ProxyServerHandle>;
18
+ export {};