@opencode-trace/plugin 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +124 -0
  2. package/dist/integration.test.d.ts +2 -0
  3. package/dist/integration.test.d.ts.map +1 -0
  4. package/dist/integration.test.js +76 -0
  5. package/dist/integration.test.js.map +1 -0
  6. package/dist/plugin-instance.d.ts +29 -0
  7. package/dist/plugin-instance.d.ts.map +1 -0
  8. package/dist/plugin-instance.js +219 -0
  9. package/dist/plugin-instance.js.map +1 -0
  10. package/dist/plugin-instance.test.d.ts +2 -0
  11. package/dist/plugin-instance.test.d.ts.map +1 -0
  12. package/dist/plugin-instance.test.js +135 -0
  13. package/dist/plugin-instance.test.js.map +1 -0
  14. package/dist/redact.d.ts +2 -0
  15. package/dist/redact.d.ts.map +1 -0
  16. package/dist/redact.js +40 -0
  17. package/dist/redact.js.map +1 -0
  18. package/dist/redact.test.d.ts +2 -0
  19. package/dist/redact.test.d.ts.map +1 -0
  20. package/dist/redact.test.js +77 -0
  21. package/dist/redact.test.js.map +1 -0
  22. package/dist/state-queue.d.ts +14 -0
  23. package/dist/state-queue.d.ts.map +1 -0
  24. package/dist/state-queue.js +44 -0
  25. package/dist/state-queue.js.map +1 -0
  26. package/dist/state-queue.test.d.ts +2 -0
  27. package/dist/state-queue.test.d.ts.map +1 -0
  28. package/dist/state-queue.test.js +99 -0
  29. package/dist/state-queue.test.js.map +1 -0
  30. package/dist/trace.d.ts +32 -0
  31. package/dist/trace.d.ts.map +1 -0
  32. package/dist/trace.js +100 -0
  33. package/dist/trace.js.map +1 -0
  34. package/dist/trace.test.d.ts +2 -0
  35. package/dist/trace.test.d.ts.map +1 -0
  36. package/dist/trace.test.js +359 -0
  37. package/dist/trace.test.js.map +1 -0
  38. package/dist/write-queue.d.ts +14 -0
  39. package/dist/write-queue.d.ts.map +1 -0
  40. package/dist/write-queue.js +62 -0
  41. package/dist/write-queue.js.map +1 -0
  42. package/dist/write-queue.test.d.ts +2 -0
  43. package/dist/write-queue.test.d.ts.map +1 -0
  44. package/dist/write-queue.test.js +92 -0
  45. package/dist/write-queue.test.js.map +1 -0
  46. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @opencode-trace/plugin
2
+
3
+ OpenCode plugin for automatically tracing AI API interactions.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@opencode-trace/plugin)](https://www.npmjs.com/package/@opencode-trace/plugin)
6
+
7
+ ## Installation
8
+
9
+ Install OpenCode plugin:
10
+
11
+ ```bash
12
+ opencode plugin @opencode-trace/plugin
13
+ ```
14
+
15
+ Or add in OpenCode configuration file (`opencode.json`):
16
+
17
+ ```json
18
+ {
19
+ "plugin": ["@opencode-trace/plugin"]
20
+ }
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ After installation, the plugin automatically intercepts all HTTP requests from OpenCode and records them to `~/.opencode-trace/`.
26
+
27
+ No manual operation required, every interaction with AI is automatically traced.
28
+
29
+ ### Tracing Content
30
+
31
+ - Complete request (URL, Headers, Body)
32
+ - Complete response (Status, Headers, Body)
33
+ - SSE stream data (incremental tokens)
34
+ - Token usage statistics (input/output)
35
+ - Latency metrics (first token latency TTFT, token interval TPOT)
36
+ - Error information (failed requests)
37
+
38
+ ### Supported APIs
39
+
40
+ - OpenAI Chat Completions API
41
+ - OpenAI Responses API (new format)
42
+ - Anthropic Messages API
43
+
44
+ ### Trace Control
45
+
46
+ Control tracing via CLI or Viewer:
47
+
48
+ ```bash
49
+ # CLI
50
+ opencode-trace enable # Enable global tracing
51
+ opencode-trace disable # Disable global tracing
52
+ opencode-trace status # View status
53
+
54
+ # Or use Viewer web interface
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ ### Environment Variables
60
+
61
+ | Variable | Description |
62
+ |----------|-------------|
63
+ | `OPENCODE_TRACE_DIR` | Custom trace directory (default `~/.opencode-trace`) |
64
+ | `OPENCODE_TRACE_REDACT` | Whether to redact sensitive information (default `true`) |
65
+
66
+ ### Redaction Rules
67
+
68
+ The plugin automatically redacts the following sensitive information:
69
+
70
+ - HTTP Headers: `authorization`, `api-key`, `x-api-key`, etc.
71
+ - Stack traces: user paths, IP addresses, ports
72
+
73
+ Can disable redaction via `OPENCODE_TRACE_REDACT=false`.
74
+
75
+ ## Data Storage
76
+
77
+ Trace data storage structure:
78
+
79
+ ```
80
+ ~/.opencode-trace/
81
+ ├── <session-id>/ # Session directory
82
+ │ ├── 1.json # 1st request record
83
+ │ ├── 1.sse # SSE stream data
84
+ │ ├── 2.json # 2nd request record
85
+ │ ├── metadata.json # Session metadata
86
+ ├── state.db # SQLite state database
87
+ ```
88
+
89
+ ## Tools
90
+
91
+ The plugin provides the following OpenCode tools:
92
+
93
+ | Tool | Description |
94
+ |------|-------------|
95
+ | `trace_enable` | Enable tracing |
96
+ | `trace_disable` | Disable tracing |
97
+ | `trace_status` | View tracing status |
98
+
99
+ Can be called directly in conversation:
100
+
101
+ ```
102
+ User: Please enable tracing
103
+ AI: [calls trace_enable tool] Tracing enabled
104
+ ```
105
+
106
+ ## Troubleshooting
107
+
108
+ ### Tracing Not Working
109
+
110
+ 1. Confirm plugin installed: check `opencode.json` configuration
111
+ 2. Confirm tracing enabled: run `opencode-trace status`
112
+ 3. Check directory permissions: ensure `~/.opencode-trace` is writable
113
+
114
+ ### SQLite Corrupted
115
+
116
+ Run repair command:
117
+
118
+ ```bash
119
+ opencode-trace sync --repair
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.d.ts","sourceRoot":"","sources":["../src/integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,76 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { TracePlugin } from "./plugin-instance.js";
3
+ import { mkdtempSync, rmSync, readdirSync, readFileSync, existsSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ async function waitForFiles(dir, count, timeoutMs = 5000) {
7
+ const startTime = Date.now();
8
+ while (true) {
9
+ if (existsSync(dir)) {
10
+ const files = readdirSync(dir).filter(f => f.endsWith(".json"));
11
+ if (files.length >= count) {
12
+ let allValid = true;
13
+ for (const file of files) {
14
+ try {
15
+ const content = readFileSync(join(dir, file), "utf-8");
16
+ if (!content || content.length === 0) {
17
+ allValid = false;
18
+ break;
19
+ }
20
+ JSON.parse(content);
21
+ }
22
+ catch {
23
+ allValid = false;
24
+ break;
25
+ }
26
+ }
27
+ if (allValid)
28
+ return;
29
+ }
30
+ }
31
+ if (Date.now() - startTime > timeoutMs) {
32
+ throw new Error(`Timeout waiting for ${count} valid files in ${dir} after ${timeoutMs}ms`);
33
+ }
34
+ await new Promise(r => setTimeout(r, 10));
35
+ }
36
+ }
37
+ describe("Integration: TracePlugin full flow", () => {
38
+ let tempDir;
39
+ let plugin;
40
+ beforeEach(async () => {
41
+ tempDir = mkdtempSync(join(tmpdir(), "integration-test-"));
42
+ plugin = new TracePlugin(tempDir);
43
+ });
44
+ afterEach(() => {
45
+ plugin.uninstallInterceptor();
46
+ rmSync(tempDir, { recursive: true, force: true });
47
+ });
48
+ test("multiple concurrent requests are recorded in order", async () => {
49
+ const originalFetch = globalThis.fetch;
50
+ globalThis.fetch = async (input) => {
51
+ const req = new Request(input);
52
+ await new Promise(resolve => setTimeout(resolve, 50));
53
+ return new Response(JSON.stringify({ url: req.url }), { status: 200 });
54
+ };
55
+ plugin.installInterceptor();
56
+ await plugin.initStateManager();
57
+ const sessionId = "concurrent-test";
58
+ plugin["stateManager"].startSession(sessionId);
59
+ const requests = Array.from({ length: 5 }, (_, i) => plugin.tracedFetch(`https://example.com/${i}`, {
60
+ method: "GET",
61
+ headers: { "x-opencode-session": sessionId }
62
+ }));
63
+ const responses = await Promise.all(requests);
64
+ expect(responses.every(r => r.status === 200)).toBe(true);
65
+ const sessionDir = join(tempDir, sessionId);
66
+ await waitForFiles(sessionDir, 5);
67
+ const files = readdirSync(sessionDir).filter(f => f.endsWith(".json")).sort();
68
+ expect(files.length).toBe(5);
69
+ for (let i = 0; i < 5; i++) {
70
+ const content = JSON.parse(readFileSync(join(sessionDir, files[i]), "utf-8"));
71
+ expect(content.id).toBe(i + 1);
72
+ }
73
+ globalThis.fetch = originalFetch;
74
+ });
75
+ });
76
+ //# sourceMappingURL=integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.test.js","sourceRoot":"","sources":["../src/integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { StateManager } from "@opencode-trace/core/state";
2
+ export declare class TracePlugin {
3
+ private origFetch;
4
+ private ids;
5
+ private writeQueue;
6
+ private stateQueue;
7
+ private stateManager;
8
+ private interceptorInstalled;
9
+ private traceDir;
10
+ constructor(traceDir: string);
11
+ tracedFetch(input: Parameters<typeof globalThis.fetch>[0], init?: Parameters<typeof globalThis.fetch>[1]): Promise<Response>;
12
+ private getSessionId;
13
+ private shouldRecord;
14
+ private parseBody;
15
+ private headersToObject;
16
+ private classifyPurpose;
17
+ private parseRequest;
18
+ private captureRequestMeta;
19
+ private wrapStreamResponse;
20
+ private recordResponse;
21
+ private sanitizeStackTrace;
22
+ private createTraceRecord;
23
+ getInterceptor(): typeof fetch;
24
+ installInterceptor(): void;
25
+ uninstallInterceptor(): void;
26
+ initStateManager(): Promise<void>;
27
+ getStateManager(): StateManager | null;
28
+ }
29
+ //# sourceMappingURL=plugin-instance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-instance.d.ts","sourceRoot":"","sources":["../src/plugin-instance.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAQ1D,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,GAAG,CAAkC;IAC7C,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,oBAAoB,CAAkB;IAC9C,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,EAAE,MAAM;IAOtB,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+BlI,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,SAAS;IAQjB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,eAAe;IAKvB,OAAO,CAAC,YAAY;YAQN,kBAAkB;IAqChC,OAAO,CAAC,kBAAkB;YAyBZ,cAAc;IAiC5B,OAAO,CAAC,kBAAkB;IAQ1B,OAAO,CAAC,iBAAiB;IAuBzB,cAAc,IAAI,OAAO,KAAK;IAI9B,kBAAkB,IAAI,IAAI;IAO1B,oBAAoB,IAAI,IAAI;IAMtB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAOvC,eAAe,IAAI,YAAY,GAAG,IAAI;CAGvC"}
@@ -0,0 +1,219 @@
1
+ import { AsyncWriteQueue } from "./write-queue.js";
2
+ import { AsyncStateQueue } from "./state-queue.js";
3
+ import { StateManager } from "@opencode-trace/core/state";
4
+ import { redactHeaders } from "./redact.js";
5
+ import { sanitizePath } from "@opencode-trace/core";
6
+ import { homedir } from "node:os";
7
+ import { logger } from "@opencode-trace/core";
8
+ export class TracePlugin {
9
+ origFetch;
10
+ ids = new Map();
11
+ writeQueue;
12
+ stateQueue;
13
+ stateManager = null;
14
+ interceptorInstalled = false;
15
+ traceDir;
16
+ constructor(traceDir) {
17
+ this.traceDir = traceDir;
18
+ this.origFetch = globalThis.fetch; // Will be updated in installInterceptor
19
+ this.writeQueue = new AsyncWriteQueue(traceDir);
20
+ this.stateQueue = new AsyncStateQueue();
21
+ }
22
+ async tracedFetch(input, init) {
23
+ const req = this.parseRequest(input, init);
24
+ if (!req)
25
+ return this.origFetch(input, init);
26
+ const meta = await this.captureRequestMeta(req);
27
+ if (!meta)
28
+ return this.origFetch(req);
29
+ let res;
30
+ try {
31
+ res = await this.origFetch(req);
32
+ }
33
+ catch (err) {
34
+ const error = err instanceof Error
35
+ ? { message: err.message, stack: this.sanitizeStackTrace(err.stack) }
36
+ : { message: String(err) };
37
+ const record = this.createTraceRecord(meta.seq, meta.purpose, meta.requestAt, meta.traceReq, null, error, { requestSentAt: meta.requestSentAt });
38
+ this.writeQueue.enqueue(meta.session, meta.seq, record);
39
+ this.stateQueue.enqueue(meta.session, meta.seq, record);
40
+ throw err;
41
+ }
42
+ let latencyMeta;
43
+ if (meta.isStream && res.body) {
44
+ res = this.wrapStreamResponse(res, meta.requestSentAt);
45
+ latencyMeta = res.__latencyMeta;
46
+ }
47
+ void this.recordResponse(meta.session, meta.seq, meta.purpose, meta.requestAt, meta.traceReq, res, latencyMeta);
48
+ return res;
49
+ }
50
+ getSessionId(req) {
51
+ return req.headers.get("x-opencode-session") ?? req.headers.get("x-session-affinity") ?? req.headers.get("session_id") ?? undefined;
52
+ }
53
+ shouldRecord(sessionId) {
54
+ if (!this.stateManager)
55
+ return true;
56
+ this.stateManager.reload();
57
+ return this.stateManager.isTraceEnabled(sessionId);
58
+ }
59
+ parseBody(text) {
60
+ try {
61
+ return JSON.parse(text);
62
+ }
63
+ catch {
64
+ return text || null;
65
+ }
66
+ }
67
+ headersToObject(headers) {
68
+ const obj = {};
69
+ headers.forEach((value, key) => { obj[key] = value; });
70
+ return obj;
71
+ }
72
+ classifyPurpose(raw) {
73
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && Array.isArray(raw.tools) && raw.tools.length > 0)
74
+ return "";
75
+ return "[meta]";
76
+ }
77
+ parseRequest(input, init) {
78
+ try {
79
+ return new Request(input, init);
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ async captureRequestMeta(req) {
86
+ const session = this.getSessionId(req);
87
+ if (session === undefined)
88
+ return null;
89
+ if (!this.shouldRecord(session))
90
+ return null;
91
+ const seq = (this.ids.get(session) ?? 0) + 1;
92
+ this.ids.set(session, seq);
93
+ const requestSentAt = performance.now();
94
+ const requestAt = new Date().toISOString();
95
+ const reqBodyText = await req.clone().text().catch((err) => {
96
+ logger.error("Failed to clone request body", { url: req.url, error: String(err) });
97
+ return "";
98
+ });
99
+ const reqBody = this.parseBody(reqBodyText);
100
+ let isStream = false;
101
+ try {
102
+ isStream = JSON.parse(reqBodyText ?? "{}")?.stream === true;
103
+ }
104
+ catch {
105
+ isStream = false;
106
+ }
107
+ const purpose = this.classifyPurpose(reqBody);
108
+ const traceReq = {
109
+ method: req.method,
110
+ url: req.url,
111
+ headers: redactHeaders(this.headersToObject(req.headers)),
112
+ body: reqBody,
113
+ };
114
+ return { session, seq, requestSentAt, requestAt, reqBody, isStream, purpose, traceReq };
115
+ }
116
+ wrapStreamResponse(res, requestSentAt) {
117
+ const latencyMeta = { requestSentAt, firstTokenAt: null, lastTokenAt: null };
118
+ const transform = new TransformStream({
119
+ transform(chunk, controller) {
120
+ if (latencyMeta.firstTokenAt === null) {
121
+ latencyMeta.firstTokenAt = performance.now();
122
+ }
123
+ controller.enqueue(chunk);
124
+ },
125
+ flush() {
126
+ latencyMeta.lastTokenAt = performance.now();
127
+ },
128
+ });
129
+ const wrappedBody = res.body.pipeThrough(transform);
130
+ const wrappedRes = new Response(wrappedBody, {
131
+ status: res.status,
132
+ statusText: res.statusText,
133
+ headers: res.headers,
134
+ });
135
+ wrappedRes.__latencyMeta = latencyMeta;
136
+ return wrappedRes;
137
+ }
138
+ async recordResponse(session, seq, purpose, requestAt, traceReq, res, latencyMeta) {
139
+ try {
140
+ const resBodyText = await res.clone().text();
141
+ const resBody = this.parseBody(resBodyText);
142
+ const traceRes = {
143
+ status: res.status,
144
+ statusText: res.statusText,
145
+ headers: redactHeaders(this.headersToObject(res.headers)),
146
+ body: resBody,
147
+ };
148
+ const normalizedLatency = latencyMeta ? {
149
+ requestSentAt: latencyMeta.requestSentAt,
150
+ firstTokenAt: latencyMeta.firstTokenAt ?? undefined,
151
+ lastTokenAt: latencyMeta.lastTokenAt ?? undefined,
152
+ } : undefined;
153
+ const record = this.createTraceRecord(seq, purpose, requestAt, traceReq, traceRes, null, normalizedLatency);
154
+ this.writeQueue.enqueue(session, seq, record);
155
+ this.stateQueue.enqueue(session, seq, record);
156
+ }
157
+ catch (err) {
158
+ const error = err instanceof Error
159
+ ? { message: err.message, stack: this.sanitizeStackTrace(err.stack) }
160
+ : { message: String(err) };
161
+ const normalizedLatency = latencyMeta ? {
162
+ requestSentAt: latencyMeta.requestSentAt,
163
+ firstTokenAt: latencyMeta.firstTokenAt ?? undefined,
164
+ lastTokenAt: latencyMeta.lastTokenAt ?? undefined,
165
+ } : undefined;
166
+ const record = this.createTraceRecord(seq, purpose, requestAt, traceReq, null, error, normalizedLatency);
167
+ this.writeQueue.enqueue(session, seq, record);
168
+ this.stateQueue.enqueue(session, seq, record);
169
+ }
170
+ }
171
+ sanitizeStackTrace(stack) {
172
+ if (!stack)
173
+ return undefined;
174
+ const userHome = homedir();
175
+ return sanitizePath(stack, userHome)
176
+ .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[IP]')
177
+ .replace(/:\d{4,5}(?=[\/\\\s]|$)/g, ':[PORT]');
178
+ }
179
+ createTraceRecord(seq, purpose, requestAt, traceReq, traceRes, error, latency) {
180
+ return {
181
+ id: seq,
182
+ purpose,
183
+ requestAt,
184
+ responseAt: new Date().toISOString(),
185
+ request: traceReq,
186
+ response: traceRes,
187
+ error,
188
+ requestSentAt: latency?.requestSentAt,
189
+ firstTokenAt: latency?.firstTokenAt ?? undefined,
190
+ lastTokenAt: latency?.lastTokenAt ?? undefined,
191
+ };
192
+ }
193
+ getInterceptor() {
194
+ return this.tracedFetch.bind(this);
195
+ }
196
+ installInterceptor() {
197
+ if (this.interceptorInstalled)
198
+ return;
199
+ this.origFetch = globalThis.fetch; // Capture current fetch at installation time
200
+ globalThis.fetch = this.getInterceptor();
201
+ this.interceptorInstalled = true;
202
+ }
203
+ uninstallInterceptor() {
204
+ if (!this.interceptorInstalled)
205
+ return;
206
+ globalThis.fetch = this.origFetch;
207
+ this.interceptorInstalled = false;
208
+ }
209
+ async initStateManager() {
210
+ this.stateManager = new StateManager(this.traceDir);
211
+ await this.stateManager.init();
212
+ this.stateManager.sync();
213
+ this.stateQueue.setStateManager(this.stateManager);
214
+ }
215
+ getStateManager() {
216
+ return this.stateManager;
217
+ }
218
+ }
219
+ //# sourceMappingURL=plugin-instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-instance.js","sourceRoot":"","sources":["../src/plugin-instance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAG9C,MAAM,OAAO,WAAW;IACd,SAAS,CAAe;IACxB,GAAG,GAAwB,IAAI,GAAG,EAAE,CAAC;IACrC,UAAU,CAAkB;IAC5B,UAAU,CAAkB;IAC5B,YAAY,GAAwB,IAAI,CAAC;IACzC,oBAAoB,GAAY,KAAK,CAAC;IACtC,QAAQ,CAAS;IAEzB,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,wCAAwC;QAC3E,IAAI,CAAC,UAAU,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,KAA6C,EAAE,IAA6C;QAC5G,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAE7C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAEtC,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK;gBAChC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;gBACrE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;YACjJ,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YACxD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,WAA2G,CAAC;QAChH,IAAI,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;YAC9B,GAAG,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;YACvD,WAAW,GAAI,GAAW,CAAC,aAAa,CAAC;QAC3C,CAAC;QAED,KAAK,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;QAEhH,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,YAAY,CAAC,GAAY;QAC/B,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,SAAS,CAAC;IACtI,CAAC;IAEO,YAAY,CAAC,SAAkB;QACrC,IAAI,CAAC,IAAI,CAAC,YAAY;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,YAAY,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IACrD,CAAC;IAEO,SAAS,CAAC,IAAY;QAC5B,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,IAAI,IAAI,CAAC;QACtB,CAAC;IACH,CAAC;IAEO,eAAe,CAAC,OAAgB;QACtC,MAAM,GAAG,GAA2B,EAAE,CAAC;QACvC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACvD,OAAO,GAAG,CAAC;IACb,CAAC;IAEO,eAAe,CAAC,GAAY;QAClC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAE,GAAW,CAAC,KAAK,CAAC,IAAK,GAAW,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACpJ,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,YAAY,CAAC,KAA6C,EAAE,IAA6C;QAC/G,IAAI,CAAC;YACH,OAAO,IAAI,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,GAAY;QAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACvC,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAEvC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAE7C,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAE3B,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAE3C,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACzD,MAAM,CAAC,KAAK,CAAC,8BAA8B,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnF,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAE5C,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAE9C,MAAM,QAAQ,GAAG;YACf,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,EAAE,OAAO;SACd,CAAC;QAEF,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAC1F,CAAC;IAEO,kBAAkB,CAAC,GAAa,EAAE,aAAqB;QAC7D,MAAM,WAAW,GAAuF,EAAE,aAAa,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QAEjK,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC;YACpC,SAAS,CAAC,KAAK,EAAE,UAAU;gBACzB,IAAI,WAAW,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC;oBACtC,WAAW,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;gBAC/C,CAAC;gBACD,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,KAAK;gBACH,WAAW,CAAC,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAC9C,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,WAAW,GAAG,GAAG,CAAC,IAAK,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,IAAI,QAAQ,CAAC,WAAW,EAAE;YAC3C,MAAM,EAAE,GAAG,CAAC,MAAM;YAClB,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,OAAO,EAAE,GAAG,CAAC,OAAO;SACrB,CAAC,CAAC;QACF,UAAkB,CAAC,aAAa,GAAG,WAAW,CAAC;QAChD,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,OAAe,EAAE,GAAW,EAAE,OAAe,EAAE,SAAiB,EAAE,QAAsB,EAAE,GAAa,EAAE,WAAkG;QACtO,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC5C,MAAM,QAAQ,GAAG;gBACf,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,OAAO,EAAE,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBACzD,IAAI,EAAE,OAAO;aACd,CAAC;YACF,MAAM,iBAAiB,GAAG,WAAW,CAAC,CAAC,CAAC;gBACtC,aAAa,EAAE,WAAW,CAAC,aAAa;gBACxC,YAAY,EAAE,WAAW,CAAC,YAAY,IAAI,SAAS;gBACnD,WAAW,EAAE,WAAW,CAAC,WAAW,IAAI,SAAS;aAClD,CAAC,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC;YAC5G,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK;gBAChC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;gBACrE,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,MAAM,iBAAiB,GAAG,WAAW,CAAC,CAAC,CAAC;gBACtC,aAAa,EAAE,WAAW,CAAC,aAAa;gBACxC,YAAY,EAAE,WAAW,CAAC,YAAY,IAAI,SAAS;gBACnD,WAAW,EAAE,WAAW,CAAC,WAAW,IAAI,SAAS;aAClD,CAAC,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,iBAAiB,CAAC,CAAC;YACzG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YAC9C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEO,kBAAkB,CAAC,KAAc;QACvC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC;QAC3B,OAAO,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC;aACjC,OAAO,CAAC,yCAAyC,EAAE,MAAM,CAAC;aAC1D,OAAO,CAAC,yBAAyB,EAAE,SAAS,CAAC,CAAC;IACnD,CAAC;IAEO,iBAAiB,CACvB,GAAW,EACX,OAAe,EACf,SAAiB,EACjB,QAAsB,EACtB,QAA8B,EAC9B,KAAiD,EACjD,OAAiF;QAEjF,OAAO;YACL,EAAE,EAAE,GAAG;YACP,OAAO;YACP,SAAS;YACT,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,OAAO,EAAE,QAAQ;YACjB,QAAQ,EAAE,QAAQ;YAClB,KAAK;YACL,aAAa,EAAE,OAAO,EAAE,aAAa;YACrC,YAAY,EAAE,OAAO,EAAE,YAAY,IAAI,SAAS;YAChD,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,SAAS;SAC/C,CAAC;IACJ,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,kBAAkB;QAChB,IAAI,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACtC,IAAI,CAAC,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,6CAA6C;QAChF,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QACzC,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;IACnC,CAAC;IAED,oBAAoB;QAClB,IAAI,CAAC,IAAI,CAAC,oBAAoB;YAAE,OAAO;QACvC,UAAU,CAAC,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;QAClC,IAAI,CAAC,oBAAoB,GAAG,KAAK,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,gBAAgB;QACpB,IAAI,CAAC,YAAY,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACpD,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,CAAC,UAAU,CAAC,eAAe,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACrD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin-instance.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-instance.test.d.ts","sourceRoot":"","sources":["../src/plugin-instance.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,135 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { TracePlugin } from "./plugin-instance.js";
3
+ import { mkdtempSync, rmSync, readFileSync, existsSync } from "node:fs";
4
+ import { tmpdir, homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ async function waitForFile(filePath, timeoutMs = 5000) {
7
+ const startTime = Date.now();
8
+ while (true) {
9
+ if (existsSync(filePath)) {
10
+ try {
11
+ const content = readFileSync(filePath, "utf-8");
12
+ if (content && content.length > 0) {
13
+ JSON.parse(content);
14
+ return;
15
+ }
16
+ }
17
+ catch {
18
+ }
19
+ }
20
+ if (Date.now() - startTime > timeoutMs) {
21
+ throw new Error(`Timeout waiting for valid file ${filePath} after ${timeoutMs}ms`);
22
+ }
23
+ await new Promise(r => setTimeout(r, 10));
24
+ }
25
+ }
26
+ describe("TracePlugin", () => {
27
+ let tempDir;
28
+ let plugin;
29
+ beforeEach(() => {
30
+ tempDir = mkdtempSync(join(tmpdir(), "plugin-test-"));
31
+ plugin = new TracePlugin(tempDir);
32
+ });
33
+ afterEach(() => {
34
+ plugin.uninstallInterceptor();
35
+ rmSync(tempDir, { recursive: true, force: true });
36
+ });
37
+ test("constructor initializes write and state queues", () => {
38
+ expect(plugin).toBeDefined();
39
+ expect(plugin["writeQueue"]).toBeDefined();
40
+ expect(plugin["stateQueue"]).toBeDefined();
41
+ });
42
+ test("installInterceptor installs traced fetch", () => {
43
+ const originalFetch = globalThis.fetch;
44
+ plugin.installInterceptor();
45
+ expect(globalThis.fetch).not.toBe(originalFetch);
46
+ });
47
+ test("uninstallInterceptor restores original fetch", () => {
48
+ const originalFetch = globalThis.fetch;
49
+ plugin.installInterceptor();
50
+ plugin.uninstallInterceptor();
51
+ expect(globalThis.fetch).toBe(originalFetch);
52
+ });
53
+ test("installInterceptor is idempotent (can be called twice)", () => {
54
+ const originalFetch = globalThis.fetch;
55
+ plugin.installInterceptor();
56
+ const firstInterceptor = globalThis.fetch;
57
+ plugin.installInterceptor(); // Should not change
58
+ expect(globalThis.fetch).toBe(firstInterceptor);
59
+ plugin.uninstallInterceptor();
60
+ expect(globalThis.fetch).toBe(originalFetch);
61
+ });
62
+ test("uninstallInterceptor is safe when not installed", () => {
63
+ const originalFetch = globalThis.fetch;
64
+ plugin.uninstallInterceptor(); // Should not throw or change
65
+ expect(globalThis.fetch).toBe(originalFetch);
66
+ });
67
+ test("can reinstall after uninstall", () => {
68
+ const originalFetch = globalThis.fetch;
69
+ plugin.installInterceptor();
70
+ plugin.uninstallInterceptor();
71
+ plugin.installInterceptor(); // Should work again
72
+ expect(globalThis.fetch).not.toBe(originalFetch);
73
+ plugin.uninstallInterceptor();
74
+ expect(globalThis.fetch).toBe(originalFetch);
75
+ });
76
+ test("tracedFetch writes records via writeQueue", async () => {
77
+ // Mock fetch BEFORE installing interceptor so origFetch captures the mock
78
+ const mockFetch = async () => {
79
+ return new Response(JSON.stringify({ result: "ok" }), {
80
+ status: 200,
81
+ headers: { "content-type": "application/json" }
82
+ });
83
+ };
84
+ globalThis.fetch = mockFetch;
85
+ plugin.installInterceptor();
86
+ const sessionId = "test-session";
87
+ const request = new Request("https://example.com", {
88
+ method: "POST",
89
+ headers: {
90
+ "x-opencode-session": sessionId,
91
+ "content-type": "application/json"
92
+ },
93
+ body: JSON.stringify({ test: true })
94
+ });
95
+ const response = await plugin.tracedFetch(request);
96
+ const filePath = join(tempDir, sessionId, "1.json");
97
+ await waitForFile(filePath, 5000);
98
+ expect(existsSync(filePath)).toBe(true);
99
+ const content = JSON.parse(readFileSync(filePath, "utf-8"));
100
+ expect(content.request.method).toBe("POST");
101
+ expect(content.response.status).toBe(200);
102
+ });
103
+ test("sanitizeStackTrace removes sensitive information", () => {
104
+ const sanitizeStackTrace = plugin["sanitizeStackTrace"];
105
+ const userHome = homedir();
106
+ const stack = `Error at ${userHome}/sensitive/path/file.ts:10:5
107
+ Connection to 192.168.1.100:8080 failed
108
+ Server running on 127.0.0.1:3000`;
109
+ const sanitized = sanitizeStackTrace(stack);
110
+ expect(sanitized).toContain('[HOME]');
111
+ expect(sanitized).toContain('[IP]');
112
+ expect(sanitized).toContain(':[PORT]');
113
+ expect(sanitized).not.toContain(userHome);
114
+ expect(sanitized).not.toContain('192.168.1.100');
115
+ expect(sanitized).not.toContain('127.0.0.1');
116
+ expect(sanitized).not.toContain(':8080');
117
+ expect(sanitized).not.toContain(':3000');
118
+ });
119
+ test("sanitizeStackTrace redacts ports in Windows paths", () => {
120
+ const sanitizeStackTrace = plugin["sanitizeStackTrace"];
121
+ const userHome = homedir();
122
+ const windowsStack = `Error at ${userHome}\\project\\file.ts:10:5
123
+ Connection to 10.0.0.1:8080 failed
124
+ Listening on 0.0.0.0:3000`;
125
+ const sanitized = sanitizeStackTrace(windowsStack);
126
+ expect(sanitized).toContain('[HOME]');
127
+ expect(sanitized).toContain('[IP]');
128
+ expect(sanitized).toContain(':[PORT]');
129
+ expect(sanitized).not.toContain('10.0.0.1');
130
+ expect(sanitized).not.toContain('0.0.0.0');
131
+ expect(sanitized).not.toContain(':8080');
132
+ expect(sanitized).not.toContain(':3000');
133
+ });
134
+ });
135
+ //# sourceMappingURL=plugin-instance.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-instance.test.js","sourceRoot":"","sources":["../src/plugin-instance.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function redactHeaders(headers: Record<string, string>): Record<string, string>;
2
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAmBA,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB"}