agenticreplay 0.1.11 → 0.1.12
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/node_modules/@agenticreplay/adapters/package.json +6 -6
- package/node_modules/@agenticreplay/core/package.json +3 -3
- package/node_modules/@agenticreplay/fs-capture/package.json +2 -2
- package/node_modules/@agenticreplay/mcp-shim/dist/cli.js +0 -0
- package/node_modules/@agenticreplay/mcp-shim/package.json +1 -1
- package/node_modules/@agenticreplay/node-instrument/package.json +1 -1
- package/node_modules/@agenticreplay/plugin-api/package.json +1 -1
- package/node_modules/@agenticreplay/providers/package.json +3 -3
- package/node_modules/@agenticreplay/proxy/dist/request-body.d.ts +9 -0
- package/node_modules/@agenticreplay/proxy/dist/request-body.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/request-body.js +65 -0
- package/node_modules/@agenticreplay/proxy/dist/request-body.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/server.d.ts +3 -0
- package/node_modules/@agenticreplay/proxy/dist/server.d.ts.map +1 -1
- package/node_modules/@agenticreplay/proxy/dist/server.js +121 -124
- package/node_modules/@agenticreplay/proxy/dist/server.js.map +1 -1
- package/node_modules/@agenticreplay/proxy/package.json +5 -5
- package/node_modules/@agenticreplay/schema/package.json +1 -1
- package/node_modules/@agenticreplay/shell-shim/package.json +3 -3
- package/node_modules/@agenticreplay/viewer/package.json +2 -2
- package/node_modules/fast-uri/.github/workflows/release.yml +35 -0
- package/node_modules/fast-uri/README.md +8 -0
- package/node_modules/fast-uri/index.js +98 -18
- package/node_modules/fast-uri/lib/utils.js +52 -0
- package/node_modules/fast-uri/package.json +1 -1
- package/node_modules/fast-uri/test/host-delimiter-security.test.js +94 -0
- package/node_modules/fast-uri/test/relative-normalization-security.test.js +156 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +32 -4
- package/node_modules/fast-uri/test/scheme-validation.test.js +53 -14
- package/package.json +12 -12
- package/packages/cli/dist/cli.js +0 -0
- package/packages/cli/dist/commands/attach.js +1 -0
- package/packages/cli/dist/commands/attach.js.map +1 -1
- package/packages/cli/dist/commands/record.d.ts.map +1 -1
- package/packages/cli/dist/commands/record.js +7 -0
- package/packages/cli/dist/commands/record.js.map +1 -1
- package/packages/cli/dist/exchange-events.d.ts +2 -0
- package/packages/cli/dist/exchange-events.d.ts.map +1 -1
- package/packages/cli/dist/exchange-events.js +36 -24
- package/packages/cli/dist/exchange-events.js.map +1 -1
- package/packages/cli/dist/main.js +1 -0
- package/packages/cli/dist/main.js.map +1 -1
- package/packages/cli/dist/out.d.ts +6 -0
- package/packages/cli/dist/out.d.ts.map +1 -1
- package/packages/cli/dist/out.js +50 -5
- package/packages/cli/dist/out.js.map +1 -1
- package/packages/cli/dist/tls-capture.d.ts.map +1 -1
- package/packages/cli/dist/tls-capture.js +4 -3
- package/packages/cli/dist/tls-capture.js.map +1 -1
- package/packages/cli/package.json +11 -11
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticreplay/adapters",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,11 +17,11 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agenticreplay/core": "0.1.
|
|
21
|
-
"@agenticreplay/node-instrument": "0.1.
|
|
22
|
-
"@agenticreplay/plugin-api": "0.1.
|
|
23
|
-
"@agenticreplay/proxy": "0.1.
|
|
24
|
-
"@agenticreplay/shell-shim": "0.1.
|
|
20
|
+
"@agenticreplay/core": "0.1.12",
|
|
21
|
+
"@agenticreplay/node-instrument": "0.1.12",
|
|
22
|
+
"@agenticreplay/plugin-api": "0.1.12",
|
|
23
|
+
"@agenticreplay/proxy": "0.1.12",
|
|
24
|
+
"@agenticreplay/shell-shim": "0.1.12"
|
|
25
25
|
},
|
|
26
26
|
"engines": {
|
|
27
27
|
"node": ">=24.0.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticreplay/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agenticreplay/plugin-api": "0.1.
|
|
21
|
-
"@agenticreplay/schema": "0.1.
|
|
20
|
+
"@agenticreplay/plugin-api": "0.1.12",
|
|
21
|
+
"@agenticreplay/schema": "0.1.12",
|
|
22
22
|
"any-ascii": "0.3.3"
|
|
23
23
|
},
|
|
24
24
|
"engines": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticreplay/fs-capture",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agenticreplay/schema": "0.1.
|
|
20
|
+
"@agenticreplay/schema": "0.1.12"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=24.0.0"
|
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agenticreplay/providers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@agenticreplay/plugin-api": "0.1.
|
|
21
|
-
"@agenticreplay/schema": "0.1.
|
|
20
|
+
"@agenticreplay/plugin-api": "0.1.12",
|
|
21
|
+
"@agenticreplay/schema": "0.1.12"
|
|
22
22
|
},
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=24.0.0"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
/** A capture limit limits retention, never the request sent to the provider. */
|
|
3
|
+
export declare function readRequestBody(req: IncomingMessage, limit: number, reserve: (bytes: number) => boolean, release: (bytes: number) => void): Promise<{
|
|
4
|
+
buffer?: Buffer;
|
|
5
|
+
stream?: ReadableStream<Uint8Array>;
|
|
6
|
+
bytes: () => number;
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
}>;
|
|
9
|
+
//# sourceMappingURL=request-body.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-body.d.ts","sourceRoot":"","sources":["../src/request-body.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGjD,gFAAgF;AAChF,wBAAsB,eAAe,CACnC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,EACnC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC;IACT,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,cAAc,CAAC,UAAU,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC,CA4DD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
/** A capture limit limits retention, never the request sent to the provider. */
|
|
3
|
+
export async function readRequestBody(req, limit, reserve, release) {
|
|
4
|
+
const chunks = [];
|
|
5
|
+
const iterator = req[Symbol.asyncIterator]();
|
|
6
|
+
let bytes = 0;
|
|
7
|
+
let reserved = 0;
|
|
8
|
+
const dispose = () => {
|
|
9
|
+
release(reserved);
|
|
10
|
+
reserved = 0;
|
|
11
|
+
chunks.length = 0;
|
|
12
|
+
};
|
|
13
|
+
try {
|
|
14
|
+
while (true) {
|
|
15
|
+
const next = await iterator.next();
|
|
16
|
+
if (next.done)
|
|
17
|
+
return { buffer: Buffer.concat(chunks), bytes: () => bytes, dispose };
|
|
18
|
+
const chunk = next.value;
|
|
19
|
+
bytes += chunk.length;
|
|
20
|
+
if (bytes <= limit && reserve(chunk.length)) {
|
|
21
|
+
reserved += chunk.length;
|
|
22
|
+
chunks.push(chunk);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
// Do not exit a for-await loop here: its implicit return() destroys IncomingMessage.
|
|
26
|
+
// Hand the same iterator to fetch and stream the remainder with its backpressure.
|
|
27
|
+
async function* forward() {
|
|
28
|
+
try {
|
|
29
|
+
while (chunks.length > 0) {
|
|
30
|
+
const prefix = chunks.shift();
|
|
31
|
+
yield prefix;
|
|
32
|
+
const held = Math.min(reserved, prefix.length);
|
|
33
|
+
reserved -= held;
|
|
34
|
+
release(held);
|
|
35
|
+
}
|
|
36
|
+
yield chunk;
|
|
37
|
+
while (true) {
|
|
38
|
+
const part = await iterator.next();
|
|
39
|
+
if (part.done)
|
|
40
|
+
return;
|
|
41
|
+
const tail = part.value;
|
|
42
|
+
bytes += tail.length;
|
|
43
|
+
yield tail;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
dispose();
|
|
48
|
+
await iterator.return?.();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
stream: Readable.toWeb(Readable.from(forward(), { objectMode: false, highWaterMark: 65536 }), {
|
|
53
|
+
strategy: { highWaterMark: 65536, size: (chunk) => chunk.byteLength },
|
|
54
|
+
}),
|
|
55
|
+
bytes: () => bytes,
|
|
56
|
+
dispose,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
dispose();
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=request-body.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-body.js","sourceRoot":"","sources":["../src/request-body.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAoB,EACpB,KAAa,EACb,OAAmC,EACnC,OAAgC;IAOhC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;IAC7C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,OAAO,CAAC,QAAQ,CAAC,CAAC;QAClB,QAAQ,GAAG,CAAC,CAAC;QACb,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IACpB,CAAC,CAAC;IACF,IAAI,CAAC;QACH,OAAO,IAAI,EAAE,CAAC;YACZ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,IAAI;gBAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;YACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAe,CAAC;YACnC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;YACtB,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC5C,QAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;gBACzB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,SAAS;YACX,CAAC;YACD,qFAAqF;YACrF,kFAAkF;YAClF,KAAK,SAAS,CAAC,CAAC,OAAO;gBACrB,IAAI,CAAC;oBACH,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACzB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,EAAG,CAAC;wBAC/B,MAAM,MAAM,CAAC;wBACb,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;wBAC/C,QAAQ,IAAI,IAAI,CAAC;wBACjB,OAAO,CAAC,IAAI,CAAC,CAAC;oBAChB,CAAC;oBACD,MAAM,KAAK,CAAC;oBACZ,OAAO,IAAI,EAAE,CAAC;wBACZ,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;wBACnC,IAAI,IAAI,CAAC,IAAI;4BAAE,OAAO;wBACtB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAe,CAAC;wBAClC,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC;wBACrB,MAAM,IAAI,CAAC;oBACb,CAAC;gBACH,CAAC;wBAAS,CAAC;oBACT,OAAO,EAAE,CAAC;oBACV,MAAM,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC5B,CAAC;YACH,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,QAAQ,CAAC,KAAK,CACpB,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,EACrE;oBACE,QAAQ,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,KAAiB,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,EAAE;iBAClF,CAC4B;gBAC/B,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK;gBAClB,OAAO;aACR,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;QACV,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -140,6 +140,8 @@ export interface ProxyOptions {
|
|
|
140
140
|
upstream?: Record<string, string>;
|
|
141
141
|
dialects?: Dialect[];
|
|
142
142
|
onExchange?: (e: RecordedExchange) => void;
|
|
143
|
+
/** Keep completed bodies for exchanges(); default true. Disk recorders should disable this. */
|
|
144
|
+
retainExchanges?: boolean;
|
|
143
145
|
onRequestStart?: (request: NetRequest) => void | Promise<void>;
|
|
144
146
|
maxCapturedBytes?: number;
|
|
145
147
|
maxBufferedBytes?: number;
|
|
@@ -171,6 +173,7 @@ export interface ProxyHandle {
|
|
|
171
173
|
url: string;
|
|
172
174
|
port: number;
|
|
173
175
|
stats(): ProxyStats;
|
|
176
|
+
/** Empty when retainExchanges is false; onExchange still receives every completed exchange. */
|
|
174
177
|
exchanges(): RecordedExchange[];
|
|
175
178
|
/** Present only when TLS interception is on. */
|
|
176
179
|
tls?: TlsInterceptInfo;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { createServer } from 'node:http';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { readRequestBody } from './request-body.js';
|
|
3
5
|
import { AUTH_REQUEST_HEADERS, AUTH_RESPONSE_HEADERS, BINARY_BODY_PREFIX, Redactor, fetchPinned, } from '@agenticreplay/core';
|
|
4
6
|
import { anthropicToCanonicalRequest, anthropicToCanonicalResponse, openaiToCanonicalRequest, openaiToCanonicalResponse, parseAnthropicSse, parseOpenaiSse, parseResponsesSse, responsesToCanonicalRequest, responsesToCanonicalResponse, } from '@agenticreplay/providers';
|
|
5
7
|
import { anthropicDialect, codexDialect, openaiDialect, responsesDialect, selectDialect, } from './dialects.js';
|
|
@@ -315,29 +317,6 @@ export function defaultDialects() {
|
|
|
315
317
|
}),
|
|
316
318
|
];
|
|
317
319
|
}
|
|
318
|
-
class BodyLimitError extends Error {
|
|
319
|
-
bytes;
|
|
320
|
-
limit;
|
|
321
|
-
constructor(bytes, limit) {
|
|
322
|
-
super(`request body exceeded capture limit of ${limit} bytes`);
|
|
323
|
-
this.bytes = bytes;
|
|
324
|
-
this.limit = limit;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
async function readBody(req, limit) {
|
|
328
|
-
const chunks = [];
|
|
329
|
-
let bytes = 0;
|
|
330
|
-
for await (const chunk of req) {
|
|
331
|
-
const buffer = chunk;
|
|
332
|
-
bytes += buffer.length;
|
|
333
|
-
if (bytes > limit) {
|
|
334
|
-
req.resume();
|
|
335
|
-
throw new BodyLimitError(bytes, limit);
|
|
336
|
-
}
|
|
337
|
-
chunks.push(buffer);
|
|
338
|
-
}
|
|
339
|
-
return { body: Buffer.concat(chunks), bytes };
|
|
340
|
-
}
|
|
341
320
|
/** Is this something a dialect could parse at all? Cheap guard, run before anything is forwarded. */
|
|
342
321
|
function isReadable(rawBody) {
|
|
343
322
|
try {
|
|
@@ -378,6 +357,13 @@ export async function createProxy(options) {
|
|
|
378
357
|
// `fetchImpl` stays the seam the tests inject through; only the default moves.
|
|
379
358
|
const doFetch = options.fetchImpl ?? fetchPinned;
|
|
380
359
|
const captured = [];
|
|
360
|
+
let exchangeSequence = 0;
|
|
361
|
+
function deliverExchange(exchange) {
|
|
362
|
+
exchangeSequence += 1;
|
|
363
|
+
if (options.retainExchanges !== false)
|
|
364
|
+
captured.push(exchange);
|
|
365
|
+
options.onExchange?.(exchange);
|
|
366
|
+
}
|
|
381
367
|
// Plain base-URL traffic needs the same aggregate bound as intercepted traffic. This counter
|
|
382
368
|
// covers bodies retained by concurrent fetch responses while each response still streams to the
|
|
383
369
|
// caller; it is released only after that response has been materialized into evidence.
|
|
@@ -438,7 +424,7 @@ export async function createProxy(options) {
|
|
|
438
424
|
status: exchange.status,
|
|
439
425
|
streamed,
|
|
440
426
|
headers: scalarHeaders(exchange.requestHeaders),
|
|
441
|
-
seq:
|
|
427
|
+
seq: exchangeSequence,
|
|
442
428
|
requestId: exchange.requestId,
|
|
443
429
|
requestStartedAt: exchange.requestStartedAt,
|
|
444
430
|
durationMs: exchange.durationMs,
|
|
@@ -452,8 +438,7 @@ export async function createProxy(options) {
|
|
|
452
438
|
// worth having in the trace. The port is kept only where https does not imply it.
|
|
453
439
|
upstream: `https://${exchange.host}${exchange.port === 443 ? '' : `:${exchange.port}`}`,
|
|
454
440
|
});
|
|
455
|
-
|
|
456
|
-
options.onExchange?.(built);
|
|
441
|
+
deliverExchange(built);
|
|
457
442
|
// Keep the authoritative wire record even when the dialect projection succeeds. The
|
|
458
443
|
// model event is a convenient replay view; the net event retains headers, framing and
|
|
459
444
|
// transport completion details needed to audit capture fidelity.
|
|
@@ -691,7 +676,7 @@ export async function createProxy(options) {
|
|
|
691
676
|
status,
|
|
692
677
|
streamed: isStreamed,
|
|
693
678
|
headers: scalarHeaders(recordableHeaders),
|
|
694
|
-
seq:
|
|
679
|
+
seq: exchangeSequence,
|
|
695
680
|
requestId,
|
|
696
681
|
requestStartedAt: startedAt,
|
|
697
682
|
durationMs: Date.now() - startedAt,
|
|
@@ -699,8 +684,7 @@ export async function createProxy(options) {
|
|
|
699
684
|
// client announced, and a fork's substitution can send it somewhere else again.
|
|
700
685
|
upstream: origin,
|
|
701
686
|
});
|
|
702
|
-
|
|
703
|
-
options.onExchange?.(exchange);
|
|
687
|
+
deliverExchange(exchange);
|
|
704
688
|
}
|
|
705
689
|
}
|
|
706
690
|
/**
|
|
@@ -779,12 +763,12 @@ export async function createProxy(options) {
|
|
|
779
763
|
const scrubbed = String(err).split(origin).join(scrubOrigin(origin));
|
|
780
764
|
return redactor.redactString(withoutCredentials(scrubbed)).value;
|
|
781
765
|
}
|
|
782
|
-
async function passThrough(path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase, requestEncoding) {
|
|
766
|
+
async function passThrough(path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase, requestEncoding, uncaptured) {
|
|
783
767
|
stats.passedThrough += 1;
|
|
784
768
|
const origin = passthroughOrigin(headers, forwardBase);
|
|
785
769
|
let upstreamRes;
|
|
786
770
|
try {
|
|
787
|
-
upstreamRes = await doFetch(`${origin}${path}`, {
|
|
771
|
+
upstreamRes = await doFetch(`${origin}${uncaptured?.upstreamPath ?? path}`, {
|
|
788
772
|
method: 'POST',
|
|
789
773
|
// `upstreamHeaders` too, as `goLive` does. Omitting them sent a gateway the agent's own
|
|
790
774
|
// credential — often the `agenticreplay-recorded` placeholder — and the call came back 401 for a
|
|
@@ -794,7 +778,8 @@ export async function createProxy(options) {
|
|
|
794
778
|
...headers,
|
|
795
779
|
...headersForOrigin(origin),
|
|
796
780
|
},
|
|
797
|
-
body: rawBody,
|
|
781
|
+
body: uncaptured?.body ?? rawBody,
|
|
782
|
+
...(uncaptured ? { duplex: 'half' } : {}),
|
|
798
783
|
});
|
|
799
784
|
}
|
|
800
785
|
catch (err) {
|
|
@@ -821,8 +806,8 @@ export async function createProxy(options) {
|
|
|
821
806
|
path,
|
|
822
807
|
requestHeaders: recordableHeaders,
|
|
823
808
|
requestBody: rawBody,
|
|
824
|
-
requestBytes: Buffer.byteLength(rawBody),
|
|
825
|
-
requestTruncated:
|
|
809
|
+
requestBytes: uncaptured?.bytes() ?? Buffer.byteLength(rawBody),
|
|
810
|
+
requestTruncated: uncaptured !== undefined,
|
|
826
811
|
status: 0,
|
|
827
812
|
responseHeaders: {},
|
|
828
813
|
responseBody: `agenticreplay did not forward this call: ${reasonFor(err, origin)}`,
|
|
@@ -856,8 +841,8 @@ export async function createProxy(options) {
|
|
|
856
841
|
intercepted: false,
|
|
857
842
|
requestHeaders: recordableHeaders,
|
|
858
843
|
requestBody: rawBody,
|
|
859
|
-
requestBytes: Buffer.byteLength(rawBody),
|
|
860
|
-
requestTruncated:
|
|
844
|
+
requestBytes: uncaptured?.bytes() ?? Buffer.byteLength(rawBody),
|
|
845
|
+
requestTruncated: uncaptured !== undefined,
|
|
861
846
|
status: upstreamRes.status,
|
|
862
847
|
responseHeaders,
|
|
863
848
|
responseBody: partial?.text ?? '',
|
|
@@ -898,8 +883,8 @@ export async function createProxy(options) {
|
|
|
898
883
|
// must not now be written down. §7 says never write it, which is not the same as never relay it.
|
|
899
884
|
requestHeaders: recordableHeaders,
|
|
900
885
|
requestBody: rawBody,
|
|
901
|
-
requestBytes: Buffer.byteLength(rawBody),
|
|
902
|
-
requestTruncated:
|
|
886
|
+
requestBytes: uncaptured?.bytes() ?? Buffer.byteLength(rawBody),
|
|
887
|
+
requestTruncated: uncaptured !== undefined,
|
|
903
888
|
status: upstreamRes.status,
|
|
904
889
|
responseHeaders,
|
|
905
890
|
responseBody: body,
|
|
@@ -914,7 +899,17 @@ export async function createProxy(options) {
|
|
|
914
899
|
durationMs: Date.now() - startedAt,
|
|
915
900
|
transformations: [
|
|
916
901
|
...captureHeaderTransformations(headers, recordableHeaders),
|
|
917
|
-
...
|
|
902
|
+
...(uncaptured
|
|
903
|
+
? [
|
|
904
|
+
{
|
|
905
|
+
kind: 'omission',
|
|
906
|
+
location: 'request.body',
|
|
907
|
+
rule: 'capture_buffer_limit',
|
|
908
|
+
count: 1,
|
|
909
|
+
reason: 'request forwarded without retaining its body',
|
|
910
|
+
},
|
|
911
|
+
]
|
|
912
|
+
: captureEncodingTransformation('request.body', requestEncoding)),
|
|
918
913
|
...captureResponseHeaderTransformations(upstreamRes.headers),
|
|
919
914
|
...captureEncodingTransformation('response.body', responseEncoding),
|
|
920
915
|
...captureUrlTransformations(origin),
|
|
@@ -1275,99 +1270,100 @@ export async function createProxy(options) {
|
|
|
1275
1270
|
json(res, 501, { error: { message } });
|
|
1276
1271
|
return;
|
|
1277
1272
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
requestReservedBytes = Buffer.byteLength(rawBody);
|
|
1288
|
-
if (plainBufferedBytes + requestReservedBytes > plainBufferLimit) {
|
|
1289
|
-
throw new BodyLimitError(requestReservedBytes, plainBufferLimit);
|
|
1290
|
-
}
|
|
1291
|
-
plainBufferedBytes += requestReservedBytes;
|
|
1273
|
+
const headers = {};
|
|
1274
|
+
const recordableHeaders = {};
|
|
1275
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
1276
|
+
const key = k.toLowerCase();
|
|
1277
|
+
if (HOP_BY_HOP_HEADERS.has(key))
|
|
1278
|
+
continue;
|
|
1279
|
+
if (v === undefined)
|
|
1280
|
+
continue;
|
|
1281
|
+
headers[k] = Array.isArray(v) ? v.join(', ') : v;
|
|
1292
1282
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
requestHeaders: {},
|
|
1304
|
-
requestBody: '',
|
|
1305
|
-
requestBytes: 0,
|
|
1306
|
-
requestTruncated: true,
|
|
1307
|
-
status: 0,
|
|
1308
|
-
responseHeaders: {},
|
|
1309
|
-
responseBody: '',
|
|
1310
|
-
responseTruncated: false,
|
|
1311
|
-
responseComplete: false,
|
|
1312
|
-
responseError: String(err),
|
|
1313
|
-
responseBytes: 0,
|
|
1314
|
-
durationMs: Date.now() - startedAt,
|
|
1315
|
-
});
|
|
1316
|
-
json(res, 400, {
|
|
1317
|
-
error: { message: `agenticreplay could not decode request body: ${String(err)}` },
|
|
1318
|
-
});
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
options.onNetExchange?.({
|
|
1322
|
-
requestId,
|
|
1323
|
-
requestStartedAt: startedAt,
|
|
1324
|
-
host: req.headers.host ?? 'local',
|
|
1325
|
-
port: 0,
|
|
1326
|
-
intercepted: false,
|
|
1327
|
-
method: req.method ?? 'POST',
|
|
1328
|
-
path: requestedPath,
|
|
1329
|
-
requestHeaders: {},
|
|
1330
|
-
requestBody: '',
|
|
1331
|
-
requestBytes: err.bytes,
|
|
1332
|
-
requestTruncated: true,
|
|
1333
|
-
status: 0,
|
|
1334
|
-
responseHeaders: {},
|
|
1335
|
-
responseBody: '',
|
|
1336
|
-
responseTruncated: false,
|
|
1337
|
-
responseComplete: false,
|
|
1338
|
-
responseError: String(err),
|
|
1339
|
-
responseBytes: 0,
|
|
1340
|
-
durationMs: Date.now() - startedAt,
|
|
1341
|
-
});
|
|
1342
|
-
json(res, 413, { error: { message: String(err) } });
|
|
1343
|
-
return;
|
|
1283
|
+
// Node's parsed headers combine repeated fields. Keep the raw field order and multiplicity
|
|
1284
|
+
// for persisted evidence while retaining the scalar map above for fetch forwarding.
|
|
1285
|
+
for (let index = 0; index + 1 < req.rawHeaders.length; index += 2) {
|
|
1286
|
+
const key = req.rawHeaders[index].toLowerCase();
|
|
1287
|
+
if (HOP_BY_HOP_HEADERS.has(key) || SECRET_REQUEST_HEADERS.has(key))
|
|
1288
|
+
continue;
|
|
1289
|
+
const value = req.rawHeaders[index + 1];
|
|
1290
|
+
const prior = recordableHeaders[key];
|
|
1291
|
+
recordableHeaders[key] =
|
|
1292
|
+
prior === undefined ? value : Array.isArray(prior) ? [...prior, value] : [prior, value];
|
|
1344
1293
|
}
|
|
1345
1294
|
const inboundRequestHeader = req.headers['content-encoding'];
|
|
1346
1295
|
const inboundRequestEncoding = Array.isArray(inboundRequestHeader)
|
|
1347
1296
|
? inboundRequestHeader.join(', ')
|
|
1348
1297
|
: inboundRequestHeader;
|
|
1298
|
+
let read;
|
|
1299
|
+
let decodedReservation = 0;
|
|
1300
|
+
const reserve = (bytes) => {
|
|
1301
|
+
if (plainBufferedBytes + bytes > plainBufferLimit)
|
|
1302
|
+
return false;
|
|
1303
|
+
plainBufferedBytes += bytes;
|
|
1304
|
+
return true;
|
|
1305
|
+
};
|
|
1306
|
+
const release = (bytes) => {
|
|
1307
|
+
plainBufferedBytes = Math.max(0, plainBufferedBytes - bytes);
|
|
1308
|
+
};
|
|
1349
1309
|
try {
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1310
|
+
let rawBody;
|
|
1311
|
+
try {
|
|
1312
|
+
read = await readRequestBody(req, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES, reserve, release);
|
|
1313
|
+
if (read.buffer) {
|
|
1314
|
+
rawBody = decodeBody(read.buffer, inboundRequestEncoding, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES);
|
|
1315
|
+
const extra = Math.max(0, Buffer.byteLength(rawBody) - read.buffer.length);
|
|
1316
|
+
if (reserve(extra))
|
|
1317
|
+
decodedReservation = extra;
|
|
1318
|
+
else
|
|
1319
|
+
rawBody = undefined;
|
|
1320
|
+
}
|
|
1359
1321
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1322
|
+
catch (error) {
|
|
1323
|
+
// A compressed conversation can be small on the wire but too large to capture decoded.
|
|
1324
|
+
// Relay its original encoding instead of returning the recorder's size error to the agent.
|
|
1325
|
+
if (!read?.buffer || error.code !== 'ERR_BUFFER_TOO_LARGE') {
|
|
1326
|
+
options.onNetExchange?.({
|
|
1327
|
+
...startRequest,
|
|
1328
|
+
requestBytes: read?.bytes() ?? 0,
|
|
1329
|
+
requestBody: '',
|
|
1330
|
+
requestTruncated: true,
|
|
1331
|
+
status: 0,
|
|
1332
|
+
responseHeaders: {},
|
|
1333
|
+
responseBody: '',
|
|
1334
|
+
responseBytes: 0,
|
|
1335
|
+
responseTruncated: false,
|
|
1336
|
+
responseComplete: false,
|
|
1337
|
+
responseError: String(error),
|
|
1338
|
+
intercepted: false,
|
|
1339
|
+
durationMs: Date.now() - startedAt,
|
|
1340
|
+
});
|
|
1341
|
+
json(res, 400, {
|
|
1342
|
+
error: { message: `agenticreplay could not decode request body: ${String(error)}` },
|
|
1343
|
+
});
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
if (rawBody === undefined) {
|
|
1348
|
+
const queryAt = requestedPath.indexOf('?');
|
|
1349
|
+
const query = queryAt === -1 ? '' : requestedPath.slice(queryAt);
|
|
1350
|
+
await passThrough(requestedPath, '',
|
|
1351
|
+
// This path forwards the original wire bytes, including compression. Preserve its
|
|
1352
|
+
// known length; providers that require Content-Length need not accept chunked uploads.
|
|
1353
|
+
{
|
|
1354
|
+
...headers,
|
|
1355
|
+
...(req.headers['content-length'] === undefined
|
|
1356
|
+
? {}
|
|
1357
|
+
: { 'content-length': req.headers['content-length'] }),
|
|
1358
|
+
}, recordableHeaders, res, startedAt, requestId, startOrigin, undefined, {
|
|
1359
|
+
body: read.stream ??
|
|
1360
|
+
Readable.toWeb(Readable.from([read.buffer])),
|
|
1361
|
+
bytes: read.bytes,
|
|
1362
|
+
upstreamPath: dialect && !forward ? `${dialect.requestPath}${query}` : requestedPath,
|
|
1363
|
+
});
|
|
1364
|
+
return;
|
|
1370
1365
|
}
|
|
1366
|
+
const requestBytes = read.bytes();
|
|
1371
1367
|
// `decodeBody` above gives the origin the serialized JSON rather than the compressed wire
|
|
1372
1368
|
// bytes. Remove the stale framing headers or the origin will try to decompress plaintext.
|
|
1373
1369
|
if (headers['content-encoding'] !== undefined) {
|
|
@@ -1419,7 +1415,8 @@ export async function createProxy(options) {
|
|
|
1419
1415
|
await goLive(dialect, requestedPath, rawBody, headers, recordableHeaders, res, startedAt, requestId, inboundRequestEncoding, forward?.base);
|
|
1420
1416
|
}
|
|
1421
1417
|
finally {
|
|
1422
|
-
|
|
1418
|
+
read?.dispose();
|
|
1419
|
+
release(decodedReservation);
|
|
1423
1420
|
}
|
|
1424
1421
|
}
|
|
1425
1422
|
const host = options.host ?? '127.0.0.1';
|