agenticreplay 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -13
- package/bin/agenticreplay.js +2 -0
- package/node_modules/@agenticreplay/adapters/dist/claude-code.d.ts +37 -0
- package/node_modules/@agenticreplay/adapters/dist/claude-code.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/claude-code.js +103 -0
- package/node_modules/@agenticreplay/adapters/dist/claude-code.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/codex.d.ts +36 -0
- package/node_modules/@agenticreplay/adapters/dist/codex.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/codex.js +130 -0
- package/node_modules/@agenticreplay/adapters/dist/codex.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/contract.d.ts +65 -0
- package/node_modules/@agenticreplay/adapters/dist/contract.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/contract.js +460 -0
- package/node_modules/@agenticreplay/adapters/dist/contract.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/cursor.d.ts +40 -0
- package/node_modules/@agenticreplay/adapters/dist/cursor.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/cursor.js +71 -0
- package/node_modules/@agenticreplay/adapters/dist/cursor.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/detect.d.ts +47 -0
- package/node_modules/@agenticreplay/adapters/dist/detect.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/detect.js +123 -0
- package/node_modules/@agenticreplay/adapters/dist/detect.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/env.d.ts +60 -0
- package/node_modules/@agenticreplay/adapters/dist/env.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/env.js +99 -0
- package/node_modules/@agenticreplay/adapters/dist/env.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/exec.d.ts +25 -0
- package/node_modules/@agenticreplay/adapters/dist/exec.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/exec.js +51 -0
- package/node_modules/@agenticreplay/adapters/dist/exec.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/generic-openai.d.ts +8 -0
- package/node_modules/@agenticreplay/adapters/dist/generic-openai.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/generic-openai.js +31 -0
- package/node_modules/@agenticreplay/adapters/dist/generic-openai.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/goose.d.ts +39 -0
- package/node_modules/@agenticreplay/adapters/dist/goose.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/goose.js +81 -0
- package/node_modules/@agenticreplay/adapters/dist/goose.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/grok.d.ts +23 -0
- package/node_modules/@agenticreplay/adapters/dist/grok.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/grok.js +50 -0
- package/node_modules/@agenticreplay/adapters/dist/grok.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/hermes.d.ts +70 -0
- package/node_modules/@agenticreplay/adapters/dist/hermes.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/hermes.js +88 -0
- package/node_modules/@agenticreplay/adapters/dist/hermes.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/index.d.ts +25 -0
- package/node_modules/@agenticreplay/adapters/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/index.js +25 -0
- package/node_modules/@agenticreplay/adapters/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/indexrag.d.ts +27 -0
- package/node_modules/@agenticreplay/adapters/dist/indexrag.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/indexrag.js +236 -0
- package/node_modules/@agenticreplay/adapters/dist/indexrag.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/kilo.d.ts +21 -0
- package/node_modules/@agenticreplay/adapters/dist/kilo.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/kilo.js +35 -0
- package/node_modules/@agenticreplay/adapters/dist/kilo.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mcode.d.ts +81 -0
- package/node_modules/@agenticreplay/adapters/dist/mcode.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mcode.js +368 -0
- package/node_modules/@agenticreplay/adapters/dist/mcode.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mcp-config.d.ts +17 -0
- package/node_modules/@agenticreplay/adapters/dist/mcp-config.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mcp-config.js +62 -0
- package/node_modules/@agenticreplay/adapters/dist/mcp-config.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mimo.d.ts +26 -0
- package/node_modules/@agenticreplay/adapters/dist/mimo.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/mimo.js +40 -0
- package/node_modules/@agenticreplay/adapters/dist/mimo.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/node.d.ts +19 -0
- package/node_modules/@agenticreplay/adapters/dist/node.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/node.js +59 -0
- package/node_modules/@agenticreplay/adapters/dist/node.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/openclaw.d.ts +19 -0
- package/node_modules/@agenticreplay/adapters/dist/openclaw.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/openclaw.js +43 -0
- package/node_modules/@agenticreplay/adapters/dist/openclaw.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-api-bases.d.ts +2 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-api-bases.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-api-bases.js +201 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-api-bases.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-capture.d.ts +26 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-capture.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-capture.js +132 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-capture.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-catalog.d.ts +5 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-catalog.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-catalog.js +43 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-catalog.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-config.d.ts +40 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-config.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-config.js +185 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode-config.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode.d.ts +10 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode.js +243 -0
- package/node_modules/@agenticreplay/adapters/dist/opencode.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/qwen.d.ts +51 -0
- package/node_modules/@agenticreplay/adapters/dist/qwen.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/qwen.js +105 -0
- package/node_modules/@agenticreplay/adapters/dist/qwen.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/registry.d.ts +19 -0
- package/node_modules/@agenticreplay/adapters/dist/registry.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/registry.js +110 -0
- package/node_modules/@agenticreplay/adapters/dist/registry.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/scaffold.d.ts +26 -0
- package/node_modules/@agenticreplay/adapters/dist/scaffold.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/scaffold.js +125 -0
- package/node_modules/@agenticreplay/adapters/dist/scaffold.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/session.d.ts +47 -0
- package/node_modules/@agenticreplay/adapters/dist/session.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/session.js +112 -0
- package/node_modules/@agenticreplay/adapters/dist/session.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/zcode.d.ts +60 -0
- package/node_modules/@agenticreplay/adapters/dist/zcode.d.ts.map +1 -0
- package/node_modules/@agenticreplay/adapters/dist/zcode.js +424 -0
- package/node_modules/@agenticreplay/adapters/dist/zcode.js.map +1 -0
- package/node_modules/@agenticreplay/adapters/package.json +29 -0
- package/node_modules/@agenticreplay/core/dist/blobs.d.ts +21 -0
- package/node_modules/@agenticreplay/core/dist/blobs.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/blobs.js +102 -0
- package/node_modules/@agenticreplay/core/dist/blobs.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/fetch.d.ts +38 -0
- package/node_modules/@agenticreplay/core/dist/fetch.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/fetch.js +47 -0
- package/node_modules/@agenticreplay/core/dist/fetch.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/graph.d.ts +106 -0
- package/node_modules/@agenticreplay/core/dist/graph.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/graph.js +394 -0
- package/node_modules/@agenticreplay/core/dist/graph.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/index.d.ts +9 -0
- package/node_modules/@agenticreplay/core/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/index.js +9 -0
- package/node_modules/@agenticreplay/core/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/paths.d.ts +45 -0
- package/node_modules/@agenticreplay/core/dist/paths.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/paths.js +149 -0
- package/node_modules/@agenticreplay/core/dist/paths.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/private-files.d.ts +37 -0
- package/node_modules/@agenticreplay/core/dist/private-files.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/private-files.js +353 -0
- package/node_modules/@agenticreplay/core/dist/private-files.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/reader.d.ts +45 -0
- package/node_modules/@agenticreplay/core/dist/reader.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/reader.js +152 -0
- package/node_modules/@agenticreplay/core/dist/reader.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/redaction.d.ts +69 -0
- package/node_modules/@agenticreplay/core/dist/redaction.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/redaction.js +768 -0
- package/node_modules/@agenticreplay/core/dist/redaction.js.map +1 -0
- package/node_modules/@agenticreplay/core/dist/writer.d.ts +75 -0
- package/node_modules/@agenticreplay/core/dist/writer.d.ts.map +1 -0
- package/node_modules/@agenticreplay/core/dist/writer.js +368 -0
- package/node_modules/@agenticreplay/core/dist/writer.js.map +1 -0
- package/node_modules/@agenticreplay/core/package.json +26 -0
- package/node_modules/@agenticreplay/fs-capture/dist/capture.d.ts +84 -0
- package/node_modules/@agenticreplay/fs-capture/dist/capture.d.ts.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/capture.js +147 -0
- package/node_modules/@agenticreplay/fs-capture/dist/capture.js.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/git.d.ts +25 -0
- package/node_modules/@agenticreplay/fs-capture/dist/git.d.ts.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/git.js +108 -0
- package/node_modules/@agenticreplay/fs-capture/dist/git.js.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/index.d.ts +4 -0
- package/node_modules/@agenticreplay/fs-capture/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/index.js +4 -0
- package/node_modules/@agenticreplay/fs-capture/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/shadow.d.ts +271 -0
- package/node_modules/@agenticreplay/fs-capture/dist/shadow.d.ts.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/dist/shadow.js +668 -0
- package/node_modules/@agenticreplay/fs-capture/dist/shadow.js.map +1 -0
- package/node_modules/@agenticreplay/fs-capture/package.json +25 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/cli.d.ts +100 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/cli.d.ts.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/cli.js +334 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/cli.js.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/framing.d.ts +41 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/framing.d.ts.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/framing.js +94 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/framing.js.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/index.d.ts +5 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/index.js +5 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/mock.d.ts +70 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/mock.d.ts.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/mock.js +138 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/mock.js.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/shim.d.ts +24 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/shim.d.ts.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/shim.js +105 -0
- package/node_modules/@agenticreplay/mcp-shim/dist/shim.js.map +1 -0
- package/node_modules/@agenticreplay/mcp-shim/package.json +26 -0
- package/node_modules/@agenticreplay/node-instrument/dist/capture.d.ts +58 -0
- package/node_modules/@agenticreplay/node-instrument/dist/capture.d.ts.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/capture.js +404 -0
- package/node_modules/@agenticreplay/node-instrument/dist/capture.js.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/index.d.ts +4 -0
- package/node_modules/@agenticreplay/node-instrument/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/index.js +4 -0
- package/node_modules/@agenticreplay/node-instrument/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/install.d.ts +47 -0
- package/node_modules/@agenticreplay/node-instrument/dist/install.d.ts.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/install.js +174 -0
- package/node_modules/@agenticreplay/node-instrument/dist/install.js.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/rewrite.d.ts +33 -0
- package/node_modules/@agenticreplay/node-instrument/dist/rewrite.d.ts.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/dist/rewrite.js +61 -0
- package/node_modules/@agenticreplay/node-instrument/dist/rewrite.js.map +1 -0
- package/node_modules/@agenticreplay/node-instrument/package.json +23 -0
- package/node_modules/@agenticreplay/plugin-api/dist/canonical.d.ts +87 -0
- package/node_modules/@agenticreplay/plugin-api/dist/canonical.d.ts.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/dist/canonical.js +10 -0
- package/node_modules/@agenticreplay/plugin-api/dist/canonical.js.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/dist/index.d.ts +3 -0
- package/node_modules/@agenticreplay/plugin-api/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/dist/index.js +3 -0
- package/node_modules/@agenticreplay/plugin-api/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/dist/plugins.d.ts +258 -0
- package/node_modules/@agenticreplay/plugin-api/dist/plugins.d.ts.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/dist/plugins.js +2 -0
- package/node_modules/@agenticreplay/plugin-api/dist/plugins.js.map +1 -0
- package/node_modules/@agenticreplay/plugin-api/package.json +22 -0
- package/node_modules/@agenticreplay/providers/dist/index.d.ts +34 -0
- package/node_modules/@agenticreplay/providers/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/index.js +34 -0
- package/node_modules/@agenticreplay/providers/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/pricing.d.ts +44 -0
- package/node_modules/@agenticreplay/providers/dist/pricing.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/pricing.js +126 -0
- package/node_modules/@agenticreplay/providers/dist/pricing.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/anthropic.d.ts +66 -0
- package/node_modules/@agenticreplay/providers/dist/translate/anthropic.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/anthropic.js +569 -0
- package/node_modules/@agenticreplay/providers/dist/translate/anthropic.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai-responses.d.ts +51 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai-responses.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai-responses.js +574 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai-responses.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai.d.ts +60 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai.js +577 -0
- package/node_modules/@agenticreplay/providers/dist/translate/openai.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/sse.d.ts +29 -0
- package/node_modules/@agenticreplay/providers/dist/translate/sse.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/sse.js +97 -0
- package/node_modules/@agenticreplay/providers/dist/translate/sse.js.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/util.d.ts +34 -0
- package/node_modules/@agenticreplay/providers/dist/translate/util.d.ts.map +1 -0
- package/node_modules/@agenticreplay/providers/dist/translate/util.js +106 -0
- package/node_modules/@agenticreplay/providers/dist/translate/util.js.map +1 -0
- package/node_modules/@agenticreplay/providers/package.json +26 -0
- package/node_modules/@agenticreplay/proxy/dist/ca.d.ts +45 -0
- package/node_modules/@agenticreplay/proxy/dist/ca.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/ca.js +264 -0
- package/node_modules/@agenticreplay/proxy/dist/ca.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/der.d.ts +51 -0
- package/node_modules/@agenticreplay/proxy/dist/der.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/der.js +144 -0
- package/node_modules/@agenticreplay/proxy/dist/der.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/dialects.d.ts +81 -0
- package/node_modules/@agenticreplay/proxy/dist/dialects.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/dialects.js +329 -0
- package/node_modules/@agenticreplay/proxy/dist/dialects.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/forward.d.ts +34 -0
- package/node_modules/@agenticreplay/proxy/dist/forward.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/forward.js +44 -0
- package/node_modules/@agenticreplay/proxy/dist/forward.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/index.d.ts +9 -0
- package/node_modules/@agenticreplay/proxy/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/index.js +9 -0
- package/node_modules/@agenticreplay/proxy/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/intercept.d.ts +234 -0
- package/node_modules/@agenticreplay/proxy/dist/intercept.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/intercept.js +1258 -0
- package/node_modules/@agenticreplay/proxy/dist/intercept.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/matching.d.ts +109 -0
- package/node_modules/@agenticreplay/proxy/dist/matching.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/matching.js +928 -0
- package/node_modules/@agenticreplay/proxy/dist/matching.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/retrieval.d.ts +93 -0
- package/node_modules/@agenticreplay/proxy/dist/retrieval.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/retrieval.js +233 -0
- package/node_modules/@agenticreplay/proxy/dist/retrieval.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/server.d.ts +327 -0
- package/node_modules/@agenticreplay/proxy/dist/server.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/server.js +2291 -0
- package/node_modules/@agenticreplay/proxy/dist/server.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/tls-hosts.d.ts +54 -0
- package/node_modules/@agenticreplay/proxy/dist/tls-hosts.d.ts.map +1 -0
- package/node_modules/@agenticreplay/proxy/dist/tls-hosts.js +178 -0
- package/node_modules/@agenticreplay/proxy/dist/tls-hosts.js.map +1 -0
- package/node_modules/@agenticreplay/proxy/package.json +28 -0
- package/node_modules/@agenticreplay/schema/dist/constants.d.ts +30 -0
- package/node_modules/@agenticreplay/schema/dist/constants.d.ts.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/constants.js +66 -0
- package/node_modules/@agenticreplay/schema/dist/constants.js.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/index.d.ts +4 -0
- package/node_modules/@agenticreplay/schema/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/index.js +4 -0
- package/node_modules/@agenticreplay/schema/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/types.d.ts +97 -0
- package/node_modules/@agenticreplay/schema/dist/types.d.ts.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/types.js +7 -0
- package/node_modules/@agenticreplay/schema/dist/types.js.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/validate.d.ts +13 -0
- package/node_modules/@agenticreplay/schema/dist/validate.d.ts.map +1 -0
- package/node_modules/@agenticreplay/schema/dist/validate.js +44 -0
- package/node_modules/@agenticreplay/schema/dist/validate.js.map +1 -0
- package/node_modules/@agenticreplay/schema/package.json +28 -0
- package/node_modules/@agenticreplay/shell-shim/dist/index.d.ts +4 -0
- package/node_modules/@agenticreplay/shell-shim/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/index.js +4 -0
- package/node_modules/@agenticreplay/shell-shim/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/install.d.ts +75 -0
- package/node_modules/@agenticreplay/shell-shim/dist/install.d.ts.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/install.js +353 -0
- package/node_modules/@agenticreplay/shell-shim/dist/install.js.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/resolve.d.ts +2 -0
- package/node_modules/@agenticreplay/shell-shim/dist/resolve.d.ts.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/resolve.js +97 -0
- package/node_modules/@agenticreplay/shell-shim/dist/resolve.js.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner-bin.d.ts +3 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner-bin.d.ts.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner-bin.js +4 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner-bin.js.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner.d.ts +45 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner.d.ts.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner.js +105 -0
- package/node_modules/@agenticreplay/shell-shim/dist/runner.js.map +1 -0
- package/node_modules/@agenticreplay/shell-shim/package.json +26 -0
- package/node_modules/@agenticreplay/viewer/dist/bundle.d.ts +11 -0
- package/node_modules/@agenticreplay/viewer/dist/bundle.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/bundle.js +36 -0
- package/node_modules/@agenticreplay/viewer/dist/bundle.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/client/main.d.ts +12 -0
- package/node_modules/@agenticreplay/viewer/dist/client/main.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/client/main.js +249 -0
- package/node_modules/@agenticreplay/viewer/dist/client/main.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/credit.d.ts +22 -0
- package/node_modules/@agenticreplay/viewer/dist/credit.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/credit.js +22 -0
- package/node_modules/@agenticreplay/viewer/dist/credit.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/css.d.ts +11 -0
- package/node_modules/@agenticreplay/viewer/dist/css.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/css.js +410 -0
- package/node_modules/@agenticreplay/viewer/dist/css.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/export.d.ts +21 -0
- package/node_modules/@agenticreplay/viewer/dist/export.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/export.js +22 -0
- package/node_modules/@agenticreplay/viewer/dist/export.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/html.d.ts +32 -0
- package/node_modules/@agenticreplay/viewer/dist/html.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/html.js +252 -0
- package/node_modules/@agenticreplay/viewer/dist/html.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/index.d.ts +16 -0
- package/node_modules/@agenticreplay/viewer/dist/index.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/index.js +16 -0
- package/node_modules/@agenticreplay/viewer/dist/index.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/render.d.ts +126 -0
- package/node_modules/@agenticreplay/viewer/dist/render.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/render.js +699 -0
- package/node_modules/@agenticreplay/viewer/dist/render.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/serve.d.ts +21 -0
- package/node_modules/@agenticreplay/viewer/dist/serve.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/serve.js +122 -0
- package/node_modules/@agenticreplay/viewer/dist/serve.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/trace-dir.d.ts +28 -0
- package/node_modules/@agenticreplay/viewer/dist/trace-dir.d.ts.map +1 -0
- package/node_modules/@agenticreplay/viewer/dist/trace-dir.js +120 -0
- package/node_modules/@agenticreplay/viewer/dist/trace-dir.js.map +1 -0
- package/node_modules/@agenticreplay/viewer/package.json +25 -0
- package/node_modules/ajv/.runkit_example.js +23 -0
- package/node_modules/ajv/LICENSE +22 -0
- package/node_modules/ajv/README.md +207 -0
- package/node_modules/ajv/dist/2019.d.ts +19 -0
- package/node_modules/ajv/dist/2019.js +61 -0
- package/node_modules/ajv/dist/2019.js.map +1 -0
- package/node_modules/ajv/dist/2020.d.ts +19 -0
- package/node_modules/ajv/dist/2020.js +55 -0
- package/node_modules/ajv/dist/2020.js.map +1 -0
- package/node_modules/ajv/dist/ajv.d.ts +18 -0
- package/node_modules/ajv/dist/ajv.js +50 -0
- package/node_modules/ajv/dist/ajv.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/code.d.ts +40 -0
- package/node_modules/ajv/dist/compile/codegen/code.js +156 -0
- package/node_modules/ajv/dist/compile/codegen/code.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/index.d.ts +79 -0
- package/node_modules/ajv/dist/compile/codegen/index.js +697 -0
- package/node_modules/ajv/dist/compile/codegen/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/codegen/scope.d.ts +79 -0
- package/node_modules/ajv/dist/compile/codegen/scope.js +143 -0
- package/node_modules/ajv/dist/compile/codegen/scope.js.map +1 -0
- package/node_modules/ajv/dist/compile/errors.d.ts +13 -0
- package/node_modules/ajv/dist/compile/errors.js +123 -0
- package/node_modules/ajv/dist/compile/errors.js.map +1 -0
- package/node_modules/ajv/dist/compile/index.d.ts +80 -0
- package/node_modules/ajv/dist/compile/index.js +242 -0
- package/node_modules/ajv/dist/compile/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/parse.d.ts +4 -0
- package/node_modules/ajv/dist/compile/jtd/parse.js +350 -0
- package/node_modules/ajv/dist/compile/jtd/parse.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.d.ts +4 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.js +236 -0
- package/node_modules/ajv/dist/compile/jtd/serialize.js.map +1 -0
- package/node_modules/ajv/dist/compile/jtd/types.d.ts +6 -0
- package/node_modules/ajv/dist/compile/jtd/types.js +14 -0
- package/node_modules/ajv/dist/compile/jtd/types.js.map +1 -0
- package/node_modules/ajv/dist/compile/names.d.ts +20 -0
- package/node_modules/ajv/dist/compile/names.js +28 -0
- package/node_modules/ajv/dist/compile/names.js.map +1 -0
- package/node_modules/ajv/dist/compile/ref_error.d.ts +6 -0
- package/node_modules/ajv/dist/compile/ref_error.js +12 -0
- package/node_modules/ajv/dist/compile/ref_error.js.map +1 -0
- package/node_modules/ajv/dist/compile/resolve.d.ts +12 -0
- package/node_modules/ajv/dist/compile/resolve.js +155 -0
- package/node_modules/ajv/dist/compile/resolve.js.map +1 -0
- package/node_modules/ajv/dist/compile/rules.d.ts +28 -0
- package/node_modules/ajv/dist/compile/rules.js +26 -0
- package/node_modules/ajv/dist/compile/rules.js.map +1 -0
- package/node_modules/ajv/dist/compile/util.d.ts +40 -0
- package/node_modules/ajv/dist/compile/util.js +178 -0
- package/node_modules/ajv/dist/compile/util.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/applicability.d.ts +6 -0
- package/node_modules/ajv/dist/compile/validate/applicability.js +19 -0
- package/node_modules/ajv/dist/compile/validate/applicability.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.d.ts +4 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.js +50 -0
- package/node_modules/ajv/dist/compile/validate/boolSchema.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/dataType.d.ts +17 -0
- package/node_modules/ajv/dist/compile/validate/dataType.js +203 -0
- package/node_modules/ajv/dist/compile/validate/dataType.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/defaults.d.ts +2 -0
- package/node_modules/ajv/dist/compile/validate/defaults.js +35 -0
- package/node_modules/ajv/dist/compile/validate/defaults.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/index.d.ts +42 -0
- package/node_modules/ajv/dist/compile/validate/index.js +520 -0
- package/node_modules/ajv/dist/compile/validate/index.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/keyword.d.ts +8 -0
- package/node_modules/ajv/dist/compile/validate/keyword.js +124 -0
- package/node_modules/ajv/dist/compile/validate/keyword.js.map +1 -0
- package/node_modules/ajv/dist/compile/validate/subschema.d.ts +47 -0
- package/node_modules/ajv/dist/compile/validate/subschema.js +81 -0
- package/node_modules/ajv/dist/compile/validate/subschema.js.map +1 -0
- package/node_modules/ajv/dist/core.d.ts +174 -0
- package/node_modules/ajv/dist/core.js +618 -0
- package/node_modules/ajv/dist/core.js.map +1 -0
- package/node_modules/ajv/dist/jtd.d.ts +47 -0
- package/node_modules/ajv/dist/jtd.js +72 -0
- package/node_modules/ajv/dist/jtd.js.map +1 -0
- package/node_modules/ajv/dist/refs/data.json +13 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.d.ts +2 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.js +28 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/index.js.map +1 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/applicator.json +53 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/content.json +17 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/core.json +57 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/format.json +14 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/meta-data.json +37 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/meta/validation.json +90 -0
- package/node_modules/ajv/dist/refs/json-schema-2019-09/schema.json +39 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.d.ts +2 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.js +30 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/index.js.map +1 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/applicator.json +48 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/content.json +17 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/core.json +51 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/format-annotation.json +14 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/meta-data.json +37 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/unevaluated.json +15 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/meta/validation.json +90 -0
- package/node_modules/ajv/dist/refs/json-schema-2020-12/schema.json +55 -0
- package/node_modules/ajv/dist/refs/json-schema-draft-06.json +137 -0
- package/node_modules/ajv/dist/refs/json-schema-draft-07.json +151 -0
- package/node_modules/ajv/dist/refs/json-schema-secure.json +88 -0
- package/node_modules/ajv/dist/refs/jtd-schema.d.ts +3 -0
- package/node_modules/ajv/dist/refs/jtd-schema.js +118 -0
- package/node_modules/ajv/dist/refs/jtd-schema.js.map +1 -0
- package/node_modules/ajv/dist/runtime/equal.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/equal.js +7 -0
- package/node_modules/ajv/dist/runtime/equal.js.map +1 -0
- package/node_modules/ajv/dist/runtime/parseJson.d.ts +18 -0
- package/node_modules/ajv/dist/runtime/parseJson.js +185 -0
- package/node_modules/ajv/dist/runtime/parseJson.js.map +1 -0
- package/node_modules/ajv/dist/runtime/quote.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/quote.js +30 -0
- package/node_modules/ajv/dist/runtime/quote.js.map +1 -0
- package/node_modules/ajv/dist/runtime/re2.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/re2.js +6 -0
- package/node_modules/ajv/dist/runtime/re2.js.map +1 -0
- package/node_modules/ajv/dist/runtime/timestamp.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/timestamp.js +42 -0
- package/node_modules/ajv/dist/runtime/timestamp.js.map +1 -0
- package/node_modules/ajv/dist/runtime/ucs2length.d.ts +5 -0
- package/node_modules/ajv/dist/runtime/ucs2length.js +24 -0
- package/node_modules/ajv/dist/runtime/ucs2length.js.map +1 -0
- package/node_modules/ajv/dist/runtime/uri.d.ts +6 -0
- package/node_modules/ajv/dist/runtime/uri.js +6 -0
- package/node_modules/ajv/dist/runtime/uri.js.map +1 -0
- package/node_modules/ajv/dist/runtime/validation_error.d.ts +7 -0
- package/node_modules/ajv/dist/runtime/validation_error.js +11 -0
- package/node_modules/ajv/dist/runtime/validation_error.js.map +1 -0
- package/node_modules/ajv/dist/standalone/index.d.ts +6 -0
- package/node_modules/ajv/dist/standalone/index.js +90 -0
- package/node_modules/ajv/dist/standalone/index.js.map +1 -0
- package/node_modules/ajv/dist/standalone/instance.d.ts +12 -0
- package/node_modules/ajv/dist/standalone/instance.js +35 -0
- package/node_modules/ajv/dist/standalone/instance.js.map +1 -0
- package/node_modules/ajv/dist/types/index.d.ts +183 -0
- package/node_modules/ajv/dist/types/index.js +3 -0
- package/node_modules/ajv/dist/types/index.js.map +1 -0
- package/node_modules/ajv/dist/types/json-schema.d.ts +125 -0
- package/node_modules/ajv/dist/types/json-schema.js +3 -0
- package/node_modules/ajv/dist/types/json-schema.js.map +1 -0
- package/node_modules/ajv/dist/types/jtd-schema.d.ts +174 -0
- package/node_modules/ajv/dist/types/jtd-schema.js +3 -0
- package/node_modules/ajv/dist/types/jtd-schema.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js +49 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js +106 -0
- package/node_modules/ajv/dist/vocabularies/applicator/additionalProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.js +23 -0
- package/node_modules/ajv/dist/vocabularies/applicator/allOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.js +12 -0
- package/node_modules/ajv/dist/vocabularies/applicator/anyOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.d.ts +7 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.js +95 -0
- package/node_modules/ajv/dist/vocabularies/applicator/contains.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.d.ts +21 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.js +85 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependencies.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.js +11 -0
- package/node_modules/ajv/dist/vocabularies/applicator/dependentSchemas.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.js +66 -0
- package/node_modules/ajv/dist/vocabularies/applicator/if.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.d.ts +13 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.js +44 -0
- package/node_modules/ajv/dist/vocabularies/applicator/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.js +52 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.js +30 -0
- package/node_modules/ajv/dist/vocabularies/applicator/items2020.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.js +26 -0
- package/node_modules/ajv/dist/vocabularies/applicator/not.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.js +60 -0
- package/node_modules/ajv/dist/vocabularies/applicator/oneOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js +75 -0
- package/node_modules/ajv/dist/vocabularies/applicator/patternProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js +12 -0
- package/node_modules/ajv/dist/vocabularies/applicator/prefixItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.js +54 -0
- package/node_modules/ajv/dist/vocabularies/applicator/properties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js +38 -0
- package/node_modules/ajv/dist/vocabularies/applicator/propertyNames.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.js +13 -0
- package/node_modules/ajv/dist/vocabularies/applicator/thenElse.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/code.d.ts +17 -0
- package/node_modules/ajv/dist/vocabularies/code.js +131 -0
- package/node_modules/ajv/dist/vocabularies/code.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/id.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/core/id.js +10 -0
- package/node_modules/ajv/dist/vocabularies/core/id.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/core/index.js +16 -0
- package/node_modules/ajv/dist/vocabularies/core/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.js +122 -0
- package/node_modules/ajv/dist/vocabularies/core/ref.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.js +104 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.js +9 -0
- package/node_modules/ajv/dist/vocabularies/discriminator/types.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.js +23 -0
- package/node_modules/ajv/dist/vocabularies/draft2020.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/draft7.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/draft7.js +17 -0
- package/node_modules/ajv/dist/vocabularies/draft7.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.js +30 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicAnchor.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.js +51 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/dynamicRef.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.js +9 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.js +16 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveAnchor.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.js +10 -0
- package/node_modules/ajv/dist/vocabularies/dynamic/recursiveRef.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/errors.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/errors.js +3 -0
- package/node_modules/ajv/dist/vocabularies/errors.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/format/format.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/format/format.js +92 -0
- package/node_modules/ajv/dist/vocabularies/format/format.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/format/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/format/index.js +6 -0
- package/node_modules/ajv/dist/vocabularies/format/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.js +71 -0
- package/node_modules/ajv/dist/vocabularies/jtd/discriminator.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.js +24 -0
- package/node_modules/ajv/dist/vocabularies/jtd/elements.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.js +43 -0
- package/node_modules/ajv/dist/vocabularies/jtd/enum.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.js +20 -0
- package/node_modules/ajv/dist/vocabularies/jtd/error.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.js +29 -0
- package/node_modules/ajv/dist/vocabularies/jtd/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.js +25 -0
- package/node_modules/ajv/dist/vocabularies/jtd/metadata.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.js +22 -0
- package/node_modules/ajv/dist/vocabularies/jtd/nullable.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.js +15 -0
- package/node_modules/ajv/dist/vocabularies/jtd/optionalProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.d.ts +22 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.js +149 -0
- package/node_modules/ajv/dist/vocabularies/jtd/properties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.d.ts +4 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.js +67 -0
- package/node_modules/ajv/dist/vocabularies/jtd/ref.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.d.ts +10 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.js +69 -0
- package/node_modules/ajv/dist/vocabularies/jtd/type.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.js +12 -0
- package/node_modules/ajv/dist/vocabularies/jtd/union.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.js +51 -0
- package/node_modules/ajv/dist/vocabularies/jtd/values.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/metadata.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/metadata.js +18 -0
- package/node_modules/ajv/dist/vocabularies/metadata.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/next.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/next.js +8 -0
- package/node_modules/ajv/dist/vocabularies/next.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.js +7 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.js +40 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.js +65 -0
- package/node_modules/ajv/dist/vocabularies/unevaluated/unevaluatedProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.d.ts +6 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.js +25 -0
- package/node_modules/ajv/dist/vocabularies/validation/const.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.d.ts +5 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.js +12 -0
- package/node_modules/ajv/dist/vocabularies/validation/dependentRequired.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.js +48 -0
- package/node_modules/ajv/dist/vocabularies/validation/enum.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.d.ts +16 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.js +33 -0
- package/node_modules/ajv/dist/vocabularies/validation/index.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.js +15 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitContains.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.js +24 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitItems.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.js +27 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitLength.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.d.ts +11 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.js +27 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitNumber.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.d.ts +3 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.js +24 -0
- package/node_modules/ajv/dist/vocabularies/validation/limitProperties.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.js +26 -0
- package/node_modules/ajv/dist/vocabularies/validation/multipleOf.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js +33 -0
- package/node_modules/ajv/dist/vocabularies/validation/pattern.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.d.ts +8 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.js +79 -0
- package/node_modules/ajv/dist/vocabularies/validation/required.js.map +1 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.d.ts +9 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js +64 -0
- package/node_modules/ajv/dist/vocabularies/validation/uniqueItems.js.map +1 -0
- package/node_modules/ajv/lib/2019.ts +81 -0
- package/node_modules/ajv/lib/2020.ts +75 -0
- package/node_modules/ajv/lib/ajv.ts +70 -0
- package/node_modules/ajv/lib/compile/codegen/code.ts +169 -0
- package/node_modules/ajv/lib/compile/codegen/index.ts +852 -0
- package/node_modules/ajv/lib/compile/codegen/scope.ts +215 -0
- package/node_modules/ajv/lib/compile/errors.ts +184 -0
- package/node_modules/ajv/lib/compile/index.ts +324 -0
- package/node_modules/ajv/lib/compile/jtd/parse.ts +411 -0
- package/node_modules/ajv/lib/compile/jtd/serialize.ts +277 -0
- package/node_modules/ajv/lib/compile/jtd/types.ts +16 -0
- package/node_modules/ajv/lib/compile/names.ts +27 -0
- package/node_modules/ajv/lib/compile/ref_error.ts +13 -0
- package/node_modules/ajv/lib/compile/resolve.ts +149 -0
- package/node_modules/ajv/lib/compile/rules.ts +50 -0
- package/node_modules/ajv/lib/compile/util.ts +213 -0
- package/node_modules/ajv/lib/compile/validate/applicability.ts +22 -0
- package/node_modules/ajv/lib/compile/validate/boolSchema.ts +47 -0
- package/node_modules/ajv/lib/compile/validate/dataType.ts +230 -0
- package/node_modules/ajv/lib/compile/validate/defaults.ts +32 -0
- package/node_modules/ajv/lib/compile/validate/index.ts +582 -0
- package/node_modules/ajv/lib/compile/validate/keyword.ts +171 -0
- package/node_modules/ajv/lib/compile/validate/subschema.ts +135 -0
- package/node_modules/ajv/lib/core.ts +892 -0
- package/node_modules/ajv/lib/jtd.ts +132 -0
- package/node_modules/ajv/lib/refs/data.json +13 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/index.ts +28 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/applicator.json +53 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/content.json +17 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/core.json +57 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/format.json +14 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/meta-data.json +37 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/meta/validation.json +90 -0
- package/node_modules/ajv/lib/refs/json-schema-2019-09/schema.json +39 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/index.ts +30 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/applicator.json +48 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/content.json +17 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/core.json +51 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/format-annotation.json +14 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/meta-data.json +37 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/unevaluated.json +15 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/meta/validation.json +90 -0
- package/node_modules/ajv/lib/refs/json-schema-2020-12/schema.json +55 -0
- package/node_modules/ajv/lib/refs/json-schema-draft-06.json +137 -0
- package/node_modules/ajv/lib/refs/json-schema-draft-07.json +151 -0
- package/node_modules/ajv/lib/refs/json-schema-secure.json +88 -0
- package/node_modules/ajv/lib/refs/jtd-schema.ts +130 -0
- package/node_modules/ajv/lib/runtime/equal.ts +7 -0
- package/node_modules/ajv/lib/runtime/parseJson.ts +177 -0
- package/node_modules/ajv/lib/runtime/quote.ts +31 -0
- package/node_modules/ajv/lib/runtime/re2.ts +6 -0
- package/node_modules/ajv/lib/runtime/timestamp.ts +46 -0
- package/node_modules/ajv/lib/runtime/ucs2length.ts +20 -0
- package/node_modules/ajv/lib/runtime/uri.ts +6 -0
- package/node_modules/ajv/lib/runtime/validation_error.ts +13 -0
- package/node_modules/ajv/lib/standalone/index.ts +100 -0
- package/node_modules/ajv/lib/standalone/instance.ts +36 -0
- package/node_modules/ajv/lib/types/index.ts +244 -0
- package/node_modules/ajv/lib/types/json-schema.ts +187 -0
- package/node_modules/ajv/lib/types/jtd-schema.ts +273 -0
- package/node_modules/ajv/lib/vocabularies/applicator/additionalItems.ts +56 -0
- package/node_modules/ajv/lib/vocabularies/applicator/additionalProperties.ts +118 -0
- package/node_modules/ajv/lib/vocabularies/applicator/allOf.ts +22 -0
- package/node_modules/ajv/lib/vocabularies/applicator/anyOf.ts +14 -0
- package/node_modules/ajv/lib/vocabularies/applicator/contains.ts +109 -0
- package/node_modules/ajv/lib/vocabularies/applicator/dependencies.ts +112 -0
- package/node_modules/ajv/lib/vocabularies/applicator/dependentSchemas.ts +11 -0
- package/node_modules/ajv/lib/vocabularies/applicator/if.ts +80 -0
- package/node_modules/ajv/lib/vocabularies/applicator/index.ts +53 -0
- package/node_modules/ajv/lib/vocabularies/applicator/items.ts +59 -0
- package/node_modules/ajv/lib/vocabularies/applicator/items2020.ts +36 -0
- package/node_modules/ajv/lib/vocabularies/applicator/not.ts +38 -0
- package/node_modules/ajv/lib/vocabularies/applicator/oneOf.ts +82 -0
- package/node_modules/ajv/lib/vocabularies/applicator/patternProperties.ts +91 -0
- package/node_modules/ajv/lib/vocabularies/applicator/prefixItems.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/applicator/properties.ts +57 -0
- package/node_modules/ajv/lib/vocabularies/applicator/propertyNames.ts +50 -0
- package/node_modules/ajv/lib/vocabularies/applicator/thenElse.ts +13 -0
- package/node_modules/ajv/lib/vocabularies/code.ts +168 -0
- package/node_modules/ajv/lib/vocabularies/core/id.ts +10 -0
- package/node_modules/ajv/lib/vocabularies/core/index.ts +16 -0
- package/node_modules/ajv/lib/vocabularies/core/ref.ts +129 -0
- package/node_modules/ajv/lib/vocabularies/discriminator/index.ts +113 -0
- package/node_modules/ajv/lib/vocabularies/discriminator/types.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/draft2020.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/draft7.ts +17 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/dynamicAnchor.ts +31 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/dynamicRef.ts +51 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/index.ts +9 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/recursiveAnchor.ts +14 -0
- package/node_modules/ajv/lib/vocabularies/dynamic/recursiveRef.ts +10 -0
- package/node_modules/ajv/lib/vocabularies/errors.ts +18 -0
- package/node_modules/ajv/lib/vocabularies/format/format.ts +120 -0
- package/node_modules/ajv/lib/vocabularies/format/index.ts +6 -0
- package/node_modules/ajv/lib/vocabularies/jtd/discriminator.ts +89 -0
- package/node_modules/ajv/lib/vocabularies/jtd/elements.ts +32 -0
- package/node_modules/ajv/lib/vocabularies/jtd/enum.ts +45 -0
- package/node_modules/ajv/lib/vocabularies/jtd/error.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/jtd/index.ts +37 -0
- package/node_modules/ajv/lib/vocabularies/jtd/metadata.ts +24 -0
- package/node_modules/ajv/lib/vocabularies/jtd/nullable.ts +21 -0
- package/node_modules/ajv/lib/vocabularies/jtd/optionalProperties.ts +15 -0
- package/node_modules/ajv/lib/vocabularies/jtd/properties.ts +184 -0
- package/node_modules/ajv/lib/vocabularies/jtd/ref.ts +76 -0
- package/node_modules/ajv/lib/vocabularies/jtd/type.ts +75 -0
- package/node_modules/ajv/lib/vocabularies/jtd/union.ts +12 -0
- package/node_modules/ajv/lib/vocabularies/jtd/values.ts +58 -0
- package/node_modules/ajv/lib/vocabularies/metadata.ts +17 -0
- package/node_modules/ajv/lib/vocabularies/next.ts +8 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/index.ts +7 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/unevaluatedItems.ts +47 -0
- package/node_modules/ajv/lib/vocabularies/unevaluated/unevaluatedProperties.ts +85 -0
- package/node_modules/ajv/lib/vocabularies/validation/const.ts +28 -0
- package/node_modules/ajv/lib/vocabularies/validation/dependentRequired.ts +23 -0
- package/node_modules/ajv/lib/vocabularies/validation/enum.ts +54 -0
- package/node_modules/ajv/lib/vocabularies/validation/index.ts +49 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitContains.ts +16 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitItems.ts +26 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitLength.ts +30 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitNumber.ts +42 -0
- package/node_modules/ajv/lib/vocabularies/validation/limitProperties.ts +26 -0
- package/node_modules/ajv/lib/vocabularies/validation/multipleOf.ts +34 -0
- package/node_modules/ajv/lib/vocabularies/validation/pattern.ts +39 -0
- package/node_modules/ajv/lib/vocabularies/validation/required.ts +98 -0
- package/node_modules/ajv/lib/vocabularies/validation/uniqueItems.ts +79 -0
- package/node_modules/ajv/package.json +126 -0
- package/node_modules/ajv-formats/LICENSE +21 -0
- package/node_modules/ajv-formats/README.md +125 -0
- package/node_modules/ajv-formats/dist/formats.d.ts +9 -0
- package/node_modules/ajv-formats/dist/formats.js +208 -0
- package/node_modules/ajv-formats/dist/formats.js.map +1 -0
- package/node_modules/ajv-formats/dist/index.d.ts +15 -0
- package/node_modules/ajv-formats/dist/index.js +37 -0
- package/node_modules/ajv-formats/dist/index.js.map +1 -0
- package/node_modules/ajv-formats/dist/limit.d.ts +10 -0
- package/node_modules/ajv-formats/dist/limit.js +69 -0
- package/node_modules/ajv-formats/dist/limit.js.map +1 -0
- package/node_modules/ajv-formats/package.json +74 -0
- package/node_modules/ajv-formats/src/formats.ts +269 -0
- package/node_modules/ajv-formats/src/index.ts +62 -0
- package/node_modules/ajv-formats/src/limit.ts +99 -0
- package/node_modules/fast-deep-equal/LICENSE +21 -0
- package/node_modules/fast-deep-equal/README.md +96 -0
- package/node_modules/fast-deep-equal/es6/index.d.ts +2 -0
- package/node_modules/fast-deep-equal/es6/index.js +72 -0
- package/node_modules/fast-deep-equal/es6/react.d.ts +2 -0
- package/node_modules/fast-deep-equal/es6/react.js +79 -0
- package/node_modules/fast-deep-equal/index.d.ts +4 -0
- package/node_modules/fast-deep-equal/index.js +46 -0
- package/node_modules/fast-deep-equal/package.json +61 -0
- package/node_modules/fast-deep-equal/react.d.ts +2 -0
- package/node_modules/fast-deep-equal/react.js +53 -0
- package/node_modules/fast-uri/.gitattributes +2 -0
- package/node_modules/fast-uri/.github/dependabot.yml +13 -0
- package/node_modules/fast-uri/.github/workflows/ci.yml +106 -0
- package/node_modules/fast-uri/.github/workflows/lock-threads.yml +19 -0
- package/node_modules/fast-uri/.github/workflows/package-manager-ci.yml +24 -0
- package/node_modules/fast-uri/LICENSE +30 -0
- package/node_modules/fast-uri/README.md +152 -0
- package/node_modules/fast-uri/benchmark/benchmark.mjs +159 -0
- package/node_modules/fast-uri/benchmark/equal.mjs +51 -0
- package/node_modules/fast-uri/benchmark/non-simple-domain.mjs +22 -0
- package/node_modules/fast-uri/benchmark/package.json +17 -0
- package/node_modules/fast-uri/benchmark/string-array-to-hex-stripped.mjs +24 -0
- package/node_modules/fast-uri/benchmark/ws-is-secure.mjs +65 -0
- package/node_modules/fast-uri/eslint.config.js +6 -0
- package/node_modules/fast-uri/index.js +631 -0
- package/node_modules/fast-uri/lib/schemes.js +272 -0
- package/node_modules/fast-uri/lib/utils.js +747 -0
- package/node_modules/fast-uri/package.json +68 -0
- package/node_modules/fast-uri/test/ajv.test.js +43 -0
- package/node_modules/fast-uri/test/component-safe-serialization.test.js +163 -0
- package/node_modules/fast-uri/test/equal.test.js +145 -0
- package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +503 -0
- package/node_modules/fast-uri/test/fixtures/uri-js-serialize.json +120 -0
- package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
- package/node_modules/fast-uri/test/ipv6-validation.test.js +124 -0
- package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
- package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
- package/node_modules/fast-uri/test/parse.test.js +327 -0
- package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
- package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
- package/node_modules/fast-uri/test/resolve.test.js +87 -0
- package/node_modules/fast-uri/test/rfc-3986.test.js +90 -0
- package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
- package/node_modules/fast-uri/test/security-normalization.test.js +178 -0
- package/node_modules/fast-uri/test/security.test.js +431 -0
- package/node_modules/fast-uri/test/serialize.test.js +151 -0
- package/node_modules/fast-uri/test/uri-js-compatibility.test.js +33 -0
- package/node_modules/fast-uri/test/uri-js.test.js +912 -0
- package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
- package/node_modules/fast-uri/test/util.test.js +38 -0
- package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
- package/node_modules/fast-uri/tsconfig.json +9 -0
- package/node_modules/fast-uri/types/index.d.ts +60 -0
- package/node_modules/fast-uri/types/index.test-d.ts +17 -0
- package/node_modules/json-schema-traverse/.eslintrc.yml +27 -0
- package/node_modules/json-schema-traverse/.github/FUNDING.yml +2 -0
- package/node_modules/json-schema-traverse/.github/workflows/build.yml +28 -0
- package/node_modules/json-schema-traverse/.github/workflows/publish.yml +27 -0
- package/node_modules/json-schema-traverse/LICENSE +21 -0
- package/node_modules/json-schema-traverse/README.md +95 -0
- package/node_modules/json-schema-traverse/index.d.ts +40 -0
- package/node_modules/json-schema-traverse/index.js +93 -0
- package/node_modules/json-schema-traverse/package.json +43 -0
- package/node_modules/json-schema-traverse/spec/.eslintrc.yml +6 -0
- package/node_modules/json-schema-traverse/spec/fixtures/schema.js +125 -0
- package/node_modules/json-schema-traverse/spec/index.spec.js +171 -0
- package/node_modules/require-from-string/index.js +34 -0
- package/node_modules/require-from-string/license +21 -0
- package/node_modules/require-from-string/package.json +28 -0
- package/node_modules/require-from-string/readme.md +56 -0
- package/package.json +49 -2
- package/packages/cli/dist/agent-spans.d.ts +238 -0
- package/packages/cli/dist/agent-spans.d.ts.map +1 -0
- package/packages/cli/dist/agent-spans.js +677 -0
- package/packages/cli/dist/agent-spans.js.map +1 -0
- package/packages/cli/dist/api.d.ts +127 -0
- package/packages/cli/dist/api.d.ts.map +1 -0
- package/packages/cli/dist/api.js +159 -0
- package/packages/cli/dist/api.js.map +1 -0
- package/packages/cli/dist/archive.d.ts +24 -0
- package/packages/cli/dist/archive.d.ts.map +1 -0
- package/packages/cli/dist/archive.js +230 -0
- package/packages/cli/dist/archive.js.map +1 -0
- package/packages/cli/dist/args.d.ts +57 -0
- package/packages/cli/dist/args.d.ts.map +1 -0
- package/packages/cli/dist/args.js +180 -0
- package/packages/cli/dist/args.js.map +1 -0
- package/packages/cli/dist/attach.d.ts +48 -0
- package/packages/cli/dist/attach.d.ts.map +1 -0
- package/packages/cli/dist/attach.js +153 -0
- package/packages/cli/dist/attach.js.map +1 -0
- package/packages/cli/dist/capture-limits.d.ts +7 -0
- package/packages/cli/dist/capture-limits.d.ts.map +1 -0
- package/packages/cli/dist/capture-limits.js +13 -0
- package/packages/cli/dist/capture-limits.js.map +1 -0
- package/packages/cli/dist/cli.d.ts +3 -0
- package/packages/cli/dist/cli.d.ts.map +1 -0
- package/packages/cli/dist/cli.js +4 -0
- package/packages/cli/dist/cli.js.map +1 -0
- package/packages/cli/dist/commands/attach.d.ts +49 -0
- package/packages/cli/dist/commands/attach.d.ts.map +1 -0
- package/packages/cli/dist/commands/attach.js +350 -0
- package/packages/cli/dist/commands/attach.js.map +1 -0
- package/packages/cli/dist/commands/compare.d.ts +27 -0
- package/packages/cli/dist/commands/compare.d.ts.map +1 -0
- package/packages/cli/dist/commands/compare.js +187 -0
- package/packages/cli/dist/commands/compare.js.map +1 -0
- package/packages/cli/dist/commands/doctor.d.ts +24 -0
- package/packages/cli/dist/commands/doctor.d.ts.map +1 -0
- package/packages/cli/dist/commands/doctor.js +402 -0
- package/packages/cli/dist/commands/doctor.js.map +1 -0
- package/packages/cli/dist/commands/gc.d.ts +24 -0
- package/packages/cli/dist/commands/gc.d.ts.map +1 -0
- package/packages/cli/dist/commands/gc.js +326 -0
- package/packages/cli/dist/commands/gc.js.map +1 -0
- package/packages/cli/dist/commands/inspect.d.ts +45 -0
- package/packages/cli/dist/commands/inspect.d.ts.map +1 -0
- package/packages/cli/dist/commands/inspect.js +375 -0
- package/packages/cli/dist/commands/inspect.js.map +1 -0
- package/packages/cli/dist/commands/quickstart.d.ts +48 -0
- package/packages/cli/dist/commands/quickstart.d.ts.map +1 -0
- package/packages/cli/dist/commands/quickstart.js +413 -0
- package/packages/cli/dist/commands/quickstart.js.map +1 -0
- package/packages/cli/dist/commands/record.d.ts +31 -0
- package/packages/cli/dist/commands/record.d.ts.map +1 -0
- package/packages/cli/dist/commands/record.js +958 -0
- package/packages/cli/dist/commands/record.js.map +1 -0
- package/packages/cli/dist/commands/replay.d.ts +184 -0
- package/packages/cli/dist/commands/replay.d.ts.map +1 -0
- package/packages/cli/dist/commands/replay.js +2014 -0
- package/packages/cli/dist/commands/replay.js.map +1 -0
- package/packages/cli/dist/commands/scrub.d.ts +34 -0
- package/packages/cli/dist/commands/scrub.d.ts.map +1 -0
- package/packages/cli/dist/commands/scrub.js +765 -0
- package/packages/cli/dist/commands/scrub.js.map +1 -0
- package/packages/cli/dist/commands/setup.d.ts +23 -0
- package/packages/cli/dist/commands/setup.d.ts.map +1 -0
- package/packages/cli/dist/commands/setup.js +239 -0
- package/packages/cli/dist/commands/setup.js.map +1 -0
- package/packages/cli/dist/commands/sync.d.ts +236 -0
- package/packages/cli/dist/commands/sync.d.ts.map +1 -0
- package/packages/cli/dist/commands/sync.js +1270 -0
- package/packages/cli/dist/commands/sync.js.map +1 -0
- package/packages/cli/dist/config.d.ts +69 -0
- package/packages/cli/dist/config.d.ts.map +1 -0
- package/packages/cli/dist/config.js +144 -0
- package/packages/cli/dist/config.js.map +1 -0
- package/packages/cli/dist/exchange-events.d.ts +80 -0
- package/packages/cli/dist/exchange-events.d.ts.map +1 -0
- package/packages/cli/dist/exchange-events.js +296 -0
- package/packages/cli/dist/exchange-events.js.map +1 -0
- package/packages/cli/dist/flags.d.ts +41 -0
- package/packages/cli/dist/flags.d.ts.map +1 -0
- package/packages/cli/dist/flags.js +292 -0
- package/packages/cli/dist/flags.js.map +1 -0
- package/packages/cli/dist/fs-events.d.ts +19 -0
- package/packages/cli/dist/fs-events.d.ts.map +1 -0
- package/packages/cli/dist/fs-events.js +89 -0
- package/packages/cli/dist/fs-events.js.map +1 -0
- package/packages/cli/dist/index.d.ts +18 -0
- package/packages/cli/dist/index.d.ts.map +1 -0
- package/packages/cli/dist/index.js +18 -0
- package/packages/cli/dist/index.js.map +1 -0
- package/packages/cli/dist/main.d.ts +2 -0
- package/packages/cli/dist/main.d.ts.map +1 -0
- package/packages/cli/dist/main.js +404 -0
- package/packages/cli/dist/main.js.map +1 -0
- package/packages/cli/dist/mcp-server.d.ts +44 -0
- package/packages/cli/dist/mcp-server.d.ts.map +1 -0
- package/packages/cli/dist/mcp-server.js +250 -0
- package/packages/cli/dist/mcp-server.js.map +1 -0
- package/packages/cli/dist/mcp.d.ts +126 -0
- package/packages/cli/dist/mcp.d.ts.map +1 -0
- package/packages/cli/dist/mcp.js +290 -0
- package/packages/cli/dist/mcp.js.map +1 -0
- package/packages/cli/dist/out.d.ts +107 -0
- package/packages/cli/dist/out.d.ts.map +1 -0
- package/packages/cli/dist/out.js +280 -0
- package/packages/cli/dist/out.js.map +1 -0
- package/packages/cli/dist/rasterize.d.ts +62 -0
- package/packages/cli/dist/rasterize.d.ts.map +1 -0
- package/packages/cli/dist/rasterize.js +154 -0
- package/packages/cli/dist/rasterize.js.map +1 -0
- package/packages/cli/dist/serial.d.ts +19 -0
- package/packages/cli/dist/serial.d.ts.map +1 -0
- package/packages/cli/dist/serial.js +34 -0
- package/packages/cli/dist/serial.js.map +1 -0
- package/packages/cli/dist/share-card.d.ts +73 -0
- package/packages/cli/dist/share-card.d.ts.map +1 -0
- package/packages/cli/dist/share-card.js +413 -0
- package/packages/cli/dist/share-card.js.map +1 -0
- package/packages/cli/dist/snapshot.d.ts +39 -0
- package/packages/cli/dist/snapshot.d.ts.map +1 -0
- package/packages/cli/dist/snapshot.js +43 -0
- package/packages/cli/dist/snapshot.js.map +1 -0
- package/packages/cli/dist/tls-capture.d.ts +135 -0
- package/packages/cli/dist/tls-capture.d.ts.map +1 -0
- package/packages/cli/dist/tls-capture.js +586 -0
- package/packages/cli/dist/tls-capture.js.map +1 -0
- package/packages/cli/dist/upstream.d.ts +29 -0
- package/packages/cli/dist/upstream.d.ts.map +1 -0
- package/packages/cli/dist/upstream.js +41 -0
- package/packages/cli/dist/upstream.js.map +1 -0
- package/packages/cli/dist/version.d.ts +2 -0
- package/packages/cli/dist/version.d.ts.map +1 -0
- package/packages/cli/dist/version.js +18 -0
- package/packages/cli/dist/version.js.map +1 -0
- package/packages/cli/package.json +11 -11
- package/.dockerignore +0 -11
- package/.gitattributes +0 -19
- package/.github/workflows/ci.yml +0 -146
- package/.grok-plugin/plugin.json +0 -12
- package/.mcp.json +0 -8
- package/.prettierignore +0 -4
- package/.prettierrc.json +0 -1
- package/CONTRIBUTING.md +0 -106
- package/Dockerfile +0 -30
- package/MAINTAINERS.md +0 -24
- package/RELEASING.md +0 -183
- package/SECURITY.md +0 -149
- package/capture/CAPTURE-RUNBOOK.md +0 -271
- package/capture/CURSOR-HTTP2.md +0 -226
- package/capture/README.md +0 -349
- package/capture/capture-run.gif +0 -0
- package/capture/capture.mjs +0 -1883
- package/capture/fable-capture.gif +0 -0
- package/capture/fable-capture.mp4 +0 -0
- package/capture/muse-shim.mjs +0 -114
- package/capture/render-run.mjs +0 -125
- package/capture/render.mjs +0 -228
- package/companion-kiokuko-dsh.patch +0 -7806
- package/docs/agenticreplay-acceptance.md +0 -135
- package/docs/architecture.md +0 -160
- package/docs/chain-card.png +0 -0
- package/docs/compare-card.png +0 -0
- package/docs/demo-cli.gif +0 -0
- package/docs/demo-viewer.gif +0 -0
- package/docs/fidelity.md +0 -16
- package/docs/good-first-issues.md +0 -78
- package/docs/graph-card.png +0 -0
- package/docs/how-to/compare-models-on-the-same-failure.md +0 -58
- package/docs/how-to/debug-a-failing-agent-run.md +0 -127
- package/docs/how-to/why-did-my-agent-delete-my-file.md +0 -92
- package/docs/i18n/README.ar.md +0 -597
- package/docs/i18n/README.de.md +0 -648
- package/docs/i18n/README.es.md +0 -642
- package/docs/i18n/README.fr.md +0 -649
- package/docs/i18n/README.ja.md +0 -608
- package/docs/i18n/README.ko.md +0 -598
- package/docs/i18n/README.zh-CN.md +0 -574
- package/docs/integrations.md +0 -628
- package/docs/launch-path.md +0 -215
- package/docs/media/README.md +0 -101
- package/docs/media/sync-transcript.txt +0 -18
- package/docs/media/transcript.txt +0 -29
- package/docs/opencode-capture.md +0 -137
- package/docs/plugins.md +0 -250
- package/docs/proposals/action-graph-and-reasoning.md +0 -354
- package/docs/sync-card.png +0 -0
- package/docs/validation.md +0 -52
- package/docs/viewer-timeline.png +0 -0
- package/examples/traces/README.md +0 -50
- package/examples/traces/run_9f2c14a03b71/blobs/1b/1b694f32b8c582ace6b35ad64050e532fb3ee3ecc1a2763a82cf42ad0f2a3dd0 +0 -10
- package/examples/traces/run_9f2c14a03b71/blobs/51/517b5b8ec6c3b13121dbaec61784e2a024247c7c4efd9ba068b582ee57bd2764 +0 -4
- package/examples/traces/run_9f2c14a03b71/blobs/5b/5bba1bd069013d86363f69c00c1ae77c84c4e113aeeb7431c8141af4fed76860 +0 -7
- package/examples/traces/run_9f2c14a03b71/blobs/84/84247e7b385fd5f99a6728a509838e52ef963af92f44ef6eccbed6910de1438d +0 -10
- package/examples/traces/run_9f2c14a03b71/blobs/ac/ac9b4be9bdf7fe63c7b5135e2d14c4c0745a48771e733804869790590455f8e8 +0 -7
- package/examples/traces/run_9f2c14a03b71/events.jsonl +0 -31
- package/examples/traces/run_9f2c14a03b71/manifest.json +0 -46
- package/examples/traces/run_9f2c14a03b71/redactions.json +0 -17
- package/llms.txt +0 -26
- package/packages/adapters/fixtures/harness/claude-code.json +0 -12
- package/packages/adapters/fixtures/harness/codex.json +0 -10
- package/packages/adapters/fixtures/harness/cursor.json +0 -10
- package/packages/adapters/fixtures/harness/exec.json +0 -10
- package/packages/adapters/fixtures/harness/generic-openai.json +0 -14
- package/packages/adapters/fixtures/harness/goose.json +0 -14
- package/packages/adapters/fixtures/harness/grok.json +0 -18
- package/packages/adapters/fixtures/harness/hermes.json +0 -10
- package/packages/adapters/fixtures/harness/indexrag.json +0 -21
- package/packages/adapters/fixtures/harness/kilo.json +0 -10
- package/packages/adapters/fixtures/harness/mcode.json +0 -10
- package/packages/adapters/fixtures/harness/mimo.json +0 -10
- package/packages/adapters/fixtures/harness/node.json +0 -22
- package/packages/adapters/fixtures/harness/openclaw.json +0 -21
- package/packages/adapters/fixtures/harness/opencode.json +0 -20
- package/packages/adapters/fixtures/harness/qwen.json +0 -20
- package/packages/adapters/fixtures/harness/zcode.json +0 -10
- package/packages/adapters/package.json +0 -29
- package/packages/adapters/src/claude-code.ts +0 -109
- package/packages/adapters/src/codex.ts +0 -135
- package/packages/adapters/src/contract.ts +0 -531
- package/packages/adapters/src/cursor.ts +0 -76
- package/packages/adapters/src/detect.ts +0 -132
- package/packages/adapters/src/env.ts +0 -118
- package/packages/adapters/src/exec.ts +0 -59
- package/packages/adapters/src/generic-openai.ts +0 -36
- package/packages/adapters/src/goose.ts +0 -87
- package/packages/adapters/src/grok.ts +0 -60
- package/packages/adapters/src/hermes.ts +0 -92
- package/packages/adapters/src/index.ts +0 -24
- package/packages/adapters/src/indexrag.ts +0 -254
- package/packages/adapters/src/kilo.ts +0 -39
- package/packages/adapters/src/mcode.ts +0 -392
- package/packages/adapters/src/mcp-config.ts +0 -84
- package/packages/adapters/src/mimo.ts +0 -44
- package/packages/adapters/src/node.ts +0 -71
- package/packages/adapters/src/openclaw.ts +0 -50
- package/packages/adapters/src/opencode-api-bases.ts +0 -200
- package/packages/adapters/src/opencode-capture.ts +0 -164
- package/packages/adapters/src/opencode-catalog.ts +0 -44
- package/packages/adapters/src/opencode-config.ts +0 -209
- package/packages/adapters/src/opencode.ts +0 -257
- package/packages/adapters/src/qwen.ts +0 -113
- package/packages/adapters/src/registry.ts +0 -121
- package/packages/adapters/src/scaffold.ts +0 -173
- package/packages/adapters/src/session.ts +0 -138
- package/packages/adapters/src/zcode.ts +0 -440
- package/packages/adapters/test/adapters.test.ts +0 -666
- package/packages/adapters/test/contract.test.ts +0 -601
- package/packages/adapters/test/exec-adapter.test.ts +0 -114
- package/packages/adapters/test/harness-versions.test.ts +0 -220
- package/packages/adapters/test/indexrag.test.ts +0 -192
- package/packages/adapters/test/mcode.test.ts +0 -678
- package/packages/adapters/test/mcp-config.test.ts +0 -118
- package/packages/adapters/test/named-agents.test.ts +0 -262
- package/packages/adapters/test/node-adapter.test.ts +0 -98
- package/packages/adapters/test/opencode-capture.test.ts +0 -137
- package/packages/adapters/test/opencode-catalog.test.ts +0 -178
- package/packages/adapters/test/opencode-config.test.ts +0 -63
- package/packages/adapters/test/scaffold.test.ts +0 -156
- package/packages/adapters/test/session.test.ts +0 -368
- package/packages/adapters/test/zcode.test.ts +0 -497
- package/packages/adapters/tsconfig.json +0 -16
- package/packages/cli/README.md +0 -92
- package/packages/cli/src/agent-spans.ts +0 -752
- package/packages/cli/src/api.ts +0 -275
- package/packages/cli/src/archive.ts +0 -264
- package/packages/cli/src/args.ts +0 -207
- package/packages/cli/src/attach.ts +0 -178
- package/packages/cli/src/capture-limits.ts +0 -21
- package/packages/cli/src/cli.ts +0 -4
- package/packages/cli/src/commands/attach.ts +0 -441
- package/packages/cli/src/commands/compare.ts +0 -218
- package/packages/cli/src/commands/doctor.ts +0 -447
- package/packages/cli/src/commands/gc.ts +0 -372
- package/packages/cli/src/commands/inspect.ts +0 -482
- package/packages/cli/src/commands/quickstart.ts +0 -479
- package/packages/cli/src/commands/record.ts +0 -1069
- package/packages/cli/src/commands/replay.ts +0 -2289
- package/packages/cli/src/commands/scrub.ts +0 -865
- package/packages/cli/src/commands/setup.ts +0 -294
- package/packages/cli/src/commands/sync.ts +0 -1429
- package/packages/cli/src/config.ts +0 -182
- package/packages/cli/src/exchange-events.ts +0 -369
- package/packages/cli/src/flags.ts +0 -316
- package/packages/cli/src/fs-events.ts +0 -103
- package/packages/cli/src/index.ts +0 -17
- package/packages/cli/src/main.ts +0 -419
- package/packages/cli/src/mcp-server.ts +0 -314
- package/packages/cli/src/mcp.ts +0 -350
- package/packages/cli/src/out.ts +0 -350
- package/packages/cli/src/rasterize.ts +0 -205
- package/packages/cli/src/serial.ts +0 -38
- package/packages/cli/src/share-card.ts +0 -514
- package/packages/cli/src/snapshot.ts +0 -80
- package/packages/cli/src/tls-capture.ts +0 -688
- package/packages/cli/src/upstream.ts +0 -75
- package/packages/cli/src/version.ts +0 -21
- package/packages/cli/test/agent-spans-drain.test.ts +0 -157
- package/packages/cli/test/agent-spans-race.test.ts +0 -97
- package/packages/cli/test/agent-spans.test.ts +0 -1032
- package/packages/cli/test/api.test.ts +0 -178
- package/packages/cli/test/archive.test.ts +0 -143
- package/packages/cli/test/args.test.ts +0 -164
- package/packages/cli/test/attach-command.test.ts +0 -356
- package/packages/cli/test/attach.test.ts +0 -198
- package/packages/cli/test/capture-oracle.test.ts +0 -160
- package/packages/cli/test/capture-warning.test.ts +0 -215
- package/packages/cli/test/chain-card.test.ts +0 -190
- package/packages/cli/test/claude-capture.test.ts +0 -242
- package/packages/cli/test/compare.test.ts +0 -329
- package/packages/cli/test/config-permission-failure.test.ts +0 -41
- package/packages/cli/test/config.test.ts +0 -239
- package/packages/cli/test/declared-inside-replay.test.ts +0 -170
- package/packages/cli/test/doctor.test.ts +0 -276
- package/packages/cli/test/e2e.test.ts +0 -1230
- package/packages/cli/test/exchange-events.test.ts +0 -345
- package/packages/cli/test/failure-lines.test.ts +0 -47
- package/packages/cli/test/fixtures/deaf-agent.mjs +0 -11
- package/packages/cli/test/fixtures/embedding-agent.mjs +0 -32
- package/packages/cli/test/fixtures/fake-agent.mjs +0 -114
- package/packages/cli/test/fixtures/fake-model.mjs +0 -105
- package/packages/cli/test/fixtures/gateway-agent.mjs +0 -20
- package/packages/cli/test/fixtures/ide-parent.mjs +0 -34
- package/packages/cli/test/fixtures/mcp-agent.mjs +0 -64
- package/packages/cli/test/fixtures/proxy-call.mjs +0 -105
- package/packages/cli/test/fixtures/responses-agent.mjs +0 -82
- package/packages/cli/test/fixtures/responses-model.mjs +0 -94
- package/packages/cli/test/fixtures/tls-agent.mjs +0 -35
- package/packages/cli/test/fixtures/vercel-agent.mjs +0 -81
- package/packages/cli/test/flag-values.test.ts +0 -162
- package/packages/cli/test/flags.test.ts +0 -313
- package/packages/cli/test/fork-provenance.test.ts +0 -224
- package/packages/cli/test/fork-tls.test.ts +0 -190
- package/packages/cli/test/gateway-e2e.test.ts +0 -110
- package/packages/cli/test/gc.test.ts +0 -531
- package/packages/cli/test/generated-esm.test.ts +0 -87
- package/packages/cli/test/graph-card.test.ts +0 -249
- package/packages/cli/test/graph-command.test.ts +0 -258
- package/packages/cli/test/grok-bot-e2e.test.ts +0 -349
- package/packages/cli/test/inspect.test.ts +0 -412
- package/packages/cli/test/intercept-failures.test.ts +0 -74
- package/packages/cli/test/json-only.test.ts +0 -88
- package/packages/cli/test/json-output.test.ts +0 -221
- package/packages/cli/test/list-gateway.test.ts +0 -770
- package/packages/cli/test/mcp-capture.test.ts +0 -498
- package/packages/cli/test/mcp-server.test.ts +0 -315
- package/packages/cli/test/nested-repo-replay.test.ts +0 -972
- package/packages/cli/test/no-credential-in-attrs.test.ts +0 -255
- package/packages/cli/test/opencode-capture.test.ts +0 -422
- package/packages/cli/test/opencode-catalog-capture.test.ts +0 -175
- package/packages/cli/test/out.test.ts +0 -491
- package/packages/cli/test/pinned-fetch.test.ts +0 -154
- package/packages/cli/test/prompt-scrub.test.ts +0 -132
- package/packages/cli/test/publish-order.test.ts +0 -87
- package/packages/cli/test/quickstart.test.ts +0 -579
- package/packages/cli/test/rasterize.test.ts +0 -86
- package/packages/cli/test/record-detection.test.ts +0 -156
- package/packages/cli/test/record-teardown.test.ts +0 -203
- package/packages/cli/test/replay-putback.test.ts +0 -242
- package/packages/cli/test/replay-spill.test.ts +0 -116
- package/packages/cli/test/replay-teardown.test.ts +0 -143
- package/packages/cli/test/replay-trace.test.ts +0 -199
- package/packages/cli/test/replay-transport.test.ts +0 -113
- package/packages/cli/test/reset-roots.test.ts +0 -67
- package/packages/cli/test/responses-e2e.test.ts +0 -119
- package/packages/cli/test/retrieval-context.test.ts +0 -168
- package/packages/cli/test/review-regressions.test.ts +0 -168
- package/packages/cli/test/route-decision.test.ts +0 -134
- package/packages/cli/test/run-dir-sidecars.test.ts +0 -779
- package/packages/cli/test/scrub.test.ts +0 -936
- package/packages/cli/test/serial.test.ts +0 -65
- package/packages/cli/test/setup.test.ts +0 -535
- package/packages/cli/test/share-card.test.ts +0 -135
- package/packages/cli/test/shell-capture.test.ts +0 -184
- package/packages/cli/test/shell-frames-drain.test.ts +0 -91
- package/packages/cli/test/snapshot-retry.test.ts +0 -54
- package/packages/cli/test/support-claims.test.ts +0 -127
- package/packages/cli/test/sync-credentials.test.ts +0 -177
- package/packages/cli/test/sync.test.ts +0 -1709
- package/packages/cli/test/tls-intercept.test.ts +0 -548
- package/packages/cli/test/upstream-visible.test.ts +0 -314
- package/packages/cli/test/vercel-sdk-e2e.test.ts +0 -105
- package/packages/cli/test/version.test.ts +0 -20
- package/packages/cli/tsconfig.json +0 -40
- package/packages/core/package.json +0 -26
- package/packages/core/src/blobs.ts +0 -109
- package/packages/core/src/fetch.ts +0 -48
- package/packages/core/src/graph.ts +0 -450
- package/packages/core/src/index.ts +0 -8
- package/packages/core/src/paths.ts +0 -180
- package/packages/core/src/private-files.ts +0 -394
- package/packages/core/src/reader.ts +0 -175
- package/packages/core/src/redaction.ts +0 -826
- package/packages/core/src/writer.ts +0 -467
- package/packages/core/test/blobs.test.ts +0 -170
- package/packages/core/test/capture-contract.test.ts +0 -179
- package/packages/core/test/data-uri-redaction.test.ts +0 -758
- package/packages/core/test/graph.test.ts +0 -520
- package/packages/core/test/integration.test.ts +0 -148
- package/packages/core/test/paths-permission-failure.test.ts +0 -53
- package/packages/core/test/paths.test.ts +0 -339
- package/packages/core/test/private-files-owner.test.ts +0 -77
- package/packages/core/test/private-files-scratch-race.test.ts +0 -62
- package/packages/core/test/private-files.test.ts +0 -251
- package/packages/core/test/reader.test.ts +0 -271
- package/packages/core/test/redaction.test.ts +0 -525
- package/packages/core/test/writer.test.ts +0 -531
- package/packages/core/tsconfig.json +0 -6
- package/packages/fs-capture/package.json +0 -25
- package/packages/fs-capture/src/capture.ts +0 -193
- package/packages/fs-capture/src/git.ts +0 -131
- package/packages/fs-capture/src/index.ts +0 -3
- package/packages/fs-capture/src/shadow.ts +0 -751
- package/packages/fs-capture/test/capture.test.ts +0 -241
- package/packages/fs-capture/test/git.test.ts +0 -118
- package/packages/fs-capture/test/helpers.ts +0 -28
- package/packages/fs-capture/test/shadow.test.ts +0 -1058
- package/packages/fs-capture/tsconfig.json +0 -6
- package/packages/mcp-shim/package.json +0 -26
- package/packages/mcp-shim/src/cli.ts +0 -372
- package/packages/mcp-shim/src/framing.ts +0 -106
- package/packages/mcp-shim/src/index.ts +0 -4
- package/packages/mcp-shim/src/mock.ts +0 -194
- package/packages/mcp-shim/src/shim.ts +0 -126
- package/packages/mcp-shim/test/cli.test.ts +0 -489
- package/packages/mcp-shim/test/framing.test.ts +0 -143
- package/packages/mcp-shim/test/mock.test.ts +0 -200
- package/packages/mcp-shim/test/shim.test.ts +0 -287
- package/packages/mcp-shim/tsconfig.json +0 -5
- package/packages/node-instrument/package.json +0 -23
- package/packages/node-instrument/src/capture.ts +0 -505
- package/packages/node-instrument/src/index.ts +0 -3
- package/packages/node-instrument/src/install.ts +0 -186
- package/packages/node-instrument/src/rewrite.ts +0 -66
- package/packages/node-instrument/test/capture.test.ts +0 -423
- package/packages/node-instrument/test/install.test.ts +0 -427
- package/packages/node-instrument/test/rewrite.test.ts +0 -85
- package/packages/node-instrument/tsconfig.json +0 -9
- package/packages/plugin-api/package.json +0 -22
- package/packages/plugin-api/src/canonical.ts +0 -73
- package/packages/plugin-api/src/index.ts +0 -2
- package/packages/plugin-api/src/plugins.ts +0 -265
- package/packages/plugin-api/test/canonical.test.ts +0 -58
- package/packages/plugin-api/tsconfig.json +0 -5
- package/packages/providers/package.json +0 -26
- package/packages/providers/src/index.ts +0 -34
- package/packages/providers/src/pricing.ts +0 -140
- package/packages/providers/src/translate/anthropic.ts +0 -620
- package/packages/providers/src/translate/openai-responses.ts +0 -638
- package/packages/providers/src/translate/openai.ts +0 -630
- package/packages/providers/src/translate/sse.ts +0 -99
- package/packages/providers/src/translate/util.ts +0 -111
- package/packages/providers/test/anthropic-translate.test.ts +0 -687
- package/packages/providers/test/openai-responses-translate.test.ts +0 -377
- package/packages/providers/test/openai-translate.test.ts +0 -884
- package/packages/providers/test/pricing.test.ts +0 -171
- package/packages/providers/tsconfig.json +0 -6
- package/packages/proxy/package.json +0 -28
- package/packages/proxy/src/ca.ts +0 -376
- package/packages/proxy/src/der.ts +0 -152
- package/packages/proxy/src/dialects.ts +0 -413
- package/packages/proxy/src/forward.ts +0 -64
- package/packages/proxy/src/index.ts +0 -8
- package/packages/proxy/src/intercept.ts +0 -1554
- package/packages/proxy/src/matching.ts +0 -1056
- package/packages/proxy/src/retrieval.ts +0 -290
- package/packages/proxy/src/server.ts +0 -2875
- package/packages/proxy/src/tls-hosts.ts +0 -205
- package/packages/proxy/test/beta-headers.test.ts +0 -39
- package/packages/proxy/test/ca.test.ts +0 -146
- package/packages/proxy/test/forward.test.ts +0 -306
- package/packages/proxy/test/hop-by-hop.test.ts +0 -118
- package/packages/proxy/test/image-matching.test.ts +0 -129
- package/packages/proxy/test/indexing-concurrency.test.ts +0 -224
- package/packages/proxy/test/lookahead.test.ts +0 -160
- package/packages/proxy/test/malformed-body.test.ts +0 -73
- package/packages/proxy/test/matching.test.ts +0 -822
- package/packages/proxy/test/passthrough.test.ts +0 -599
- package/packages/proxy/test/recorded-upstream.test.ts +0 -253
- package/packages/proxy/test/responses-dialect.test.ts +0 -239
- package/packages/proxy/test/retrieval.test.ts +0 -589
- package/packages/proxy/test/server.test.ts +0 -1281
- package/packages/proxy/test/tls-hosts.test.ts +0 -190
- package/packages/proxy/test/tls-intercept.test.ts +0 -1769
- package/packages/proxy/tsconfig.json +0 -22
- package/packages/schema/package.json +0 -28
- package/packages/schema/src/constants.ts +0 -73
- package/packages/schema/src/index.ts +0 -3
- package/packages/schema/src/types.ts +0 -103
- package/packages/schema/src/validate.ts +0 -60
- package/packages/schema/test/schema-parity.test.ts +0 -116
- package/packages/schema/tsconfig.json +0 -5
- package/packages/shell-shim/package.json +0 -26
- package/packages/shell-shim/src/index.ts +0 -3
- package/packages/shell-shim/src/install.ts +0 -406
- package/packages/shell-shim/src/resolve.ts +0 -98
- package/packages/shell-shim/src/runner-bin.ts +0 -4
- package/packages/shell-shim/src/runner.ts +0 -149
- package/packages/shell-shim/test/resolve.test.ts +0 -125
- package/packages/shell-shim/test/shim.test.ts +0 -548
- package/packages/shell-shim/test/transport-race.test.ts +0 -109
- package/packages/shell-shim/tsconfig.json +0 -13
- package/packages/viewer/package.json +0 -25
- package/packages/viewer/src/bundle.ts +0 -36
- package/packages/viewer/src/client/main.ts +0 -248
- package/packages/viewer/src/credit.ts +0 -24
- package/packages/viewer/src/css.ts +0 -409
- package/packages/viewer/src/export.ts +0 -38
- package/packages/viewer/src/html.ts +0 -356
- package/packages/viewer/src/index.ts +0 -16
- package/packages/viewer/src/render.ts +0 -791
- package/packages/viewer/src/serve.ts +0 -150
- package/packages/viewer/src/trace-dir.ts +0 -140
- package/packages/viewer/test/bundle.test.ts +0 -47
- package/packages/viewer/test/client.test.ts +0 -378
- package/packages/viewer/test/export.test.ts +0 -131
- package/packages/viewer/test/fixtures.ts +0 -69
- package/packages/viewer/test/html.test.ts +0 -453
- package/packages/viewer/test/playback.test.ts +0 -54
- package/packages/viewer/test/render.test.ts +0 -714
- package/packages/viewer/test/serve.test.ts +0 -125
- package/packages/viewer/tsconfig.json +0 -6
- package/prompt/AIDER/deepseek-v4-1-flash-system-prompt.md +0 -29
- package/prompt/CLAUDECODE/claude-fable-5-1-print-system-prompt.md +0 -127
- package/prompt/CLAUDECODE/claude-fable-5-1-system-prompt.md +0 -169
- package/prompt/CLAUDECODE/claude-opus-4-8-system-prompt.md +0 -131
- package/prompt/CLAUDECODE/claude-opus-5-system-prompt.md +0 -149
- package/prompt/CLINE/openai-gpt-4o-mini-system-prompt.md +0 -37
- package/prompt/CODEX/gpt-5-6-luna-system-prompt.md +0 -183
- package/prompt/CODEX/gpt-5-6-sol-system-prompt.md +0 -218
- package/prompt/CODEX/gpt-6-1-sol-system-prompt.md +0 -242
- package/prompt/CODEX/gpt-6-astra-system-prompt.md +0 -170
- package/prompt/CODEX/gpt-6-luna-system-prompt.md +0 -223
- package/prompt/CODEX/gpt-6-sol-system-prompt.md +0 -226
- package/prompt/CRUSH/deepseek-v4-1-flash-system-prompt.md +0 -472
- package/prompt/CURSOR/grok-4-5-high-system-prompt.md +0 -40
- package/prompt/DSH/deepseek-flash-system-prompt.md +0 -31
- package/prompt/GEMINICLI/gemini-3-5-flash-system-prompt.md +0 -168
- package/prompt/GOOSE/deepseek-v4-1-flash-system-prompt.md +0 -180
- package/prompt/HACKERAI/openai-gpt-4o-mini-system-prompt.md +0 -70
- package/prompt/HERMES/nemotron-3-5-lightning-free-system-prompt.md +0 -111
- package/prompt/KILOCODE/kilo-auto-free-system-prompt.md +0 -129
- package/prompt/MCODE/deepseek-deepseek-v4-flash-free-coding-system-prompt.md +0 -128
- package/prompt/MCODE/deepseek-deepseek-v4-flash-free-tui-system-prompt.md +0 -119
- package/prompt/MCODE/deepseek-deepseek-v4-flash-free-work-system-prompt.md +0 -140
- package/prompt/MIMOCODE/mimo-v2-5-pro-system-prompt.md +0 -411
- package/prompt/MIMOCODE/mimo-v2-5-system-prompt.md +0 -411
- package/prompt/MUSECODE/muse-spark-1-3-system-prompt.md +0 -78
- package/prompt/OPENCLAW/gpt-5-6-sol-system-prompt.md +0 -333
- package/prompt/OPENCODE/big-pickle-system-prompt.md +0 -114
- package/prompt/OPENCODE/gpt-5-6-sol-system-prompt.md +0 -126
- package/prompt/OPENCODE/ling-3-0-flash-fin-free-system-prompt.md +0 -114
- package/prompt/OPENCODE/mimo-v2-5-free-system-prompt.md +0 -114
- package/prompt/OPENCODE/muse-spark-1-2-contributor-free-system-prompt.md +0 -84
- package/prompt/OPENCODE/nemotron-3-5-lightning-free-system-prompt.md +0 -114
- package/prompt/OPENCODE/nemotron-3-ultra-free-system-prompt.md +0 -114
- package/prompt/PI/openai-gpt-4o-mini-system-prompt.md +0 -31
- package/prompt/QWENCODE/gpt-5-6-sol-system-prompt.md +0 -290
- package/prompt/ZCODE/glm-4-6-system-prompt.md +0 -91
- package/python/README.md +0 -132
- package/python/agenticreplay_trace/__init__.py +0 -58
- package/python/agenticreplay_trace/graph.py +0 -137
- package/python/agenticreplay_trace/models.py +0 -491
- package/python/agenticreplay_trace/py.typed +0 -0
- package/python/agenticreplay_trace/reader.py +0 -295
- package/python/conftest.py +0 -12
- package/python/examples/analyze.py +0 -114
- package/python/pyproject.toml +0 -45
- package/python/tests/conftest.py +0 -102
- package/python/tests/test_capture_contract.py +0 -24
- package/python/tests/test_conformance.py +0 -235
- package/python/tests/test_examples.py +0 -60
- package/python/tests/test_graph.py +0 -247
- package/python/tests/test_models.py +0 -342
- package/python/tests/test_reader.py +0 -423
- package/python-langgraph/README.md +0 -159
- package/python-langgraph/agenticreplay_langgraph/__init__.py +0 -21
- package/python-langgraph/agenticreplay_langgraph/handler.py +0 -317
- package/python-langgraph/agenticreplay_langgraph/hook.py +0 -168
- package/python-langgraph/pyproject.toml +0 -35
- package/python-langgraph/tests/test_handler.py +0 -370
- package/python-langgraph/tests/test_integration.py +0 -484
- package/python-openai-agents/README.md +0 -145
- package/python-openai-agents/agenticreplay_openai_agents/__init__.py +0 -11
- package/python-openai-agents/agenticreplay_openai_agents/processor.py +0 -331
- package/python-openai-agents/pyproject.toml +0 -34
- package/python-openai-agents/tests/test_processor.py +0 -457
- package/scripts/check-neutrality.mjs +0 -62
- package/scripts/codex-client-acceptance.mjs +0 -133
- package/scripts/conformance.mjs +0 -245
- package/scripts/fidelity.mjs +0 -170
- package/scripts/hermes-client-acceptance.mjs +0 -158
- package/scripts/host-client-acceptance.mjs +0 -732
- package/scripts/installed-smoke.mjs +0 -316
- package/scripts/publish-order.mjs +0 -96
- package/scripts/regenerate-quickstart.mjs +0 -79
- package/scripts/render-cards.mjs +0 -178
- package/scripts/render-demo.mjs +0 -139
- package/scripts/render-sync-card.mjs +0 -96
- package/scripts/set-version.mjs +0 -152
- package/scripts/set-version.test.ts +0 -220
- package/scripts/sync-server-json.mjs +0 -13
- package/scripts/update-opencode-api-bases.mjs +0 -29
- package/server.json +0 -28
- package/skills/agenticreplay-replay/SKILL.md +0 -211
- package/spec/agenticreplay-trace-v0.md +0 -223
- package/test/integrations/README.md +0 -133
- package/test/integrations/agents/ai_sdk_openai.mjs +0 -24
- package/test/integrations/agents/anthropic_sdk.py +0 -10
- package/test/integrations/agents/browser_use_agent.py +0 -24
- package/test/integrations/agents/crewai_agent.py +0 -44
- package/test/integrations/agents/crewai_model_names.py +0 -72
- package/test/integrations/agents/hardcoded_origin.mjs +0 -13
- package/test/integrations/agents/haystack_pipeline.py +0 -28
- package/test/integrations/agents/haystack_rag.py +0 -35
- package/test/integrations/agents/langgraph_agent.py +0 -54
- package/test/integrations/agents/langgraph_nodes.py +0 -62
- package/test/integrations/agents/litellm_agent.py +0 -11
- package/test/integrations/agents/llama_index_agent.py +0 -27
- package/test/integrations/agents/llama_index_model_names.py +0 -67
- package/test/integrations/agents/mastra_agent.mjs +0 -20
- package/test/integrations/agents/mcp_agent.py +0 -44
- package/test/integrations/agents/mcp_server.py +0 -20
- package/test/integrations/agents/openai_agents_handoff.py +0 -52
- package/test/integrations/agents/openai_agents_sdk.py +0 -29
- package/test/integrations/agents/openai_async.py +0 -17
- package/test/integrations/agents/openai_sdk.py +0 -10
- package/test/integrations/agents/openhands_agent.py +0 -34
- package/test/integrations/agents/rag_pipeline.py +0 -79
- package/test/integrations/agents/vision_agent.py +0 -63
- package/test/integrations/run.mjs +0 -719
- package/test/integrations/stub-origin.mjs +0 -229
- package/tsconfig.base.json +0 -20
- package/tsconfig.json +0 -41
- package/vitest.config.ts +0 -39
- package/vitest.setup.ts +0 -25
- /package/{packages → node_modules/@agenticreplay}/schema/schema/event.schema.json +0 -0
- /package/{packages → node_modules/@agenticreplay}/schema/schema/manifest.schema.json +0 -0
|
@@ -0,0 +1,2291 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { AUTH_REQUEST_HEADERS, AUTH_RESPONSE_HEADERS, BINARY_BODY_PREFIX, Redactor, fetchPinned, } from '@agenticreplay/core';
|
|
4
|
+
import { anthropicToCanonicalRequest, anthropicToCanonicalResponse, canonicalToAnthropicRequest, canonicalToAnthropicResponse, canonicalToAnthropicSse, canonicalToOpenaiRequest, canonicalToOpenaiResponse, canonicalToOpenaiSse, canonicalToResponsesRequest, canonicalToResponsesResponse, canonicalToResponsesSse, openaiToCanonicalRequest, openaiToCanonicalResponse, parseAnthropicSse, parseOpenaiSse, parseResponsesSse, responsesToCanonicalRequest, responsesToCanonicalResponse, } from '@agenticreplay/providers';
|
|
5
|
+
import { anthropicDialect, codexDialect, openaiDialect, responsesDialect, selectDialect, } from './dialects.js';
|
|
6
|
+
import { decodeForwardPath } from './forward.js';
|
|
7
|
+
import { consume, defaultRetrievalRules, indexRetrievals, selectRetrievalRule, } from './retrieval.js';
|
|
8
|
+
import { attachTlsIntercept, decodeBody, DEFAULT_MAX_CAPTURED_BYTES, } from './intercept.js';
|
|
9
|
+
import { RequestMatcher } from './matching.js';
|
|
10
|
+
function scalarHeaders(headers) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
13
|
+
out[key] = Array.isArray(value) ? value.join(', ') : value;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function captureHeaderTransformations(original, recorded) {
|
|
18
|
+
const excluded = Object.keys(original).filter((name) => !(name in recorded));
|
|
19
|
+
if (excluded.length === 0)
|
|
20
|
+
return [];
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
kind: 'header_excluded',
|
|
24
|
+
location: 'request.headers',
|
|
25
|
+
rule: 'authentication-and-hop-by-hop-header-exclusion',
|
|
26
|
+
count: excluded.length,
|
|
27
|
+
reason: excluded
|
|
28
|
+
.map((name) => name.toLowerCase())
|
|
29
|
+
.sort()
|
|
30
|
+
.join(', '),
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
function captureResponseHeaders(headers, decoded = false) {
|
|
35
|
+
const result = {};
|
|
36
|
+
headers.forEach((value, name) => {
|
|
37
|
+
const key = name.toLowerCase();
|
|
38
|
+
if (AUTH_RESPONSE_HEADERS.includes(key))
|
|
39
|
+
return;
|
|
40
|
+
if (decoded && (key === 'content-encoding' || key === 'content-length'))
|
|
41
|
+
return;
|
|
42
|
+
result[key] = value;
|
|
43
|
+
});
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
function captureResponseHeaderTransformations(headers) {
|
|
47
|
+
const excluded = [];
|
|
48
|
+
headers.forEach((_value, name) => {
|
|
49
|
+
if (AUTH_RESPONSE_HEADERS.includes(name.toLowerCase()))
|
|
50
|
+
excluded.push(name.toLowerCase());
|
|
51
|
+
});
|
|
52
|
+
return [
|
|
53
|
+
...(excluded.length === 0
|
|
54
|
+
? []
|
|
55
|
+
: [
|
|
56
|
+
{
|
|
57
|
+
kind: 'header_excluded',
|
|
58
|
+
location: 'response.headers',
|
|
59
|
+
rule: 'authentication-header-exclusion',
|
|
60
|
+
count: excluded.length,
|
|
61
|
+
reason: [...new Set(excluded)].sort().join(', '),
|
|
62
|
+
},
|
|
63
|
+
]),
|
|
64
|
+
{
|
|
65
|
+
kind: 'encoding',
|
|
66
|
+
location: 'response.headers',
|
|
67
|
+
rule: 'fetch-headers-boundary-normalization',
|
|
68
|
+
count: 1,
|
|
69
|
+
reason: 'fetch Headers lowercases names and combines repeated fields before capture',
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
function captureEncodingTransformation(location, encoding) {
|
|
74
|
+
const values = (encoding ?? '')
|
|
75
|
+
.split(',')
|
|
76
|
+
.map((value) => value.trim().toLowerCase())
|
|
77
|
+
.filter((value) => value !== '' && value !== 'identity');
|
|
78
|
+
if (values.length === 0)
|
|
79
|
+
return [];
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
kind: 'decompression',
|
|
83
|
+
location,
|
|
84
|
+
rule: 'content-encoding',
|
|
85
|
+
count: values.length,
|
|
86
|
+
reason: values.join(', '),
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
function protocolTranslationTransformation(from, to, location = 'response.body') {
|
|
91
|
+
return {
|
|
92
|
+
kind: 'encoding',
|
|
93
|
+
location,
|
|
94
|
+
rule: 'provider-protocol-translation',
|
|
95
|
+
count: 1,
|
|
96
|
+
reason: `${from} response translated to ${to} for the client`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function modelOverrideTransformation(from, to) {
|
|
100
|
+
return {
|
|
101
|
+
kind: 'encoding',
|
|
102
|
+
location: 'request.body',
|
|
103
|
+
rule: 'model-override-and-serialization',
|
|
104
|
+
count: 1,
|
|
105
|
+
reason: `${from} request model overridden with ${to} and serialized for the live fork`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function captureUrlTransformations(origin) {
|
|
109
|
+
const scrubbed = recordableOrigin(origin);
|
|
110
|
+
if (scrubbed === undefined || scrubbed === origin)
|
|
111
|
+
return [];
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
kind: 'url_scrubbed',
|
|
115
|
+
location: 'destination.url',
|
|
116
|
+
rule: 'credential-and-query-secret-scrub',
|
|
117
|
+
count: 1,
|
|
118
|
+
reason: 'destination credentials or secret query parameters removed before persistence',
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
function binaryBodyTransformation(location, body) {
|
|
123
|
+
if (!body.startsWith(BINARY_BODY_PREFIX))
|
|
124
|
+
return [];
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
kind: 'encoding',
|
|
128
|
+
location,
|
|
129
|
+
rule: 'binary-body-base64-persistence',
|
|
130
|
+
count: 1,
|
|
131
|
+
reason: 'invalid UTF-8 preserved as base64 because the transport body is binary',
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Beta flags that belong to the model that was recorded, not to the request.
|
|
137
|
+
*
|
|
138
|
+
* `anthropic-beta` is a header, so substituting a model in the body leaves it untouched — and a
|
|
139
|
+
* fork of a run made on a 1M-context model onto one without that entitlement comes back
|
|
140
|
+
* `400 The long context beta is not yet available for this subscription`. The failure names the
|
|
141
|
+
* subscription rather than the flag, so it reads as an account problem and not as agenticreplay carrying
|
|
142
|
+
* over something it should have dropped.
|
|
143
|
+
*
|
|
144
|
+
* Only entitlement-gated flags are removed. The rest of the header is what the harness needs to
|
|
145
|
+
* speak its own protocol — tool shapes, output formats — and dropping those would break the fork
|
|
146
|
+
* in a way that is much harder to see than a 400.
|
|
147
|
+
*/
|
|
148
|
+
const MODEL_GATED_BETAS = [/^context-\d+m\b/i];
|
|
149
|
+
/** `anthropic-beta` with the recorded model's entitlements taken out, or undefined if empty. */
|
|
150
|
+
export function betasForModelChange(value) {
|
|
151
|
+
const kept = value
|
|
152
|
+
.split(',')
|
|
153
|
+
.map((flag) => flag.trim())
|
|
154
|
+
.filter((flag) => flag !== '' && !MODEL_GATED_BETAS.some((re) => re.test(flag)));
|
|
155
|
+
return kept.length > 0 ? kept.join(',') : undefined;
|
|
156
|
+
}
|
|
157
|
+
/** The outbound headers for a live call, reconciled with a model the recording did not use. */
|
|
158
|
+
function headersForModel(headers, forkModel) {
|
|
159
|
+
if (forkModel === undefined)
|
|
160
|
+
return headers;
|
|
161
|
+
const out = {};
|
|
162
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
163
|
+
if (key.toLowerCase() !== 'anthropic-beta') {
|
|
164
|
+
out[key] = value;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const kept = betasForModelChange(value);
|
|
168
|
+
if (kept !== undefined)
|
|
169
|
+
out[key] = kept;
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* An origin with the parts a credential rides in taken out.
|
|
175
|
+
*
|
|
176
|
+
* `agenticreplay setup --gateway` accepts whatever URL it is handed and stores it in `~/.agenticreplay/config.json`,
|
|
177
|
+
* and a gateway that authenticates by URL rather than by header is handed one that carries the key:
|
|
178
|
+
* `https://user:pw@gw.example` or `https://gw.example?key=…`. The proxy has to *send* that — it is
|
|
179
|
+
* how the request authenticates — but recording it verbatim would have written the key into every
|
|
180
|
+
* trace made on that machine, breaking the promise stated where the key is read:
|
|
181
|
+
*
|
|
182
|
+
* > the proxy adds it to the outbound request only, while what gets recorded is derived from the
|
|
183
|
+
* > *incoming* request with auth stripped, so a gateway key agenticreplay injects is invisible to the
|
|
184
|
+
* > recording by construction rather than by a rule someone has to remember.
|
|
185
|
+
*
|
|
186
|
+
* So the stripping happens here, in the one place an exchange is built, rather than at each call
|
|
187
|
+
* site — for the same reason that sentence gives.
|
|
188
|
+
*
|
|
189
|
+
* The path is kept. It is not a credential, and it is load-bearing: a gateway serving tenants at
|
|
190
|
+
* `/team-a/v1` and `/team-b/v1` answers from two different places, and the exchange's own `path`
|
|
191
|
+
* is the *client's* (`/chat/completions`), not the upstream's base. Only userinfo, query and
|
|
192
|
+
* fragment come out.
|
|
193
|
+
*
|
|
194
|
+
* An origin that will not parse is dropped rather than passed through: it cannot be sanitised, and
|
|
195
|
+
* absent already means "not recorded".
|
|
196
|
+
*/
|
|
197
|
+
export function recordableOrigin(origin) {
|
|
198
|
+
if (origin === undefined)
|
|
199
|
+
return undefined;
|
|
200
|
+
try {
|
|
201
|
+
const url = new URL(origin);
|
|
202
|
+
const port = url.port === '' ? '' : `:${url.port}`;
|
|
203
|
+
// `new URL('https://h').pathname` is '/', which is not part of how anyone writes an origin.
|
|
204
|
+
const path = url.pathname === '/' ? '' : url.pathname.replace(/\/$/, '');
|
|
205
|
+
return `${url.protocol}//${url.hostname}${port}${path}`;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Why a string cannot be used as an origin at all, or nothing if it can.
|
|
213
|
+
*
|
|
214
|
+
* Narrower than it first was, because the first version refused configurations that work. A
|
|
215
|
+
* gateway that authenticates by query — `https://gw.example/v1?key=…` — is an ordinary way to
|
|
216
|
+
* configure one, and undici sends it without complaint; refusing it broke a working setup to close
|
|
217
|
+
* a leak that was never about sending. Userinfo is the same story one step along: undici does
|
|
218
|
+
* reject `https://u:pw@gw` at request time, but it is *sanitisable* — {@link recordableOrigin}
|
|
219
|
+
* removes it cleanly — and the recording path already accepts it and keeps it out of the trace.
|
|
220
|
+
*
|
|
221
|
+
* What is left is the case where there is nothing to sanitise, because there is no origin. A
|
|
222
|
+
* gateway typed without a scheme parses with the username as the protocol:
|
|
223
|
+
*
|
|
224
|
+
* new URL('myuser:PASSWORD@gw.example/v1') // protocol 'myuser:', pathname the rest
|
|
225
|
+
*
|
|
226
|
+
* so {@link recordableOrigin} answers `myuser://PASSWORD@gw.example/v1` — a value, which means a
|
|
227
|
+
* caller guarding with `?? url` never notices, and the password is on the line anyway. There is no
|
|
228
|
+
* fixing that by rewriting: with no scheme there is no telling which half of `a:b` was meant as
|
|
229
|
+
* the host. And nothing refused here could have worked — undici cannot parse it either.
|
|
230
|
+
*/
|
|
231
|
+
export function unusableOrigin(origin) {
|
|
232
|
+
let url;
|
|
233
|
+
try {
|
|
234
|
+
url = new URL(origin);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return 'it is not a URL — an origin needs a scheme, like https://gateway.example';
|
|
238
|
+
}
|
|
239
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
240
|
+
// Named without echoing the value: a scheme-less URL puts the username here, so `protocol` is
|
|
241
|
+
// the one part of it that is safe to quote back.
|
|
242
|
+
return `its scheme is "${url.protocol.replace(/:$/, '')}" — an origin has to be http or https`;
|
|
243
|
+
}
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Free text with the credential taken out of any URL it names.
|
|
248
|
+
*
|
|
249
|
+
* For error messages, which nobody composes and everybody prints. A failed `fetch` reports the URL
|
|
250
|
+
* it was given, so an origin configured as `https://user:pw@gw` or `https://gw?key=…` arrives
|
|
251
|
+
* inside the exception text and goes straight to a terminal:
|
|
252
|
+
*
|
|
253
|
+
* warn gateway.unreachable why="Request cannot be constructed from a URL that includes
|
|
254
|
+
* credentials: http://someone:PASSWORD@127.0.0.1:50164/v1/models"
|
|
255
|
+
*
|
|
256
|
+
* {@link recordableOrigin} cannot help there — it takes a URL, and this is prose with a URL in it.
|
|
257
|
+
*
|
|
258
|
+
* The query goes as well as the userinfo. That loses the occasional harmless parameter from an
|
|
259
|
+
* error message, which is the right trade: a key in a query is the commonest way a gateway
|
|
260
|
+
* authenticates by URL, and an error string is not where anyone should be reading parameters back.
|
|
261
|
+
*/
|
|
262
|
+
export function withoutCredentials(text) {
|
|
263
|
+
return (text
|
|
264
|
+
// Up to the *last* `@` before the path, not the first. `[^/\s@]*` could not cross an `@`, so
|
|
265
|
+
// an ordinary password containing one — `p@ssw0rd` — ended the match early and the remainder
|
|
266
|
+
// stayed in userinfo position: `https://myuser:p@ssw0rd@gw` came out as `https://ssw0rd@gw`.
|
|
267
|
+
// `[^\s/]*` may cross `@` and backtracks to the last one, and still cannot reach past the
|
|
268
|
+
// path, so an `@` in a path segment (`/v1/@scope/pkg`) is not mistaken for userinfo.
|
|
269
|
+
.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/]*@/gi, '$1')
|
|
270
|
+
.replace(/([a-z][a-z0-9+.-]*:\/\/[^\s?#"']*)\?[^\s#"']*/gi, '$1')
|
|
271
|
+
// And once more without a scheme. undici reports an origin it could not parse verbatim —
|
|
272
|
+
// `Failed to parse URL from my.gateway.example?key=…` — and both rules above need a
|
|
273
|
+
// `scheme://` to fire, so that string went through untouched into the 500 body the agent
|
|
274
|
+
// prints. A host-shaped token is one with a dot in it and no whitespace. The cost is that
|
|
275
|
+
// an error naming a file ending in '?' loses the question mark, which is a fair price in
|
|
276
|
+
// prose nobody reads for punctuation.
|
|
277
|
+
.replace(/([a-z0-9][a-z0-9.-]*\.[a-z][a-z0-9-]*(?::\d+)?[^\s?#"']*)\?[^\s#"']*/gi, '$1'));
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* An origin agenticreplay could not parse, reduced to the host it names and nothing else.
|
|
281
|
+
*
|
|
282
|
+
* Three rounds of review here, each finding a shape the last one let through, and the lesson is
|
|
283
|
+
* the same each time: a string no parser accepted has no structure to rely on, so nothing may be
|
|
284
|
+
* kept because of where it sits — only because of what it is made of.
|
|
285
|
+
*
|
|
286
|
+
* `withoutCredentials` went first. It is written for prose and its rules need prose shapes: a
|
|
287
|
+
* dotted host for the scheme-less query rule, no whitespace inside userinfo. `gw?key=SECRET123`,
|
|
288
|
+
* `localhost?key=…`, `192.168.0.1?key=…` and `https://user:pa ss@gw` all went through it whole.
|
|
289
|
+
*
|
|
290
|
+
* Cutting by position went second, and left `https://gw key=SECRET123`: a secret in a
|
|
291
|
+
* whitespace-separated token after the host is still "after the last `@`", so it was kept as part
|
|
292
|
+
* of the host.
|
|
293
|
+
*
|
|
294
|
+
* So the last step is a whitelist — the characters a host and a port are made of, and the first
|
|
295
|
+
* one that is not ends it. A path is not returned at all: the success branch of `originParts`
|
|
296
|
+
* gives `url.hostname`, the exchange records its request path in its own field, and a path that
|
|
297
|
+
* reached here unparsed could carry a secret as easily as a query could.
|
|
298
|
+
*
|
|
299
|
+
* An origin with no host left — `https://user:pass@` — yields the empty string, which is the
|
|
300
|
+
* honest answer: there was no host in it to name.
|
|
301
|
+
*
|
|
302
|
+
* `redactString` still runs over the result at the call site, as a backstop for a long key rather
|
|
303
|
+
* than the thing doing the work: its shape rules and 20-character entropy floor catch neither
|
|
304
|
+
* `hunter2` nor `SECRET123`.
|
|
305
|
+
*/
|
|
306
|
+
export function scrubOrigin(origin) {
|
|
307
|
+
const withoutQuery = origin.replace(/[?#][\s\S]*$/, '');
|
|
308
|
+
const schemeEnd = withoutQuery.indexOf('://');
|
|
309
|
+
const rest = schemeEnd === -1 ? withoutQuery : withoutQuery.slice(schemeEnd + 3);
|
|
310
|
+
const pathStart = rest.indexOf('/');
|
|
311
|
+
const authority = pathStart === -1 ? rest : rest.slice(0, pathStart);
|
|
312
|
+
const at = authority.lastIndexOf('@');
|
|
313
|
+
const host = at === -1 ? authority : authority.slice(at + 1);
|
|
314
|
+
// A host, then a port only if it is one. Allowing a bare `:` for the port let a whole
|
|
315
|
+
// `user:password` through when the origin had no `@` to cut at — `https://myuser:hunter2`
|
|
316
|
+
// came back whole — because userinfo and host:port are the same shape until something
|
|
317
|
+
// says which it is. Digits after the colon say it. An IPv6 literal keeps its brackets,
|
|
318
|
+
// which is what separates its colons from that one.
|
|
319
|
+
return /^(?:\[[0-9A-Fa-f:.]+\]|[A-Za-z0-9._-]+)(?::[0-9]+)?/.exec(host)?.[0] ?? '';
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Hop-by-hop headers, dropped before forwarding because they describe *this* connection.
|
|
323
|
+
*
|
|
324
|
+
* The full RFC 7230 §6.1 set, plus `host` and `accept-encoding`, which describe this hop for the
|
|
325
|
+
* same reason. `transfer-encoding` is the one that was missing and the one that bites: an SDK
|
|
326
|
+
* that hands `fetch` a `Request` — or any stream body — sends `transfer-encoding: chunked` and no
|
|
327
|
+
* `content-length`, and agenticreplay then copied that header onto an outbound call whose body it had
|
|
328
|
+
* already buffered. undici refuses the contradiction, so the agent got
|
|
329
|
+
* `500 {"error":{"message":"TypeError: fetch failed"}}` on every such turn, with nothing in the
|
|
330
|
+
* message pointing at a header, at agenticreplay, or at the agent.
|
|
331
|
+
*/
|
|
332
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
333
|
+
'host',
|
|
334
|
+
'connection',
|
|
335
|
+
'content-length',
|
|
336
|
+
'accept-encoding',
|
|
337
|
+
'transfer-encoding',
|
|
338
|
+
'te',
|
|
339
|
+
'trailer',
|
|
340
|
+
'upgrade',
|
|
341
|
+
'keep-alive',
|
|
342
|
+
'proxy-connection',
|
|
343
|
+
'proxy-authenticate',
|
|
344
|
+
]);
|
|
345
|
+
/**
|
|
346
|
+
* Auth material. Forwarded upstream — an agent that cannot authenticate is an agent that cannot
|
|
347
|
+
* run — but never written into a trace. Claude Code under a subscription login sends its own
|
|
348
|
+
* `authorization: Bearer` and ignores any injected key, so dropping these would break it outright.
|
|
349
|
+
* §7 says never *write* auth material, which is a different requirement from never relaying it.
|
|
350
|
+
*
|
|
351
|
+
* From core, not restated here: this list had drifted from the interceptor's copy of it, and the
|
|
352
|
+
* two headers only the interceptor knew about were an Azure key and a Google one.
|
|
353
|
+
*/
|
|
354
|
+
const SECRET_REQUEST_HEADERS = new Set(AUTH_REQUEST_HEADERS);
|
|
355
|
+
export function defaultDialects() {
|
|
356
|
+
return [
|
|
357
|
+
codexDialect(),
|
|
358
|
+
anthropicDialect({
|
|
359
|
+
toCanonicalRequest: anthropicToCanonicalRequest,
|
|
360
|
+
toCanonicalResponse: anthropicToCanonicalResponse,
|
|
361
|
+
parseSse: parseAnthropicSse,
|
|
362
|
+
fromCanonicalRequest: canonicalToAnthropicRequest,
|
|
363
|
+
fromCanonicalResponse: canonicalToAnthropicResponse,
|
|
364
|
+
toSse: canonicalToAnthropicSse,
|
|
365
|
+
}),
|
|
366
|
+
openaiDialect({
|
|
367
|
+
toCanonicalRequest: openaiToCanonicalRequest,
|
|
368
|
+
toCanonicalResponse: openaiToCanonicalResponse,
|
|
369
|
+
parseSse: parseOpenaiSse,
|
|
370
|
+
fromCanonicalRequest: canonicalToOpenaiRequest,
|
|
371
|
+
fromCanonicalResponse: canonicalToOpenaiResponse,
|
|
372
|
+
toSse: canonicalToOpenaiSse,
|
|
373
|
+
}),
|
|
374
|
+
// After the chat dialect deliberately: see the note on `responsesDialect`.
|
|
375
|
+
responsesDialect({
|
|
376
|
+
toCanonicalRequest: responsesToCanonicalRequest,
|
|
377
|
+
toCanonicalResponse: responsesToCanonicalResponse,
|
|
378
|
+
parseSse: parseResponsesSse,
|
|
379
|
+
fromCanonicalRequest: canonicalToResponsesRequest,
|
|
380
|
+
fromCanonicalResponse: canonicalToResponsesResponse,
|
|
381
|
+
toSse: canonicalToResponsesSse,
|
|
382
|
+
}),
|
|
383
|
+
];
|
|
384
|
+
}
|
|
385
|
+
class BodyLimitError extends Error {
|
|
386
|
+
bytes;
|
|
387
|
+
limit;
|
|
388
|
+
constructor(bytes, limit) {
|
|
389
|
+
super(`request body exceeded capture limit of ${limit} bytes`);
|
|
390
|
+
this.bytes = bytes;
|
|
391
|
+
this.limit = limit;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
async function readBody(req, limit) {
|
|
395
|
+
const chunks = [];
|
|
396
|
+
let bytes = 0;
|
|
397
|
+
for await (const chunk of req) {
|
|
398
|
+
const buffer = chunk;
|
|
399
|
+
bytes += buffer.length;
|
|
400
|
+
if (bytes > limit) {
|
|
401
|
+
req.resume();
|
|
402
|
+
throw new BodyLimitError(bytes, limit);
|
|
403
|
+
}
|
|
404
|
+
chunks.push(buffer);
|
|
405
|
+
}
|
|
406
|
+
return { body: Buffer.concat(chunks), bytes };
|
|
407
|
+
}
|
|
408
|
+
/** Is this something a dialect could parse at all? Cheap guard, run before anything is forwarded. */
|
|
409
|
+
function isReadable(rawBody) {
|
|
410
|
+
try {
|
|
411
|
+
const parsed = JSON.parse(rawBody);
|
|
412
|
+
return typeof parsed === 'object' && parsed !== null;
|
|
413
|
+
}
|
|
414
|
+
catch {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function json(res, status, body) {
|
|
419
|
+
const text = JSON.stringify(body);
|
|
420
|
+
res.writeHead(status, { 'content-type': 'application/json' });
|
|
421
|
+
res.end(text);
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Whether two origin strings name the same scheme, host and port — the test a credential must
|
|
425
|
+
* pass before travelling with a request. Same shape as the copy in the CLI's upstream plan,
|
|
426
|
+
* which cannot be imported from here: the proxy is a leaf, and the comparison is two lines.
|
|
427
|
+
*/
|
|
428
|
+
function sameOrigin(a, b) {
|
|
429
|
+
try {
|
|
430
|
+
return new URL(a).origin === new URL(b).origin;
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
return a.replace(/\/+$/, '') === b.replace(/\/+$/, '');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
export async function createProxy(options) {
|
|
437
|
+
const dialects = options.dialects ?? defaultDialects();
|
|
438
|
+
// PINNED BY DEFAULT (orcacode-review). This proxy is handed the stored gateway
|
|
439
|
+
// key as `upstreamHeaders` and attaches it to both live call sites, so it is a
|
|
440
|
+
// carrier of exactly the credential fetchPinned exists to protect — and it
|
|
441
|
+
// escaped the CLI's fence twice over: that fence walked only packages/cli/src,
|
|
442
|
+
// and this call is an ALIAS, so a scan for a bare `fetch(` would not have seen
|
|
443
|
+
// it even with the wider scope.
|
|
444
|
+
//
|
|
445
|
+
// `fetchImpl` stays the seam the tests inject through; only the default moves.
|
|
446
|
+
const doFetch = options.fetchImpl ?? fetchPinned;
|
|
447
|
+
const recorded = options.exchanges ?? [];
|
|
448
|
+
const captured = [];
|
|
449
|
+
// Plain base-URL traffic needs the same aggregate bound as intercepted traffic. This counter
|
|
450
|
+
// covers bodies retained by concurrent fetch responses while each response still streams to the
|
|
451
|
+
// caller; it is released only after that response has been materialized into evidence.
|
|
452
|
+
let plainBufferedBytes = 0;
|
|
453
|
+
const plainBufferLimit = options.maxBufferedBytes ?? 256 * 1024 * 1024;
|
|
454
|
+
const stats = {
|
|
455
|
+
mode: options.mode,
|
|
456
|
+
recorded: recorded.length,
|
|
457
|
+
matchedExact: 0,
|
|
458
|
+
matchedInexact: 0,
|
|
459
|
+
divergences: 0,
|
|
460
|
+
liveCalls: 0,
|
|
461
|
+
unmatched: 0,
|
|
462
|
+
intercepted: 0,
|
|
463
|
+
tunnelled: 0,
|
|
464
|
+
passedThrough: 0,
|
|
465
|
+
retrievalServed: 0,
|
|
466
|
+
retrievalTotal: 0,
|
|
467
|
+
retrievalLive: 0,
|
|
468
|
+
retrievalReordered: 0,
|
|
469
|
+
reordered: 0,
|
|
470
|
+
};
|
|
471
|
+
// In hybrid mode only the exchanges below the fork point are replayable; everything at or above
|
|
472
|
+
// it must go live, which is exactly what makes a fork a fork.
|
|
473
|
+
const replayable = options.mode === 'hybrid' ? recorded.slice(0, options.forkAt ?? recorded.length) : recorded;
|
|
474
|
+
/**
|
|
475
|
+
* The most requests this proxy has had open at once, and the count it is derived from.
|
|
476
|
+
*
|
|
477
|
+
* Only the proxy can know this, and the matcher needs it: a pipeline that issues ten requests
|
|
478
|
+
* together is answered in whatever order the origin finished them, so a replay's request can
|
|
479
|
+
* arrive up to nine positions from the cursor. See {@link LOOKAHEAD}.
|
|
480
|
+
*/
|
|
481
|
+
let inFlight = 0;
|
|
482
|
+
let peakInFlight = 0;
|
|
483
|
+
function opened() {
|
|
484
|
+
inFlight += 1;
|
|
485
|
+
if (inFlight > peakInFlight)
|
|
486
|
+
peakInFlight = inFlight;
|
|
487
|
+
}
|
|
488
|
+
function closed() {
|
|
489
|
+
if (inFlight > 0)
|
|
490
|
+
inFlight -= 1;
|
|
491
|
+
}
|
|
492
|
+
// The recorded requests carry placeholders where secrets were; a live replay request carries the
|
|
493
|
+
// secrets themselves. Without a redactor on this side the two are never in the same
|
|
494
|
+
// representation, and a recording of any real harness — whose own system prompt carries a
|
|
495
|
+
// session id — cannot match itself.
|
|
496
|
+
// Shared with the passthrough failure path, which writes an upstream's own error message into
|
|
497
|
+
// the trace: a gateway that quotes the call it refused can quote the credential with it, and §7
|
|
498
|
+
// says never write it — not "write it and let a later layer catch it".
|
|
499
|
+
const redactor = new Redactor();
|
|
500
|
+
const matcher = new RequestMatcher(replayable.map((e) => e.canonicalRequest), { redactor, concurrency: () => peakInFlight });
|
|
501
|
+
/**
|
|
502
|
+
* The recording's retrieval calls, by key, ready to be consumed one at a time.
|
|
503
|
+
*
|
|
504
|
+
* Not filtered by the fork point. A fork keeps replaying these past the cursor on purpose: the
|
|
505
|
+
* vector for a paragraph does not depend on which chat model answers the question afterwards,
|
|
506
|
+
* so re-embedding it live would spend money to recompute an identical value and leave the
|
|
507
|
+
* fork's index differing from its parent's for no reason. See `RetrievalRule.forkable`.
|
|
508
|
+
*/
|
|
509
|
+
const retrievalRules = options.retrievalRules ?? defaultRetrievalRules();
|
|
510
|
+
const retrievalIndex = indexRetrievals(options.retrievals ?? []);
|
|
511
|
+
stats.retrievalTotal = (options.retrievals ?? []).length;
|
|
512
|
+
const server = createServer((req, res) => {
|
|
513
|
+
opened();
|
|
514
|
+
void handle(req, res)
|
|
515
|
+
.catch((err) => {
|
|
516
|
+
// Whatever failed, its message may name the origin it was given — and this body reaches
|
|
517
|
+
// the agent, which prints it. See {@link withoutCredentials}.
|
|
518
|
+
json(res, 500, { error: { message: withoutCredentials(String(err)) } });
|
|
519
|
+
})
|
|
520
|
+
.finally(closed);
|
|
521
|
+
});
|
|
522
|
+
/**
|
|
523
|
+
* A decrypted exchange that turns out to be a model call is recorded as one.
|
|
524
|
+
*
|
|
525
|
+
* This is the reason the feature exists rather than a refinement of it: a harness that ignores
|
|
526
|
+
* base-URL variables produces a trace indistinguishable from one captured the ordinary way,
|
|
527
|
+
* which means replay, fork and compare all work on it. Anything the dialects do not recognise
|
|
528
|
+
* falls through to `net.request` / `net.response`, where it is described but not interpreted.
|
|
529
|
+
*/
|
|
530
|
+
function onDecrypted(exchange) {
|
|
531
|
+
// On the path alone. A dialect matches an endpoint, and a query string is not part of the
|
|
532
|
+
// endpoint -- Azure OpenAI's are all `?api-version=...`, so matching the raw path recorded
|
|
533
|
+
// those runs as opaque network traffic that cannot be replayed or forked, while telling the
|
|
534
|
+
// operator no dialect claimed the path. `onInterceptedRequest` below has always split it off
|
|
535
|
+
// for exactly this reason; the recording side had not.
|
|
536
|
+
const dialect = selectDialect(dialects, exchange.path.split('?')[0] ?? exchange.path);
|
|
537
|
+
// `status === 0` means no response header was ever seen: the client abandoned the call before
|
|
538
|
+
// the origin answered. The request is still worth keeping -- it is what the agent asked --
|
|
539
|
+
// but a model exchange with no response is not one, and inserting it in the replay set gives
|
|
540
|
+
// the matcher an entry that can answer nothing and cannot be forked. It goes below as network
|
|
541
|
+
// traffic instead, which is what it is.
|
|
542
|
+
if (dialect &&
|
|
543
|
+
exchange.method === 'POST' &&
|
|
544
|
+
!exchange.requestTruncated &&
|
|
545
|
+
exchange.responseComplete &&
|
|
546
|
+
!exchange.responseTruncated &&
|
|
547
|
+
exchange.status !== 0) {
|
|
548
|
+
try {
|
|
549
|
+
// Codex's HTTPS fallback currently labels this SSE body as application/json. The wire
|
|
550
|
+
// framing is authoritative when the provider header is not, otherwise we lose the
|
|
551
|
+
// canonical response and replay sends the right bytes with the wrong content type.
|
|
552
|
+
const streamed = (exchange.responseHeaders['content-type'] ?? '').includes('event-stream') ||
|
|
553
|
+
exchange.responseBody.startsWith('event:');
|
|
554
|
+
const built = buildExchange({
|
|
555
|
+
dialect,
|
|
556
|
+
path: exchange.path,
|
|
557
|
+
rawRequest: exchange.requestBody,
|
|
558
|
+
rawResponse: exchange.responseBody,
|
|
559
|
+
status: exchange.status,
|
|
560
|
+
streamed,
|
|
561
|
+
headers: scalarHeaders(exchange.requestHeaders),
|
|
562
|
+
seq: captured.length,
|
|
563
|
+
requestId: exchange.requestId,
|
|
564
|
+
requestStartedAt: exchange.requestStartedAt,
|
|
565
|
+
durationMs: exchange.durationMs,
|
|
566
|
+
// The two things only the interceptor witnessed. Without them a promoted exchange is
|
|
567
|
+
// indistinguishable from one captured over the base-URL route, which is the point --
|
|
568
|
+
// except where the difference is the thing being debugged.
|
|
569
|
+
alpn: exchange.alpn,
|
|
570
|
+
responseDecodedFrom: exchange.responseDecodedFrom,
|
|
571
|
+
// Under interception agenticreplay did not choose this origin, the agent did — which is the
|
|
572
|
+
// case where "who answered" is least obvious from the command line, and so the one most
|
|
573
|
+
// worth having in the trace. The port is kept only where https does not imply it.
|
|
574
|
+
upstream: `https://${exchange.host}${exchange.port === 443 ? '' : `:${exchange.port}`}`,
|
|
575
|
+
});
|
|
576
|
+
captured.push(built);
|
|
577
|
+
options.onExchange?.(built);
|
|
578
|
+
// Keep the authoritative wire record even when the dialect projection succeeds. The
|
|
579
|
+
// model event is a convenient replay view; the net event retains headers, framing and
|
|
580
|
+
// transport completion details needed to audit capture fidelity.
|
|
581
|
+
options.tls?.onNetExchange?.({ ...exchange, modelDialect: dialect.id });
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
catch {
|
|
585
|
+
// Not a model call after all — a path that merely looks like one, or a body this dialect
|
|
586
|
+
// cannot read. Describing it as plain network traffic is honest; guessing is not.
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
options.tls?.onNetExchange?.(exchange);
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Replay hook for requests inside an intercepted TLS session. The ordinary HTTP proxy reaches
|
|
593
|
+
* `handle()` below, but the TLS interceptor has already terminated the outer CONNECT and needs
|
|
594
|
+
* the same matcher before it opens an origin connection.
|
|
595
|
+
*
|
|
596
|
+
* Live calls used to return `undefined` here, which forwarded the client's bytes unchanged —
|
|
597
|
+
* so `agenticreplay replay --from N --model X` on a TLS-intercepted recording echoed `model=X` and then
|
|
598
|
+
* called the recorded model. `replayed=0 live=0` was the tell: `goLive` never ran, substitution
|
|
599
|
+
* never had a chance. Same-provider `--model` now rewrites the body and still talks to the
|
|
600
|
+
* intercepted origin (the only origin that origin's credential can reach). A cross-provider
|
|
601
|
+
* target would have to leave that host, which this path cannot do; that fails loudly instead of
|
|
602
|
+
* succeeding with the old model.
|
|
603
|
+
*/
|
|
604
|
+
function onInterceptedRequest(request) {
|
|
605
|
+
const path = request.path.split('?')[0] ?? '/';
|
|
606
|
+
const dialect = selectDialect(dialects, path);
|
|
607
|
+
if (!dialect || request.method !== 'POST' || request.requestTruncated)
|
|
608
|
+
return undefined;
|
|
609
|
+
let rawBody;
|
|
610
|
+
try {
|
|
611
|
+
rawBody = decodeBody(request.requestBytes, Array.isArray(request.requestHeaders['content-encoding'])
|
|
612
|
+
? request.requestHeaders['content-encoding'].join(', ')
|
|
613
|
+
: request.requestHeaders['content-encoding'], options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES);
|
|
614
|
+
}
|
|
615
|
+
catch (err) {
|
|
616
|
+
if (options.mode !== 'replay' || !options.loose) {
|
|
617
|
+
stats.unmatched += 1;
|
|
618
|
+
return replayError(`cannot decode intercepted request body: ${String(err)}`);
|
|
619
|
+
}
|
|
620
|
+
return undefined;
|
|
621
|
+
}
|
|
622
|
+
const result = tryReplay(dialect, rawBody);
|
|
623
|
+
if (result?.exchange) {
|
|
624
|
+
return {
|
|
625
|
+
status: result.exchange.status,
|
|
626
|
+
headers: {
|
|
627
|
+
'content-type': result.exchange.streamed ? 'text/event-stream' : 'application/json',
|
|
628
|
+
},
|
|
629
|
+
body: result.exchange.rawResponse,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
if (result?.error)
|
|
633
|
+
return replayError(result.error);
|
|
634
|
+
return liveIntercepted(dialect, request, rawBody);
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* A decrypted model call the recording will not serve — past the fork point, or unmatched on a
|
|
638
|
+
* hybrid / `--loose` run. Counted as live the way `handle()` → `goLive` is, so `fork.done`
|
|
639
|
+
* `live=N` is the number of exchanges the fork actually owned.
|
|
640
|
+
*/
|
|
641
|
+
function liveIntercepted(dialect, request, rawBody) {
|
|
642
|
+
if (options.forkModel === undefined) {
|
|
643
|
+
stats.liveCalls += 1;
|
|
644
|
+
return undefined;
|
|
645
|
+
}
|
|
646
|
+
const target = dialect.ownsModel(options.forkModel)
|
|
647
|
+
? dialect
|
|
648
|
+
: (dialects.find((d) => d.ownsModel(options.forkModel)) ?? dialect);
|
|
649
|
+
const crossProvider = target.id !== dialect.id;
|
|
650
|
+
if (crossProvider) {
|
|
651
|
+
return replayError(`--model ${options.forkModel} is served by ${target.id}, not the intercepted ` +
|
|
652
|
+
`${dialect.id} origin ${request.host}:${request.port}. A TLS-intercepted fork cannot ` +
|
|
653
|
+
`leave the host the recording talked to. Pick a model that origin serves`);
|
|
654
|
+
}
|
|
655
|
+
let parsed;
|
|
656
|
+
try {
|
|
657
|
+
parsed = JSON.parse(rawBody);
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
return replayError(`unparseable request body: ${String(err)}`);
|
|
661
|
+
}
|
|
662
|
+
const outboundBody = Buffer.from(JSON.stringify(dialect.withModel(parsed, options.forkModel)), 'utf8');
|
|
663
|
+
stats.liveCalls += 1;
|
|
664
|
+
const origin = `https://${request.host}${request.port === 443 ? '' : `:${request.port}`}`;
|
|
665
|
+
const recordable = recordableOrigin(origin);
|
|
666
|
+
options.onRoute?.({
|
|
667
|
+
model: options.forkModel,
|
|
668
|
+
target: target.id,
|
|
669
|
+
recorded: dialect.id,
|
|
670
|
+
...(recordable === undefined ? {} : { origin: recordable }),
|
|
671
|
+
crossProvider: false,
|
|
672
|
+
reason: `served by the recorded dialect ${dialect.id}`,
|
|
673
|
+
});
|
|
674
|
+
return { outboundBody };
|
|
675
|
+
}
|
|
676
|
+
function tryReplay(dialect, rawBody) {
|
|
677
|
+
let canonical;
|
|
678
|
+
try {
|
|
679
|
+
canonical = dialect.toCanonicalRequest(JSON.parse(rawBody));
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
return { error: `unparseable request body: ${String(err)}` };
|
|
683
|
+
}
|
|
684
|
+
const beyondFork = options.mode === 'hybrid' && matcher.cursor >= (options.forkAt ?? replayable.length);
|
|
685
|
+
if (beyondFork)
|
|
686
|
+
return undefined;
|
|
687
|
+
const result = matcher.match(canonical);
|
|
688
|
+
if (result.matched) {
|
|
689
|
+
const exchange = replayable[result.index];
|
|
690
|
+
if (result.reordered)
|
|
691
|
+
stats.reordered += 1;
|
|
692
|
+
if (result.divergence) {
|
|
693
|
+
stats.matchedInexact += 1;
|
|
694
|
+
stats.divergences += 1;
|
|
695
|
+
options.onDivergence?.({ ...result.divergence, seq: exchange.seq });
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
stats.matchedExact += 1;
|
|
699
|
+
}
|
|
700
|
+
return { exchange };
|
|
701
|
+
}
|
|
702
|
+
stats.unmatched += 1;
|
|
703
|
+
if (options.mode === 'hybrid' || options.loose) {
|
|
704
|
+
stats.divergences += 1;
|
|
705
|
+
options.onDivergence?.({
|
|
706
|
+
level: 'major',
|
|
707
|
+
rung: 4,
|
|
708
|
+
distance: -1,
|
|
709
|
+
detail: result.reason ?? 'request does not match the recording; served live instead',
|
|
710
|
+
seq: replayable[result.index]?.seq ?? -1,
|
|
711
|
+
});
|
|
712
|
+
return undefined;
|
|
713
|
+
}
|
|
714
|
+
const reason = result.reason ?? 'request does not match the recording';
|
|
715
|
+
options.onUnmatched?.({
|
|
716
|
+
seq: replayable[result.index]?.seq ?? -1,
|
|
717
|
+
index: result.index,
|
|
718
|
+
reason,
|
|
719
|
+
});
|
|
720
|
+
return { error: `agenticreplay: replay halted — ${reason}` };
|
|
721
|
+
}
|
|
722
|
+
function replayError(message) {
|
|
723
|
+
return {
|
|
724
|
+
status: 400,
|
|
725
|
+
headers: { 'content-type': 'application/json' },
|
|
726
|
+
body: JSON.stringify({
|
|
727
|
+
type: 'error',
|
|
728
|
+
error: {
|
|
729
|
+
type: 'invalid_request_error',
|
|
730
|
+
message: `${message}. Re-run with \`agenticreplay replay <run> --loose\` to continue live from this point, ` +
|
|
731
|
+
'or `agenticreplay show <run>` to see what was recorded.',
|
|
732
|
+
},
|
|
733
|
+
}),
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
const interception = options.tls
|
|
737
|
+
? attachTlsIntercept(server, {
|
|
738
|
+
...options.tls,
|
|
739
|
+
...(options.maxCapturedBytes === undefined
|
|
740
|
+
? {}
|
|
741
|
+
: { maxCapturedBytes: options.maxCapturedBytes }),
|
|
742
|
+
...(options.maxBufferedBytes === undefined
|
|
743
|
+
? {}
|
|
744
|
+
: { maxBufferedBytes: options.maxBufferedBytes }),
|
|
745
|
+
onRequestStart: options.onRequestStart,
|
|
746
|
+
...(options.mode === 'record' ? {} : { onRequest: onInterceptedRequest }),
|
|
747
|
+
onNetExchange: onDecrypted,
|
|
748
|
+
}, stats)
|
|
749
|
+
: undefined;
|
|
750
|
+
/**
|
|
751
|
+
* The configured extra headers for one outbound call — only when the call is going where they
|
|
752
|
+
* belong.
|
|
753
|
+
*
|
|
754
|
+
* `upstreamHeaders` exist to put a gateway's credential on calls to that gateway. With a
|
|
755
|
+
* forwarded request naming its own destination, "every live call" would put the credential on
|
|
756
|
+
* someone else's origin, so the origin the headers name is attached to them and compared here.
|
|
757
|
+
* Callers that set headers without an origin get the old behaviour: on every call, their
|
|
758
|
+
* responsibility.
|
|
759
|
+
*/
|
|
760
|
+
function headersForOrigin(origin) {
|
|
761
|
+
const extra = options.upstreamHeaders;
|
|
762
|
+
if (extra === undefined)
|
|
763
|
+
return {};
|
|
764
|
+
if (options.upstreamHeadersOrigin === undefined)
|
|
765
|
+
return extra;
|
|
766
|
+
return sameOrigin(origin, options.upstreamHeadersOrigin) ? extra : {};
|
|
767
|
+
}
|
|
768
|
+
async function goLive(dialect, path, rawBody,
|
|
769
|
+
/** Sent upstream, auth included. */
|
|
770
|
+
headers,
|
|
771
|
+
/** Written to the trace, auth removed. */
|
|
772
|
+
recordableHeaders, res, startedAt, requestId, requestEncoding,
|
|
773
|
+
/**
|
|
774
|
+
* The base URL the request itself names, from a `/forward/` path. It restores the destination
|
|
775
|
+
* agenticreplay took the call away from, so it outranks only the dialect's vendor default — an upstream
|
|
776
|
+
* the operator configured is an instruction and wins. Dropped on a cross-provider fork, where
|
|
777
|
+
* the model asked for is being served by a different origin on purpose.
|
|
778
|
+
*/
|
|
779
|
+
forwardBase) {
|
|
780
|
+
stats.liveCalls += 1;
|
|
781
|
+
// Which dialect actually serves the model we are about to ask for. Without this the origin
|
|
782
|
+
// came from the *recorded* request, so forking an Anthropic run onto gpt-5.2 posted an
|
|
783
|
+
// Anthropic body to api.anthropic.com naming a model that does not exist there — the
|
|
784
|
+
// cross-provider comparison the tool is pitched on could not have worked.
|
|
785
|
+
// The recorded dialect wins whenever it can serve the model, and only then do we go looking.
|
|
786
|
+
// Two dialects now share a provider — chat completions and Responses both answer for anything
|
|
787
|
+
// that is not Claude — so a bare `find` would route every Responses fork to chat completions
|
|
788
|
+
// and translate a turn that never needed translating.
|
|
789
|
+
const target = options.forkModel === undefined || dialect.ownsModel(options.forkModel)
|
|
790
|
+
? dialect
|
|
791
|
+
: (dialects.find((d) => d.ownsModel(options.forkModel)) ?? dialect);
|
|
792
|
+
const crossProvider = target.id !== dialect.id;
|
|
793
|
+
let parsed = JSON.parse(rawBody);
|
|
794
|
+
if (crossProvider) {
|
|
795
|
+
// Through canonical, which is the only representation both dialects agree on.
|
|
796
|
+
const canonical = dialect.toCanonicalRequest(parsed);
|
|
797
|
+
parsed = target.fromCanonicalRequest({ ...canonical, model: options.forkModel });
|
|
798
|
+
}
|
|
799
|
+
else if (options.forkModel) {
|
|
800
|
+
parsed = dialect.withModel(parsed, options.forkModel);
|
|
801
|
+
}
|
|
802
|
+
// A recording must retain the provider's serialized bytes. Re-serializing an unchanged
|
|
803
|
+
// request silently loses whitespace, field ordering, and unknown textual fields before the
|
|
804
|
+
// endpoint oracle can compare it. Only a model/provider fork is allowed to construct a new
|
|
805
|
+
// request body.
|
|
806
|
+
const outboundBody = options.forkModel === undefined ? rawBody : JSON.stringify(parsed);
|
|
807
|
+
const requestTranslation = options.forkModel === undefined
|
|
808
|
+
? []
|
|
809
|
+
: [
|
|
810
|
+
crossProvider
|
|
811
|
+
? protocolTranslationTransformation(dialect.id, target.id, 'request.body')
|
|
812
|
+
: modelOverrideTransformation(dialect.id, options.forkModel),
|
|
813
|
+
];
|
|
814
|
+
// Prefer an override for the provider we are actually calling; fall back to the override for
|
|
815
|
+
// the recorded provider before the vendor default. That fallback is the gateway case, and it
|
|
816
|
+
// is the common one: someone who passed --upstream-anthropic pointed *this run* at a specific
|
|
817
|
+
// origin, and a gateway serves both dialects from it. Going to api.openai.com instead because
|
|
818
|
+
// the fork changed model would ignore an instruction the user gave explicitly.
|
|
819
|
+
// Where this call goes, in strict order of who decided it. A `/forward/` path names its own
|
|
820
|
+
// destination — that is the whole point of the encoding — so on a live call that agenticreplay is
|
|
821
|
+
// merely relaying (record, or a loose replay continuing past its recording) the request's own
|
|
822
|
+
// base outranks every configured origin: a gateway picked up from `agenticreplay setup` is a default
|
|
823
|
+
// for calls agenticreplay would otherwise have to guess at, not a licence to readdress one that
|
|
824
|
+
// announces where it was headed. A fork is different: substituting the model is the operator
|
|
825
|
+
// choosing where answers come from, so the fork target's explicit upstream — or the gateway —
|
|
826
|
+
// decides, and the forwarded base is dropped as incoherent for the substitution.
|
|
827
|
+
const relaying = options.forkModel === undefined && !crossProvider;
|
|
828
|
+
const origin = relaying && forwardBase !== undefined
|
|
829
|
+
? forwardBase
|
|
830
|
+
: (options.upstream?.[target.id] ??
|
|
831
|
+
options.upstream?.[dialect.id] ??
|
|
832
|
+
(target.id === 'openai-responses' || target.id === 'codex'
|
|
833
|
+
? options.upstream?.openai
|
|
834
|
+
: undefined) ??
|
|
835
|
+
target.defaultUpstream);
|
|
836
|
+
// A relayed call keeps the path the client appended, because the base it names expects
|
|
837
|
+
// exactly that. Everything else is answered by an origin agenticreplay chose, which expects the
|
|
838
|
+
// dialect's own path — and the incoming path cannot be trusted to be it: a bare base url
|
|
839
|
+
// (no `/v1`) reaches this proxy as `/chat/completions`, and a gateway handed that without the
|
|
840
|
+
// version segment answers 404.
|
|
841
|
+
//
|
|
842
|
+
// The query survives the substitution either way, because it is not part of the endpoint's
|
|
843
|
+
// shape. A path says which endpoint; a query says how to call it, and the client set it —
|
|
844
|
+
// `?api-version=` is required on every Azure OpenAI call, and dropping it turns a working
|
|
845
|
+
// configuration into `404 Resource not found` with nothing in the run explaining why.
|
|
846
|
+
// Replacing a path is agenticreplay normalising an address it chose; replacing the parameters would
|
|
847
|
+
// be agenticreplay editing the request.
|
|
848
|
+
const queryAt = path.indexOf('?');
|
|
849
|
+
const query = queryAt === -1 ? '' : path.slice(queryAt);
|
|
850
|
+
const upstreamPath = relaying && forwardBase !== undefined ? path : `${target.requestPath}${query}`;
|
|
851
|
+
// Spec §2: "a gateway chose a model". AgenticReplay *is* the gateway on this path — it substitutes the
|
|
852
|
+
// model, picks the wire format that serves it, and picks the origin — and it was making all
|
|
853
|
+
// three of those choices silently. Which is the one thing a comparison must not do: reading
|
|
854
|
+
// `claude-opus-5 vs gpt-5.2` in a verdict table tells you nothing about where either went, and
|
|
855
|
+
// a fork that quietly fell back to the recorded provider's origin looked identical to one that
|
|
856
|
+
// did not. Emitted only when a decision was actually taken, so an ordinary recording — where
|
|
857
|
+
// agenticreplay forwards what it was given — stays free of an event saying "nothing was chosen".
|
|
858
|
+
if (options.forkModel !== undefined) {
|
|
859
|
+
// Sanitised here for the reason {@link buildExchange} sanitises the exchange's `upstream`,
|
|
860
|
+
// and it is the same value: an origin that came from configuration rather than off the wire,
|
|
861
|
+
// so a gateway that authenticates by URL carries its key in it. This payload is not just
|
|
862
|
+
// reported — `agenticreplay replay --model` and `agenticreplay compare --models` write it into the fork's
|
|
863
|
+
// trace as `route.decision.attrs` unchanged, and the trace redactor cannot help, because it
|
|
864
|
+
// matches key shapes and field names and this is a password inside a URL under `origin`.
|
|
865
|
+
// Omitted rather than replaced when it will not parse: no consumer reads it — the viewer
|
|
866
|
+
// renders model, target and reason — and a placeholder in a field others may parse as a URL
|
|
867
|
+
// is worse than the field being absent.
|
|
868
|
+
const recordable = recordableOrigin(origin);
|
|
869
|
+
options.onRoute?.({
|
|
870
|
+
model: options.forkModel,
|
|
871
|
+
target: target.id,
|
|
872
|
+
recorded: dialect.id,
|
|
873
|
+
...(recordable === undefined ? {} : { origin: recordable }),
|
|
874
|
+
crossProvider,
|
|
875
|
+
// Deliberately does not open with the model name: the viewer already renders that as the
|
|
876
|
+
// row's label, so a reason that repeats it produces `gpt-5.2 gpt-5.2 is served by…` and
|
|
877
|
+
// spends the row's width saying the same thing twice.
|
|
878
|
+
reason: crossProvider
|
|
879
|
+
? `served by ${target.id}, not the recorded ${dialect.id}`
|
|
880
|
+
: `served by the recorded dialect ${dialect.id}`,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
let upstreamRes;
|
|
884
|
+
try {
|
|
885
|
+
upstreamRes = await doFetch(`${origin}${upstreamPath}`, {
|
|
886
|
+
method: 'POST',
|
|
887
|
+
headers: {
|
|
888
|
+
'content-type': 'application/json',
|
|
889
|
+
...headersForModel(headers, options.forkModel),
|
|
890
|
+
...headersForOrigin(origin),
|
|
891
|
+
},
|
|
892
|
+
body: outboundBody,
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
catch (err) {
|
|
896
|
+
// An upstream that never answered, on the path agenticreplay *does* understand.
|
|
897
|
+
//
|
|
898
|
+
// `passThrough` learned this already; this is the same fact one branch over, and it was the
|
|
899
|
+
// worse half. A call agenticreplay recognised as a model exchange and could not forward left no
|
|
900
|
+
// event at all: `run.start`, a snapshot, `run.end`. The agent got its 500 and the run then
|
|
901
|
+
// warned `capture.empty ... "the agent never called the proxy — it may not read a base-URL
|
|
902
|
+
// variable"` — which is the opposite of what happened, and sends the operator to check
|
|
903
|
+
// environment variables when the upstream is simply unreachable.
|
|
904
|
+
//
|
|
905
|
+
// Recorded as an exchange rather than as network traffic, because that is what it is: the
|
|
906
|
+
// same call, recorded the same way, whether the upstream answered 503 or never answered at
|
|
907
|
+
// all. `status: 500` is what the agent receives from the catch-all above, so a replay of
|
|
908
|
+
// this trace reproduces the failure the way §4 asks — and `>= 400` is what makes
|
|
909
|
+
// `capture.errors` count it, where `0` would have filed a failed call as a healthy one.
|
|
910
|
+
//
|
|
911
|
+
// The body is `reasonFor`'s, not the catch-all's: both name the origin they were given, and
|
|
912
|
+
// this one is the copy that gets written down. See `scrubOrigin`.
|
|
913
|
+
//
|
|
914
|
+
// Answered here rather than rethrown into the catch-all, so the bytes the agent receives
|
|
915
|
+
// and the bytes the trace holds are the same ones. Rethrowing gave the agent the
|
|
916
|
+
// catch-all's `TypeError: fetch failed` while the trace held this scrubbed reason, and a
|
|
917
|
+
// replay then served the trace's version — a recording that did not reproduce itself.
|
|
918
|
+
// The agent also gets the better of the two messages: one that names agenticreplay and the origin.
|
|
919
|
+
const body = JSON.stringify({
|
|
920
|
+
error: { message: `agenticreplay did not forward this call: ${reasonFor(err, origin)}` },
|
|
921
|
+
});
|
|
922
|
+
options.onNetExchange?.({
|
|
923
|
+
requestId,
|
|
924
|
+
requestStartedAt: startedAt,
|
|
925
|
+
...originParts(origin),
|
|
926
|
+
intercepted: false,
|
|
927
|
+
method: 'POST',
|
|
928
|
+
path,
|
|
929
|
+
requestHeaders: recordableHeaders,
|
|
930
|
+
requestBody: outboundBody,
|
|
931
|
+
requestBytes: Buffer.byteLength(outboundBody),
|
|
932
|
+
requestTruncated: false,
|
|
933
|
+
status: 0,
|
|
934
|
+
responseHeaders: {},
|
|
935
|
+
responseBody: '',
|
|
936
|
+
responseTruncated: false,
|
|
937
|
+
responseComplete: false,
|
|
938
|
+
responseError: reasonFor(err, origin),
|
|
939
|
+
responseBytes: 0,
|
|
940
|
+
durationMs: Date.now() - startedAt,
|
|
941
|
+
transformations: [
|
|
942
|
+
...captureHeaderTransformations(headers, recordableHeaders),
|
|
943
|
+
...captureEncodingTransformation('request.body', requestEncoding),
|
|
944
|
+
...captureUrlTransformations(origin),
|
|
945
|
+
...binaryBodyTransformation('request.body', outboundBody),
|
|
946
|
+
...binaryBodyTransformation('response.body', body),
|
|
947
|
+
],
|
|
948
|
+
});
|
|
949
|
+
recordExchange(body, 500, false);
|
|
950
|
+
res.writeHead(500, { 'content-type': 'application/json' });
|
|
951
|
+
res.end(body);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const upstreamType = upstreamRes.headers.get('content-type') ?? 'application/json';
|
|
955
|
+
const upstreamStreamed = upstreamType.includes('event-stream');
|
|
956
|
+
const responseEncoding = upstreamRes.headers.get('content-encoding') ?? undefined;
|
|
957
|
+
const responseDecoded = responseEncoding !== undefined && responseEncoding.trim() !== '';
|
|
958
|
+
// The agent asked one provider and must be answered in that provider's shape, whatever served
|
|
959
|
+
// the request. Handing an agent a `chat.completion` body it cannot parse is indistinguishable
|
|
960
|
+
// from the model having failed.
|
|
961
|
+
//
|
|
962
|
+
// A cross-provider reply cannot be teed: translating it means having all of it, so this path
|
|
963
|
+
// buffers where the same-provider path streams. That is a real cost and it only applies to a
|
|
964
|
+
// fork that deliberately changed provider — the recording path, which is what an interactive
|
|
965
|
+
// session runs through, still streams.
|
|
966
|
+
if (crossProvider) {
|
|
967
|
+
const received = await readBoundedResponse(upstreamRes, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES, plainBufferLimit);
|
|
968
|
+
if (received.truncated) {
|
|
969
|
+
emitNetExchange({
|
|
970
|
+
path: upstreamPath,
|
|
971
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
972
|
+
responseBody: received.text,
|
|
973
|
+
responseBytes: received.bytes,
|
|
974
|
+
responseTruncated: true,
|
|
975
|
+
status: upstreamRes.status,
|
|
976
|
+
responseComplete: false,
|
|
977
|
+
responseError: 'cross-provider response exceeded capture limit',
|
|
978
|
+
streamed: upstreamStreamed,
|
|
979
|
+
modelDialect: target.id,
|
|
980
|
+
extraTransformations: [protocolTranslationTransformation(target.id, dialect.id)],
|
|
981
|
+
});
|
|
982
|
+
res.writeHead(502, { 'content-type': 'application/json' });
|
|
983
|
+
res.end(JSON.stringify({ error: { message: 'cross-provider response exceeded capture limit' } }));
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
const raw = received.text;
|
|
987
|
+
// Keep the origin bytes even when protocol translation fails. The client receives an error,
|
|
988
|
+
// but the recorder must still prove that the origin answered and preserve the response that
|
|
989
|
+
// caused the translation failure.
|
|
990
|
+
let body;
|
|
991
|
+
const wantsStream = requestedStream(rawBody);
|
|
992
|
+
try {
|
|
993
|
+
const canonical = upstreamStreamed
|
|
994
|
+
? target.parseStream(raw)
|
|
995
|
+
: target.toCanonicalResponse(JSON.parse(raw));
|
|
996
|
+
// Match the shape the agent asked for, not the shape the other provider happened to send.
|
|
997
|
+
body = dialect.fromCanonicalResponse(canonical, wantsStream);
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
1000
|
+
emitNetExchange({
|
|
1001
|
+
path: upstreamPath,
|
|
1002
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
1003
|
+
responseBody: raw,
|
|
1004
|
+
responseBytes: received.bytes,
|
|
1005
|
+
responseTruncated: false,
|
|
1006
|
+
status: upstreamRes.status,
|
|
1007
|
+
responseComplete: received.encodingError === undefined,
|
|
1008
|
+
responseError: received.encodingError ?? `cross-provider response translation failed: ${String(err)}`,
|
|
1009
|
+
streamed: upstreamStreamed,
|
|
1010
|
+
modelDialect: target.id,
|
|
1011
|
+
extraTransformations: [protocolTranslationTransformation(target.id, dialect.id)],
|
|
1012
|
+
});
|
|
1013
|
+
res.writeHead(502, { 'content-type': 'application/json' });
|
|
1014
|
+
res.end(JSON.stringify({ error: { message: 'cross-provider response translation failed' } }));
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (Buffer.byteLength(body) > (options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES)) {
|
|
1018
|
+
emitNetExchange({
|
|
1019
|
+
path: upstreamPath,
|
|
1020
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
1021
|
+
responseBody: raw,
|
|
1022
|
+
responseBytes: received.bytes,
|
|
1023
|
+
responseTruncated: false,
|
|
1024
|
+
status: upstreamRes.status,
|
|
1025
|
+
responseComplete: false,
|
|
1026
|
+
responseError: 'cross-provider translated response exceeded capture limit',
|
|
1027
|
+
streamed: wantsStream,
|
|
1028
|
+
modelDialect: target.id,
|
|
1029
|
+
extraTransformations: [protocolTranslationTransformation(target.id, dialect.id)],
|
|
1030
|
+
});
|
|
1031
|
+
res.writeHead(502, { 'content-type': 'application/json' });
|
|
1032
|
+
res.end(JSON.stringify({
|
|
1033
|
+
error: { message: 'cross-provider translated response exceeded capture limit' },
|
|
1034
|
+
}));
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
res.writeHead(upstreamRes.status, {
|
|
1038
|
+
'content-type': wantsStream ? 'text/event-stream' : 'application/json',
|
|
1039
|
+
});
|
|
1040
|
+
res.end(body);
|
|
1041
|
+
emitNetExchange({
|
|
1042
|
+
path: upstreamPath,
|
|
1043
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
1044
|
+
responseBody: raw,
|
|
1045
|
+
responseBytes: received.bytes,
|
|
1046
|
+
status: upstreamRes.status,
|
|
1047
|
+
responseComplete: received.encodingError === undefined,
|
|
1048
|
+
...(received.encodingError === undefined ? {} : { responseError: received.encodingError }),
|
|
1049
|
+
streamed: wantsStream,
|
|
1050
|
+
modelDialect: target.id,
|
|
1051
|
+
extraTransformations: [protocolTranslationTransformation(target.id, dialect.id)],
|
|
1052
|
+
});
|
|
1053
|
+
recordExchange(body, upstreamRes.status, wantsStream);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const contentType = upstreamType;
|
|
1057
|
+
const streamed = upstreamStreamed;
|
|
1058
|
+
res.writeHead(upstreamRes.status, { 'content-type': contentType });
|
|
1059
|
+
// Tee rather than buffer. A model response arrives over seconds, and reading it to completion
|
|
1060
|
+
// before writing a byte makes every turn of an interactive session appear to hang for its full
|
|
1061
|
+
// duration — the agent's own progressive rendering stops working because there is nothing
|
|
1062
|
+
// progressive left to render. Chunks go straight through; the copy we keep is assembled on the
|
|
1063
|
+
// way past, so the recording is still the complete body.
|
|
1064
|
+
let piped;
|
|
1065
|
+
try {
|
|
1066
|
+
piped = await pipeThrough(upstreamRes, res, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES, plainBufferLimit);
|
|
1067
|
+
}
|
|
1068
|
+
catch (err) {
|
|
1069
|
+
const partial = err instanceof ResponsePipeAbortedError
|
|
1070
|
+
? err.partial
|
|
1071
|
+
: err.partial;
|
|
1072
|
+
emitNetExchange({
|
|
1073
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
1074
|
+
responseBody: partial?.text ?? '',
|
|
1075
|
+
responseBytes: partial?.bytes ?? 0,
|
|
1076
|
+
status: upstreamRes.status,
|
|
1077
|
+
responseComplete: false,
|
|
1078
|
+
responseTruncated: partial?.truncated ?? false,
|
|
1079
|
+
responseError: String(err),
|
|
1080
|
+
streamed,
|
|
1081
|
+
abandoned: err instanceof ResponsePipeAbortedError,
|
|
1082
|
+
});
|
|
1083
|
+
if (err instanceof ResponsePipeAbortedError)
|
|
1084
|
+
return;
|
|
1085
|
+
throw err;
|
|
1086
|
+
}
|
|
1087
|
+
emitNetExchange({
|
|
1088
|
+
responseHeaders: captureResponseHeaders(upstreamRes.headers, responseDecoded),
|
|
1089
|
+
responseBody: piped.text,
|
|
1090
|
+
responseBytes: piped.bytes,
|
|
1091
|
+
responseTruncated: piped.truncated,
|
|
1092
|
+
status: upstreamRes.status,
|
|
1093
|
+
responseComplete: piped.encodingError === undefined,
|
|
1094
|
+
...(piped.encodingError === undefined ? {} : { responseError: piped.encodingError }),
|
|
1095
|
+
streamed,
|
|
1096
|
+
});
|
|
1097
|
+
if (!piped.truncated && piped.encodingError === undefined)
|
|
1098
|
+
recordExchange(piped.text, upstreamRes.status, streamed);
|
|
1099
|
+
function emitNetExchange(input) {
|
|
1100
|
+
options.onNetExchange?.({
|
|
1101
|
+
requestId,
|
|
1102
|
+
requestStartedAt: startedAt,
|
|
1103
|
+
...originParts(origin),
|
|
1104
|
+
intercepted: false,
|
|
1105
|
+
method: 'POST',
|
|
1106
|
+
path: input.path ?? path,
|
|
1107
|
+
requestHeaders: recordableHeaders,
|
|
1108
|
+
requestBody: outboundBody,
|
|
1109
|
+
requestBytes: Buffer.byteLength(outboundBody),
|
|
1110
|
+
requestTruncated: false,
|
|
1111
|
+
status: input.status,
|
|
1112
|
+
responseHeaders: input.responseHeaders,
|
|
1113
|
+
responseBody: input.responseBody,
|
|
1114
|
+
responseComplete: input.responseComplete,
|
|
1115
|
+
responseTruncated: input.responseTruncated ?? false,
|
|
1116
|
+
...(input.abandoned === true ? { abandoned: true } : {}),
|
|
1117
|
+
...(input.responseError === undefined ? {} : { responseError: input.responseError }),
|
|
1118
|
+
responseBytes: input.responseBytes,
|
|
1119
|
+
durationMs: Date.now() - startedAt,
|
|
1120
|
+
modelDialect: input.modelDialect ?? dialect.id,
|
|
1121
|
+
transformations: [
|
|
1122
|
+
...captureHeaderTransformations(headers, recordableHeaders),
|
|
1123
|
+
...requestTranslation,
|
|
1124
|
+
...captureEncodingTransformation('request.body', requestEncoding),
|
|
1125
|
+
...captureResponseHeaderTransformations(upstreamRes.headers),
|
|
1126
|
+
...captureEncodingTransformation('response.body', responseEncoding),
|
|
1127
|
+
...(input.extraTransformations ?? []),
|
|
1128
|
+
...captureUrlTransformations(origin),
|
|
1129
|
+
...binaryBodyTransformation('request.body', outboundBody),
|
|
1130
|
+
...binaryBodyTransformation('response.body', input.responseBody),
|
|
1131
|
+
],
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Recorded whatever the status. A run that died on rate limits used to produce a trace with no
|
|
1136
|
+
* evidence of it — and replay would then be short exactly the exchanges that explain the
|
|
1137
|
+
* failure you are trying to reproduce.
|
|
1138
|
+
*
|
|
1139
|
+
* The recorded body is what the *agent* received, so a cross-provider fork replays as a run of
|
|
1140
|
+
* the dialect the agent speaks. Recording the other provider's bytes would produce a trace
|
|
1141
|
+
* that no adapter could replay.
|
|
1142
|
+
*/
|
|
1143
|
+
function recordExchange(rawResponse, status, isStreamed) {
|
|
1144
|
+
const exchange = buildExchange({
|
|
1145
|
+
dialect,
|
|
1146
|
+
path,
|
|
1147
|
+
rawRequest: outboundBody,
|
|
1148
|
+
rawResponse,
|
|
1149
|
+
status,
|
|
1150
|
+
streamed: isStreamed,
|
|
1151
|
+
headers: scalarHeaders(recordableHeaders),
|
|
1152
|
+
seq: captured.length,
|
|
1153
|
+
requestId,
|
|
1154
|
+
requestStartedAt: startedAt,
|
|
1155
|
+
durationMs: Date.now() - startedAt,
|
|
1156
|
+
// The origin resolved above, not the one configured: a relayed call keeps the base the
|
|
1157
|
+
// client announced, and a fork's substitution can send it somewhere else again.
|
|
1158
|
+
upstream: origin,
|
|
1159
|
+
});
|
|
1160
|
+
captured.push(exchange);
|
|
1161
|
+
options.onExchange?.(exchange);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Where a request agenticreplay could not read should have gone.
|
|
1166
|
+
*
|
|
1167
|
+
* Explicit first, then the gateway: someone who pointed this run at one origin pointed *all* of
|
|
1168
|
+
* it there, and splitting a run across two origins because agenticreplay could not read one call is not a
|
|
1169
|
+
* choice they made. Only then the guess — and the guess is narrow, because it is not choosing a
|
|
1170
|
+
* destination so much as restoring one. AgenticReplay took this request away from a provider by rewriting
|
|
1171
|
+
* a base URL; the client's own headers say which provider that was, and its own credential is
|
|
1172
|
+
* already on the request addressed to them.
|
|
1173
|
+
*/
|
|
1174
|
+
function passthroughOrigin(headers, forwardBase, opts = {}) {
|
|
1175
|
+
if (options.passthroughUpstream !== undefined)
|
|
1176
|
+
return options.passthroughUpstream;
|
|
1177
|
+
// A relayed request's own destination, before anything configured: on a live call with no
|
|
1178
|
+
// model substitution, the request says where it was headed, and a gateway picked up from
|
|
1179
|
+
// setup is a default for calls agenticreplay would otherwise guess at — not a readdressing of one that
|
|
1180
|
+
// announces its own. A fork is a substitution, and its configured origins decide instead.
|
|
1181
|
+
//
|
|
1182
|
+
// `verbatim` is for a call the fork does *not* substitute anything in. A retrieval call is
|
|
1183
|
+
// forwarded byte for byte — same headers, same body, no `withModel` — so the substitution the
|
|
1184
|
+
// fork rule exists for never happens to it, and dropping its announced destination on account
|
|
1185
|
+
// of one is how a fork's live embedding call left for the chat origin carrying the embedding
|
|
1186
|
+
// provider's own key: a 401 at best, and at worst an OpenAI-compatible gateway that answers
|
|
1187
|
+
// `/v1/embeddings` from a different model, putting foreign vectors in the fork's index.
|
|
1188
|
+
if (forwardBase !== undefined && (opts.verbatim === true || options.forkModel === undefined)) {
|
|
1189
|
+
return forwardBase;
|
|
1190
|
+
}
|
|
1191
|
+
const configured = [...new Set(Object.values(options.upstream ?? {}))];
|
|
1192
|
+
if (configured.length === 1)
|
|
1193
|
+
return configured[0];
|
|
1194
|
+
const names = new Set(Object.keys(headers).map((h) => h.toLowerCase()));
|
|
1195
|
+
// `x-api-key` alongside the version headers: this request carries the caller's own credential,
|
|
1196
|
+
// and a wrong guess does not merely fail — it hands one vendor a key issued by another.
|
|
1197
|
+
// Anthropic's client is the one that announces itself, so absence of a signal means OpenAI.
|
|
1198
|
+
const anthropic = names.has('anthropic-version') || names.has('anthropic-beta') || names.has('x-api-key');
|
|
1199
|
+
// The origin configured for the family this request belongs to, before any vendor default.
|
|
1200
|
+
// Two configured values only means the run has more than one destination — not that this
|
|
1201
|
+
// request's own destination is unknown — and picking a vendor default over an origin the
|
|
1202
|
+
// operator named is how a run configured with `--upstream-openai <stub>` alongside a gateway
|
|
1203
|
+
// left in `~/.agenticreplay/config.json` sent its embedding calls to `api.openai.com`: a third place,
|
|
1204
|
+
// neither of the two configured, carrying the agent's own credential. For a team whose
|
|
1205
|
+
// gateway is the point of the gateway, that is every retrieval call bypassing it silently.
|
|
1206
|
+
const forFamily = anthropic ? options.upstream?.['anthropic'] : options.upstream?.['openai'];
|
|
1207
|
+
if (forFamily !== undefined)
|
|
1208
|
+
return forFamily;
|
|
1209
|
+
return anthropic ? 'https://api.anthropic.com' : 'https://api.openai.com';
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Serve a retrieval call: from the recording where there is one, live where there is not.
|
|
1213
|
+
*
|
|
1214
|
+
* The lookup is by the request itself rather than by a cursor. Two reasons, and both matter.
|
|
1215
|
+
* The answer is a function of the request, so position carries no information — the same text
|
|
1216
|
+
* embedded on turn 3 and on turn 40 has the same vector. And an index build issues these
|
|
1217
|
+
* hundreds at a time from a worker pool, so there is no order for a cursor to follow.
|
|
1218
|
+
*
|
|
1219
|
+
* A recorded call is consumed once, earliest first, so a run that asked the same thing twice
|
|
1220
|
+
* gets both of its answers back and `retrieval=N/N` means every recorded call was reused —
|
|
1221
|
+
* rather than one of them being served twice while another was never asked for.
|
|
1222
|
+
*/
|
|
1223
|
+
async function serveRetrieval(rule, path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase) {
|
|
1224
|
+
let key;
|
|
1225
|
+
let parsed;
|
|
1226
|
+
try {
|
|
1227
|
+
parsed = JSON.parse(rawBody);
|
|
1228
|
+
key = rule.key(parsed);
|
|
1229
|
+
}
|
|
1230
|
+
catch (err) {
|
|
1231
|
+
// A body this rule cannot key is a body agenticreplay cannot promise anything about. Passthrough is
|
|
1232
|
+
// the honest fallback: forward it, record the bytes, say it is not replayable.
|
|
1233
|
+
if (options.mode === 'replay') {
|
|
1234
|
+
stats.unmatched += 1;
|
|
1235
|
+
const reason = `${path} could not be keyed for replay: ${String(err)}`;
|
|
1236
|
+
options.onUnmatched?.({ seq: -1, index: -1, reason });
|
|
1237
|
+
json(res, 502, {
|
|
1238
|
+
error: { message: `agenticreplay replay cannot reproduce ${path}: ${reason}` },
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
await passThrough(path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
// Across the fork cursor deliberately, in every mode that has a recording. A fork asks the
|
|
1246
|
+
// same documents about a different chat model; re-embedding them live would spend money to
|
|
1247
|
+
// recompute a value that cannot have changed, and would make the fork's index differ from the
|
|
1248
|
+
// parent's for no reason anyone asked for. `RetrievalRule.forkable` is `false` for the same
|
|
1249
|
+
// reason, one level up.
|
|
1250
|
+
//
|
|
1251
|
+
// Exact first, then the batch. A pipeline assembles its batch from a worker pool, so the same
|
|
1252
|
+
// set of texts arrives in a different order on every run and an exact key alone found nothing
|
|
1253
|
+
// — measured: two replays of one recording, two different keys, neither recorded. Where the
|
|
1254
|
+
// endpoint's own contract says `data[i]` answers `input[i]`, the recorded answer for each
|
|
1255
|
+
// text can be handed back against that text's new position, which is not an approximation.
|
|
1256
|
+
// See {@link BatchRetrieval}.
|
|
1257
|
+
let match = retrievalIndex.byKey.get(key)?.[0];
|
|
1258
|
+
let rebuilt;
|
|
1259
|
+
if (match === undefined) {
|
|
1260
|
+
const batchKey = rule.batch?.key(parsed);
|
|
1261
|
+
const candidate = batchKey === undefined ? undefined : retrievalIndex.byBatch.get(batchKey)?.[0];
|
|
1262
|
+
if (candidate !== undefined && candidate.rawResponse !== '') {
|
|
1263
|
+
rebuilt = rule.batch?.reorder(safeParse(candidate.rawRequest), candidate.rawResponse, parsed);
|
|
1264
|
+
// An inexact reorder is no match at all. Filling the gaps would put another document's
|
|
1265
|
+
// vector in the index, and nothing downstream could tell.
|
|
1266
|
+
if (rebuilt !== undefined)
|
|
1267
|
+
match = candidate;
|
|
1268
|
+
}
|
|
1269
|
+
else if (candidate !== undefined) {
|
|
1270
|
+
match = candidate;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (match !== undefined) {
|
|
1274
|
+
// Consumed only where it is actually served. A recorded call agenticreplay cannot serve is still the
|
|
1275
|
+
// answer to this request, and dropping it turned a client's retry — langchain retries a 502
|
|
1276
|
+
// — into "no recorded retrieval call matches this request", which is false and sends the
|
|
1277
|
+
// reader looking for a mismatch that is not there.
|
|
1278
|
+
if (rebuilt !== undefined) {
|
|
1279
|
+
consume(retrievalIndex, match);
|
|
1280
|
+
stats.retrievalServed += 1;
|
|
1281
|
+
stats.retrievalReordered += 1;
|
|
1282
|
+
res.writeHead(match.status, { 'content-type': match.contentType });
|
|
1283
|
+
res.end(rebuilt);
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
if (match.rawResponse === '') {
|
|
1287
|
+
// Recorded with `--retrieval-store=digest`: agenticreplay kept proof of the answer, not the answer.
|
|
1288
|
+
const reason = `${path} was recorded with --retrieval-store=digest, so the trace holds a digest of ` +
|
|
1289
|
+
'the response and not the response. Re-record with the default (full) to replay it';
|
|
1290
|
+
if (options.mode === 'replay' && !options.loose) {
|
|
1291
|
+
stats.unmatched += 1;
|
|
1292
|
+
options.onUnmatched?.({ seq: match.seq, index: -1, reason });
|
|
1293
|
+
json(res, 502, {
|
|
1294
|
+
error: { message: `agenticreplay replay cannot reproduce ${path}: ${reason}` },
|
|
1295
|
+
});
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
consume(retrievalIndex, match);
|
|
1301
|
+
stats.retrievalServed += 1;
|
|
1302
|
+
res.writeHead(match.status, { 'content-type': match.contentType });
|
|
1303
|
+
res.end(match.rawResponse);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
if (options.mode === 'replay' && !options.loose) {
|
|
1308
|
+
stats.unmatched += 1;
|
|
1309
|
+
const reason = `no recorded retrieval call matches this request (rule ${rule.id}, key ` +
|
|
1310
|
+
`${key.slice(0, 12)}…). The recording holds ${stats.retrievalTotal} retrieval ` +
|
|
1311
|
+
`call${stats.retrievalTotal === 1 ? '' : 's'}, and this is not one of them`;
|
|
1312
|
+
options.onUnmatched?.({ seq: -1, index: -1, reason });
|
|
1313
|
+
json(res, 502, {
|
|
1314
|
+
error: { message: `agenticreplay replay cannot reproduce ${path}: ${reason}` },
|
|
1315
|
+
});
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
// Record, a fork past what it can serve, or a loose replay: make the call and keep the answer.
|
|
1319
|
+
if (options.mode !== 'record')
|
|
1320
|
+
stats.retrievalLive += 1;
|
|
1321
|
+
await forwardRetrieval(rule, key, rule.batch?.key(parsed), path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase);
|
|
1322
|
+
}
|
|
1323
|
+
/** A body that has already parsed once. Undefined rather than a throw where it has not. */
|
|
1324
|
+
function safeParse(raw) {
|
|
1325
|
+
try {
|
|
1326
|
+
return JSON.parse(raw);
|
|
1327
|
+
}
|
|
1328
|
+
catch {
|
|
1329
|
+
return undefined;
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
/** Make a retrieval call for real, and file it as a replayable `net.*` pair. */
|
|
1333
|
+
async function forwardRetrieval(rule, key, batchKey, path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase) {
|
|
1334
|
+
stats.passedThrough += 1;
|
|
1335
|
+
// On a recording, the count of retrieval calls *captured*. It is the same field replay uses
|
|
1336
|
+
// for "how many the recording holds", which is the same quantity one command later — and it
|
|
1337
|
+
// is what lets `capture.empty` tell an index build, whose every call is a retrieval call and
|
|
1338
|
+
// every one of them replayable, apart from a run that captured nothing at all.
|
|
1339
|
+
if (options.mode === 'record')
|
|
1340
|
+
stats.retrievalTotal += 1;
|
|
1341
|
+
// Verbatim: everything below forwards this request unchanged, so where it says it was going
|
|
1342
|
+
// stands, fork or no fork. See `passthroughOrigin`.
|
|
1343
|
+
const origin = passthroughOrigin(headers, forwardBase, { verbatim: true });
|
|
1344
|
+
let upstreamRes;
|
|
1345
|
+
try {
|
|
1346
|
+
upstreamRes = await doFetch(`${origin}${path}`, {
|
|
1347
|
+
method: 'POST',
|
|
1348
|
+
headers: { 'content-type': 'application/json', ...headers, ...headersForOrigin(origin) },
|
|
1349
|
+
body: rawBody,
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
catch (err) {
|
|
1353
|
+
// The third of the three forwarding paths, and it had the same hole. An index build issues
|
|
1354
|
+
// these hundreds at a time; a gateway that stops answering half way through used to leave
|
|
1355
|
+
// a trace that simply had fewer retrieval calls in it than the run made, with nothing
|
|
1356
|
+
// saying which ones or why.
|
|
1357
|
+
//
|
|
1358
|
+
// `status: 0` and the `net.*` shape, matching `passThrough`: no response header was ever
|
|
1359
|
+
// seen, and a retrieval call is already recorded as network traffic rather than as a
|
|
1360
|
+
// replayable model exchange.
|
|
1361
|
+
options.onNetExchange?.({
|
|
1362
|
+
requestId,
|
|
1363
|
+
requestStartedAt: startedAt,
|
|
1364
|
+
...originParts(origin),
|
|
1365
|
+
method: 'POST',
|
|
1366
|
+
path,
|
|
1367
|
+
intercepted: false,
|
|
1368
|
+
rule: rule.id,
|
|
1369
|
+
replayKey: key,
|
|
1370
|
+
...(batchKey === undefined ? {} : { batchKey }),
|
|
1371
|
+
requestHeaders: recordableHeaders,
|
|
1372
|
+
requestBody: rawBody,
|
|
1373
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1374
|
+
requestTruncated: false,
|
|
1375
|
+
status: 0,
|
|
1376
|
+
responseHeaders: {},
|
|
1377
|
+
responseBody: `agenticreplay did not forward this call: ${reasonFor(err, origin)}`,
|
|
1378
|
+
responseTruncated: false,
|
|
1379
|
+
responseComplete: false,
|
|
1380
|
+
responseError: reasonFor(err, origin),
|
|
1381
|
+
responseBytes: 0,
|
|
1382
|
+
durationMs: Date.now() - startedAt,
|
|
1383
|
+
});
|
|
1384
|
+
throw err;
|
|
1385
|
+
}
|
|
1386
|
+
const responseEncoding = upstreamRes.headers.get('content-encoding') ?? undefined;
|
|
1387
|
+
const responseHeaders = captureResponseHeaders(upstreamRes.headers, responseEncoding !== undefined && responseEncoding.trim() !== '');
|
|
1388
|
+
res.writeHead(upstreamRes.status, {
|
|
1389
|
+
'content-type': responseHeaders['content-type'] ?? 'application/json',
|
|
1390
|
+
});
|
|
1391
|
+
let piped;
|
|
1392
|
+
try {
|
|
1393
|
+
piped = await pipeThrough(upstreamRes, res, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES, plainBufferLimit);
|
|
1394
|
+
}
|
|
1395
|
+
catch (err) {
|
|
1396
|
+
const partial = err instanceof ResponsePipeAbortedError
|
|
1397
|
+
? err.partial
|
|
1398
|
+
: err.partial;
|
|
1399
|
+
options.onNetExchange?.({
|
|
1400
|
+
requestId,
|
|
1401
|
+
requestStartedAt: startedAt,
|
|
1402
|
+
...originParts(origin),
|
|
1403
|
+
method: 'POST',
|
|
1404
|
+
path,
|
|
1405
|
+
intercepted: false,
|
|
1406
|
+
rule: rule.id,
|
|
1407
|
+
replayKey: key,
|
|
1408
|
+
...(batchKey === undefined ? {} : { batchKey }),
|
|
1409
|
+
requestHeaders: recordableHeaders,
|
|
1410
|
+
requestBody: rawBody,
|
|
1411
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1412
|
+
requestTruncated: false,
|
|
1413
|
+
status: upstreamRes.status,
|
|
1414
|
+
responseHeaders,
|
|
1415
|
+
responseBody: partial?.text ?? '',
|
|
1416
|
+
responseTruncated: partial?.truncated ?? false,
|
|
1417
|
+
responseComplete: false,
|
|
1418
|
+
responseError: String(err),
|
|
1419
|
+
responseBytes: partial?.bytes ?? 0,
|
|
1420
|
+
...(err instanceof ResponsePipeAbortedError ? { abandoned: true } : {}),
|
|
1421
|
+
durationMs: Date.now() - startedAt,
|
|
1422
|
+
});
|
|
1423
|
+
if (err instanceof ResponsePipeAbortedError)
|
|
1424
|
+
return;
|
|
1425
|
+
throw err;
|
|
1426
|
+
}
|
|
1427
|
+
const body = piped.text;
|
|
1428
|
+
const digest = createHash('sha256').update(body).digest('hex');
|
|
1429
|
+
const keepBody = (options.retrievalStore ?? 'full') === 'full';
|
|
1430
|
+
const { host, port } = originParts(origin);
|
|
1431
|
+
options.onNetExchange?.({
|
|
1432
|
+
requestId,
|
|
1433
|
+
requestStartedAt: startedAt,
|
|
1434
|
+
host,
|
|
1435
|
+
port,
|
|
1436
|
+
method: 'POST',
|
|
1437
|
+
path,
|
|
1438
|
+
intercepted: false,
|
|
1439
|
+
rule: rule.id,
|
|
1440
|
+
replayKey: key,
|
|
1441
|
+
// Only where the rule offers one. Its absence in a trace means exactly what it says: this
|
|
1442
|
+
// call can be found again by its own body and by nothing else.
|
|
1443
|
+
...(batchKey === undefined ? {} : { batchKey }),
|
|
1444
|
+
requestHeaders: recordableHeaders,
|
|
1445
|
+
requestBody: rawBody,
|
|
1446
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1447
|
+
requestTruncated: false,
|
|
1448
|
+
status: upstreamRes.status,
|
|
1449
|
+
responseHeaders,
|
|
1450
|
+
// Dropped rather than trimmed under `digest`: half a vector is not a smaller answer, it is
|
|
1451
|
+
// a wrong one, and the digest already says whether a later run agreed.
|
|
1452
|
+
responseBody: keepBody ? body : '',
|
|
1453
|
+
responseDigest: digest,
|
|
1454
|
+
responseTruncated: piped.truncated,
|
|
1455
|
+
responseComplete: !piped.truncated && piped.encodingError === undefined,
|
|
1456
|
+
...(piped.truncated
|
|
1457
|
+
? { responseError: 'response exceeded capture limit' }
|
|
1458
|
+
: piped.encodingError === undefined
|
|
1459
|
+
? {}
|
|
1460
|
+
: { responseError: piped.encodingError }),
|
|
1461
|
+
responseBytes: piped.bytes,
|
|
1462
|
+
durationMs: Date.now() - startedAt,
|
|
1463
|
+
transformations: [
|
|
1464
|
+
...captureHeaderTransformations(headers, recordableHeaders),
|
|
1465
|
+
...captureResponseHeaderTransformations(upstreamRes.headers),
|
|
1466
|
+
...captureEncodingTransformation('response.body', responseEncoding),
|
|
1467
|
+
...captureUrlTransformations(origin),
|
|
1468
|
+
...binaryBodyTransformation('request.body', rawBody),
|
|
1469
|
+
...binaryBodyTransformation('response.body', body),
|
|
1470
|
+
],
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
/** Split a configured origin into the host and port an exchange records. */
|
|
1474
|
+
function originParts(origin) {
|
|
1475
|
+
try {
|
|
1476
|
+
const url = new URL(origin);
|
|
1477
|
+
return {
|
|
1478
|
+
host: url.hostname,
|
|
1479
|
+
port: url.port !== '' ? Number(url.port) : url.protocol === 'http:' ? 80 : 443,
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
catch {
|
|
1483
|
+
// An origin that does not parse is still worth recording under the string we were given —
|
|
1484
|
+
// less anything secret in it. `host` goes into the trace verbatim (`persistNetExchange`
|
|
1485
|
+
// writes it as a `net.request` attr), and §7 says a credential never gets written down.
|
|
1486
|
+
// The trace's own redactor does not cover this: `hunter2` and `key=SECRET123` are under
|
|
1487
|
+
// the 20-character entropy floor and match no shape rule.
|
|
1488
|
+
//
|
|
1489
|
+
// Dead above, live below. A caller that already has a response held a URL undici parsed,
|
|
1490
|
+
// so the fallback cannot run on the success path; `passThrough`'s failure path is the one
|
|
1491
|
+
// place it does, because an origin `new URL` rejects is exactly what made `doFetch` throw.
|
|
1492
|
+
//
|
|
1493
|
+
// `redactString` after the cut, not instead of it: its shape rules and 20-character
|
|
1494
|
+
// entropy floor catch neither `hunter2` nor `key=SECRET123`, so it is a backstop for a
|
|
1495
|
+
// real key that happens to be in there, not the thing doing the work.
|
|
1496
|
+
return { host: redactor.redactString(scrubOrigin(origin)).value, port: 443 };
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Forward a POST no dialect claims, and record that it happened.
|
|
1501
|
+
*
|
|
1502
|
+
* It is recorded as `NetExchange` rather than `RecordedExchange` on purpose: agenticreplay holds the bytes
|
|
1503
|
+
* but not the meaning, so it cannot match this request on replay or rewrite its model on a fork.
|
|
1504
|
+
* Filing it with the model exchanges would inflate `reused=n/m` with turns replay will never
|
|
1505
|
+
* serve, and an operator would be reading a fidelity number that is not one.
|
|
1506
|
+
*/
|
|
1507
|
+
/**
|
|
1508
|
+
* An origin string as a host and a port, for the two places that record one.
|
|
1509
|
+
*
|
|
1510
|
+
* An origin that does not parse is still worth recording under the string we were given.
|
|
1511
|
+
*/
|
|
1512
|
+
/**
|
|
1513
|
+
* Why the call did not go out, with the origin in it made safe first.
|
|
1514
|
+
*
|
|
1515
|
+
* The message names the request, so it carries the origin — and undici quotes it verbatim:
|
|
1516
|
+
* `Failed to parse URL from gw?key=SECRET123/v1/...`. Running only the prose rules over that
|
|
1517
|
+
* leaves the key in, for the same shapes `scrubOrigin` exists to handle, so the known origin is
|
|
1518
|
+
* replaced by its scrubbed form before the prose pass ever looks at the sentence.
|
|
1519
|
+
*/
|
|
1520
|
+
function reasonFor(err, origin) {
|
|
1521
|
+
const scrubbed = String(err).split(origin).join(scrubOrigin(origin));
|
|
1522
|
+
return redactor.redactString(withoutCredentials(scrubbed)).value;
|
|
1523
|
+
}
|
|
1524
|
+
async function passThrough(path, rawBody, headers, recordableHeaders, res, startedAt, requestId, forwardBase, requestEncoding) {
|
|
1525
|
+
// `hybrid` is a fork, and a fork runs a live agent — so it forwards, exactly as `record` does.
|
|
1526
|
+
// Refusing here killed the fork on the first call agenticreplay could not read, which is the failure
|
|
1527
|
+
// passthrough exists to prevent, reintroduced one mode over.
|
|
1528
|
+
if (options.mode === 'replay') {
|
|
1529
|
+
// Strict replay has the network blocked and no recording to serve from — agenticreplay never
|
|
1530
|
+
// understood this call well enough to match it. Say so: a bare 502 mid-replay reads as agenticreplay
|
|
1531
|
+
// being broken, when the honest fact is narrower and more useful than that.
|
|
1532
|
+
stats.unmatched += 1;
|
|
1533
|
+
const reason = `${path} was captured as opaque network traffic, not as a model exchange, so it ` +
|
|
1534
|
+
'cannot be replayed. Recorded with --tls-intercept, or on a path no dialect claims.';
|
|
1535
|
+
options.onUnmatched?.({ seq: -1, index: -1, reason });
|
|
1536
|
+
json(res, 502, {
|
|
1537
|
+
error: { message: `agenticreplay replay cannot reproduce ${path}: ${reason}` },
|
|
1538
|
+
});
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
stats.passedThrough += 1;
|
|
1542
|
+
const origin = passthroughOrigin(headers, forwardBase);
|
|
1543
|
+
let upstreamRes;
|
|
1544
|
+
try {
|
|
1545
|
+
upstreamRes = await doFetch(`${origin}${path}`, {
|
|
1546
|
+
method: 'POST',
|
|
1547
|
+
// `upstreamHeaders` too, as `goLive` does. Omitting them sent a gateway the agent's own
|
|
1548
|
+
// credential — often the `agenticreplay-recorded` placeholder — and the call came back 401 for a
|
|
1549
|
+
// reason nothing in the trace explained.
|
|
1550
|
+
headers: {
|
|
1551
|
+
'content-type': 'application/json',
|
|
1552
|
+
...headers,
|
|
1553
|
+
...headersForOrigin(origin),
|
|
1554
|
+
},
|
|
1555
|
+
body: rawBody,
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
catch (err) {
|
|
1559
|
+
// An upstream that never answered is still something the agent asked for, and a trace
|
|
1560
|
+
// that leaves it out says the run made fewer calls than it did. Before this the throw
|
|
1561
|
+
// reached the server's catch-all: the agent got its 500 — which was right — and nothing
|
|
1562
|
+
// else happened. No event, no warning, and the replay later blamed "opaque network
|
|
1563
|
+
// traffic" that had never been captured.
|
|
1564
|
+
//
|
|
1565
|
+
// Not an exotic path. `fetchPinned` refuses every redirect on purpose, and a gateway
|
|
1566
|
+
// behind a CDN answers an unknown path with one: `api.orcarouter.ai` sends `301` to its
|
|
1567
|
+
// own marketing site, and MiniMax Code posts to `/v1/responses/input_tokens` before every
|
|
1568
|
+
// call, so two requests per run went missing and said nothing.
|
|
1569
|
+
//
|
|
1570
|
+
// `status: 0` is the same thing it means for an intercepted exchange: no response header
|
|
1571
|
+
// was ever seen. The reason travels in the body, scrubbed, because the message names the
|
|
1572
|
+
// origin it was given.
|
|
1573
|
+
options.onNetExchange?.({
|
|
1574
|
+
requestId,
|
|
1575
|
+
requestStartedAt: startedAt,
|
|
1576
|
+
...originParts(origin),
|
|
1577
|
+
method: 'POST',
|
|
1578
|
+
intercepted: false,
|
|
1579
|
+
path,
|
|
1580
|
+
requestHeaders: recordableHeaders,
|
|
1581
|
+
requestBody: rawBody,
|
|
1582
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1583
|
+
requestTruncated: false,
|
|
1584
|
+
status: 0,
|
|
1585
|
+
responseHeaders: {},
|
|
1586
|
+
responseBody: `agenticreplay did not forward this call: ${reasonFor(err, origin)}`,
|
|
1587
|
+
responseTruncated: false,
|
|
1588
|
+
responseComplete: false,
|
|
1589
|
+
responseError: reasonFor(err, origin),
|
|
1590
|
+
responseBytes: 0,
|
|
1591
|
+
durationMs: Date.now() - startedAt,
|
|
1592
|
+
});
|
|
1593
|
+
throw err;
|
|
1594
|
+
}
|
|
1595
|
+
const responseEncoding = upstreamRes.headers.get('content-encoding') ?? undefined;
|
|
1596
|
+
const responseHeaders = captureResponseHeaders(upstreamRes.headers, responseEncoding !== undefined && responseEncoding.trim() !== '');
|
|
1597
|
+
res.writeHead(upstreamRes.status, {
|
|
1598
|
+
'content-type': responseHeaders['content-type'] ?? 'application/json',
|
|
1599
|
+
});
|
|
1600
|
+
let piped;
|
|
1601
|
+
try {
|
|
1602
|
+
piped = await pipeThrough(upstreamRes, res, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES, plainBufferLimit);
|
|
1603
|
+
}
|
|
1604
|
+
catch (err) {
|
|
1605
|
+
const partial = err instanceof ResponsePipeAbortedError
|
|
1606
|
+
? err.partial
|
|
1607
|
+
: err.partial;
|
|
1608
|
+
options.onNetExchange?.({
|
|
1609
|
+
requestId,
|
|
1610
|
+
requestStartedAt: startedAt,
|
|
1611
|
+
...originParts(origin),
|
|
1612
|
+
method: 'POST',
|
|
1613
|
+
path,
|
|
1614
|
+
intercepted: false,
|
|
1615
|
+
requestHeaders: recordableHeaders,
|
|
1616
|
+
requestBody: rawBody,
|
|
1617
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1618
|
+
requestTruncated: false,
|
|
1619
|
+
status: upstreamRes.status,
|
|
1620
|
+
responseHeaders,
|
|
1621
|
+
responseBody: partial?.text ?? '',
|
|
1622
|
+
responseTruncated: partial?.truncated ?? false,
|
|
1623
|
+
responseComplete: false,
|
|
1624
|
+
responseError: String(err),
|
|
1625
|
+
responseBytes: partial?.bytes ?? 0,
|
|
1626
|
+
...(err instanceof ResponsePipeAbortedError ? { abandoned: true } : {}),
|
|
1627
|
+
durationMs: Date.now() - startedAt,
|
|
1628
|
+
});
|
|
1629
|
+
if (err instanceof ResponsePipeAbortedError)
|
|
1630
|
+
return;
|
|
1631
|
+
throw err;
|
|
1632
|
+
}
|
|
1633
|
+
const body = piped.text;
|
|
1634
|
+
let host = origin;
|
|
1635
|
+
let port = 443;
|
|
1636
|
+
try {
|
|
1637
|
+
const url = new URL(origin);
|
|
1638
|
+
host = url.hostname;
|
|
1639
|
+
port = url.port !== '' ? Number(url.port) : url.protocol === 'http:' ? 80 : 443;
|
|
1640
|
+
}
|
|
1641
|
+
catch {
|
|
1642
|
+
// An origin that does not parse is still worth recording under the string we were given.
|
|
1643
|
+
}
|
|
1644
|
+
options.onNetExchange?.({
|
|
1645
|
+
requestId,
|
|
1646
|
+
requestStartedAt: startedAt,
|
|
1647
|
+
host,
|
|
1648
|
+
port,
|
|
1649
|
+
method: 'POST',
|
|
1650
|
+
path,
|
|
1651
|
+
// AgenticReplay decrypted nothing here: the harness sent this in plaintext to the local proxy, and
|
|
1652
|
+
// agenticreplay relayed it. Saying otherwise puts a claim about TLS termination in the trace over a
|
|
1653
|
+
// host agenticreplay never terminated TLS for.
|
|
1654
|
+
intercepted: false,
|
|
1655
|
+
// `recordableHeaders`, never `headers`: the auth material was forwarded a moment ago and
|
|
1656
|
+
// must not now be written down. §7 says never write it, which is not the same as never relay it.
|
|
1657
|
+
requestHeaders: recordableHeaders,
|
|
1658
|
+
requestBody: rawBody,
|
|
1659
|
+
requestBytes: Buffer.byteLength(rawBody),
|
|
1660
|
+
requestTruncated: false,
|
|
1661
|
+
status: upstreamRes.status,
|
|
1662
|
+
responseHeaders,
|
|
1663
|
+
responseBody: body,
|
|
1664
|
+
responseTruncated: piped.truncated,
|
|
1665
|
+
responseComplete: !piped.truncated && piped.encodingError === undefined,
|
|
1666
|
+
...(piped.truncated
|
|
1667
|
+
? { responseError: 'response exceeded capture limit' }
|
|
1668
|
+
: piped.encodingError === undefined
|
|
1669
|
+
? {}
|
|
1670
|
+
: { responseError: piped.encodingError }),
|
|
1671
|
+
responseBytes: piped.bytes,
|
|
1672
|
+
durationMs: Date.now() - startedAt,
|
|
1673
|
+
transformations: [
|
|
1674
|
+
...captureHeaderTransformations(headers, recordableHeaders),
|
|
1675
|
+
...captureEncodingTransformation('request.body', requestEncoding),
|
|
1676
|
+
...captureResponseHeaderTransformations(upstreamRes.headers),
|
|
1677
|
+
...captureEncodingTransformation('response.body', responseEncoding),
|
|
1678
|
+
...captureUrlTransformations(origin),
|
|
1679
|
+
...binaryBodyTransformation('request.body', rawBody),
|
|
1680
|
+
...binaryBodyTransformation('response.body', body),
|
|
1681
|
+
],
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
/**
|
|
1685
|
+
* The host a `/forward/` path names, for a rule that wants to tell two vendors apart.
|
|
1686
|
+
*
|
|
1687
|
+
* Only where the request announced its own destination. On an ordinary redirected call agenticreplay knows
|
|
1688
|
+
* where it is *sending* the request but not where the client thought it was going, and inventing
|
|
1689
|
+
* a host would let a rule claim an endpoint on evidence that does not exist.
|
|
1690
|
+
*/
|
|
1691
|
+
function forwardHost(forward) {
|
|
1692
|
+
if (forward === undefined)
|
|
1693
|
+
return undefined;
|
|
1694
|
+
try {
|
|
1695
|
+
return new URL(forward.base).hostname;
|
|
1696
|
+
}
|
|
1697
|
+
catch {
|
|
1698
|
+
return undefined;
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
/** Did the agent ask for a stream? Both dialects spell it the same way. */
|
|
1702
|
+
function requestedStream(rawBody) {
|
|
1703
|
+
try {
|
|
1704
|
+
return JSON.parse(rawBody).stream === true;
|
|
1705
|
+
}
|
|
1706
|
+
catch {
|
|
1707
|
+
return false;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
function recordableUtf8(bytes) {
|
|
1711
|
+
const text = bytes.toString('utf8');
|
|
1712
|
+
if (Buffer.compare(Buffer.from(text, 'utf8'), bytes) === 0)
|
|
1713
|
+
return { text };
|
|
1714
|
+
return {
|
|
1715
|
+
text: `${BINARY_BODY_PREFIX}${bytes.toString('base64')}`,
|
|
1716
|
+
encodingError: 'unsupported_binary_body',
|
|
1717
|
+
};
|
|
1718
|
+
}
|
|
1719
|
+
class ResponsePipeAbortedError extends Error {
|
|
1720
|
+
partial;
|
|
1721
|
+
constructor(partial, message = 'client closed response') {
|
|
1722
|
+
super(message);
|
|
1723
|
+
this.partial = partial;
|
|
1724
|
+
this.name = 'ResponsePipeAbortedError';
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
async function readBoundedResponse(upstream, captureLimit, aggregateLimit) {
|
|
1728
|
+
if (!upstream.body) {
|
|
1729
|
+
const bytesBuffer = Buffer.from(await upstream.arrayBuffer());
|
|
1730
|
+
const bytes = bytesBuffer.byteLength;
|
|
1731
|
+
const decoded = recordableUtf8(bytesBuffer.subarray(0, captureLimit));
|
|
1732
|
+
return {
|
|
1733
|
+
text: decoded.text,
|
|
1734
|
+
bytes,
|
|
1735
|
+
truncated: bytes > captureLimit,
|
|
1736
|
+
...(decoded.encodingError === undefined ? {} : { encodingError: decoded.encodingError }),
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
const reader = upstream.body.getReader();
|
|
1740
|
+
const chunks = [];
|
|
1741
|
+
let capturedBytes = 0;
|
|
1742
|
+
let bytes = 0;
|
|
1743
|
+
try {
|
|
1744
|
+
for (;;) {
|
|
1745
|
+
const { done, value } = await reader.read();
|
|
1746
|
+
if (done)
|
|
1747
|
+
break;
|
|
1748
|
+
bytes += value.byteLength;
|
|
1749
|
+
const room = Math.min(captureLimit - capturedBytes, aggregateLimit - plainBufferedBytes);
|
|
1750
|
+
if (room > 0) {
|
|
1751
|
+
const keep = Math.min(room, value.byteLength);
|
|
1752
|
+
chunks.push(Buffer.from(value.subarray(0, keep)));
|
|
1753
|
+
capturedBytes += keep;
|
|
1754
|
+
plainBufferedBytes += keep;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const decoded = recordableUtf8(Buffer.concat(chunks));
|
|
1758
|
+
return {
|
|
1759
|
+
text: decoded.text,
|
|
1760
|
+
bytes,
|
|
1761
|
+
truncated: bytes > captureLimit || capturedBytes < bytes,
|
|
1762
|
+
...(decoded.encodingError === undefined ? {} : { encodingError: decoded.encodingError }),
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
finally {
|
|
1766
|
+
plainBufferedBytes = Math.max(0, plainBufferedBytes - capturedBytes);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
async function pipeThrough(upstream, res, captureLimit = Number.MAX_SAFE_INTEGER, aggregateLimit = Number.MAX_SAFE_INTEGER) {
|
|
1770
|
+
const body = upstream.body;
|
|
1771
|
+
if (!body) {
|
|
1772
|
+
const bytesBuffer = Buffer.from(await upstream.arrayBuffer());
|
|
1773
|
+
res.end(bytesBuffer);
|
|
1774
|
+
const bytes = bytesBuffer.byteLength;
|
|
1775
|
+
const decoded = recordableUtf8(bytesBuffer.subarray(0, captureLimit));
|
|
1776
|
+
return {
|
|
1777
|
+
text: decoded.text,
|
|
1778
|
+
bytes,
|
|
1779
|
+
truncated: bytes > captureLimit,
|
|
1780
|
+
...(decoded.encodingError === undefined ? {} : { encodingError: decoded.encodingError }),
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
const reader = body.getReader();
|
|
1784
|
+
const captured = [];
|
|
1785
|
+
let capturedBytes = 0;
|
|
1786
|
+
let bytes = 0;
|
|
1787
|
+
let closed = res.destroyed || res.writableEnded;
|
|
1788
|
+
const onClose = () => {
|
|
1789
|
+
closed = true;
|
|
1790
|
+
void reader.cancel();
|
|
1791
|
+
};
|
|
1792
|
+
res.once('close', onClose);
|
|
1793
|
+
const partial = () => {
|
|
1794
|
+
const buffer = Buffer.concat(captured);
|
|
1795
|
+
const decoded = recordableUtf8(buffer);
|
|
1796
|
+
return {
|
|
1797
|
+
text: decoded.text,
|
|
1798
|
+
bytes,
|
|
1799
|
+
truncated: bytes > captureLimit || capturedBytes < bytes,
|
|
1800
|
+
...(decoded.encodingError === undefined ? {} : { encodingError: decoded.encodingError }),
|
|
1801
|
+
};
|
|
1802
|
+
};
|
|
1803
|
+
try {
|
|
1804
|
+
for (;;) {
|
|
1805
|
+
if (closed)
|
|
1806
|
+
throw new ResponsePipeAbortedError(partial());
|
|
1807
|
+
const { done, value } = await reader.read();
|
|
1808
|
+
if (done)
|
|
1809
|
+
break;
|
|
1810
|
+
bytes += value.byteLength;
|
|
1811
|
+
if (capturedBytes < captureLimit) {
|
|
1812
|
+
const aggregateRoom = Math.max(0, aggregateLimit - plainBufferedBytes);
|
|
1813
|
+
const keep = Math.min(value.byteLength, captureLimit - capturedBytes, aggregateRoom);
|
|
1814
|
+
if (keep > 0) {
|
|
1815
|
+
captured.push(Buffer.from(value.subarray(0, keep)));
|
|
1816
|
+
capturedBytes += keep;
|
|
1817
|
+
plainBufferedBytes += keep;
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
// Backpressure: if the socket says it is full, wait for it rather than buffering in memory
|
|
1821
|
+
// on the agent's behalf.
|
|
1822
|
+
if (!res.write(value)) {
|
|
1823
|
+
await new Promise((resolve, reject) => {
|
|
1824
|
+
const onDrain = () => {
|
|
1825
|
+
cleanup();
|
|
1826
|
+
resolve();
|
|
1827
|
+
};
|
|
1828
|
+
const onResponseClose = () => {
|
|
1829
|
+
cleanup();
|
|
1830
|
+
closed = true;
|
|
1831
|
+
reject(new ResponsePipeAbortedError(partial()));
|
|
1832
|
+
};
|
|
1833
|
+
const cleanup = () => {
|
|
1834
|
+
res.off('drain', onDrain);
|
|
1835
|
+
res.off('close', onResponseClose);
|
|
1836
|
+
};
|
|
1837
|
+
res.once('drain', onDrain);
|
|
1838
|
+
res.once('close', onResponseClose);
|
|
1839
|
+
if (res.destroyed || res.writableEnded)
|
|
1840
|
+
onResponseClose();
|
|
1841
|
+
});
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
catch (err) {
|
|
1846
|
+
plainBufferedBytes = Math.max(0, plainBufferedBytes - capturedBytes);
|
|
1847
|
+
if (err instanceof ResponsePipeAbortedError)
|
|
1848
|
+
throw err;
|
|
1849
|
+
if (err instanceof Error) {
|
|
1850
|
+
Object.defineProperty(err, 'partial', { value: partial(), enumerable: false });
|
|
1851
|
+
throw err;
|
|
1852
|
+
}
|
|
1853
|
+
const wrapped = new Error(String(err));
|
|
1854
|
+
Object.defineProperty(wrapped, 'partial', { value: partial(), enumerable: false });
|
|
1855
|
+
throw wrapped;
|
|
1856
|
+
}
|
|
1857
|
+
finally {
|
|
1858
|
+
res.off('close', onClose);
|
|
1859
|
+
res.end();
|
|
1860
|
+
}
|
|
1861
|
+
const buffer = Buffer.concat(captured);
|
|
1862
|
+
const decoded = recordableUtf8(buffer);
|
|
1863
|
+
plainBufferedBytes = Math.max(0, plainBufferedBytes - capturedBytes);
|
|
1864
|
+
return {
|
|
1865
|
+
text: decoded.text,
|
|
1866
|
+
bytes,
|
|
1867
|
+
truncated: bytes > captureLimit || capturedBytes < bytes,
|
|
1868
|
+
...(decoded.encodingError === undefined ? {} : { encodingError: decoded.encodingError }),
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function buildExchange(input) {
|
|
1872
|
+
const canonicalRequest = input.dialect.toCanonicalRequest(JSON.parse(input.rawRequest));
|
|
1873
|
+
let canonicalResponse;
|
|
1874
|
+
try {
|
|
1875
|
+
canonicalResponse = input.streamed
|
|
1876
|
+
? input.dialect.parseStream(input.rawResponse)
|
|
1877
|
+
: input.dialect.toCanonicalResponse(JSON.parse(input.rawResponse));
|
|
1878
|
+
}
|
|
1879
|
+
catch {
|
|
1880
|
+
// An unparseable response is still worth recording verbatim — the raw bytes are the
|
|
1881
|
+
// authoritative record, and a canonical view we could not build is not a reason to lose them.
|
|
1882
|
+
canonicalResponse = undefined;
|
|
1883
|
+
}
|
|
1884
|
+
return {
|
|
1885
|
+
seq: input.seq,
|
|
1886
|
+
...(input.requestId === undefined ? {} : { requestId: input.requestId }),
|
|
1887
|
+
...(input.requestStartedAt === undefined ? {} : { requestStartedAt: input.requestStartedAt }),
|
|
1888
|
+
dialect: input.dialect.id,
|
|
1889
|
+
path: input.path,
|
|
1890
|
+
rawRequest: input.rawRequest,
|
|
1891
|
+
rawResponse: input.rawResponse,
|
|
1892
|
+
status: input.status,
|
|
1893
|
+
streamed: input.streamed,
|
|
1894
|
+
canonicalRequest,
|
|
1895
|
+
canonicalResponse,
|
|
1896
|
+
usage: canonicalResponse?.usage,
|
|
1897
|
+
requestHeaders: input.headers,
|
|
1898
|
+
...(input.alpn === undefined ? {} : { alpn: input.alpn }),
|
|
1899
|
+
...(input.responseDecodedFrom === undefined
|
|
1900
|
+
? {}
|
|
1901
|
+
: { responseDecodedFrom: input.responseDecodedFrom }),
|
|
1902
|
+
...(recordableOrigin(input.upstream) === undefined
|
|
1903
|
+
? {}
|
|
1904
|
+
: { upstream: recordableOrigin(input.upstream) }),
|
|
1905
|
+
durationMs: input.durationMs,
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
async function handle(req, res) {
|
|
1909
|
+
const startedAt = Date.now();
|
|
1910
|
+
// Split, not discarded. Two different questions are asked of a request line here and they
|
|
1911
|
+
// want different halves of it: *which endpoint is this* is answered by the path alone —
|
|
1912
|
+
// `selectDialect` matches an endpoint and a query is not part of one — while *what do I
|
|
1913
|
+
// send upstream* wants the line as the client wrote it. Dropping the query at the top answered
|
|
1914
|
+
// the first question and silently lost the second: a client calling
|
|
1915
|
+
// `<base>/chat/completions?api-version=2026-02-01` reached its origin as
|
|
1916
|
+
// `/v1/chat/completions`, which is `404 Resource not found` on Azure OpenAI, where that
|
|
1917
|
+
// parameter is required on every call. The trace lost it too, so nothing in the run said why.
|
|
1918
|
+
const rawPath = req.url ?? '/';
|
|
1919
|
+
const requestId = randomUUID();
|
|
1920
|
+
const path = rawPath.split('?')[0] ?? '/';
|
|
1921
|
+
const query = rawPath.slice(path.length);
|
|
1922
|
+
if (path === '/__agenticreplay/health') {
|
|
1923
|
+
json(res, 200, { ok: true, ...stats });
|
|
1924
|
+
return;
|
|
1925
|
+
}
|
|
1926
|
+
// A `/forward/` path carries the base URL the client was actually given — see
|
|
1927
|
+
// `decodeForwardPath`. The dialect reads the real path behind the prefix, and everything
|
|
1928
|
+
// recorded below does too, so the trace shows the conversation the agent had, not the
|
|
1929
|
+
// envelope agenticreplay wrapped it in.
|
|
1930
|
+
const forward = decodeForwardPath(path);
|
|
1931
|
+
const dialectPath = forward?.path ?? path;
|
|
1932
|
+
/** What goes upstream and into the trace: the endpoint agenticreplay resolved, called the way it was. */
|
|
1933
|
+
const requestedPath = `${dialectPath}${query}`;
|
|
1934
|
+
const dialect = selectDialect(dialects, dialectPath);
|
|
1935
|
+
// Only a POST is ever a model call. Relaxing this to `!dialect && …` let a PUT or a DELETE on
|
|
1936
|
+
// a dialect path through to `goLive`, which forwarded it upstream and recorded it as a model
|
|
1937
|
+
// exchange; and it turned `GET /v1/messages` into a 400 about unreadable JSON rather than the
|
|
1938
|
+
// honest answer. Method first, then passthrough decides what to do with a POST agenticreplay cannot read.
|
|
1939
|
+
if (req.method !== 'POST' && req.headers.upgrade === undefined) {
|
|
1940
|
+
json(res, 404, {
|
|
1941
|
+
error: {
|
|
1942
|
+
message: `agenticreplay proxy does not handle ${req.method ?? 'GET'} ${dialectPath}`,
|
|
1943
|
+
},
|
|
1944
|
+
});
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
const startHeaders = {};
|
|
1948
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
1949
|
+
const key = name.toLowerCase();
|
|
1950
|
+
if (HOP_BY_HOP_HEADERS.has(key) || SECRET_REQUEST_HEADERS.has(key) || value === undefined)
|
|
1951
|
+
continue;
|
|
1952
|
+
startHeaders[key] = value;
|
|
1953
|
+
}
|
|
1954
|
+
const configuredDialectOrigin = dialect
|
|
1955
|
+
? (options.upstream?.[dialect.id] ??
|
|
1956
|
+
(dialect.id === 'openai-responses' || dialect.id === 'codex'
|
|
1957
|
+
? options.upstream?.openai
|
|
1958
|
+
: undefined) ??
|
|
1959
|
+
dialect.defaultUpstream)
|
|
1960
|
+
: undefined;
|
|
1961
|
+
const startOrigin = dialect
|
|
1962
|
+
? (forward?.base ?? configuredDialectOrigin)
|
|
1963
|
+
: passthroughOrigin(startHeaders, forward?.base);
|
|
1964
|
+
if (startOrigin === undefined)
|
|
1965
|
+
throw new Error('unable to resolve request destination');
|
|
1966
|
+
const startDestination = originParts(startOrigin);
|
|
1967
|
+
const startRequest = {
|
|
1968
|
+
requestId,
|
|
1969
|
+
requestStartedAt: startedAt,
|
|
1970
|
+
host: startDestination.host,
|
|
1971
|
+
port: startDestination.port,
|
|
1972
|
+
method: req.method ?? 'GET',
|
|
1973
|
+
path: requestedPath,
|
|
1974
|
+
requestHeaders: startHeaders,
|
|
1975
|
+
requestBytes: Buffer.alloc(0),
|
|
1976
|
+
requestTruncated: false,
|
|
1977
|
+
};
|
|
1978
|
+
try {
|
|
1979
|
+
await options.onRequestStart?.(startRequest);
|
|
1980
|
+
}
|
|
1981
|
+
catch (err) {
|
|
1982
|
+
try {
|
|
1983
|
+
options.onNetExchange?.({
|
|
1984
|
+
...startRequest,
|
|
1985
|
+
requestBytes: 0,
|
|
1986
|
+
requestBody: '',
|
|
1987
|
+
status: 0,
|
|
1988
|
+
responseHeaders: {},
|
|
1989
|
+
responseBody: '',
|
|
1990
|
+
responseTruncated: false,
|
|
1991
|
+
responseComplete: false,
|
|
1992
|
+
responseError: `request-start recorder failed: ${String(err)}`,
|
|
1993
|
+
responseBytes: 0,
|
|
1994
|
+
intercepted: false,
|
|
1995
|
+
durationMs: Date.now() - startedAt,
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
catch {
|
|
1999
|
+
// Preserve the recorder failure when terminal persistence cannot itself be recorded.
|
|
2000
|
+
}
|
|
2001
|
+
throw err;
|
|
2002
|
+
}
|
|
2003
|
+
if (req.headers.upgrade !== undefined) {
|
|
2004
|
+
const message = 'unsupported WebSocket transport';
|
|
2005
|
+
const responseBody = JSON.stringify({ error: { message } });
|
|
2006
|
+
options.onNetExchange?.({
|
|
2007
|
+
...startRequest,
|
|
2008
|
+
requestBytes: 0,
|
|
2009
|
+
requestBody: '',
|
|
2010
|
+
status: 501,
|
|
2011
|
+
responseHeaders: { 'content-type': 'application/json' },
|
|
2012
|
+
responseBody,
|
|
2013
|
+
responseBytes: Buffer.byteLength(responseBody),
|
|
2014
|
+
responseComplete: false,
|
|
2015
|
+
responseTruncated: false,
|
|
2016
|
+
responseError: message,
|
|
2017
|
+
intercepted: false,
|
|
2018
|
+
durationMs: Date.now() - startedAt,
|
|
2019
|
+
transformations: [
|
|
2020
|
+
{
|
|
2021
|
+
kind: 'omission',
|
|
2022
|
+
location: 'transport',
|
|
2023
|
+
rule: 'unsupported_transport',
|
|
2024
|
+
count: 1,
|
|
2025
|
+
reason: 'WebSocket frames are not captured',
|
|
2026
|
+
},
|
|
2027
|
+
],
|
|
2028
|
+
});
|
|
2029
|
+
json(res, 501, { error: { message } });
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
let rawBody;
|
|
2033
|
+
let requestBytes;
|
|
2034
|
+
let requestReservedBytes = 0;
|
|
2035
|
+
try {
|
|
2036
|
+
const read = await readBody(req, options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES);
|
|
2037
|
+
rawBody = decodeBody(read.body, Array.isArray(req.headers['content-encoding'])
|
|
2038
|
+
? req.headers['content-encoding'].join(', ')
|
|
2039
|
+
: req.headers['content-encoding'], options.maxCapturedBytes ?? DEFAULT_MAX_CAPTURED_BYTES);
|
|
2040
|
+
requestBytes = read.bytes;
|
|
2041
|
+
requestReservedBytes = Buffer.byteLength(rawBody);
|
|
2042
|
+
if (plainBufferedBytes + requestReservedBytes > plainBufferLimit) {
|
|
2043
|
+
throw new BodyLimitError(requestReservedBytes, plainBufferLimit);
|
|
2044
|
+
}
|
|
2045
|
+
plainBufferedBytes += requestReservedBytes;
|
|
2046
|
+
}
|
|
2047
|
+
catch (err) {
|
|
2048
|
+
if (!(err instanceof BodyLimitError)) {
|
|
2049
|
+
options.onNetExchange?.({
|
|
2050
|
+
requestId,
|
|
2051
|
+
requestStartedAt: startedAt,
|
|
2052
|
+
host: req.headers.host ?? 'local',
|
|
2053
|
+
port: 0,
|
|
2054
|
+
intercepted: false,
|
|
2055
|
+
method: req.method ?? 'POST',
|
|
2056
|
+
path: requestedPath,
|
|
2057
|
+
requestHeaders: {},
|
|
2058
|
+
requestBody: '',
|
|
2059
|
+
requestBytes: 0,
|
|
2060
|
+
requestTruncated: true,
|
|
2061
|
+
status: 0,
|
|
2062
|
+
responseHeaders: {},
|
|
2063
|
+
responseBody: '',
|
|
2064
|
+
responseTruncated: false,
|
|
2065
|
+
responseComplete: false,
|
|
2066
|
+
responseError: String(err),
|
|
2067
|
+
responseBytes: 0,
|
|
2068
|
+
durationMs: Date.now() - startedAt,
|
|
2069
|
+
});
|
|
2070
|
+
json(res, 400, {
|
|
2071
|
+
error: { message: `agenticreplay could not decode request body: ${String(err)}` },
|
|
2072
|
+
});
|
|
2073
|
+
return;
|
|
2074
|
+
}
|
|
2075
|
+
options.onNetExchange?.({
|
|
2076
|
+
requestId,
|
|
2077
|
+
requestStartedAt: startedAt,
|
|
2078
|
+
host: req.headers.host ?? 'local',
|
|
2079
|
+
port: 0,
|
|
2080
|
+
intercepted: false,
|
|
2081
|
+
method: req.method ?? 'POST',
|
|
2082
|
+
path: requestedPath,
|
|
2083
|
+
requestHeaders: {},
|
|
2084
|
+
requestBody: '',
|
|
2085
|
+
requestBytes: err.bytes,
|
|
2086
|
+
requestTruncated: true,
|
|
2087
|
+
status: 0,
|
|
2088
|
+
responseHeaders: {},
|
|
2089
|
+
responseBody: '',
|
|
2090
|
+
responseTruncated: false,
|
|
2091
|
+
responseComplete: false,
|
|
2092
|
+
responseError: String(err),
|
|
2093
|
+
responseBytes: 0,
|
|
2094
|
+
durationMs: Date.now() - startedAt,
|
|
2095
|
+
});
|
|
2096
|
+
json(res, 413, { error: { message: String(err) } });
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
const inboundRequestHeader = req.headers['content-encoding'];
|
|
2100
|
+
const inboundRequestEncoding = Array.isArray(inboundRequestHeader)
|
|
2101
|
+
? inboundRequestHeader.join(', ')
|
|
2102
|
+
: inboundRequestHeader;
|
|
2103
|
+
try {
|
|
2104
|
+
const headers = {};
|
|
2105
|
+
const recordableHeaders = {};
|
|
2106
|
+
for (const [k, v] of Object.entries(req.headers)) {
|
|
2107
|
+
const key = k.toLowerCase();
|
|
2108
|
+
if (HOP_BY_HOP_HEADERS.has(key))
|
|
2109
|
+
continue;
|
|
2110
|
+
if (v === undefined)
|
|
2111
|
+
continue;
|
|
2112
|
+
headers[k] = Array.isArray(v) ? v.join(', ') : v;
|
|
2113
|
+
}
|
|
2114
|
+
// Node's parsed headers combine repeated fields. Keep the raw field order and multiplicity
|
|
2115
|
+
// for persisted evidence while retaining the scalar map above for fetch forwarding.
|
|
2116
|
+
for (let index = 0; index + 1 < req.rawHeaders.length; index += 2) {
|
|
2117
|
+
const key = req.rawHeaders[index].toLowerCase();
|
|
2118
|
+
if (HOP_BY_HOP_HEADERS.has(key) || SECRET_REQUEST_HEADERS.has(key))
|
|
2119
|
+
continue;
|
|
2120
|
+
const value = req.rawHeaders[index + 1];
|
|
2121
|
+
const prior = recordableHeaders[key];
|
|
2122
|
+
recordableHeaders[key] =
|
|
2123
|
+
prior === undefined ? value : Array.isArray(prior) ? [...prior, value] : [prior, value];
|
|
2124
|
+
}
|
|
2125
|
+
// `decodeBody` above gives the origin the serialized JSON rather than the compressed wire
|
|
2126
|
+
// bytes. Remove the stale framing headers or the origin will try to decompress plaintext.
|
|
2127
|
+
if (headers['content-encoding'] !== undefined) {
|
|
2128
|
+
delete headers['content-encoding'];
|
|
2129
|
+
delete recordableHeaders['content-encoding'];
|
|
2130
|
+
const length = String(Buffer.byteLength(rawBody));
|
|
2131
|
+
headers['content-length'] = length;
|
|
2132
|
+
recordableHeaders['content-length'] = length;
|
|
2133
|
+
}
|
|
2134
|
+
if (!dialect) {
|
|
2135
|
+
// Before passthrough, and only where no dialect claims the path: a dialect is the richer
|
|
2136
|
+
// reading of a call — replayable, forkable, counted in `exact` — and a rule that outranked
|
|
2137
|
+
// one would turn a chat completion into opaque bytes keyed by their own body.
|
|
2138
|
+
const rule = selectRetrievalRule(retrievalRules, dialectPath, forwardHost(forward));
|
|
2139
|
+
if (rule && isReadable(rawBody)) {
|
|
2140
|
+
await serveRetrieval(rule, requestedPath, rawBody, headers, recordableHeaders, res, startedAt, requestId, forward?.base);
|
|
2141
|
+
return;
|
|
2142
|
+
}
|
|
2143
|
+
await passThrough(requestedPath, rawBody, headers, recordableHeaders, res, startedAt, requestId, forward?.base, inboundRequestEncoding);
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
// Parsed here rather than inside `goLive`, which read it unguarded — so a body agenticreplay could not
|
|
2147
|
+
// read reached the server's catch-all and came back as `500 SyntaxError: …`. To whoever is
|
|
2148
|
+
// running the agent that reads as agenticreplay falling over, and sends them to agenticreplay's issue tracker
|
|
2149
|
+
// instead of to the line of their own harness that sent it. Replay already answered 400.
|
|
2150
|
+
if (!isReadable(rawBody)) {
|
|
2151
|
+
const errorBody = {
|
|
2152
|
+
error: {
|
|
2153
|
+
message: `agenticreplay proxy could not read the body of POST ${dialectPath}: expected JSON, got ` +
|
|
2154
|
+
`${rawBody === '' ? 'an empty body' : `${rawBody.length} bytes that do not parse`}`,
|
|
2155
|
+
},
|
|
2156
|
+
};
|
|
2157
|
+
options.onNetExchange?.({
|
|
2158
|
+
requestId,
|
|
2159
|
+
requestStartedAt: startedAt,
|
|
2160
|
+
host: req.headers.host ?? 'local',
|
|
2161
|
+
port: 0,
|
|
2162
|
+
intercepted: false,
|
|
2163
|
+
method: req.method ?? 'POST',
|
|
2164
|
+
path: requestedPath,
|
|
2165
|
+
requestHeaders: recordableHeaders,
|
|
2166
|
+
requestBody: rawBody,
|
|
2167
|
+
requestBytes,
|
|
2168
|
+
requestTruncated: false,
|
|
2169
|
+
status: 400,
|
|
2170
|
+
responseHeaders: { 'content-type': 'application/json' },
|
|
2171
|
+
responseBody: JSON.stringify(errorBody),
|
|
2172
|
+
responseTruncated: false,
|
|
2173
|
+
responseComplete: true,
|
|
2174
|
+
responseBytes: Buffer.byteLength(JSON.stringify(errorBody)),
|
|
2175
|
+
durationMs: Date.now() - startedAt,
|
|
2176
|
+
transformations: binaryBodyTransformation('request.body', rawBody),
|
|
2177
|
+
});
|
|
2178
|
+
json(res, 400, errorBody);
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
if (options.mode === 'record') {
|
|
2182
|
+
await goLive(dialect, requestedPath, rawBody, headers, recordableHeaders, res, startedAt, requestId, inboundRequestEncoding, forward?.base);
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
// Replay and hybrid: try the recording first.
|
|
2186
|
+
let canonical;
|
|
2187
|
+
try {
|
|
2188
|
+
canonical = dialect.toCanonicalRequest(JSON.parse(rawBody));
|
|
2189
|
+
}
|
|
2190
|
+
catch (err) {
|
|
2191
|
+
json(res, 400, { error: { message: `unparseable request body: ${String(err)}` } });
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
const beyondFork = options.mode === 'hybrid' && matcher.cursor >= (options.forkAt ?? replayable.length);
|
|
2195
|
+
if (!beyondFork) {
|
|
2196
|
+
const result = matcher.match(canonical);
|
|
2197
|
+
if (result.matched) {
|
|
2198
|
+
const exchange = replayable[result.index];
|
|
2199
|
+
if (result.reordered)
|
|
2200
|
+
stats.reordered += 1;
|
|
2201
|
+
if (result.divergence) {
|
|
2202
|
+
stats.matchedInexact += 1;
|
|
2203
|
+
stats.divergences += 1;
|
|
2204
|
+
options.onDivergence?.({ ...result.divergence, seq: exchange.seq });
|
|
2205
|
+
}
|
|
2206
|
+
else {
|
|
2207
|
+
stats.matchedExact += 1;
|
|
2208
|
+
}
|
|
2209
|
+
res.writeHead(exchange.status, {
|
|
2210
|
+
'content-type': exchange.streamed ? 'text/event-stream' : 'application/json',
|
|
2211
|
+
});
|
|
2212
|
+
res.end(exchange.rawResponse);
|
|
2213
|
+
return;
|
|
2214
|
+
}
|
|
2215
|
+
stats.unmatched += 1;
|
|
2216
|
+
// Hybrid mode is *supposed* to continue live here — that is what forking means — but spec §4
|
|
2217
|
+
// says replay must not silently approximate, and every inexact match is an event in the
|
|
2218
|
+
// trace. Without this a fork could start diverging below its own fork point and every
|
|
2219
|
+
// artifact it produced would look clean. Going live is the right behaviour; being quiet
|
|
2220
|
+
// about it is not.
|
|
2221
|
+
if (options.mode === 'hybrid' || options.loose) {
|
|
2222
|
+
stats.divergences += 1;
|
|
2223
|
+
options.onDivergence?.({
|
|
2224
|
+
level: 'major',
|
|
2225
|
+
rung: 4,
|
|
2226
|
+
distance: -1,
|
|
2227
|
+
detail: result.reason ?? 'request does not match the recording; served live instead',
|
|
2228
|
+
seq: replayable[result.index]?.seq ?? -1,
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
if (!options.loose && options.mode === 'replay') {
|
|
2232
|
+
// Halt loudly. Inventing a reply here would make every downstream conclusion worthless.
|
|
2233
|
+
const reason = result.reason ?? 'request does not match the recording';
|
|
2234
|
+
options.onUnmatched?.({
|
|
2235
|
+
seq: replayable[result.index]?.seq ?? -1,
|
|
2236
|
+
index: result.index,
|
|
2237
|
+
reason,
|
|
2238
|
+
});
|
|
2239
|
+
// 400, emphatically not 409. Every mainstream client retries 408/409/429/5xx by default,
|
|
2240
|
+
// so a 409 halt is re-sent until the retry budget is gone — the operator gets a stalled
|
|
2241
|
+
// terminal and no reason, which is strictly worse than a wrong answer because it looks
|
|
2242
|
+
// like a hang in agenticreplay rather than a mismatch in the recording. 400 is terminal everywhere.
|
|
2243
|
+
//
|
|
2244
|
+
// The envelope is the provider's own error shape for the same reason: clients read
|
|
2245
|
+
// `error.message` and print it, so this sentence is what actually reaches the human.
|
|
2246
|
+
json(res, 400, {
|
|
2247
|
+
type: 'error',
|
|
2248
|
+
error: {
|
|
2249
|
+
type: 'invalid_request_error',
|
|
2250
|
+
message: `agenticreplay: replay halted — ${reason}. ` +
|
|
2251
|
+
'Re-run with `agenticreplay replay <run> --loose` to continue live from this point, ' +
|
|
2252
|
+
'or `agenticreplay show <run>` to see what was recorded.',
|
|
2253
|
+
},
|
|
2254
|
+
});
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
await goLive(dialect, requestedPath, rawBody, headers, recordableHeaders, res, startedAt, requestId, inboundRequestEncoding, forward?.base);
|
|
2259
|
+
}
|
|
2260
|
+
finally {
|
|
2261
|
+
plainBufferedBytes = Math.max(0, plainBufferedBytes - requestReservedBytes);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
const host = options.host ?? '127.0.0.1';
|
|
2265
|
+
await new Promise((resolve) => server.listen(options.port ?? 0, host, resolve));
|
|
2266
|
+
const port = server.address().port;
|
|
2267
|
+
return {
|
|
2268
|
+
url: `http://${host}:${port}`,
|
|
2269
|
+
port,
|
|
2270
|
+
stats: () => ({ ...stats }),
|
|
2271
|
+
exchanges: () => captured.slice(),
|
|
2272
|
+
...(options.tls && interception
|
|
2273
|
+
? {
|
|
2274
|
+
tls: {
|
|
2275
|
+
hosts: interception.policy.describe(),
|
|
2276
|
+
fingerprint: options.tls.ca.fingerprint,
|
|
2277
|
+
caCertPath: options.tls.ca.certPath,
|
|
2278
|
+
caBundlePath: options.tls.ca.bundlePath,
|
|
2279
|
+
},
|
|
2280
|
+
}
|
|
2281
|
+
: {}),
|
|
2282
|
+
close: () => new Promise((resolve, reject) => {
|
|
2283
|
+
// Tunnels and decrypted sessions are long-lived by design, and `close` waits for every
|
|
2284
|
+
// open connection. Without this a recorded run would hang on exit behind an agent's idle
|
|
2285
|
+
// keep-alive socket.
|
|
2286
|
+
interception?.close();
|
|
2287
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
2288
|
+
}),
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
//# sourceMappingURL=server.js.map
|