rehydra 0.3.3 → 0.4.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 (83) hide show
  1. package/README.md +173 -873
  2. package/dist/core/anonymizer.d.ts +9 -1
  3. package/dist/core/anonymizer.d.ts.map +1 -1
  4. package/dist/core/anonymizer.js +29 -7
  5. package/dist/core/anonymizer.js.map +1 -1
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/proxy/index.d.ts +12 -0
  11. package/dist/proxy/index.d.ts.map +1 -0
  12. package/dist/proxy/index.js +11 -0
  13. package/dist/proxy/index.js.map +1 -0
  14. package/dist/proxy/providers/anthropic.d.ts +17 -0
  15. package/dist/proxy/providers/anthropic.d.ts.map +1 -0
  16. package/dist/proxy/providers/anthropic.js +117 -0
  17. package/dist/proxy/providers/anthropic.js.map +1 -0
  18. package/dist/proxy/providers/index.d.ts +19 -0
  19. package/dist/proxy/providers/index.d.ts.map +1 -0
  20. package/dist/proxy/providers/index.js +40 -0
  21. package/dist/proxy/providers/index.js.map +1 -0
  22. package/dist/proxy/providers/openai.d.ts +17 -0
  23. package/dist/proxy/providers/openai.d.ts.map +1 -0
  24. package/dist/proxy/providers/openai.js +92 -0
  25. package/dist/proxy/providers/openai.js.map +1 -0
  26. package/dist/proxy/providers/types.d.ts +29 -0
  27. package/dist/proxy/providers/types.d.ts.map +1 -0
  28. package/dist/proxy/providers/types.js +6 -0
  29. package/dist/proxy/providers/types.js.map +1 -0
  30. package/dist/proxy/proxy-server.d.ts +53 -0
  31. package/dist/proxy/proxy-server.d.ts.map +1 -0
  32. package/dist/proxy/proxy-server.js +146 -0
  33. package/dist/proxy/proxy-server.js.map +1 -0
  34. package/dist/proxy/rehydra-fetch.d.ts +35 -0
  35. package/dist/proxy/rehydra-fetch.d.ts.map +1 -0
  36. package/dist/proxy/rehydra-fetch.js +217 -0
  37. package/dist/proxy/rehydra-fetch.js.map +1 -0
  38. package/dist/proxy/rehydra-proxy.d.ts +40 -0
  39. package/dist/proxy/rehydra-proxy.d.ts.map +1 -0
  40. package/dist/proxy/rehydra-proxy.js +82 -0
  41. package/dist/proxy/rehydra-proxy.js.map +1 -0
  42. package/dist/proxy/sse-parser.d.ts +59 -0
  43. package/dist/proxy/sse-parser.d.ts.map +1 -0
  44. package/dist/proxy/sse-parser.js +112 -0
  45. package/dist/proxy/sse-parser.js.map +1 -0
  46. package/dist/proxy/types.d.ts +49 -0
  47. package/dist/proxy/types.d.ts.map +1 -0
  48. package/dist/proxy/types.js +5 -0
  49. package/dist/proxy/types.js.map +1 -0
  50. package/dist/proxy/wrap-client.d.ts +47 -0
  51. package/dist/proxy/wrap-client.d.ts.map +1 -0
  52. package/dist/proxy/wrap-client.js +70 -0
  53. package/dist/proxy/wrap-client.js.map +1 -0
  54. package/dist/storage/session.d.ts +3 -0
  55. package/dist/storage/session.d.ts.map +1 -1
  56. package/dist/storage/session.js +24 -1
  57. package/dist/storage/session.js.map +1 -1
  58. package/dist/storage/types.d.ts +16 -0
  59. package/dist/storage/types.d.ts.map +1 -1
  60. package/dist/streaming/anonymizer-stream.d.ts +63 -0
  61. package/dist/streaming/anonymizer-stream.d.ts.map +1 -0
  62. package/dist/streaming/anonymizer-stream.js +184 -0
  63. package/dist/streaming/anonymizer-stream.js.map +1 -0
  64. package/dist/streaming/index.d.ts +9 -0
  65. package/dist/streaming/index.d.ts.map +1 -0
  66. package/dist/streaming/index.js +8 -0
  67. package/dist/streaming/index.js.map +1 -0
  68. package/dist/streaming/sentence-buffer.d.ts +78 -0
  69. package/dist/streaming/sentence-buffer.d.ts.map +1 -0
  70. package/dist/streaming/sentence-buffer.js +238 -0
  71. package/dist/streaming/sentence-buffer.js.map +1 -0
  72. package/dist/streaming/stream-factory.d.ts +38 -0
  73. package/dist/streaming/stream-factory.d.ts.map +1 -0
  74. package/dist/streaming/stream-factory.js +69 -0
  75. package/dist/streaming/stream-factory.js.map +1 -0
  76. package/dist/streaming/types.d.ts +121 -0
  77. package/dist/streaming/types.d.ts.map +1 -0
  78. package/dist/streaming/types.js +5 -0
  79. package/dist/streaming/types.js.map +1 -0
  80. package/dist/types/index.d.ts +8 -2
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/index.js.map +1 -1
  83. package/package.json +19 -2
@@ -0,0 +1,6 @@
1
+ /**
2
+ * LLM Content Provider Interface
3
+ * Abstracts LLM-specific request/response formats for the proxy middleware.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/proxy/providers/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Rehydra Proxy Server
3
+ * Standalone HTTP server that proxies LLM API requests with
4
+ * automatic PII anonymization and rehydration.
5
+ */
6
+ import { type Server } from "node:http";
7
+ import type { RehydraProxyConfig } from "./types.js";
8
+ /**
9
+ * Configuration for the standalone proxy server
10
+ */
11
+ export interface RehydraProxyServerConfig extends RehydraProxyConfig {
12
+ /** Port to listen on */
13
+ port: number;
14
+ /** Host to bind to (default: "127.0.0.1") */
15
+ host?: string;
16
+ }
17
+ /**
18
+ * A running Rehydra proxy server
19
+ */
20
+ export interface RehydraProxyServer {
21
+ /** The underlying HTTP server */
22
+ server: Server;
23
+ /** The port the server is listening on */
24
+ port: number;
25
+ /** The host the server is bound to */
26
+ host: string;
27
+ /** Gracefully close the server */
28
+ close(): Promise<void>;
29
+ }
30
+ /**
31
+ * Creates and starts a standalone HTTP proxy server that anonymizes
32
+ * LLM API requests and rehydrates responses.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * const proxy = await createRehydraProxyServer({
37
+ * port: 8080,
38
+ * upstream: 'https://api.openai.com',
39
+ * keyProvider: new ConfigKeyProvider(process.env.PII_KEY!),
40
+ * piiStorageProvider: new SQLitePIIStorageProvider('proxy.db'),
41
+ * });
42
+ *
43
+ * console.log(`Proxy running on http://${proxy.host}:${proxy.port}`);
44
+ *
45
+ * // Point your OpenAI client at the proxy:
46
+ * const openai = new OpenAI({ baseURL: `http://localhost:${proxy.port}/v1` });
47
+ *
48
+ * // Stop the server
49
+ * await proxy.close();
50
+ * ```
51
+ */
52
+ export declare function createRehydraProxyServer(config: RehydraProxyServerConfig): Promise<RehydraProxyServer>;
53
+ //# sourceMappingURL=proxy-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-server.d.ts","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAEjG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD;;GAEG;AACH,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAClE,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,wBAAwB,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CAwC7B"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Rehydra Proxy Server
3
+ * Standalone HTTP server that proxies LLM API requests with
4
+ * automatic PII anonymization and rehydration.
5
+ */
6
+ import { createServer } from "node:http";
7
+ import { createRehydraProxy } from "./rehydra-proxy.js";
8
+ /**
9
+ * Creates and starts a standalone HTTP proxy server that anonymizes
10
+ * LLM API requests and rehydrates responses.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const proxy = await createRehydraProxyServer({
15
+ * port: 8080,
16
+ * upstream: 'https://api.openai.com',
17
+ * keyProvider: new ConfigKeyProvider(process.env.PII_KEY!),
18
+ * piiStorageProvider: new SQLitePIIStorageProvider('proxy.db'),
19
+ * });
20
+ *
21
+ * console.log(`Proxy running on http://${proxy.host}:${proxy.port}`);
22
+ *
23
+ * // Point your OpenAI client at the proxy:
24
+ * const openai = new OpenAI({ baseURL: `http://localhost:${proxy.port}/v1` });
25
+ *
26
+ * // Stop the server
27
+ * await proxy.close();
28
+ * ```
29
+ */
30
+ export async function createRehydraProxyServer(config) {
31
+ const host = config.host ?? "127.0.0.1";
32
+ const proxy = createRehydraProxy(config);
33
+ const server = createServer((req, res) => {
34
+ void (async () => {
35
+ try {
36
+ const webRequest = incomingMessageToRequest(req, host, config.port);
37
+ const webResponse = await proxy(webRequest);
38
+ await writeResponse(res, webResponse);
39
+ }
40
+ catch (error) {
41
+ res.writeHead(502, { "Content-Type": "application/json" });
42
+ res.end(JSON.stringify({
43
+ error: "proxy_error",
44
+ message: error instanceof Error ? error.message : "Unknown proxy error",
45
+ }));
46
+ }
47
+ })();
48
+ });
49
+ await new Promise((resolve, reject) => {
50
+ server.on("error", reject);
51
+ server.listen(config.port, host, () => {
52
+ resolve();
53
+ });
54
+ });
55
+ return {
56
+ server,
57
+ port: config.port,
58
+ host,
59
+ async close() {
60
+ return new Promise((resolve, reject) => {
61
+ server.close((err) => {
62
+ if (err !== undefined)
63
+ reject(err);
64
+ else
65
+ resolve();
66
+ });
67
+ });
68
+ },
69
+ };
70
+ }
71
+ /**
72
+ * Convert a Node.js IncomingMessage to a Web API Request
73
+ */
74
+ function incomingMessageToRequest(req, host, port) {
75
+ const url = `http://${host}:${port}${req.url ?? "/"}`;
76
+ const headers = new Headers();
77
+ for (const [key, value] of Object.entries(req.headers)) {
78
+ if (value !== undefined) {
79
+ if (Array.isArray(value)) {
80
+ for (const v of value) {
81
+ headers.append(key, v);
82
+ }
83
+ }
84
+ else {
85
+ headers.set(key, value);
86
+ }
87
+ }
88
+ }
89
+ const method = req.method ?? "GET";
90
+ const hasBody = method !== "GET" && method !== "HEAD";
91
+ return new Request(url, {
92
+ method,
93
+ headers,
94
+ body: hasBody ? nodeStreamToReadableStream(req) : undefined,
95
+ // @ts-expect-error - duplex is needed for streaming request bodies
96
+ duplex: hasBody ? "half" : undefined,
97
+ });
98
+ }
99
+ /**
100
+ * Convert a Node.js Readable stream to a Web API ReadableStream
101
+ */
102
+ function nodeStreamToReadableStream(nodeStream) {
103
+ return new ReadableStream({
104
+ start(controller) {
105
+ nodeStream.on("data", (chunk) => {
106
+ controller.enqueue(new Uint8Array(chunk));
107
+ });
108
+ nodeStream.on("end", () => {
109
+ controller.close();
110
+ });
111
+ nodeStream.on("error", (err) => {
112
+ controller.error(err);
113
+ });
114
+ },
115
+ });
116
+ }
117
+ /**
118
+ * Write a Web API Response to a Node.js ServerResponse
119
+ */
120
+ async function writeResponse(res, webResponse) {
121
+ // Copy status and headers
122
+ const headers = {};
123
+ webResponse.headers.forEach((value, key) => {
124
+ headers[key] = value;
125
+ });
126
+ res.writeHead(webResponse.status, headers);
127
+ if (webResponse.body === null) {
128
+ res.end();
129
+ return;
130
+ }
131
+ // Stream the response body
132
+ const reader = webResponse.body.getReader();
133
+ try {
134
+ for (;;) {
135
+ const { done, value } = await reader.read();
136
+ if (done)
137
+ break;
138
+ res.write(value);
139
+ }
140
+ }
141
+ finally {
142
+ reader.releaseLock();
143
+ res.end();
144
+ }
145
+ }
146
+ //# sourceMappingURL=proxy-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-server.js","sourceRoot":"","sources":["../../src/proxy/proxy-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Rehydra Fetch Wrapper
3
+ * Wraps the native fetch to anonymize outgoing LLM requests
4
+ * and rehydrate incoming LLM responses.
5
+ */
6
+ import type { RehydraFetchConfig } from "./types.js";
7
+ /**
8
+ * Creates a fetch-compatible function that automatically:
9
+ * 1. Anonymizes text in outgoing LLM API requests
10
+ * 2. Rehydrates text in incoming LLM API responses
11
+ *
12
+ * PII never leaves your infrastructure.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import OpenAI from 'openai';
17
+ * import { createRehydraFetch, InMemoryKeyProvider, InMemoryPIIStorageProvider } from 'rehydra';
18
+ *
19
+ * const rehydraFetch = createRehydraFetch({
20
+ * keyProvider: new InMemoryKeyProvider(),
21
+ * piiStorageProvider: new InMemoryPIIStorageProvider(),
22
+ * });
23
+ *
24
+ * const openai = new OpenAI({ fetch: rehydraFetch });
25
+ *
26
+ * // PII is automatically anonymized before being sent to OpenAI
27
+ * const response = await openai.chat.completions.create({
28
+ * model: 'gpt-4',
29
+ * messages: [{ role: 'user', content: 'Email john@example.com about the meeting' }],
30
+ * });
31
+ * // Response is automatically rehydrated — contains original PII
32
+ * ```
33
+ */
34
+ export declare function createRehydraFetch(config: RehydraFetchConfig): typeof globalThis.fetch;
35
+ //# sourceMappingURL=rehydra-fetch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydra-fetch.d.ts","sourceRoot":"","sources":["../../src/proxy/rehydra-fetch.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQrD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,kBAAkB,GACzB,OAAO,UAAU,CAAC,KAAK,CA0FzB"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Rehydra Fetch Wrapper
3
+ * Wraps the native fetch to anonymize outgoing LLM requests
4
+ * and rehydrate incoming LLM responses.
5
+ */
6
+ import { createAnonymizer } from "../core/anonymizer.js";
7
+ import { decryptPIIMap } from "../crypto/index.js";
8
+ import { rehydrate } from "../pipeline/tagger.js";
9
+ import { AnonymizerSessionImpl } from "../storage/session.js";
10
+ import { SSEParser, isSSEDone, serializeSSEEvent } from "./sse-parser.js";
11
+ import { detectProvider } from "./providers/index.js";
12
+ let sessionCounter = 0;
13
+ function defaultGetSessionId() {
14
+ return `rehydra-proxy-${Date.now()}-${++sessionCounter}`;
15
+ }
16
+ /**
17
+ * Creates a fetch-compatible function that automatically:
18
+ * 1. Anonymizes text in outgoing LLM API requests
19
+ * 2. Rehydrates text in incoming LLM API responses
20
+ *
21
+ * PII never leaves your infrastructure.
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import OpenAI from 'openai';
26
+ * import { createRehydraFetch, InMemoryKeyProvider, InMemoryPIIStorageProvider } from 'rehydra';
27
+ *
28
+ * const rehydraFetch = createRehydraFetch({
29
+ * keyProvider: new InMemoryKeyProvider(),
30
+ * piiStorageProvider: new InMemoryPIIStorageProvider(),
31
+ * });
32
+ *
33
+ * const openai = new OpenAI({ fetch: rehydraFetch });
34
+ *
35
+ * // PII is automatically anonymized before being sent to OpenAI
36
+ * const response = await openai.chat.completions.create({
37
+ * model: 'gpt-4',
38
+ * messages: [{ role: 'user', content: 'Email john@example.com about the meeting' }],
39
+ * });
40
+ * // Response is automatically rehydrated — contains original PII
41
+ * ```
42
+ */
43
+ export function createRehydraFetch(config) {
44
+ const anonymizer = createAnonymizer({
45
+ ...config.anonymizer,
46
+ keyProvider: config.keyProvider,
47
+ piiStorageProvider: config.piiStorageProvider,
48
+ });
49
+ let initialized = false;
50
+ const getSessionId = config.getSessionId ?? defaultGetSessionId;
51
+ const handleStreaming = config.handleStreaming !== false;
52
+ async function ensureInitialized() {
53
+ if (!initialized) {
54
+ await anonymizer.initialize();
55
+ initialized = true;
56
+ }
57
+ }
58
+ return async (input, init) => {
59
+ await ensureInitialized();
60
+ const request = new Request(input, init);
61
+ // Only intercept POST requests with JSON bodies (LLM API calls)
62
+ if (request.method !== "POST") {
63
+ return fetch(request);
64
+ }
65
+ const contentType = request.headers.get("content-type");
66
+ if (contentType === null || !contentType.includes("application/json")) {
67
+ return fetch(request);
68
+ }
69
+ // Detect the LLM provider
70
+ const provider = detectProvider(request.url, request.headers, config.provider);
71
+ // Parse request body
72
+ const body = await request.json();
73
+ // Get session ID for PII map persistence
74
+ const sessionId = await getSessionId(request);
75
+ // Create a session for this request
76
+ const session = new AnonymizerSessionImpl(anonymizer, sessionId, config.piiStorageProvider, config.keyProvider);
77
+ // Extract and anonymize text from the request
78
+ const texts = provider.extractRequestText(body);
79
+ const anonymizedTexts = [];
80
+ for (const text of texts) {
81
+ const result = await session.anonymize(text, config.locale, config.policy);
82
+ anonymizedTexts.push(result.anonymizedText);
83
+ }
84
+ // Rebuild the request body with anonymized text
85
+ const anonymizedBody = provider.rebuildRequestBody(body, anonymizedTexts);
86
+ // Forward the anonymized request
87
+ const upstreamRequest = new Request(request, {
88
+ body: JSON.stringify(anonymizedBody),
89
+ });
90
+ const response = await fetch(upstreamRequest);
91
+ // Determine if the response is a streaming SSE response
92
+ const responseContentType = response.headers.get("content-type");
93
+ const isSSE = handleStreaming &&
94
+ provider.isStreamingRequest(body) &&
95
+ responseContentType !== null &&
96
+ responseContentType.includes("text/event-stream");
97
+ if (isSSE && response.body !== null) {
98
+ return rehydrateSSEResponse(response, session, provider, config);
99
+ }
100
+ return rehydrateJSONResponse(response, session, provider);
101
+ };
102
+ }
103
+ /**
104
+ * Rehydrate a non-streaming JSON response.
105
+ */
106
+ async function rehydrateJSONResponse(response, session, provider) {
107
+ const body = await response.json();
108
+ // Extract response text and rehydrate
109
+ const responseTexts = provider.extractResponseText(body);
110
+ const rehydratedTexts = [];
111
+ for (const text of responseTexts) {
112
+ const rehydrated = await session.rehydrate(text);
113
+ rehydratedTexts.push(rehydrated);
114
+ }
115
+ // Rebuild response body
116
+ const rehydratedBody = provider.rebuildResponseBody(body, rehydratedTexts);
117
+ return new Response(JSON.stringify(rehydratedBody), {
118
+ status: response.status,
119
+ statusText: response.statusText,
120
+ headers: response.headers,
121
+ });
122
+ }
123
+ /**
124
+ * Rehydrate a streaming SSE response.
125
+ * Returns a new Response with a transformed body stream.
126
+ */
127
+ function rehydrateSSEResponse(response, session, provider, config) {
128
+ const sseParser = new SSEParser();
129
+ const decoder = new TextDecoder();
130
+ const encoder = new TextEncoder();
131
+ // Buffer for incomplete PII tags that span SSE chunks
132
+ let tagBuffer = "";
133
+ // We load the PII map once, lazily
134
+ let piiMapPromise = null;
135
+ function getPiiMap() {
136
+ if (piiMapPromise === null) {
137
+ piiMapPromise = (async () => {
138
+ const stored = await config.piiStorageProvider.load(session.sessionId);
139
+ if (stored === null)
140
+ return new Map();
141
+ const key = await config.keyProvider.getKey();
142
+ return decryptPIIMap(stored.piiMap, key);
143
+ })();
144
+ }
145
+ return piiMapPromise;
146
+ }
147
+ const transformStream = new TransformStream({
148
+ async transform(chunk, controller) {
149
+ const text = decoder.decode(chunk, { stream: true });
150
+ const events = sseParser.parse(text);
151
+ for (const event of events) {
152
+ if (isSSEDone(event.data)) {
153
+ controller.enqueue(encoder.encode(serializeSSEEvent(event)));
154
+ continue;
155
+ }
156
+ let parsed;
157
+ try {
158
+ parsed = JSON.parse(event.data);
159
+ }
160
+ catch {
161
+ // Not JSON — pass through as-is
162
+ controller.enqueue(encoder.encode(serializeSSEEvent(event)));
163
+ continue;
164
+ }
165
+ const delta = provider.extractSSEDelta(parsed);
166
+ if (delta === null) {
167
+ controller.enqueue(encoder.encode(serializeSSEEvent(event)));
168
+ continue;
169
+ }
170
+ // Handle incomplete PII tags spanning chunks
171
+ const fullText = tagBuffer + delta;
172
+ const incompleteTagIdx = fullText.lastIndexOf("<PII");
173
+ const lastCloseIdx = fullText.lastIndexOf("/>");
174
+ let textToRehydrate;
175
+ if (incompleteTagIdx !== -1 &&
176
+ (lastCloseIdx === -1 || lastCloseIdx < incompleteTagIdx)) {
177
+ textToRehydrate = fullText.slice(0, incompleteTagIdx);
178
+ tagBuffer = fullText.slice(incompleteTagIdx);
179
+ }
180
+ else {
181
+ textToRehydrate = fullText;
182
+ tagBuffer = "";
183
+ }
184
+ if (textToRehydrate.length > 0) {
185
+ const piiMap = await getPiiMap();
186
+ const rehydrated = rehydrate(textToRehydrate, piiMap);
187
+ const rebuilt = provider.rebuildSSEDelta(parsed, rehydrated);
188
+ controller.enqueue(encoder.encode(serializeSSEEvent({
189
+ event: event.event,
190
+ data: JSON.stringify(rebuilt),
191
+ })));
192
+ }
193
+ }
194
+ },
195
+ async flush(controller) {
196
+ const events = sseParser.flush();
197
+ for (const event of events) {
198
+ controller.enqueue(encoder.encode(serializeSSEEvent(event)));
199
+ }
200
+ if (tagBuffer.length > 0) {
201
+ const piiMap = await getPiiMap();
202
+ const rehydrated = rehydrate(tagBuffer, piiMap);
203
+ if (rehydrated.length > 0) {
204
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify({ content: rehydrated })}\n\n`));
205
+ }
206
+ tagBuffer = "";
207
+ }
208
+ },
209
+ });
210
+ const transformedBody = response.body.pipeThrough(transformStream);
211
+ return new Response(transformedBody, {
212
+ status: response.status,
213
+ statusText: response.statusText,
214
+ headers: response.headers,
215
+ });
216
+ }
217
+ //# sourceMappingURL=rehydra-fetch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydra-fetch.js","sourceRoot":"","sources":["../../src/proxy/rehydra-fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Rehydra Proxy Middleware
3
+ * Generic (Request → Response) proxy that anonymizes LLM requests
4
+ * and rehydrates responses. Uses standard Web APIs — no framework dependency.
5
+ */
6
+ import type { RehydraProxyConfig } from "./types.js";
7
+ /**
8
+ * Creates a proxy middleware function that forwards requests to an upstream
9
+ * LLM API, anonymizing requests and rehydrating responses.
10
+ *
11
+ * Returns a standard `(Request) => Promise<Response>` function that works
12
+ * with any framework supporting Web Request/Response APIs.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Hono
17
+ * import { Hono } from 'hono';
18
+ * const app = new Hono();
19
+ * const proxy = createRehydraProxy({
20
+ * upstream: 'https://api.openai.com',
21
+ * keyProvider: new ConfigKeyProvider(process.env.PII_KEY!),
22
+ * piiStorageProvider: new SQLitePIIStorageProvider('proxy.db'),
23
+ * });
24
+ * app.post('/v1/*', (c) => proxy(c.req.raw));
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```typescript
29
+ * // Bun.serve
30
+ * const proxy = createRehydraProxy({ upstream: 'https://api.openai.com', ... });
31
+ * Bun.serve({
32
+ * fetch(req) {
33
+ * if (new URL(req.url).pathname.startsWith('/v1/')) return proxy(req);
34
+ * return new Response('Not Found', { status: 404 });
35
+ * },
36
+ * });
37
+ * ```
38
+ */
39
+ export declare function createRehydraProxy(config: RehydraProxyConfig): (request: Request) => Promise<Response>;
40
+ //# sourceMappingURL=rehydra-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydra-proxy.d.ts","sourceRoot":"","sources":["../../src/proxy/rehydra-proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAWrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,kBAAkB,GACzB,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAwCzC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Rehydra Proxy Middleware
3
+ * Generic (Request → Response) proxy that anonymizes LLM requests
4
+ * and rehydrates responses. Uses standard Web APIs — no framework dependency.
5
+ */
6
+ import { createRehydraFetch } from "./rehydra-fetch.js";
7
+ const DEFAULT_FORWARD_HEADERS = [
8
+ "authorization",
9
+ "content-type",
10
+ "x-api-key",
11
+ "anthropic-version",
12
+ "openai-organization",
13
+ "openai-project",
14
+ ];
15
+ /**
16
+ * Creates a proxy middleware function that forwards requests to an upstream
17
+ * LLM API, anonymizing requests and rehydrating responses.
18
+ *
19
+ * Returns a standard `(Request) => Promise<Response>` function that works
20
+ * with any framework supporting Web Request/Response APIs.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Hono
25
+ * import { Hono } from 'hono';
26
+ * const app = new Hono();
27
+ * const proxy = createRehydraProxy({
28
+ * upstream: 'https://api.openai.com',
29
+ * keyProvider: new ConfigKeyProvider(process.env.PII_KEY!),
30
+ * piiStorageProvider: new SQLitePIIStorageProvider('proxy.db'),
31
+ * });
32
+ * app.post('/v1/*', (c) => proxy(c.req.raw));
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Bun.serve
38
+ * const proxy = createRehydraProxy({ upstream: 'https://api.openai.com', ... });
39
+ * Bun.serve({
40
+ * fetch(req) {
41
+ * if (new URL(req.url).pathname.startsWith('/v1/')) return proxy(req);
42
+ * return new Response('Not Found', { status: 404 });
43
+ * },
44
+ * });
45
+ * ```
46
+ */
47
+ export function createRehydraProxy(config) {
48
+ const forwardHeaders = config.forwardHeaders ?? DEFAULT_FORWARD_HEADERS;
49
+ const upstream = config.upstream.replace(/\/$/, ""); // Remove trailing slash
50
+ // Create the underlying Rehydra fetch wrapper
51
+ const rehydraFetch = createRehydraFetch(config);
52
+ return async (request) => {
53
+ // Build upstream URL
54
+ const requestUrl = new URL(request.url);
55
+ let pathname = requestUrl.pathname;
56
+ // Strip prefix if configured
57
+ if (config.stripPrefix !== undefined && config.stripPrefix !== "" && pathname.startsWith(config.stripPrefix)) {
58
+ pathname = pathname.slice(config.stripPrefix.length);
59
+ if (!pathname.startsWith("/")) {
60
+ pathname = "/" + pathname;
61
+ }
62
+ }
63
+ const upstreamUrl = upstream + pathname + requestUrl.search;
64
+ // Filter headers to forward
65
+ const headers = new Headers();
66
+ for (const name of forwardHeaders) {
67
+ const value = request.headers.get(name);
68
+ if (value !== null) {
69
+ headers.set(name, value);
70
+ }
71
+ }
72
+ // Forward via rehydraFetch (which handles anonymization/rehydration)
73
+ return rehydraFetch(upstreamUrl, {
74
+ method: request.method,
75
+ headers,
76
+ body: request.body,
77
+ // @ts-expect-error - duplex is needed for streaming request bodies
78
+ duplex: "half",
79
+ });
80
+ };
81
+ }
82
+ //# sourceMappingURL=rehydra-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rehydra-proxy.js","sourceRoot":"","sources":["../../src/proxy/rehydra-proxy.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,MAAM,uBAAuB,GAAG;IAC9B,eAAe;IACf,cAAc;IACd,WAAW;IACX,mBAAmB;IACnB,qBAAqB;IACrB,gBAAgB;CACjB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA0B;IAE1B,MAAM,cAAc,GAAG,MAAM,CAAC,cAAc,IAAI,uBAAuB,CAAC;IACxE,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,wBAAwB;IAE7E,8CAA8C;IAC9C,MAAM,YAAY,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAEhD,OAAO,KAAK,EAAE,OAAgB,EAAqB,EAAE;QACnD,qBAAqB;QACrB,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC;QAEnC,6BAA6B;QAC7B,IAAI,MAAM,CAAC,WAAW,KAAK,SAAS,IAAI,MAAM,CAAC,WAAW,KAAK,EAAE,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;YAC7G,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;YACrD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,QAAQ,GAAG,GAAG,GAAG,QAAQ,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC;QAE5D,4BAA4B;QAC5B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,OAAO,YAAY,CAAC,WAAW,EAAE;YAC/B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO;YACP,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,mEAAmE;YACnE,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}