@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,15 @@
1
+ export { start, defaultConfig } from "./server.js";
2
+ export type { ProxyServerHandle, StartOptions } from "./server.js";
3
+ export { Ledger } from "./ledger.js";
4
+ export type { SavingsSummary } from "./ledger.js";
5
+ export { dollarsFor, addUsage, emptyUsage, isKnownModel } from "./pricing.js";
6
+ export { SSEParser } from "./proxy/sse.js";
7
+ export { StreamUsageAccumulator, usageFromJson } from "./proxy/usage.js";
8
+ export { handleAnthropicRequest, setProcessorEnabled, getProcessorEnabledIds, getResponseCacheStats, } from "./proxy/anthropic-passthrough.js";
9
+ export { providerForPath, anthropic } from "./providers/registry.js";
10
+ export type { Provider, Conversation, ConvMessage, ConvBlock, ProviderId } from "./providers/types.js";
11
+ export { Pipeline } from "./processors/pipeline.js";
12
+ export { conversationDedup } from "./processors/conversation-dedup.js";
13
+ export { ResponseCache } from "./processors/response-cache.js";
14
+ export type { Processor, ProcessorContext, ProcessorEffect, ProcessorResult, } from "./processors/types.js";
15
+ export type { RequestRecord, ProxyConfig, UsageCounts, ModelId, SSEEvent, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { start, defaultConfig } from "./server.js";
2
+ export { Ledger } from "./ledger.js";
3
+ export { dollarsFor, addUsage, emptyUsage, isKnownModel } from "./pricing.js";
4
+ export { SSEParser } from "./proxy/sse.js";
5
+ export { StreamUsageAccumulator, usageFromJson } from "./proxy/usage.js";
6
+ export { handleAnthropicRequest, setProcessorEnabled, getProcessorEnabledIds, getResponseCacheStats, } from "./proxy/anthropic-passthrough.js";
7
+ export { providerForPath, anthropic } from "./providers/registry.js";
8
+ export { Pipeline } from "./processors/pipeline.js";
9
+ export { conversationDedup } from "./processors/conversation-dedup.js";
10
+ export { ResponseCache } from "./processors/response-cache.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { RequestRecord } from "./types.js";
2
+ export interface SavingsSummary {
3
+ windowStart: number;
4
+ windowEnd: number;
5
+ requestCount: number;
6
+ totalInputTokensRaw: number;
7
+ totalInputTokensSent: number;
8
+ totalOutputTokensRaw: number;
9
+ totalOutputTokensSent: number;
10
+ dollarsRaw: number;
11
+ dollarsSent: number;
12
+ dollarsSaved: number;
13
+ byModel: Array<{
14
+ model: string;
15
+ requests: number;
16
+ inputTokens: number;
17
+ outputTokens: number;
18
+ dollars: number;
19
+ }>;
20
+ }
21
+ export declare class Ledger {
22
+ private db;
23
+ private insertStmt;
24
+ private summaryStmt;
25
+ private recentStmt;
26
+ private pruneStmt;
27
+ constructor(path: string);
28
+ record(r: RequestRecord): void;
29
+ summary(sinceMs: number): SavingsSummary;
30
+ recent(limit?: number): RequestRecord[];
31
+ prune(olderThanMs: number): number;
32
+ close(): void;
33
+ }
package/dist/ledger.js ADDED
@@ -0,0 +1,141 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { mkdirSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ export class Ledger {
5
+ db;
6
+ insertStmt;
7
+ summaryStmt;
8
+ recentStmt;
9
+ pruneStmt;
10
+ constructor(path) {
11
+ mkdirSync(dirname(path), { recursive: true });
12
+ this.db = new DatabaseSync(path);
13
+ this.db.exec("PRAGMA journal_mode = WAL");
14
+ this.db.exec("PRAGMA synchronous = NORMAL");
15
+ this.db.exec(`
16
+ CREATE TABLE IF NOT EXISTS requests (
17
+ id TEXT PRIMARY KEY,
18
+ timestamp INTEGER NOT NULL,
19
+ model TEXT NOT NULL,
20
+ endpoint TEXT NOT NULL,
21
+ streamed INTEGER NOT NULL,
22
+ duration_ms INTEGER NOT NULL,
23
+ upstream_status INTEGER NOT NULL,
24
+ upstream_error TEXT,
25
+ input_tokens_raw INTEGER NOT NULL,
26
+ input_tokens_sent INTEGER NOT NULL,
27
+ output_tokens_raw INTEGER NOT NULL,
28
+ output_tokens_sent INTEGER NOT NULL,
29
+ cache_create_raw INTEGER NOT NULL,
30
+ cache_read_raw INTEGER NOT NULL,
31
+ dollars_raw REAL NOT NULL,
32
+ dollars_sent REAL NOT NULL,
33
+ dollars_saved REAL NOT NULL,
34
+ processors TEXT NOT NULL
35
+ );
36
+ `);
37
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_requests_ts ON requests(timestamp)");
38
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_requests_model ON requests(model)");
39
+ this.insertStmt = this.db.prepare(`
40
+ INSERT INTO requests (
41
+ id, timestamp, model, endpoint, streamed, duration_ms,
42
+ upstream_status, upstream_error,
43
+ input_tokens_raw, input_tokens_sent,
44
+ output_tokens_raw, output_tokens_sent,
45
+ cache_create_raw, cache_read_raw,
46
+ dollars_raw, dollars_sent, dollars_saved, processors
47
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
48
+ `);
49
+ this.summaryStmt = this.db.prepare(`
50
+ SELECT model,
51
+ COUNT(*) as requests,
52
+ SUM(input_tokens_raw) as input_tokens_raw,
53
+ SUM(input_tokens_sent) as input_tokens_sent,
54
+ SUM(output_tokens_raw) as output_tokens_raw,
55
+ SUM(output_tokens_sent) as output_tokens_sent,
56
+ SUM(dollars_raw) as dollars_raw,
57
+ SUM(dollars_sent) as dollars_sent,
58
+ SUM(dollars_saved) as dollars_saved
59
+ FROM requests
60
+ WHERE timestamp >= ?
61
+ GROUP BY model
62
+ ORDER BY dollars_raw DESC
63
+ `);
64
+ this.recentStmt = this.db.prepare(`SELECT * FROM requests ORDER BY timestamp DESC LIMIT ?`);
65
+ this.pruneStmt = this.db.prepare(`DELETE FROM requests WHERE timestamp < ?`);
66
+ }
67
+ record(r) {
68
+ this.insertStmt.run(r.id, r.timestamp, r.model, r.endpoint, r.streamed ? 1 : 0, r.durationMs, r.upstreamStatus, r.upstreamError, r.usageRaw.inputTokens, r.usageSent.inputTokens, r.usageRaw.outputTokens, r.usageSent.outputTokens, r.usageRaw.cacheCreationInputTokens, r.usageRaw.cacheReadInputTokens, r.dollarsRaw, r.dollarsSent, r.dollarsSaved, JSON.stringify(r.processorsApplied));
69
+ }
70
+ summary(sinceMs) {
71
+ const now = Date.now();
72
+ const rows = this.summaryStmt.all(sinceMs);
73
+ let totalIRaw = 0, totalISent = 0, totalORaw = 0, totalOSent = 0, dRaw = 0, dSent = 0, dSaved = 0, reqCount = 0;
74
+ for (const r of rows) {
75
+ reqCount += r.requests;
76
+ totalIRaw += r.input_tokens_raw ?? 0;
77
+ totalISent += r.input_tokens_sent ?? 0;
78
+ totalORaw += r.output_tokens_raw ?? 0;
79
+ totalOSent += r.output_tokens_sent ?? 0;
80
+ dRaw += r.dollars_raw ?? 0;
81
+ dSent += r.dollars_sent ?? 0;
82
+ dSaved += r.dollars_saved ?? 0;
83
+ }
84
+ return {
85
+ windowStart: sinceMs,
86
+ windowEnd: now,
87
+ requestCount: reqCount,
88
+ totalInputTokensRaw: totalIRaw,
89
+ totalInputTokensSent: totalISent,
90
+ totalOutputTokensRaw: totalORaw,
91
+ totalOutputTokensSent: totalOSent,
92
+ dollarsRaw: dRaw,
93
+ dollarsSent: dSent,
94
+ dollarsSaved: dSaved,
95
+ byModel: rows.map((r) => ({
96
+ model: r.model,
97
+ requests: r.requests,
98
+ inputTokens: r.input_tokens_raw ?? 0,
99
+ outputTokens: r.output_tokens_raw ?? 0,
100
+ dollars: r.dollars_raw ?? 0,
101
+ })),
102
+ };
103
+ }
104
+ recent(limit = 50) {
105
+ const rows = this.recentStmt.all(limit);
106
+ return rows.map((r) => ({
107
+ id: r.id,
108
+ timestamp: r.timestamp,
109
+ model: r.model,
110
+ endpoint: r.endpoint,
111
+ streamed: r.streamed === 1,
112
+ durationMs: r.duration_ms,
113
+ upstreamStatus: r.upstream_status,
114
+ upstreamError: r.upstream_error,
115
+ usageRaw: {
116
+ inputTokens: r.input_tokens_raw,
117
+ outputTokens: r.output_tokens_raw,
118
+ cacheCreationInputTokens: r.cache_create_raw,
119
+ cacheReadInputTokens: r.cache_read_raw,
120
+ },
121
+ usageSent: {
122
+ inputTokens: r.input_tokens_sent,
123
+ outputTokens: r.output_tokens_sent,
124
+ cacheCreationInputTokens: r.cache_create_raw,
125
+ cacheReadInputTokens: r.cache_read_raw,
126
+ },
127
+ dollarsRaw: r.dollars_raw,
128
+ dollarsSent: r.dollars_sent,
129
+ dollarsSaved: r.dollars_saved,
130
+ processorsApplied: JSON.parse(r.processors ?? "[]"),
131
+ }));
132
+ }
133
+ prune(olderThanMs) {
134
+ const result = this.pruneStmt.run(olderThanMs);
135
+ return Number(result.changes);
136
+ }
137
+ close() {
138
+ this.db.close();
139
+ }
140
+ }
141
+ //# sourceMappingURL=ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { ModelId, UsageCounts } from "./types.js";
2
+ export declare function dollarsFor(model: ModelId, u: UsageCounts): number;
3
+ export declare function emptyUsage(): UsageCounts;
4
+ export declare function addUsage(a: UsageCounts, b: UsageCounts): UsageCounts;
5
+ export declare function isKnownModel(model: ModelId): boolean;
@@ -0,0 +1,83 @@
1
+ const PRICING = {
2
+ "claude-opus-4-7": {
3
+ inputPerMTok: 15,
4
+ outputPerMTok: 75,
5
+ cacheWritePerMTok: 18.75,
6
+ cacheReadPerMTok: 1.5,
7
+ },
8
+ "claude-opus-4-6": {
9
+ inputPerMTok: 15,
10
+ outputPerMTok: 75,
11
+ cacheWritePerMTok: 18.75,
12
+ cacheReadPerMTok: 1.5,
13
+ },
14
+ "claude-sonnet-4-6": {
15
+ inputPerMTok: 3,
16
+ outputPerMTok: 15,
17
+ cacheWritePerMTok: 3.75,
18
+ cacheReadPerMTok: 0.3,
19
+ },
20
+ "claude-sonnet-4-5": {
21
+ inputPerMTok: 3,
22
+ outputPerMTok: 15,
23
+ cacheWritePerMTok: 3.75,
24
+ cacheReadPerMTok: 0.3,
25
+ },
26
+ "claude-haiku-4-5": {
27
+ inputPerMTok: 1,
28
+ outputPerMTok: 5,
29
+ cacheWritePerMTok: 1.25,
30
+ cacheReadPerMTok: 0.1,
31
+ },
32
+ };
33
+ const DEFAULT_PRICING = PRICING["claude-sonnet-4-6"];
34
+ function lookup(model) {
35
+ const exact = PRICING[model];
36
+ if (exact)
37
+ return exact;
38
+ for (const [key, val] of Object.entries(PRICING)) {
39
+ if (model.startsWith(key))
40
+ return val;
41
+ }
42
+ const lower = model.toLowerCase();
43
+ if (lower.includes("opus"))
44
+ return PRICING["claude-opus-4-7"];
45
+ if (lower.includes("haiku"))
46
+ return PRICING["claude-haiku-4-5"];
47
+ if (lower.includes("sonnet"))
48
+ return PRICING["claude-sonnet-4-6"];
49
+ return DEFAULT_PRICING;
50
+ }
51
+ export function dollarsFor(model, u) {
52
+ const p = lookup(model);
53
+ return ((u.inputTokens * p.inputPerMTok) / 1_000_000 +
54
+ (u.outputTokens * p.outputPerMTok) / 1_000_000 +
55
+ (u.cacheCreationInputTokens * p.cacheWritePerMTok) / 1_000_000 +
56
+ (u.cacheReadInputTokens * p.cacheReadPerMTok) / 1_000_000);
57
+ }
58
+ export function emptyUsage() {
59
+ return {
60
+ inputTokens: 0,
61
+ outputTokens: 0,
62
+ cacheCreationInputTokens: 0,
63
+ cacheReadInputTokens: 0,
64
+ };
65
+ }
66
+ export function addUsage(a, b) {
67
+ return {
68
+ inputTokens: a.inputTokens + b.inputTokens,
69
+ outputTokens: a.outputTokens + b.outputTokens,
70
+ cacheCreationInputTokens: a.cacheCreationInputTokens + b.cacheCreationInputTokens,
71
+ cacheReadInputTokens: a.cacheReadInputTokens + b.cacheReadInputTokens,
72
+ };
73
+ }
74
+ export function isKnownModel(model) {
75
+ if (PRICING[model])
76
+ return true;
77
+ for (const key of Object.keys(PRICING)) {
78
+ if (model.startsWith(key))
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ //# sourceMappingURL=pricing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pricing.js","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Conversation } from "../providers/types.js";
2
+ import type { Processor, ProcessorContext, ProcessorResult } from "./types.js";
3
+ /**
4
+ * Conversation deduplication.
5
+ *
6
+ * Walks the conversation in message order. The first occurrence of any
7
+ * tool_result content (keyed by its content hash) is kept verbatim. Subsequent
8
+ * occurrences are replaced with a compact pointer that references the prior one.
9
+ *
10
+ * Determinism: same input always produces the same output (hash-based ordering),
11
+ * so Anthropic prompt caching remains valid.
12
+ *
13
+ * Fail-open: if any block can't be handled cleanly, it's left untouched.
14
+ */
15
+ declare class ConversationDedup implements Processor {
16
+ readonly id = "conversation-dedup";
17
+ readonly enabledByDefault = true;
18
+ /** Only elide payloads worth eliding — avoids stub-overhead on tiny results. */
19
+ static readonly MIN_ELIDE_BYTES = 256;
20
+ onRequest(conv: Conversation, _ctx: ProcessorContext): ProcessorResult;
21
+ }
22
+ export declare const conversationDedup: ConversationDedup;
23
+ export {};
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Conversation deduplication.
3
+ *
4
+ * Walks the conversation in message order. The first occurrence of any
5
+ * tool_result content (keyed by its content hash) is kept verbatim. Subsequent
6
+ * occurrences are replaced with a compact pointer that references the prior one.
7
+ *
8
+ * Determinism: same input always produces the same output (hash-based ordering),
9
+ * so Anthropic prompt caching remains valid.
10
+ *
11
+ * Fail-open: if any block can't be handled cleanly, it's left untouched.
12
+ */
13
+ class ConversationDedup {
14
+ id = "conversation-dedup";
15
+ enabledByDefault = true;
16
+ /** Only elide payloads worth eliding — avoids stub-overhead on tiny results. */
17
+ static MIN_ELIDE_BYTES = 256;
18
+ onRequest(conv, _ctx) {
19
+ const seen = new Map();
20
+ let elidedBytesTotal = 0;
21
+ let elidedCount = 0;
22
+ let mutated = false;
23
+ const newMessages = conv.messages.map((msg, msgIdx) => {
24
+ const newBlocks = msg.blocks.map((block) => {
25
+ if (block.kind !== "tool_result")
26
+ return block;
27
+ if (block.pointer)
28
+ return block; // already deduped (idempotency)
29
+ const prior = seen.get(block.contentHash);
30
+ if (prior === undefined) {
31
+ seen.set(block.contentHash, {
32
+ messageIndex: msgIdx,
33
+ toolUseId: block.tool_use_id,
34
+ });
35
+ return block;
36
+ }
37
+ if (block.contentBytes < ConversationDedup.MIN_ELIDE_BYTES) {
38
+ // Not worth the stub overhead; leave verbatim.
39
+ return block;
40
+ }
41
+ mutated = true;
42
+ elidedBytesTotal += block.contentBytes;
43
+ elidedCount++;
44
+ return {
45
+ ...block,
46
+ pointer: {
47
+ priorMessageIndex: prior.messageIndex,
48
+ priorToolUseId: prior.toolUseId,
49
+ elidedBytes: block.contentBytes,
50
+ },
51
+ };
52
+ });
53
+ return mutated ? { ...msg, blocks: newBlocks } : msg;
54
+ });
55
+ if (!mutated) {
56
+ return { conversation: conv, effects: [] };
57
+ }
58
+ return {
59
+ conversation: { ...conv, messages: newMessages },
60
+ effects: [
61
+ {
62
+ name: this.id,
63
+ bytesSaved: elidedBytesTotal,
64
+ detail: { elidedCount, distinctContent: seen.size },
65
+ },
66
+ ],
67
+ };
68
+ }
69
+ }
70
+ export const conversationDedup = new ConversationDedup();
71
+ //# sourceMappingURL=conversation-dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conversation-dedup.js","sourceRoot":"","sources":["../../src/processors/conversation-dedup.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,iBAAiB;IACZ,EAAE,GAAG,oBAAoB,CAAC;IAC1B,gBAAgB,GAAG,IAAI,CAAC;IAEjC,gFAAgF;IAChF,MAAM,CAAU,eAAe,GAAG,GAAG,CAAC;IAEtC,SAAS,CAAC,IAAkB,EAAE,IAAsB;QAClD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAuD,CAAC;QAC5E,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACpD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAY,CAAC,KAAK,EAAE,EAAE;gBACpD,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa;oBAAE,OAAO,KAAK,CAAC;gBAC/C,IAAI,KAAK,CAAC,OAAO;oBAAE,OAAO,KAAK,CAAC,CAAC,gCAAgC;gBACjE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;gBAC1C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;oBACxB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE;wBAC1B,YAAY,EAAE,MAAM;wBACpB,SAAS,EAAE,KAAK,CAAC,WAAW;qBAC7B,CAAC,CAAC;oBACH,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,IAAI,KAAK,CAAC,YAAY,GAAG,iBAAiB,CAAC,eAAe,EAAE,CAAC;oBAC3D,+CAA+C;oBAC/C,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;gBACf,gBAAgB,IAAI,KAAK,CAAC,YAAY,CAAC;gBACvC,WAAW,EAAE,CAAC;gBACd,OAAO;oBACL,GAAG,KAAK;oBACR,OAAO,EAAE;wBACP,iBAAiB,EAAE,KAAK,CAAC,YAAY;wBACrC,cAAc,EAAE,KAAK,CAAC,SAAS;wBAC/B,WAAW,EAAE,KAAK,CAAC,YAAY;qBAChC;iBACF,CAAC;YACJ,CAAC,CAAC,CAAC;YACH,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC7C,CAAC;QAED,OAAO;YACL,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE;YAChD,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,IAAI,CAAC,EAAE;oBACb,UAAU,EAAE,gBAAgB;oBAC5B,MAAM,EAAE,EAAE,WAAW,EAAE,eAAe,EAAE,IAAI,CAAC,IAAI,EAAE;iBACpD;aACF;SACF,CAAC;IACJ,CAAC;;AAGH,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Conversation } from "../providers/types.js";
2
+ import type { ProcessorContext, PipelineOptions, PipelineRunResult } from "./types.js";
3
+ export declare class Pipeline {
4
+ private readonly processors;
5
+ private readonly enabled;
6
+ private readonly breaker;
7
+ constructor(opts: PipelineOptions);
8
+ run(input: Conversation, ctx: ProcessorContext, sizeOf: (c: Conversation) => number): PipelineRunResult;
9
+ }
10
+ export type { Processor, ProcessorContext, ProcessorEffect } from "./types.js";
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Per-process circuit breaker. If a processor throws N times within W ms,
3
+ * disable it for D ms. Bounded recovery so a transient bug doesn't cost
4
+ * the user the whole session.
5
+ */
6
+ class CircuitBreaker {
7
+ threshold;
8
+ windowMs;
9
+ cooldownMs;
10
+ states = new Map();
11
+ constructor(threshold = 3, windowMs = 60_000, cooldownMs = 5 * 60_000) {
12
+ this.threshold = threshold;
13
+ this.windowMs = windowMs;
14
+ this.cooldownMs = cooldownMs;
15
+ }
16
+ isOpen(id) {
17
+ const s = this.states.get(id);
18
+ return s !== undefined && s.trippedUntil > Date.now();
19
+ }
20
+ recordFailure(id) {
21
+ const now = Date.now();
22
+ let s = this.states.get(id);
23
+ if (s === undefined) {
24
+ s = { failuresInWindow: 0, windowStart: now, trippedUntil: 0 };
25
+ this.states.set(id, s);
26
+ }
27
+ if (now - s.windowStart > this.windowMs) {
28
+ s.windowStart = now;
29
+ s.failuresInWindow = 0;
30
+ }
31
+ s.failuresInWindow++;
32
+ if (s.failuresInWindow >= this.threshold) {
33
+ s.trippedUntil = now + this.cooldownMs;
34
+ }
35
+ }
36
+ recordSuccess(id) {
37
+ const s = this.states.get(id);
38
+ if (s === undefined)
39
+ return;
40
+ if (s.failuresInWindow > 0)
41
+ s.failuresInWindow = Math.max(0, s.failuresInWindow - 1);
42
+ }
43
+ }
44
+ export class Pipeline {
45
+ processors;
46
+ enabled;
47
+ breaker = new CircuitBreaker();
48
+ constructor(opts) {
49
+ this.processors = opts.processors;
50
+ this.enabled = opts.enabled;
51
+ }
52
+ run(input, ctx, sizeOf) {
53
+ const bytesIn = sizeOf(input);
54
+ let current = input;
55
+ const effects = [];
56
+ const errors = [];
57
+ for (const p of this.processors) {
58
+ if (!this.enabled.has(p.id))
59
+ continue;
60
+ if (this.breaker.isOpen(p.id))
61
+ continue;
62
+ try {
63
+ const result = p.onRequest(current, ctx);
64
+ if (result.conversation !== current) {
65
+ current = result.conversation;
66
+ }
67
+ for (const e of result.effects)
68
+ effects.push(e);
69
+ this.breaker.recordSuccess(p.id);
70
+ }
71
+ catch (err) {
72
+ this.breaker.recordFailure(p.id);
73
+ errors.push({
74
+ processor: p.id,
75
+ message: err instanceof Error ? err.message : String(err),
76
+ });
77
+ // current is unchanged — fail-open
78
+ }
79
+ }
80
+ return {
81
+ conversation: current,
82
+ effects,
83
+ bytesIn,
84
+ bytesOut: sizeOf(current),
85
+ errors,
86
+ };
87
+ }
88
+ }
89
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/processors/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ export interface CacheHit {
2
+ status: number;
3
+ headers: Record<string, string>;
4
+ body: Buffer;
5
+ usage: {
6
+ inputTokens: number;
7
+ outputTokens: number;
8
+ };
9
+ model: string;
10
+ cachedAgoMs: number;
11
+ cachedBytes: number;
12
+ }
13
+ /**
14
+ * Tiny, conservative response cache for the Anthropic JSON endpoint.
15
+ *
16
+ * Caches IFF: temperature === 0 AND stream === false. Anthropic only guarantees
17
+ * deterministic outputs under these conditions, so caching anything else risks
18
+ * serving a stale response a user wouldn't expect.
19
+ *
20
+ * Bounded by total byte budget (default 64 MB) with LRU eviction.
21
+ * Default TTL: 10 minutes.
22
+ */
23
+ export declare class ResponseCache {
24
+ private readonly maxBytes;
25
+ private readonly defaultTtlMs;
26
+ private readonly map;
27
+ private currentBytes;
28
+ private hits;
29
+ private misses;
30
+ constructor(maxBytes?: number, defaultTtlMs?: number);
31
+ private static keyFor;
32
+ /** Returns a hit if the body is cacheable AND fresh; otherwise null. */
33
+ lookup(body: unknown): CacheHit | null;
34
+ /** Store a response for a cacheable request. No-op if request isn't cacheable. */
35
+ store(body: unknown, response: {
36
+ status: number;
37
+ headers: Record<string, string>;
38
+ body: Buffer;
39
+ usage: {
40
+ inputTokens: number;
41
+ outputTokens: number;
42
+ };
43
+ model: string;
44
+ }, ttlMs?: number): void;
45
+ private evictIfNeeded;
46
+ private evict;
47
+ stats(): {
48
+ hits: number;
49
+ misses: number;
50
+ entries: number;
51
+ bytes: number;
52
+ };
53
+ }