@ai-sdk/devtools 1.0.0-beta.9 → 1.0.0-canary.21
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/dist/client/assets/index-B1quREL_.css +1 -0
- package/dist/client/assets/index-DYruJ8X0.js +181 -0
- package/dist/client/index.html +2 -2
- package/dist/index.d.ts +19 -1
- package/dist/index.js +342 -3
- package/dist/viewer/server.js +42 -5
- package/package.json +28 -4
- package/src/db.ts +32 -5
- package/src/index.ts +1 -0
- package/src/integration.ts +509 -0
- package/src/middleware.ts +5 -5
- package/src/viewer/client/app.tsx +97 -1878
- package/src/viewer/client/components/message-components.tsx +342 -0
- package/src/viewer/client/components/output-components.tsx +145 -0
- package/src/viewer/client/components/shared-components.tsx +691 -0
- package/src/viewer/client/components/step-card.tsx +472 -0
- package/src/viewer/client/components/trace-timeline.tsx +529 -0
- package/src/viewer/client/components/ui/badge.tsx +0 -1
- package/src/viewer/client/components/ui/button.tsx +0 -1
- package/src/viewer/client/types.ts +183 -0
- package/src/viewer/client/utils.ts +708 -0
- package/src/viewer/server.ts +49 -5
- package/dist/client/assets/index-BkyOIjbt.css +0 -1
- package/dist/client/assets/index-BwqXGbSV.js +0 -176
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
14
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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
|
-
|
|
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 = {
|
|
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
|
};
|
package/dist/viewer/server.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
3
|
+
"version": "1.0.0-canary.21",
|
|
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-
|
|
30
|
+
"@ai-sdk/provider": "4.0.0-canary.16"
|
|
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-
|
|
59
|
+
"ai": "7.0.0-canary.120"
|
|
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 (
|
|
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 = {
|
|
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