@opencode-trace/plugin 0.0.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.
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 +45 -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 +218 -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 +102 -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 +104 -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 +339 -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,45 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { TracePlugin } from "./plugin-instance.js";
3
+ import { mkdtempSync, rmSync, readdirSync, readFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ describe("Integration: TracePlugin full flow", () => {
7
+ let tempDir;
8
+ let plugin;
9
+ beforeEach(async () => {
10
+ tempDir = mkdtempSync(join(tmpdir(), "integration-test-"));
11
+ plugin = new TracePlugin(tempDir);
12
+ });
13
+ afterEach(() => {
14
+ plugin.uninstallInterceptor();
15
+ rmSync(tempDir, { recursive: true, force: true });
16
+ });
17
+ test("multiple concurrent requests are recorded in order", async () => {
18
+ const originalFetch = globalThis.fetch;
19
+ globalThis.fetch = async (input) => {
20
+ const req = new Request(input);
21
+ await new Promise(resolve => setTimeout(resolve, 50));
22
+ return new Response(JSON.stringify({ url: req.url }), { status: 200 });
23
+ };
24
+ plugin.installInterceptor();
25
+ await plugin.initStateManager();
26
+ const sessionId = "concurrent-test";
27
+ plugin["stateManager"].startSession(sessionId);
28
+ const requests = Array.from({ length: 5 }, (_, i) => plugin.tracedFetch(`https://example.com/${i}`, {
29
+ method: "GET",
30
+ headers: { "x-opencode-session": sessionId }
31
+ }));
32
+ const responses = await Promise.all(requests);
33
+ expect(responses.every(r => r.status === 200)).toBe(true);
34
+ await new Promise(resolve => setTimeout(resolve, 200));
35
+ const sessionDir = join(tempDir, sessionId);
36
+ const files = readdirSync(sessionDir).filter(f => f.endsWith(".json")).sort();
37
+ expect(files.length).toBe(5);
38
+ for (let i = 0; i < 5; i++) {
39
+ const content = JSON.parse(readFileSync(join(sessionDir, files[i]), "utf-8"));
40
+ expect(content.id).toBe(i + 1);
41
+ }
42
+ globalThis.fetch = originalFetch;
43
+ });
44
+ });
45
+ //# 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;AAM1D,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;IAS1B,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,218 @@
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 { logger } from "@opencode-trace/core";
6
+ export class TracePlugin {
7
+ origFetch;
8
+ ids = new Map();
9
+ writeQueue;
10
+ stateQueue;
11
+ stateManager = null;
12
+ interceptorInstalled = false;
13
+ traceDir;
14
+ constructor(traceDir) {
15
+ this.traceDir = traceDir;
16
+ this.origFetch = globalThis.fetch; // Will be updated in installInterceptor
17
+ this.writeQueue = new AsyncWriteQueue(traceDir);
18
+ this.stateQueue = new AsyncStateQueue();
19
+ }
20
+ async tracedFetch(input, init) {
21
+ const req = this.parseRequest(input, init);
22
+ if (!req)
23
+ return this.origFetch(input, init);
24
+ const meta = await this.captureRequestMeta(req);
25
+ if (!meta)
26
+ return this.origFetch(req);
27
+ let res;
28
+ try {
29
+ res = await this.origFetch(req);
30
+ }
31
+ catch (err) {
32
+ const error = err instanceof Error
33
+ ? { message: err.message, stack: this.sanitizeStackTrace(err.stack) }
34
+ : { message: String(err) };
35
+ const record = this.createTraceRecord(meta.seq, meta.purpose, meta.requestAt, meta.traceReq, null, error, { requestSentAt: meta.requestSentAt });
36
+ this.writeQueue.enqueue(meta.session, meta.seq, record);
37
+ this.stateQueue.enqueue(meta.session, meta.seq, record);
38
+ throw err;
39
+ }
40
+ let latencyMeta;
41
+ if (meta.isStream && res.body) {
42
+ res = this.wrapStreamResponse(res, meta.requestSentAt);
43
+ latencyMeta = res.__latencyMeta;
44
+ }
45
+ void this.recordResponse(meta.session, meta.seq, meta.purpose, meta.requestAt, meta.traceReq, res, latencyMeta);
46
+ return res;
47
+ }
48
+ getSessionId(req) {
49
+ return req.headers.get("x-opencode-session") ?? req.headers.get("x-session-affinity") ?? req.headers.get("session_id") ?? undefined;
50
+ }
51
+ shouldRecord(sessionId) {
52
+ if (!this.stateManager)
53
+ return true;
54
+ this.stateManager.reload();
55
+ return this.stateManager.isTraceEnabled(sessionId);
56
+ }
57
+ parseBody(text) {
58
+ try {
59
+ return JSON.parse(text);
60
+ }
61
+ catch {
62
+ return text || null;
63
+ }
64
+ }
65
+ headersToObject(headers) {
66
+ const obj = {};
67
+ headers.forEach((value, key) => { obj[key] = value; });
68
+ return obj;
69
+ }
70
+ classifyPurpose(raw) {
71
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && Array.isArray(raw.tools) && raw.tools.length > 0)
72
+ return "";
73
+ return "[meta]";
74
+ }
75
+ parseRequest(input, init) {
76
+ try {
77
+ return new Request(input, init);
78
+ }
79
+ catch {
80
+ return null;
81
+ }
82
+ }
83
+ async captureRequestMeta(req) {
84
+ const session = this.getSessionId(req);
85
+ if (session === undefined)
86
+ return null;
87
+ if (!this.shouldRecord(session))
88
+ return null;
89
+ const seq = (this.ids.get(session) ?? 0) + 1;
90
+ this.ids.set(session, seq);
91
+ const requestSentAt = performance.now();
92
+ const requestAt = new Date().toISOString();
93
+ const reqBodyText = await req.clone().text().catch((err) => {
94
+ logger.error("Failed to clone request body", { url: req.url, error: String(err) });
95
+ return "";
96
+ });
97
+ const reqBody = this.parseBody(reqBodyText);
98
+ let isStream = false;
99
+ try {
100
+ isStream = JSON.parse(reqBodyText ?? "{}")?.stream === true;
101
+ }
102
+ catch {
103
+ isStream = false;
104
+ }
105
+ const purpose = this.classifyPurpose(reqBody);
106
+ const traceReq = {
107
+ method: req.method,
108
+ url: req.url,
109
+ headers: redactHeaders(this.headersToObject(req.headers)),
110
+ body: reqBody,
111
+ };
112
+ return { session, seq, requestSentAt, requestAt, reqBody, isStream, purpose, traceReq };
113
+ }
114
+ wrapStreamResponse(res, requestSentAt) {
115
+ const latencyMeta = { requestSentAt, firstTokenAt: null, lastTokenAt: null };
116
+ const transform = new TransformStream({
117
+ transform(chunk, controller) {
118
+ if (latencyMeta.firstTokenAt === null) {
119
+ latencyMeta.firstTokenAt = performance.now();
120
+ }
121
+ controller.enqueue(chunk);
122
+ },
123
+ flush() {
124
+ latencyMeta.lastTokenAt = performance.now();
125
+ },
126
+ });
127
+ const wrappedBody = res.body.pipeThrough(transform);
128
+ const wrappedRes = new Response(wrappedBody, {
129
+ status: res.status,
130
+ statusText: res.statusText,
131
+ headers: res.headers,
132
+ });
133
+ wrappedRes.__latencyMeta = latencyMeta;
134
+ return wrappedRes;
135
+ }
136
+ async recordResponse(session, seq, purpose, requestAt, traceReq, res, latencyMeta) {
137
+ try {
138
+ const resBodyText = await res.clone().text();
139
+ const resBody = this.parseBody(resBodyText);
140
+ const traceRes = {
141
+ status: res.status,
142
+ statusText: res.statusText,
143
+ headers: redactHeaders(this.headersToObject(res.headers)),
144
+ body: resBody,
145
+ };
146
+ const normalizedLatency = latencyMeta ? {
147
+ requestSentAt: latencyMeta.requestSentAt,
148
+ firstTokenAt: latencyMeta.firstTokenAt ?? undefined,
149
+ lastTokenAt: latencyMeta.lastTokenAt ?? undefined,
150
+ } : undefined;
151
+ const record = this.createTraceRecord(seq, purpose, requestAt, traceReq, traceRes, null, normalizedLatency);
152
+ this.writeQueue.enqueue(session, seq, record);
153
+ this.stateQueue.enqueue(session, seq, record);
154
+ }
155
+ catch (err) {
156
+ const error = err instanceof Error
157
+ ? { message: err.message, stack: this.sanitizeStackTrace(err.stack) }
158
+ : { message: String(err) };
159
+ const normalizedLatency = latencyMeta ? {
160
+ requestSentAt: latencyMeta.requestSentAt,
161
+ firstTokenAt: latencyMeta.firstTokenAt ?? undefined,
162
+ lastTokenAt: latencyMeta.lastTokenAt ?? undefined,
163
+ } : undefined;
164
+ const record = this.createTraceRecord(seq, purpose, requestAt, traceReq, null, error, normalizedLatency);
165
+ this.writeQueue.enqueue(session, seq, record);
166
+ this.stateQueue.enqueue(session, seq, record);
167
+ }
168
+ }
169
+ sanitizeStackTrace(stack) {
170
+ if (!stack)
171
+ return undefined;
172
+ return stack
173
+ .replace(/\/home\/[^\/]+/g, '/home/[USER]')
174
+ .replace(/\/Users\/[^\/]+/g, '/Users/[USER]')
175
+ .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '[IP]')
176
+ .replace(/:\d{4,5}(?=[\/\s]|$)/g, ':[PORT]');
177
+ }
178
+ createTraceRecord(seq, purpose, requestAt, traceReq, traceRes, error, latency) {
179
+ return {
180
+ id: seq,
181
+ purpose,
182
+ requestAt,
183
+ responseAt: new Date().toISOString(),
184
+ request: traceReq,
185
+ response: traceRes,
186
+ error,
187
+ requestSentAt: latency?.requestSentAt,
188
+ firstTokenAt: latency?.firstTokenAt ?? undefined,
189
+ lastTokenAt: latency?.lastTokenAt ?? undefined,
190
+ };
191
+ }
192
+ getInterceptor() {
193
+ return this.tracedFetch.bind(this);
194
+ }
195
+ installInterceptor() {
196
+ if (this.interceptorInstalled)
197
+ return;
198
+ this.origFetch = globalThis.fetch; // Capture current fetch at installation time
199
+ globalThis.fetch = this.getInterceptor();
200
+ this.interceptorInstalled = true;
201
+ }
202
+ uninstallInterceptor() {
203
+ if (!this.interceptorInstalled)
204
+ return;
205
+ globalThis.fetch = this.origFetch;
206
+ this.interceptorInstalled = false;
207
+ }
208
+ async initStateManager() {
209
+ this.stateManager = new StateManager(this.traceDir);
210
+ await this.stateManager.init();
211
+ this.stateManager.sync();
212
+ this.stateQueue.setStateManager(this.stateManager);
213
+ }
214
+ getStateManager() {
215
+ return this.stateManager;
216
+ }
217
+ }
218
+ //# sourceMappingURL=plugin-instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-instance.js","sourceRoot":"","sources":["../src/plugin-instance.ts"],"names":[],"mappings":"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"}
@@ -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,102 @@
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 } from "node:os";
5
+ import { join } from "node:path";
6
+ describe("TracePlugin", () => {
7
+ let tempDir;
8
+ let plugin;
9
+ beforeEach(() => {
10
+ tempDir = mkdtempSync(join(tmpdir(), "plugin-test-"));
11
+ plugin = new TracePlugin(tempDir);
12
+ });
13
+ afterEach(() => {
14
+ plugin.uninstallInterceptor();
15
+ rmSync(tempDir, { recursive: true, force: true });
16
+ });
17
+ test("constructor initializes write and state queues", () => {
18
+ expect(plugin).toBeDefined();
19
+ expect(plugin["writeQueue"]).toBeDefined();
20
+ expect(plugin["stateQueue"]).toBeDefined();
21
+ });
22
+ test("installInterceptor installs traced fetch", () => {
23
+ const originalFetch = globalThis.fetch;
24
+ plugin.installInterceptor();
25
+ expect(globalThis.fetch).not.toBe(originalFetch);
26
+ });
27
+ test("uninstallInterceptor restores original fetch", () => {
28
+ const originalFetch = globalThis.fetch;
29
+ plugin.installInterceptor();
30
+ plugin.uninstallInterceptor();
31
+ expect(globalThis.fetch).toBe(originalFetch);
32
+ });
33
+ test("installInterceptor is idempotent (can be called twice)", () => {
34
+ const originalFetch = globalThis.fetch;
35
+ plugin.installInterceptor();
36
+ const firstInterceptor = globalThis.fetch;
37
+ plugin.installInterceptor(); // Should not change
38
+ expect(globalThis.fetch).toBe(firstInterceptor);
39
+ plugin.uninstallInterceptor();
40
+ expect(globalThis.fetch).toBe(originalFetch);
41
+ });
42
+ test("uninstallInterceptor is safe when not installed", () => {
43
+ const originalFetch = globalThis.fetch;
44
+ plugin.uninstallInterceptor(); // Should not throw or change
45
+ expect(globalThis.fetch).toBe(originalFetch);
46
+ });
47
+ test("can reinstall after uninstall", () => {
48
+ const originalFetch = globalThis.fetch;
49
+ plugin.installInterceptor();
50
+ plugin.uninstallInterceptor();
51
+ plugin.installInterceptor(); // Should work again
52
+ expect(globalThis.fetch).not.toBe(originalFetch);
53
+ plugin.uninstallInterceptor();
54
+ expect(globalThis.fetch).toBe(originalFetch);
55
+ });
56
+ test("tracedFetch writes records via writeQueue", async () => {
57
+ // Mock fetch BEFORE installing interceptor so origFetch captures the mock
58
+ const mockFetch = async () => {
59
+ return new Response(JSON.stringify({ result: "ok" }), {
60
+ status: 200,
61
+ headers: { "content-type": "application/json" }
62
+ });
63
+ };
64
+ globalThis.fetch = mockFetch;
65
+ plugin.installInterceptor();
66
+ const sessionId = "test-session";
67
+ const request = new Request("https://example.com", {
68
+ method: "POST",
69
+ headers: {
70
+ "x-opencode-session": sessionId,
71
+ "content-type": "application/json"
72
+ },
73
+ body: JSON.stringify({ test: true })
74
+ });
75
+ const response = await plugin.tracedFetch(request);
76
+ await new Promise(resolve => setTimeout(resolve, 100));
77
+ const filePath = join(tempDir, sessionId, "1.json");
78
+ expect(existsSync(filePath)).toBe(true);
79
+ const content = JSON.parse(readFileSync(filePath, "utf-8"));
80
+ expect(content.request.method).toBe("POST");
81
+ expect(content.response.status).toBe(200);
82
+ });
83
+ test("sanitizeStackTrace removes sensitive information", () => {
84
+ const sanitizeStackTrace = plugin["sanitizeStackTrace"];
85
+ const stack = `Error at /home/li/sensitive/path/file.ts:10:5
86
+ Error at /Users/john/private/project/src/index.ts:20:10
87
+ Connection to 192.168.1.100:8080 failed
88
+ Server running on 127.0.0.1:3000`;
89
+ const sanitized = sanitizeStackTrace(stack);
90
+ expect(sanitized).toContain('/home/[USER]');
91
+ expect(sanitized).toContain('/Users/[USER]');
92
+ expect(sanitized).toContain('[IP]');
93
+ expect(sanitized).toContain(':[PORT]');
94
+ expect(sanitized).not.toContain('/home/li');
95
+ expect(sanitized).not.toContain('/Users/john');
96
+ expect(sanitized).not.toContain('192.168.1.100');
97
+ expect(sanitized).not.toContain('127.0.0.1');
98
+ expect(sanitized).not.toContain(':8080');
99
+ expect(sanitized).not.toContain(':3000');
100
+ });
101
+ });
102
+ //# 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"}
package/dist/redact.js ADDED
@@ -0,0 +1,40 @@
1
+ const SENSITIVE_HEADERS = [
2
+ "authorization",
3
+ "api-key",
4
+ "x-api-key",
5
+ "apikey",
6
+ "x-apikey",
7
+ "token",
8
+ "x-token",
9
+ "access-token",
10
+ "x-access-token",
11
+ "secret",
12
+ "x-secret",
13
+ "cookie",
14
+ ];
15
+ function isSensitiveHeader(key) {
16
+ return SENSITIVE_HEADERS.includes(key.toLowerCase());
17
+ }
18
+ export function redactHeaders(headers) {
19
+ const disabled = process.env.OPENCODE_TRACE_REDACT === "false";
20
+ if (disabled) {
21
+ return headers;
22
+ }
23
+ const result = {};
24
+ for (const [key, value] of Object.entries(headers)) {
25
+ if (isSensitiveHeader(key)) {
26
+ const lowerValue = value.toLowerCase();
27
+ if (lowerValue.startsWith("bearer ")) {
28
+ result[key] = "Bearer [REDACTED]";
29
+ }
30
+ else {
31
+ result[key] = "[REDACTED]";
32
+ }
33
+ }
34
+ else {
35
+ result[key] = value;
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAAA,MAAM,iBAAiB,GAAG;IACxB,eAAe;IACf,SAAS;IACT,WAAW;IACX,QAAQ;IACR,UAAU;IACV,OAAO;IACP,SAAS;IACT,cAAc;IACd,gBAAgB;IAChB,QAAQ;IACR,UAAU;IACV,QAAQ;CACT,CAAC;AAEF,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,iBAAiB,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,OAA+B;IAE/B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,OAAO,CAAC;IAC/D,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrC,MAAM,CAAC,GAAG,CAAC,GAAG,mBAAmB,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC;YAC7B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=redact.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../src/redact.test.ts"],"names":[],"mappings":""}