@ai-sdk/devtools 1.0.0-beta.9 → 1.0.0-canary.20

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.
@@ -10,8 +10,8 @@
10
10
  href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600;700&display=swap"
11
11
  rel="stylesheet"
12
12
  />
13
- <script type="module" crossorigin src="/assets/index-BwqXGbSV.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-BkyOIjbt.css">
13
+ <script type="module" crossorigin src="/assets/index-DYruJ8X0.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-B1quREL_.css">
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { LanguageModelV4Middleware } from '@ai-sdk/provider';
2
+ import { Telemetry } from 'ai';
2
3
 
3
4
  /**
4
5
  * Factory function that creates a devtools middleware instance.
@@ -18,4 +19,21 @@ import { LanguageModelV4Middleware } from '@ai-sdk/provider';
18
19
  */
19
20
  declare const devToolsMiddleware: () => LanguageModelV4Middleware;
20
21
 
21
- export { devToolsMiddleware };
22
+ /**
23
+ * Creates a devtools telemetry integration that logs all AI SDK operations
24
+ * to the devtools viewer.
25
+ *
26
+ * Usage:
27
+ * ```ts
28
+ * import { registerTelemetry } from 'ai';
29
+ * import { DevToolsTelemetry } from '@ai-sdk/devtools';
30
+ *
31
+ * registerTelemetry(DevToolsTelemetry());
32
+ * ```
33
+ *
34
+ * Telemetry is enabled by default — no need to set `telemetry`
35
+ * unless you want to configure `functionId`, `recordInputs`, or `recordOutputs`.
36
+ */
37
+ declare function DevToolsTelemetry(): Telemetry;
38
+
39
+ export { DevToolsTelemetry, devToolsMiddleware };
package/dist/index.js CHANGED
@@ -12,7 +12,7 @@ var notifyServerAsync = async (event) => {
12
12
  await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
13
13
  method: "POST",
14
14
  headers: { "Content-Type": "application/json" },
15
- body: JSON.stringify({ event, timestamp: Date.now() })
15
+ body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH })
16
16
  });
17
17
  } catch {
18
18
  }
@@ -64,14 +64,20 @@ var saveDb = (db) => {
64
64
  dbCache = db;
65
65
  writeDb(db);
66
66
  };
67
- var createRun = async (id) => {
67
+ var createRun = async (id, parent, functionId) => {
68
68
  const db = getDb();
69
69
  const started_at = (/* @__PURE__ */ new Date()).toISOString();
70
70
  const existing = db.runs.find((r) => r.id === id);
71
71
  if (existing) {
72
72
  return existing;
73
73
  }
74
- const run = { id, started_at };
74
+ const run = {
75
+ id,
76
+ started_at,
77
+ parent_run_id: parent?.runId ?? null,
78
+ parent_step_id: parent?.stepId ?? null,
79
+ function_id: functionId ?? null
80
+ };
75
81
  db.runs.push(run);
76
82
  saveDb(db);
77
83
  notifyServer("run");
@@ -379,6 +385,339 @@ var devToolsMiddleware = () => {
379
385
  }
380
386
  };
381
387
  };
388
+
389
+ // src/integration.ts
390
+ var activeSteps2 = /* @__PURE__ */ new Map();
391
+ var signalHandlersRegistered2 = false;
392
+ var registerSignalHandlers2 = () => {
393
+ if (signalHandlersRegistered2) return;
394
+ signalHandlersRegistered2 = true;
395
+ const cleanup = async () => {
396
+ if (activeSteps2.size === 0) return;
397
+ const promises = Array.from(activeSteps2.entries()).map(
398
+ async ([stepId, data]) => {
399
+ const durationMs = Date.now() - data.startTime;
400
+ await updateStepResult(stepId, {
401
+ duration_ms: durationMs,
402
+ output: null,
403
+ usage: null,
404
+ error: "Request aborted",
405
+ raw_request: null,
406
+ raw_response: null,
407
+ raw_chunks: null
408
+ });
409
+ }
410
+ );
411
+ await Promise.all(promises);
412
+ await notifyServerAsync("step-update");
413
+ };
414
+ process.on("SIGINT", () => {
415
+ cleanup().then(() => process.exit(130));
416
+ });
417
+ process.on("SIGTERM", () => {
418
+ cleanup().then(() => process.exit(143));
419
+ });
420
+ };
421
+ var generateRunId2 = () => {
422
+ const now = /* @__PURE__ */ new Date();
423
+ const timestamp = now.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
424
+ const uniqueId = crypto.randomUUID().slice(0, 8);
425
+ return `${timestamp}-${uniqueId}`;
426
+ };
427
+ function getOperationType(operationId) {
428
+ if (operationId === "ai.streamText" || operationId === "ai.streamObject") {
429
+ return "stream";
430
+ }
431
+ return "generate";
432
+ }
433
+ function DevToolsTelemetry() {
434
+ if (process.env.NODE_ENV === "production") {
435
+ throw new Error(
436
+ "@ai-sdk/devtools should not be used in production. Remove DevToolsTelemetry from your telemetry configuration for production builds."
437
+ );
438
+ }
439
+ registerSignalHandlers2();
440
+ const callStates = /* @__PURE__ */ new Map();
441
+ const toolContextMap = /* @__PURE__ */ new Map();
442
+ let currentToolCallId = null;
443
+ function resolveParentInfo() {
444
+ if (!currentToolCallId) return void 0;
445
+ const ctx = toolContextMap.get(currentToolCallId);
446
+ if (!ctx) return void 0;
447
+ const parentState = callStates.get(ctx.parentCallId);
448
+ if (!parentState) return void 0;
449
+ let latestStepId;
450
+ let latestStepNumber = -1;
451
+ for (const [stepNumber, stepState] of parentState.stepStates) {
452
+ if (stepNumber > latestStepNumber) {
453
+ latestStepNumber = stepNumber;
454
+ latestStepId = stepState.stepId;
455
+ }
456
+ }
457
+ if (!latestStepId) return void 0;
458
+ return { runId: parentState.runId, stepId: latestStepId };
459
+ }
460
+ function getOrCreateCallState(callId, operationId, event) {
461
+ let state = callStates.get(callId);
462
+ if (state) return state;
463
+ state = {
464
+ runId: generateRunId2(),
465
+ operationType: getOperationType(operationId),
466
+ functionId: event.functionId,
467
+ settings: {
468
+ maxOutputTokens: event.maxOutputTokens,
469
+ temperature: event.temperature,
470
+ topP: event.topP,
471
+ topK: event.topK,
472
+ presencePenalty: event.presencePenalty,
473
+ frequencyPenalty: event.frequencyPenalty,
474
+ seed: event.seed
475
+ },
476
+ stepStates: /* @__PURE__ */ new Map()
477
+ };
478
+ callStates.set(callId, state);
479
+ return state;
480
+ }
481
+ const integration = {
482
+ onStart: async (event) => {
483
+ const operationId = event.operationId;
484
+ if (operationId === "ai.embed" || operationId === "ai.embedMany" || operationId === "ai.rerank") {
485
+ return;
486
+ }
487
+ const startEvent = event;
488
+ const parentInfo = resolveParentInfo();
489
+ const state = getOrCreateCallState(
490
+ startEvent.callId,
491
+ operationId,
492
+ startEvent
493
+ );
494
+ await createRun(state.runId, parentInfo, state.functionId);
495
+ },
496
+ onStepStart: async (event) => {
497
+ const stepStartEvent = event;
498
+ const state = callStates.get(stepStartEvent.callId);
499
+ if (!state) return;
500
+ const stepId = crypto.randomUUID();
501
+ const startTime = Date.now();
502
+ const stepState = {
503
+ stepId,
504
+ startTime,
505
+ streamChunks: [],
506
+ rawStreamChunks: []
507
+ };
508
+ const stepNumber = stepStartEvent.steps.length;
509
+ state.stepStates.set(stepNumber, stepState);
510
+ activeSteps2.set(stepId, stepState);
511
+ const prompt = stepStartEvent.promptMessages ?? stepStartEvent.messages;
512
+ await createStep({
513
+ id: stepId,
514
+ run_id: state.runId,
515
+ step_number: stepNumber + 1,
516
+ type: state.operationType,
517
+ model_id: stepStartEvent.modelId,
518
+ provider: stepStartEvent.provider ?? null,
519
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
520
+ input: JSON.stringify({
521
+ prompt,
522
+ tools: stepStartEvent.tools ? Object.entries(stepStartEvent.tools).map(([name, tool]) => ({
523
+ name,
524
+ description: tool.description,
525
+ parameters: tool.parameters
526
+ })) : void 0,
527
+ toolChoice: stepStartEvent.toolChoice,
528
+ maxOutputTokens: state.settings.maxOutputTokens,
529
+ temperature: state.settings.temperature,
530
+ topP: state.settings.topP,
531
+ topK: state.settings.topK,
532
+ presencePenalty: state.settings.presencePenalty,
533
+ frequencyPenalty: state.settings.frequencyPenalty,
534
+ seed: state.settings.seed
535
+ }),
536
+ provider_options: stepStartEvent.providerOptions ? JSON.stringify(stepStartEvent.providerOptions) : null
537
+ });
538
+ },
539
+ onObjectStepStart: async (event) => {
540
+ const stepStartEvent = event;
541
+ const state = callStates.get(stepStartEvent.callId);
542
+ if (!state) return;
543
+ const stepId = crypto.randomUUID();
544
+ const startTime = Date.now();
545
+ const stepState = {
546
+ stepId,
547
+ startTime,
548
+ streamChunks: [],
549
+ rawStreamChunks: []
550
+ };
551
+ state.stepStates.set(stepStartEvent.stepNumber, stepState);
552
+ activeSteps2.set(stepId, stepState);
553
+ await createStep({
554
+ id: stepId,
555
+ run_id: state.runId,
556
+ step_number: stepStartEvent.stepNumber + 1,
557
+ type: state.operationType,
558
+ model_id: stepStartEvent.modelId,
559
+ provider: stepStartEvent.provider ?? null,
560
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
561
+ input: JSON.stringify({
562
+ prompt: stepStartEvent.promptMessages,
563
+ maxOutputTokens: state.settings.maxOutputTokens,
564
+ temperature: state.settings.temperature,
565
+ topP: state.settings.topP,
566
+ topK: state.settings.topK,
567
+ presencePenalty: state.settings.presencePenalty,
568
+ frequencyPenalty: state.settings.frequencyPenalty,
569
+ seed: state.settings.seed
570
+ }),
571
+ provider_options: stepStartEvent.providerOptions ? JSON.stringify(stepStartEvent.providerOptions) : null
572
+ });
573
+ },
574
+ onChunk: async (event) => {
575
+ const { chunk } = event;
576
+ if (chunk.type === "raw") {
577
+ const rawValue = chunk.rawValue;
578
+ for (const [, state] of callStates) {
579
+ let latestStepState;
580
+ let latestStepNumber = -1;
581
+ for (const [stepNumber, ss] of state.stepStates) {
582
+ if (stepNumber > latestStepNumber) {
583
+ latestStepNumber = stepNumber;
584
+ latestStepState = ss;
585
+ }
586
+ }
587
+ if (latestStepState) {
588
+ latestStepState.rawStreamChunks.push(rawValue);
589
+ return;
590
+ }
591
+ }
592
+ return;
593
+ }
594
+ if ("callId" in chunk && "stepNumber" in chunk) {
595
+ const typed = chunk;
596
+ const state = callStates.get(typed.callId);
597
+ if (!state) return;
598
+ const stepState = state.stepStates.get(typed.stepNumber);
599
+ if (!stepState) return;
600
+ stepState.streamChunks.push(chunk);
601
+ return;
602
+ }
603
+ for (const [, state] of callStates) {
604
+ let latestStepState;
605
+ let latestStepNumber = -1;
606
+ for (const [stepNumber, ss] of state.stepStates) {
607
+ if (stepNumber > latestStepNumber) {
608
+ latestStepNumber = stepNumber;
609
+ latestStepState = ss;
610
+ }
611
+ }
612
+ if (latestStepState) {
613
+ latestStepState.streamChunks.push(chunk);
614
+ return;
615
+ }
616
+ }
617
+ },
618
+ onStepFinish: async (event) => {
619
+ const stepResult = event;
620
+ const state = callStates.get(stepResult.callId);
621
+ if (!state) return;
622
+ const stepState = state.stepStates.get(stepResult.stepNumber);
623
+ if (!stepState) return;
624
+ activeSteps2.delete(stepState.stepId);
625
+ const durationMs = Date.now() - stepState.startTime;
626
+ const output = {
627
+ content: stepResult.content,
628
+ finishReason: stepResult.finishReason,
629
+ response: {
630
+ id: stepResult.response.id,
631
+ modelId: stepResult.response.modelId,
632
+ timestamp: stepResult.response.timestamp,
633
+ messages: stepResult.response.messages
634
+ }
635
+ };
636
+ const hasStreamChunks = stepState.streamChunks.length > 0;
637
+ const hasRawStreamChunks = stepState.rawStreamChunks.length > 0;
638
+ await updateStepResult(stepState.stepId, {
639
+ duration_ms: durationMs,
640
+ output: JSON.stringify(output),
641
+ usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
642
+ error: null,
643
+ raw_request: stepResult.request?.body ? JSON.stringify(stepResult.request.body) : null,
644
+ raw_response: stepResult.response?.body ? JSON.stringify(stepResult.response.body) : hasStreamChunks ? JSON.stringify(stepState.streamChunks) : null,
645
+ raw_chunks: hasRawStreamChunks ? JSON.stringify(stepState.rawStreamChunks) : null
646
+ });
647
+ state.stepStates.delete(stepResult.stepNumber);
648
+ },
649
+ onObjectStepFinish: async (event) => {
650
+ const stepResult = event;
651
+ const state = callStates.get(stepResult.callId);
652
+ if (!state) return;
653
+ const stepState = state.stepStates.get(stepResult.stepNumber);
654
+ if (!stepState) return;
655
+ activeSteps2.delete(stepState.stepId);
656
+ const durationMs = Date.now() - stepState.startTime;
657
+ const output = {
658
+ finishReason: stepResult.finishReason,
659
+ objectText: stepResult.objectText,
660
+ response: {
661
+ id: stepResult.response.id,
662
+ modelId: stepResult.response.modelId,
663
+ timestamp: stepResult.response.timestamp
664
+ }
665
+ };
666
+ await updateStepResult(stepState.stepId, {
667
+ duration_ms: durationMs,
668
+ output: JSON.stringify(output),
669
+ usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
670
+ error: null,
671
+ raw_request: stepResult.request?.body ? JSON.stringify(stepResult.request.body) : null,
672
+ raw_response: stepResult.response?.body ? JSON.stringify(stepResult.response.body) : null
673
+ });
674
+ state.stepStates.delete(stepResult.stepNumber);
675
+ },
676
+ onFinish: async (event) => {
677
+ const finishEvent = event;
678
+ callStates.delete(finishEvent.callId);
679
+ },
680
+ onError: async (error) => {
681
+ const errorObj = error;
682
+ const callId = errorObj?.callId;
683
+ if (!callId) return;
684
+ const state = callStates.get(callId);
685
+ if (!state) return;
686
+ const cause = errorObj?.error ?? error;
687
+ const errorMessage = cause instanceof Error ? cause.message : String(cause);
688
+ for (const [, stepState] of state.stepStates) {
689
+ activeSteps2.delete(stepState.stepId);
690
+ const durationMs = Date.now() - stepState.startTime;
691
+ await updateStepResult(stepState.stepId, {
692
+ duration_ms: durationMs,
693
+ output: null,
694
+ usage: null,
695
+ error: errorMessage,
696
+ raw_request: null,
697
+ raw_response: null,
698
+ raw_chunks: null
699
+ });
700
+ }
701
+ callStates.delete(callId);
702
+ },
703
+ executeTool: async ({ callId, toolCallId, execute }) => {
704
+ toolContextMap.set(toolCallId, {
705
+ parentCallId: callId,
706
+ parentToolCallId: toolCallId
707
+ });
708
+ const previousToolCallId = currentToolCallId;
709
+ currentToolCallId = toolCallId;
710
+ try {
711
+ return await execute();
712
+ } finally {
713
+ currentToolCallId = previousToolCallId;
714
+ toolContextMap.delete(toolCallId);
715
+ }
716
+ }
717
+ };
718
+ return integration;
719
+ }
382
720
  export {
721
+ DevToolsTelemetry,
383
722
  devToolsMiddleware
384
723
  };
@@ -22,7 +22,7 @@ var notifyServerAsync = async (event) => {
22
22
  await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
23
23
  method: "POST",
24
24
  headers: { "Content-Type": "application/json" },
25
- body: JSON.stringify({ event, timestamp: Date.now() })
25
+ body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH })
26
26
  });
27
27
  } catch {
28
28
  }
@@ -74,7 +74,17 @@ var saveDb = (db) => {
74
74
  dbCache = db;
75
75
  writeDb(db);
76
76
  };
77
- var reloadDb = async () => {
77
+ var reloadDb = async (remoteDbPath2) => {
78
+ if (remoteDbPath2) {
79
+ try {
80
+ if (fs.existsSync(remoteDbPath2)) {
81
+ const content = fs.readFileSync(remoteDbPath2, "utf-8");
82
+ dbCache = JSON.parse(content);
83
+ return;
84
+ }
85
+ } catch {
86
+ }
87
+ }
78
88
  dbCache = readDb();
79
89
  };
80
90
  var getRuns = async () => {
@@ -120,12 +130,15 @@ var devEnv = process.env.AI_SDK_DEVTOOLS_DEV;
120
130
  var isDevMode = devEnv !== void 0 && devEnv !== "false" && devEnv !== "0";
121
131
  var projectRoot = path2.resolve(__dirname, "../..");
122
132
  var clientDir = path2.join(projectRoot, "dist/client");
133
+ var remoteDbPath;
123
134
  var app = new Hono();
124
135
  app.use("/*", cors());
125
136
  app.get("/api/runs", async (c) => {
137
+ await reloadDb(remoteDbPath);
126
138
  const runs = await getRuns();
139
+ const rootRuns = runs.filter((r) => !r.parent_run_id);
127
140
  const runsWithMeta = await Promise.all(
128
- runs.map(async (run) => {
141
+ rootRuns.map(async (run) => {
129
142
  const steps = await getStepsForRun(run.id);
130
143
  let firstMessage = "No user message";
131
144
  let hasError = false;
@@ -159,14 +172,35 @@ app.get("/api/runs", async (c) => {
159
172
  return c.json(runsWithMeta);
160
173
  });
161
174
  app.get("/api/runs/:id", async (c) => {
175
+ await reloadDb(remoteDbPath);
162
176
  const data = await getRunWithSteps(c.req.param("id"));
163
177
  if (!data) {
164
178
  return c.json({ error: "Run not found" }, 404);
165
179
  }
166
180
  const isInProgress = data.steps.some((s) => s.duration_ms === null && !s.error);
181
+ const allRuns = await getRuns();
182
+ async function getDescendantRuns(parentRunId) {
183
+ const children = allRuns.filter((r) => r.parent_run_id === parentRunId);
184
+ return Promise.all(
185
+ children.map(async (childRun) => {
186
+ const childSteps = await getStepsForRun(childRun.id);
187
+ const childIsInProgress = childSteps.some(
188
+ (s) => s.duration_ms === null && !s.error
189
+ );
190
+ const grandchildren = await getDescendantRuns(childRun.id);
191
+ return {
192
+ run: { ...childRun, isInProgress: childIsInProgress },
193
+ steps: childSteps,
194
+ childRuns: grandchildren
195
+ };
196
+ })
197
+ );
198
+ }
199
+ const childRuns = await getDescendantRuns(data.run.id);
167
200
  return c.json({
168
201
  run: { ...data.run, isInProgress },
169
- steps: data.steps
202
+ steps: data.steps,
203
+ childRuns
170
204
  });
171
205
  });
172
206
  app.post("/api/clear", async (c) => {
@@ -224,7 +258,10 @@ app.get("/api/events", (c) => {
224
258
  });
225
259
  app.post("/api/notify", async (c) => {
226
260
  const body = await c.req.json();
227
- await reloadDb();
261
+ if (body.dbPath) {
262
+ remoteDbPath = body.dbPath;
263
+ }
264
+ await reloadDb(remoteDbPath);
228
265
  broadcastToClients("update", body);
229
266
  return c.json({ success: true });
230
267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/devtools",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.0-canary.20",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@hono/node-server": "^1.13.7",
29
29
  "hono": "^4.6.14",
30
- "@ai-sdk/provider": "4.0.0-beta.9"
30
+ "@ai-sdk/provider": "4.0.0-canary.15"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@radix-ui/react-collapsible": "^1.1.12",
@@ -49,14 +49,36 @@
49
49
  "tailwind-merge": "^3.4.0",
50
50
  "tailwindcss": "^4.1.17",
51
51
  "tsup": "^8",
52
+ "vitest": "^4.1.5",
52
53
  "tsx": "^4.19.2",
53
54
  "tw-animate-css": "^1.4.0",
54
55
  "typescript": "5.8.3",
55
56
  "vaul": "^1.1.2",
56
57
  "vite": "^6.0.3",
57
58
  "zod": "3.25.76",
58
- "ai": "7.0.0-beta.74"
59
+ "ai": "7.0.0-canary.117"
59
60
  },
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "provenance": true
64
+ },
65
+ "homepage": "https://ai-sdk.dev/docs",
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "https://github.com/vercel/ai",
69
+ "directory": "packages/devtools"
70
+ },
71
+ "bugs": {
72
+ "url": "https://github.com/vercel/ai/issues"
73
+ },
74
+ "license": "Apache-2.0",
75
+ "sideEffects": false,
76
+ "engines": {
77
+ "node": ">=18"
78
+ },
79
+ "keywords": [
80
+ "ai"
81
+ ],
60
82
  "scripts": {
61
83
  "dev": "concurrently -k \"pnpm dev:api\" \"pnpm dev:client\"",
62
84
  "dev:api": "AI_SDK_DEVTOOLS_DEV=true tsx src/viewer/server.ts",
@@ -65,6 +87,8 @@
65
87
  "start": "node dist/viewer/server.js",
66
88
  "build": "pnpm build:client && pnpm build:server",
67
89
  "build:client": "vite build",
68
- "build:server": "tsup"
90
+ "build:server": "tsup --tsconfig tsconfig.build.json",
91
+ "test": "vitest --config vitest.node.config.js --run",
92
+ "test:watch": "vitest --config vitest.node.config.js"
69
93
  }
70
94
  }
package/src/db.ts CHANGED
@@ -26,7 +26,7 @@ export const notifyServerAsync = async (
26
26
  await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
27
27
  method: 'POST',
28
28
  headers: { 'Content-Type': 'application/json' },
29
- body: JSON.stringify({ event, timestamp: Date.now() }),
29
+ body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH }),
30
30
  });
31
31
  } catch {
32
32
  // Ignore errors - server might not be running
@@ -36,6 +36,9 @@ export const notifyServerAsync = async (
36
36
  export interface Run {
37
37
  id: string;
38
38
  started_at: string;
39
+ parent_run_id: string | null;
40
+ parent_step_id: string | null;
41
+ function_id: string | null;
39
42
  }
40
43
 
41
44
  export interface Step {
@@ -139,13 +142,31 @@ const saveDb = (db: Database): void => {
139
142
 
140
143
  /**
141
144
  * Reload the database from disk.
142
- * Used by the viewer server to pick up changes made by the middleware.
145
+ * Used by the viewer server to pick up changes made by the middleware/integration.
146
+ * When a remote dbPath is provided (from a notify POST), reads from that path
147
+ * instead of the local CWD-based path, so the viewer works regardless of where
148
+ * it was started.
143
149
  */
144
- export const reloadDb = async (): Promise<void> => {
150
+ export const reloadDb = async (remoteDbPath?: string): Promise<void> => {
151
+ if (remoteDbPath) {
152
+ try {
153
+ if (fs.existsSync(remoteDbPath)) {
154
+ const content = fs.readFileSync(remoteDbPath, 'utf-8');
155
+ dbCache = JSON.parse(content);
156
+ return;
157
+ }
158
+ } catch {
159
+ // Fall through to default
160
+ }
161
+ }
145
162
  dbCache = readDb();
146
163
  };
147
164
 
148
- export const createRun = async (id: string): Promise<Run> => {
165
+ export const createRun = async (
166
+ id: string,
167
+ parent?: { runId: string; stepId: string },
168
+ functionId?: string,
169
+ ): Promise<Run> => {
149
170
  const db = getDb();
150
171
  const started_at = new Date().toISOString();
151
172
 
@@ -155,7 +176,13 @@ export const createRun = async (id: string): Promise<Run> => {
155
176
  return existing;
156
177
  }
157
178
 
158
- const run: Run = { id, started_at };
179
+ const run: Run = {
180
+ id,
181
+ started_at,
182
+ parent_run_id: parent?.runId ?? null,
183
+ parent_step_id: parent?.stepId ?? null,
184
+ function_id: functionId ?? null,
185
+ };
159
186
  db.runs.push(run);
160
187
  saveDb(db);
161
188
  notifyServer('run');
package/src/index.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { devToolsMiddleware } from './middleware.js';
2
+ export { DevToolsTelemetry } from './integration.js';