@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.
- package/README.md +124 -0
- package/dist/integration.test.d.ts +2 -0
- package/dist/integration.test.d.ts.map +1 -0
- package/dist/integration.test.js +45 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/plugin-instance.d.ts +29 -0
- package/dist/plugin-instance.d.ts.map +1 -0
- package/dist/plugin-instance.js +218 -0
- package/dist/plugin-instance.js.map +1 -0
- package/dist/plugin-instance.test.d.ts +2 -0
- package/dist/plugin-instance.test.d.ts.map +1 -0
- package/dist/plugin-instance.test.js +102 -0
- package/dist/plugin-instance.test.js.map +1 -0
- package/dist/redact.d.ts +2 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +40 -0
- package/dist/redact.js.map +1 -0
- package/dist/redact.test.d.ts +2 -0
- package/dist/redact.test.d.ts.map +1 -0
- package/dist/redact.test.js +77 -0
- package/dist/redact.test.js.map +1 -0
- package/dist/state-queue.d.ts +14 -0
- package/dist/state-queue.d.ts.map +1 -0
- package/dist/state-queue.js +44 -0
- package/dist/state-queue.js.map +1 -0
- package/dist/state-queue.test.d.ts +2 -0
- package/dist/state-queue.test.d.ts.map +1 -0
- package/dist/state-queue.test.js +99 -0
- package/dist/state-queue.test.js.map +1 -0
- package/dist/trace.d.ts +32 -0
- package/dist/trace.d.ts.map +1 -0
- package/dist/trace.js +104 -0
- package/dist/trace.js.map +1 -0
- package/dist/trace.test.d.ts +2 -0
- package/dist/trace.test.d.ts.map +1 -0
- package/dist/trace.test.js +339 -0
- package/dist/trace.test.js.map +1 -0
- package/dist/write-queue.d.ts +14 -0
- package/dist/write-queue.d.ts.map +1 -0
- package/dist/write-queue.js +62 -0
- package/dist/write-queue.js.map +1 -0
- package/dist/write-queue.test.d.ts +2 -0
- package/dist/write-queue.test.d.ts.map +1 -0
- package/dist/write-queue.test.js +92 -0
- package/dist/write-queue.test.js.map +1 -0
- 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
|
+
[](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 @@
|
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,IAAI,OAAe,CAAC;IACpB,IAAI,MAAmB,CAAC;IAExB,UAAU,CAAC,KAAK,IAAI,EAAE;QACpB,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,mBAAmB,CAAC,CAAC,CAAC;QAC3D,MAAM,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,oBAAoB,EAAE,CAAC;QAC9B,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QACpE,MAAM,aAAa,GAAG,UAAU,CAAC,KAAK,CAAC;QACvC,UAAU,CAAC,KAAK,GAAG,KAAK,EAAE,KAAK,EAAE,EAAE;YACjC,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC;YAC/B,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YACtD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,EAAC,GAAG,EAAE,GAAG,CAAC,GAAG,EAAC,CAAC,EAAE,EAAC,MAAM,EAAE,GAAG,EAAC,CAAC,CAAC;QACrE,CAAC,CAAC;QAEF,MAAM,CAAC,kBAAkB,EAAE,CAAC;QAC5B,MAAM,MAAM,CAAC,gBAAgB,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,iBAAiB,CAAC;QACpC,MAAM,CAAC,cAAc,CAAE,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAEhD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,EAAC,MAAM,EAAE,CAAC,EAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAChD,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,EAAE,EAAE;YAC7C,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAC,oBAAoB,EAAE,SAAS,EAAC;SAC3C,CAAC,CACH,CAAC;QAEF,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE1D,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAEvD,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;YAC9E,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACjC,CAAC;QAED,UAAU,CAAC,KAAK,GAAG,aAAa,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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":"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;AAE5C,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,OAAO,KAAK;aACT,OAAO,CAAC,iBAAiB,EAAE,cAAc,CAAC;aAC1C,OAAO,CAAC,kBAAkB,EAAE,eAAe,CAAC;aAC5C,OAAO,CAAC,yCAAyC,EAAE,MAAM,CAAC;aAC1D,OAAO,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC;IACjD,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 @@
|
|
|
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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"}
|
package/dist/redact.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"redact.test.d.ts","sourceRoot":"","sources":["../src/redact.test.ts"],"names":[],"mappings":""}
|