@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
@@ -0,0 +1,339 @@
1
+ import { describe, test, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { mkdtempSync, rmSync, existsSync, readFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import entrypoint, { _resetForTesting } from "./trace.js";
6
+ let testDir;
7
+ beforeEach(() => {
8
+ testDir = mkdtempSync(join(tmpdir(), "plugin-test-"));
9
+ vi.stubEnv("HOME", testDir);
10
+ });
11
+ afterEach(() => {
12
+ rmSync(testDir, { recursive: true, force: true });
13
+ vi.unstubAllEnvs();
14
+ });
15
+ describe("Plugin - Hooks 返回值", () => {
16
+ test("plugin 返回包含 event hook", async () => {
17
+ const hooks = await entrypoint.server({
18
+ client: {},
19
+ project: {},
20
+ directory: testDir,
21
+ worktree: testDir,
22
+ experimental_workspace: { register: vi.fn() },
23
+ serverUrl: new URL("http://localhost"),
24
+ $: {},
25
+ });
26
+ expect(hooks.event).toBeDefined();
27
+ });
28
+ test("plugin 返回包含 tool.execute.after hook", async () => {
29
+ const hooks = await entrypoint.server({
30
+ client: {},
31
+ project: {},
32
+ directory: testDir,
33
+ worktree: testDir,
34
+ experimental_workspace: { register: vi.fn() },
35
+ serverUrl: new URL("http://localhost"),
36
+ $: {},
37
+ });
38
+ expect(hooks["tool.execute.after"]).toBeDefined();
39
+ });
40
+ });
41
+ describe("Plugin - event hook 处理 session.created", () => {
42
+ test("event hook 处理 session.created 更新元数据", async () => {
43
+ const hooks = await entrypoint.server({
44
+ client: {},
45
+ project: {},
46
+ directory: testDir,
47
+ worktree: testDir,
48
+ experimental_workspace: { register: vi.fn() },
49
+ serverUrl: new URL("http://localhost"),
50
+ $: {},
51
+ });
52
+ const sessionId = "test-session-123";
53
+ await hooks.event({
54
+ event: {
55
+ type: "session.created",
56
+ properties: {
57
+ info: {
58
+ id: sessionId,
59
+ projectID: "test-project",
60
+ directory: testDir,
61
+ title: "Test Session Title",
62
+ version: "1.0",
63
+ time: { created: Date.now(), updated: Date.now() },
64
+ },
65
+ },
66
+ },
67
+ });
68
+ const traceDir = join(testDir, ".opencode-trace");
69
+ const sessionDir = join(traceDir, sessionId);
70
+ expect(existsSync(sessionDir)).toBe(true);
71
+ const dbPath = join(traceDir, "state.db");
72
+ expect(existsSync(dbPath)).toBe(true);
73
+ const metaPath = join(sessionDir, "metadata.json");
74
+ expect(existsSync(metaPath)).toBe(true);
75
+ const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
76
+ expect(meta.folderPath).toBe(testDir);
77
+ });
78
+ });
79
+ describe("Stream request detection", () => {
80
+ test("should detect stream request from body", () => {
81
+ const init = {
82
+ body: JSON.stringify({ stream: true, messages: [] }),
83
+ };
84
+ const isStream = JSON.parse(init.body ?? "{}").stream === true;
85
+ expect(isStream).toBe(true);
86
+ });
87
+ test("should detect non-stream request", () => {
88
+ const init = {
89
+ body: JSON.stringify({ stream: false, messages: [] }),
90
+ };
91
+ const isStream = JSON.parse(init.body ?? "{}").stream === true;
92
+ expect(isStream).toBe(false);
93
+ });
94
+ test("should handle missing stream field", () => {
95
+ const init = {
96
+ body: JSON.stringify({ messages: [] }),
97
+ };
98
+ const isStream = JSON.parse(init.body ?? "{}").stream === true;
99
+ expect(isStream).toBe(false);
100
+ });
101
+ test("should handle invalid JSON body", () => {
102
+ const init = {
103
+ body: "not valid json",
104
+ };
105
+ let isStream = false;
106
+ try {
107
+ isStream = JSON.parse(init.body ?? "{}")?.stream === true;
108
+ }
109
+ catch {
110
+ isStream = false;
111
+ }
112
+ expect(isStream).toBe(false);
113
+ });
114
+ });
115
+ describe("Latency metadata in TraceRecord", () => {
116
+ test("TraceRecord interface should have latency fields", () => {
117
+ const record = {
118
+ requestSentAt: 1234567.89,
119
+ firstTokenAt: 1234570.12,
120
+ lastTokenAt: 1234590.34,
121
+ };
122
+ expect(record.requestSentAt).toBe(1234567.89);
123
+ expect(record.firstTokenAt).toBe(1234570.12);
124
+ expect(record.lastTokenAt).toBe(1234590.34);
125
+ });
126
+ });
127
+ describe("tracedFetch stream integration", () => {
128
+ test("should apply TransformStream wrapper and record latency metadata", async () => {
129
+ _resetForTesting();
130
+ const mockFetch = vi.fn();
131
+ const originalFetch = globalThis.fetch;
132
+ globalThis.fetch = mockFetch;
133
+ const chunks = ["data: {\"content\": \"Hello\"}\n", "data: {\"content\": \"World\"}\n", "data: [DONE]\n"];
134
+ const encoder = new TextEncoder();
135
+ const streamChunks = chunks.map(c => encoder.encode(c));
136
+ const mockStream = new ReadableStream({
137
+ start(controller) {
138
+ for (const chunk of streamChunks) {
139
+ controller.enqueue(chunk);
140
+ }
141
+ controller.close();
142
+ },
143
+ });
144
+ mockFetch.mockResolvedValueOnce(new Response(mockStream, {
145
+ status: 200,
146
+ headers: { "content-type": "text/event-stream" },
147
+ }));
148
+ const hooks = await entrypoint.server({
149
+ client: {},
150
+ project: {},
151
+ directory: testDir,
152
+ worktree: testDir,
153
+ experimental_workspace: { register: vi.fn() },
154
+ serverUrl: new URL("http://localhost"),
155
+ $: {},
156
+ });
157
+ const sessionId = "stream-test-session";
158
+ await hooks.event({
159
+ event: {
160
+ type: "session.created",
161
+ properties: {
162
+ info: {
163
+ id: sessionId,
164
+ projectID: "test-project",
165
+ directory: testDir,
166
+ title: "Stream Test Session",
167
+ version: "1.0",
168
+ time: { created: Date.now(), updated: Date.now() },
169
+ },
170
+ },
171
+ },
172
+ });
173
+ const streamRequest = new Request("https://api.example.com/v1/chat/completions", {
174
+ method: "POST",
175
+ headers: {
176
+ "content-type": "application/json",
177
+ "x-opencode-session": sessionId,
178
+ },
179
+ body: JSON.stringify({ stream: true, messages: [{ role: "user", content: "test" }] }),
180
+ });
181
+ const response = await globalThis.fetch(streamRequest);
182
+ expect(response.__latencyMeta).toBeDefined();
183
+ expect(response.__latencyMeta.requestSentAt).toBeDefined();
184
+ expect(response.__latencyMeta.firstTokenAt).toBeNull();
185
+ expect(response.__latencyMeta.lastTokenAt).toBeNull();
186
+ const reader = response.body.getReader();
187
+ const receivedChunks = [];
188
+ while (true) {
189
+ const { done, value } = await reader.read();
190
+ if (done)
191
+ break;
192
+ receivedChunks.push(value);
193
+ }
194
+ expect(receivedChunks.length).toBe(3);
195
+ expect(response.__latencyMeta.firstTokenAt).not.toBeNull();
196
+ expect(response.__latencyMeta.lastTokenAt).not.toBeNull();
197
+ await new Promise(resolve => setTimeout(resolve, 100));
198
+ const traceDir = join(testDir, ".opencode-trace");
199
+ const sessionDir = join(traceDir, sessionId);
200
+ const recordFile = join(sessionDir, "1.json");
201
+ if (existsSync(recordFile)) {
202
+ const record = JSON.parse(readFileSync(recordFile, "utf-8"));
203
+ expect(record.requestSentAt).toBeDefined();
204
+ expect(record.firstTokenAt).toBeDefined();
205
+ expect(record.lastTokenAt).toBeDefined();
206
+ }
207
+ globalThis.fetch = originalFetch;
208
+ });
209
+ test("should not wrap non-stream responses", async () => {
210
+ _resetForTesting();
211
+ const mockFetch = vi.fn();
212
+ const originalFetch = globalThis.fetch;
213
+ globalThis.fetch = mockFetch;
214
+ mockFetch.mockResolvedValueOnce(new Response(JSON.stringify({ result: "ok" }), {
215
+ status: 200,
216
+ headers: { "content-type": "application/json" },
217
+ }));
218
+ const hooks = await entrypoint.server({
219
+ client: {},
220
+ project: {},
221
+ directory: testDir,
222
+ worktree: testDir,
223
+ experimental_workspace: { register: vi.fn() },
224
+ serverUrl: new URL("http://localhost"),
225
+ $: {},
226
+ });
227
+ const sessionId = "non-stream-test-session";
228
+ await hooks.event({
229
+ event: {
230
+ type: "session.created",
231
+ properties: {
232
+ info: {
233
+ id: sessionId,
234
+ projectID: "test-project",
235
+ directory: testDir,
236
+ title: "Non-Stream Test",
237
+ version: "1.0",
238
+ time: { created: Date.now(), updated: Date.now() },
239
+ },
240
+ },
241
+ },
242
+ });
243
+ const request = new Request("https://api.example.com/v1/chat/completions", {
244
+ method: "POST",
245
+ headers: {
246
+ "content-type": "application/json",
247
+ "x-opencode-session": sessionId,
248
+ },
249
+ body: JSON.stringify({ stream: false, messages: [{ role: "user", content: "test" }] }),
250
+ });
251
+ const response = await globalThis.fetch(request);
252
+ expect(response.__latencyMeta).toBeUndefined();
253
+ globalThis.fetch = originalFetch;
254
+ });
255
+ });
256
+ describe("Plugin - tool.execute.after hook 处理 Task 工具", () => {
257
+ test("tool.execute.after hook 处理 Task 工具记录 sub session", async () => {
258
+ const hooks = await entrypoint.server({
259
+ client: {},
260
+ project: {},
261
+ directory: testDir,
262
+ worktree: testDir,
263
+ experimental_workspace: { register: vi.fn() },
264
+ serverUrl: new URL("http://localhost"),
265
+ $: {},
266
+ });
267
+ const parentSessionId = "parent-session-123";
268
+ const subSessionId = "sub-session-456";
269
+ await hooks.event({
270
+ event: {
271
+ type: "session.created",
272
+ properties: {
273
+ info: {
274
+ id: parentSessionId,
275
+ projectID: "test-project",
276
+ directory: testDir,
277
+ title: "Parent Session",
278
+ version: "1.0",
279
+ time: { created: Date.now(), updated: Date.now() },
280
+ },
281
+ },
282
+ },
283
+ });
284
+ await hooks["tool.execute.after"]({
285
+ tool: "task",
286
+ sessionID: parentSessionId,
287
+ callID: "call-123",
288
+ args: { description: "test", prompt: "test" },
289
+ }, {
290
+ title: "Task completed",
291
+ output: "success",
292
+ metadata: { session_id: subSessionId },
293
+ });
294
+ const traceDir = join(testDir, ".opencode-trace");
295
+ const dbPath = join(traceDir, "state.db");
296
+ expect(existsSync(dbPath)).toBe(true);
297
+ });
298
+ test("tool.execute.after hook 对非 Task 工具不记录 sub session", async () => {
299
+ const hooks = await entrypoint.server({
300
+ client: {},
301
+ project: {},
302
+ directory: testDir,
303
+ worktree: testDir,
304
+ experimental_workspace: { register: vi.fn() },
305
+ serverUrl: new URL("http://localhost"),
306
+ $: {},
307
+ });
308
+ const sessionId = "test-session-123";
309
+ await hooks.event({
310
+ event: {
311
+ type: "session.created",
312
+ properties: {
313
+ info: {
314
+ id: sessionId,
315
+ projectID: "test-project",
316
+ directory: testDir,
317
+ title: "Test Session",
318
+ version: "1.0",
319
+ time: { created: Date.now(), updated: Date.now() },
320
+ },
321
+ },
322
+ },
323
+ });
324
+ await hooks["tool.execute.after"]({
325
+ tool: "bash",
326
+ sessionID: sessionId,
327
+ callID: "call-123",
328
+ args: { command: "ls" },
329
+ }, {
330
+ title: "Command executed",
331
+ output: "file1.txt\nfile2.txt",
332
+ metadata: {},
333
+ });
334
+ const traceDir = join(testDir, ".opencode-trace");
335
+ const dbPath = join(traceDir, "state.db");
336
+ expect(existsSync(dbPath)).toBe(true);
337
+ });
338
+ });
339
+ //# sourceMappingURL=trace.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.test.js","sourceRoot":"","sources":["../src/trace.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { TraceRecord } from "./trace.js";
2
+ export declare class AsyncWriteQueue {
3
+ private queue;
4
+ private writing;
5
+ private traceDir;
6
+ private batchSize;
7
+ constructor(traceDir: string, batchSize?: number);
8
+ enqueue(session: string, seq: number, record: TraceRecord): void;
9
+ private processQueue;
10
+ flush(): Promise<void>;
11
+ private writeBatch;
12
+ private writeFallback;
13
+ }
14
+ //# sourceMappingURL=write-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue.d.ts","sourceRoot":"","sources":["../src/write-queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAoE;IACjF,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAE,MAAW;IAKpD,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;YAOlD,YAAY;IAapB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAMd,UAAU;YAYV,aAAa;CAiB5B"}
@@ -0,0 +1,62 @@
1
+ import { promises as fs } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { logger } from "@opencode-trace/core";
4
+ export class AsyncWriteQueue {
5
+ queue = [];
6
+ writing = false;
7
+ traceDir;
8
+ batchSize;
9
+ constructor(traceDir, batchSize = 10) {
10
+ this.traceDir = traceDir;
11
+ this.batchSize = batchSize;
12
+ }
13
+ enqueue(session, seq, record) {
14
+ this.queue.push({ session, seq, record });
15
+ if (!this.writing) {
16
+ this.processQueue();
17
+ }
18
+ }
19
+ async processQueue() {
20
+ this.writing = true;
21
+ while (this.queue.length > 0) {
22
+ const batch = this.queue.splice(0, this.batchSize);
23
+ await this.writeBatch(batch);
24
+ }
25
+ this.writing = false;
26
+ if (this.queue.length > 0 && !this.writing) {
27
+ this.processQueue();
28
+ }
29
+ }
30
+ async flush() {
31
+ while (this.writing || this.queue.length > 0) {
32
+ await new Promise(resolve => setTimeout(resolve, 10));
33
+ }
34
+ }
35
+ async writeBatch(items) {
36
+ for (const { session, seq, record } of items) {
37
+ try {
38
+ const sessionDir = join(this.traceDir, session);
39
+ await fs.mkdir(sessionDir, { recursive: true });
40
+ await fs.writeFile(join(sessionDir, `${seq}.json`), JSON.stringify(record, null, 2));
41
+ }
42
+ catch (err) {
43
+ await this.writeFallback(session, seq, record, err);
44
+ }
45
+ }
46
+ }
47
+ async writeFallback(session, seq, record, err) {
48
+ const fallbackDir = join(this.traceDir, "fallback");
49
+ await fs.mkdir(fallbackDir, { recursive: true });
50
+ const filename = `${session}-${seq}-${Date.now()}.json`;
51
+ await fs.writeFile(join(fallbackDir, filename), JSON.stringify({
52
+ record,
53
+ error: {
54
+ name: err.name,
55
+ message: err.message,
56
+ stack: err.stack
57
+ }
58
+ }, null, 2));
59
+ logger.error("Write failed, saved to fallback", { filename, error: err.message });
60
+ }
61
+ }
62
+ //# sourceMappingURL=write-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue.js","sourceRoot":"","sources":["../src/write-queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=write-queue.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue.test.d.ts","sourceRoot":"","sources":["../src/write-queue.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "vitest";
2
+ import { AsyncWriteQueue } from "./write-queue.js";
3
+ import { mkdtempSync, rmSync, existsSync, readFileSync, promises as fs } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ describe("AsyncWriteQueue", () => {
7
+ let tempDir;
8
+ let queue;
9
+ beforeEach(() => {
10
+ tempDir = mkdtempSync(join(tmpdir(), "write-queue-test-"));
11
+ queue = new AsyncWriteQueue(tempDir);
12
+ });
13
+ afterEach(() => {
14
+ rmSync(tempDir, { recursive: true, force: true });
15
+ });
16
+ test("enqueue writes JSON file to session directory", async () => {
17
+ const record = {
18
+ id: 1,
19
+ purpose: "test",
20
+ requestAt: "2026-05-07T00:00:00Z",
21
+ responseAt: "2026-05-07T00:00:01Z",
22
+ request: { method: "GET", url: "https://example.com", headers: {}, body: null },
23
+ response: { status: 200, statusText: "OK", headers: {}, body: null },
24
+ error: null,
25
+ };
26
+ queue.enqueue("session-1", 1, record);
27
+ await new Promise(resolve => setTimeout(resolve, 100));
28
+ const filePath = join(tempDir, "session-1", "1.json");
29
+ expect(existsSync(filePath)).toBe(true);
30
+ const content = JSON.parse(readFileSync(filePath, "utf-8"));
31
+ expect(content).toEqual(record);
32
+ });
33
+ test("enqueue processes items in batches of 10", async () => {
34
+ const records = Array.from({ length: 25 }, (_, i) => ({
35
+ id: i + 1,
36
+ purpose: `test-${i}`,
37
+ requestAt: `2026-05-07T00:00:${i.toString().padStart(2, "0")}Z`,
38
+ responseAt: `2026-05-07T00:00:${(i + 1).toString().padStart(2, "0")}Z`,
39
+ request: { method: "GET", url: `https://example.com/${i}`, headers: {}, body: null },
40
+ response: { status: 200, statusText: "OK", headers: {}, body: null },
41
+ error: null,
42
+ }));
43
+ for (let i = 0; i < 25; i++) {
44
+ queue.enqueue("batch-test", i + 1, records[i]);
45
+ }
46
+ await new Promise(resolve => setTimeout(resolve, 200));
47
+ for (let i = 1; i <= 25; i++) {
48
+ const filePath = join(tempDir, "batch-test", `${i}.json`);
49
+ expect(existsSync(filePath)).toBe(true);
50
+ }
51
+ });
52
+ test("enqueue writes to fallback directory when primary write fails", async () => {
53
+ const record = {
54
+ id: 1,
55
+ purpose: "fallback-test",
56
+ requestAt: "2026-05-07T00:00:00Z",
57
+ responseAt: "2026-05-07T00:00:01Z",
58
+ request: { method: "GET", url: "https://example.com", headers: {}, body: null },
59
+ response: { status: 200, statusText: "OK", headers: {}, body: null },
60
+ error: null,
61
+ };
62
+ const sessionDir = join(tempDir, "readonly-session");
63
+ await fs.mkdir(sessionDir, { recursive: true });
64
+ await fs.chmod(sessionDir, 0o000);
65
+ queue.enqueue("readonly-session", 1, record);
66
+ await new Promise(resolve => setTimeout(resolve, 100));
67
+ await fs.chmod(sessionDir, 0o755);
68
+ const fallbackDir = join(tempDir, "fallback");
69
+ const files = await fs.readdir(fallbackDir);
70
+ expect(files.length).toBeGreaterThan(0);
71
+ });
72
+ test("flush waits for queue to drain", async () => {
73
+ const records = Array.from({ length: 15 }, (_, i) => ({
74
+ id: i + 1,
75
+ purpose: `flush-test-${i}`,
76
+ requestAt: "2026-05-07T00:00:00Z",
77
+ responseAt: "2026-05-07T00:00:01Z",
78
+ request: { method: "GET", url: "https://example.com", headers: {}, body: null },
79
+ response: { status: 200, statusText: "OK", headers: {}, body: null },
80
+ error: null,
81
+ }));
82
+ for (let i = 0; i < 15; i++) {
83
+ queue.enqueue("flush-test", i + 1, records[i]);
84
+ }
85
+ await queue.flush();
86
+ for (let i = 1; i <= 15; i++) {
87
+ const filePath = join(tempDir, "flush-test", `${i}.json`);
88
+ expect(existsSync(filePath)).toBe(true);
89
+ }
90
+ });
91
+ });
92
+ //# sourceMappingURL=write-queue.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-queue.test.js","sourceRoot":"","sources":["../src/write-queue.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@opencode-trace/plugin",
3
+ "version": "0.0.2",
4
+ "type": "module",
5
+ "description": "OpenCode plugin for trace recording",
6
+ "main": "./dist/trace.js",
7
+ "types": "./dist/trace.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/trace.js",
11
+ "types": "./dist/trace.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist/",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest watch",
22
+ "clean": "rm -rf dist *.tsbuildinfo"
23
+ },
24
+ "dependencies": {
25
+ "@opencode-trace/core": "0.0.2",
26
+ "@opencode-ai/plugin": "^1.14.22",
27
+ "@opencode-ai/sdk": "^1.14.41"
28
+ },
29
+ "devDependencies": {
30
+ "typescript": "^5.3.0",
31
+ "vitest": "^4.1.5"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/aixmoyu/opencode-trace.git",
40
+ "directory": "packages/plugin"
41
+ },
42
+ "keywords": [
43
+ "opencode",
44
+ "trace",
45
+ "plugin"
46
+ ],
47
+ "license": "MIT"
48
+ }