@ai-sdk/devtools 1.0.0-beta.3 → 1.0.0-beta.31
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 +40 -9
- package/dist/client/assets/index-BVPj3Knk.css +1 -0
- package/dist/client/assets/index-DKzNaLaQ.js +190 -0
- package/dist/client/index.html +2 -2
- package/dist/index.d.ts +19 -1
- package/dist/index.js +297 -5
- package/dist/viewer/server.js +106 -10
- package/package.json +44 -20
- package/src/db.ts +75 -7
- package/src/index.ts +1 -0
- package/src/integration.ts +447 -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 +695 -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 +1 -2
- package/src/viewer/client/styles.css +16 -8
- package/src/viewer/client/types.ts +183 -0
- package/src/viewer/client/utils.ts +711 -0
- package/src/viewer/server.ts +90 -10
- package/dist/client/assets/index-BkyOIjbt.css +0 -1
- package/dist/client/assets/index-DmPGHSLs.js +0 -185
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-DKzNaLaQ.js"></script>
|
|
14
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BVPj3Knk.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
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// src/db.ts
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var DEVTOOLS_DB_DIR = ".devtools";
|
|
5
|
+
var DEVTOOLS_DB_FILE = "generations.json";
|
|
6
|
+
var MAX_DB_BYTES = 100 * 1024 * 1024;
|
|
7
|
+
var DB_DIR = path.join(process.cwd(), DEVTOOLS_DB_DIR);
|
|
8
|
+
var DB_PATH = path.join(DB_DIR, DEVTOOLS_DB_FILE);
|
|
6
9
|
var DEVTOOLS_PORT = process.env.AI_SDK_DEVTOOLS_PORT ? parseInt(process.env.AI_SDK_DEVTOOLS_PORT) : 4983;
|
|
7
10
|
var notifyServer = (event) => {
|
|
8
11
|
notifyServerAsync(event);
|
|
@@ -12,7 +15,7 @@ var notifyServerAsync = async (event) => {
|
|
|
12
15
|
await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
|
|
13
16
|
method: "POST",
|
|
14
17
|
headers: { "Content-Type": "application/json" },
|
|
15
|
-
body: JSON.stringify({ event, timestamp: Date.now() })
|
|
18
|
+
body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH })
|
|
16
19
|
});
|
|
17
20
|
} catch {
|
|
18
21
|
}
|
|
@@ -64,14 +67,20 @@ var saveDb = (db) => {
|
|
|
64
67
|
dbCache = db;
|
|
65
68
|
writeDb(db);
|
|
66
69
|
};
|
|
67
|
-
var createRun = async (id) => {
|
|
70
|
+
var createRun = async (id, parent, functionId) => {
|
|
68
71
|
const db = getDb();
|
|
69
72
|
const started_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
70
73
|
const existing = db.runs.find((r) => r.id === id);
|
|
71
74
|
if (existing) {
|
|
72
75
|
return existing;
|
|
73
76
|
}
|
|
74
|
-
const run = {
|
|
77
|
+
const run = {
|
|
78
|
+
id,
|
|
79
|
+
started_at,
|
|
80
|
+
parent_run_id: parent?.runId ?? null,
|
|
81
|
+
parent_step_id: parent?.stepId ?? null,
|
|
82
|
+
function_id: functionId ?? null
|
|
83
|
+
};
|
|
75
84
|
db.runs.push(run);
|
|
76
85
|
saveDb(db);
|
|
77
86
|
notifyServer("run");
|
|
@@ -379,6 +388,289 @@ var devToolsMiddleware = () => {
|
|
|
379
388
|
}
|
|
380
389
|
};
|
|
381
390
|
};
|
|
391
|
+
|
|
392
|
+
// src/integration.ts
|
|
393
|
+
var activeSteps2 = /* @__PURE__ */ new Map();
|
|
394
|
+
var signalHandlersRegistered2 = false;
|
|
395
|
+
var registerSignalHandlers2 = () => {
|
|
396
|
+
if (signalHandlersRegistered2) return;
|
|
397
|
+
signalHandlersRegistered2 = true;
|
|
398
|
+
const cleanup = async () => {
|
|
399
|
+
if (activeSteps2.size === 0) return;
|
|
400
|
+
const promises = Array.from(activeSteps2.entries()).map(
|
|
401
|
+
async ([stepId, data]) => {
|
|
402
|
+
const durationMs = Date.now() - data.startTime;
|
|
403
|
+
await updateStepResult(stepId, {
|
|
404
|
+
duration_ms: durationMs,
|
|
405
|
+
output: null,
|
|
406
|
+
usage: null,
|
|
407
|
+
error: "Request aborted",
|
|
408
|
+
raw_request: null,
|
|
409
|
+
raw_response: null,
|
|
410
|
+
raw_chunks: null
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
await Promise.all(promises);
|
|
415
|
+
await notifyServerAsync("step-update");
|
|
416
|
+
};
|
|
417
|
+
process.on("SIGINT", () => {
|
|
418
|
+
cleanup().then(() => process.exit(130));
|
|
419
|
+
});
|
|
420
|
+
process.on("SIGTERM", () => {
|
|
421
|
+
cleanup().then(() => process.exit(143));
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
var generateRunId2 = () => {
|
|
425
|
+
const now = /* @__PURE__ */ new Date();
|
|
426
|
+
const timestamp = now.toISOString().replace(/[-:T.Z]/g, "").slice(0, 17);
|
|
427
|
+
const uniqueId = crypto.randomUUID().slice(0, 8);
|
|
428
|
+
return `${timestamp}-${uniqueId}`;
|
|
429
|
+
};
|
|
430
|
+
function getOperationType(operationId) {
|
|
431
|
+
if (operationId === "ai.streamText" || operationId === "ai.streamObject") {
|
|
432
|
+
return "stream";
|
|
433
|
+
}
|
|
434
|
+
return "generate";
|
|
435
|
+
}
|
|
436
|
+
function DevToolsTelemetry() {
|
|
437
|
+
if (process.env.NODE_ENV === "production") {
|
|
438
|
+
throw new Error(
|
|
439
|
+
"@ai-sdk/devtools should not be used in production. Remove DevToolsTelemetry from your telemetry configuration for production builds."
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
registerSignalHandlers2();
|
|
443
|
+
const callStates = /* @__PURE__ */ new Map();
|
|
444
|
+
const toolContextMap = /* @__PURE__ */ new Map();
|
|
445
|
+
let currentToolCallId = null;
|
|
446
|
+
function resolveParentInfo() {
|
|
447
|
+
if (!currentToolCallId) return void 0;
|
|
448
|
+
const toolCallContext = toolContextMap.get(currentToolCallId);
|
|
449
|
+
if (!toolCallContext) return void 0;
|
|
450
|
+
const parentState = callStates.get(toolCallContext.parentCallId);
|
|
451
|
+
if (!parentState) return void 0;
|
|
452
|
+
let latestStepId;
|
|
453
|
+
let latestStepNumber = -1;
|
|
454
|
+
for (const [stepNumber, stepState] of parentState.stepStates) {
|
|
455
|
+
if (stepNumber > latestStepNumber) {
|
|
456
|
+
latestStepNumber = stepNumber;
|
|
457
|
+
latestStepId = stepState.stepId;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (!latestStepId) return void 0;
|
|
461
|
+
return { runId: parentState.runId, stepId: latestStepId };
|
|
462
|
+
}
|
|
463
|
+
function getOrCreateCallState(callId, operationId, event) {
|
|
464
|
+
let state = callStates.get(callId);
|
|
465
|
+
if (state) return state;
|
|
466
|
+
state = {
|
|
467
|
+
runId: generateRunId2(),
|
|
468
|
+
operationType: getOperationType(operationId),
|
|
469
|
+
functionId: event.functionId,
|
|
470
|
+
settings: {
|
|
471
|
+
maxOutputTokens: event.maxOutputTokens,
|
|
472
|
+
temperature: event.temperature,
|
|
473
|
+
topP: event.topP,
|
|
474
|
+
topK: event.topK,
|
|
475
|
+
presencePenalty: event.presencePenalty,
|
|
476
|
+
frequencyPenalty: event.frequencyPenalty,
|
|
477
|
+
seed: event.seed
|
|
478
|
+
},
|
|
479
|
+
stepStates: /* @__PURE__ */ new Map()
|
|
480
|
+
};
|
|
481
|
+
callStates.set(callId, state);
|
|
482
|
+
return state;
|
|
483
|
+
}
|
|
484
|
+
const integration = {
|
|
485
|
+
onStart: async (event) => {
|
|
486
|
+
const operationId = event.operationId;
|
|
487
|
+
if (operationId === "ai.embed" || operationId === "ai.embedMany" || operationId === "ai.rerank") {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const startEvent = event;
|
|
491
|
+
const parentInfo = resolveParentInfo();
|
|
492
|
+
const state = getOrCreateCallState(
|
|
493
|
+
startEvent.callId,
|
|
494
|
+
operationId,
|
|
495
|
+
startEvent
|
|
496
|
+
);
|
|
497
|
+
await createRun(state.runId, parentInfo, state.functionId);
|
|
498
|
+
},
|
|
499
|
+
onStepStart: async (event) => {
|
|
500
|
+
const stepStartEvent = event;
|
|
501
|
+
const state = callStates.get(stepStartEvent.callId);
|
|
502
|
+
if (!state) return;
|
|
503
|
+
const stepId = crypto.randomUUID();
|
|
504
|
+
const startTime = Date.now();
|
|
505
|
+
const stepState = {
|
|
506
|
+
stepId,
|
|
507
|
+
startTime
|
|
508
|
+
};
|
|
509
|
+
const stepNumber = stepStartEvent.steps.length;
|
|
510
|
+
state.stepStates.set(stepNumber, stepState);
|
|
511
|
+
activeSteps2.set(stepId, stepState);
|
|
512
|
+
const prompt = stepStartEvent.promptMessages ?? stepStartEvent.messages;
|
|
513
|
+
await createStep({
|
|
514
|
+
id: stepId,
|
|
515
|
+
run_id: state.runId,
|
|
516
|
+
step_number: stepNumber + 1,
|
|
517
|
+
type: state.operationType,
|
|
518
|
+
model_id: stepStartEvent.modelId,
|
|
519
|
+
provider: stepStartEvent.provider ?? null,
|
|
520
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
521
|
+
input: JSON.stringify({
|
|
522
|
+
prompt,
|
|
523
|
+
tools: stepStartEvent.tools ? Object.entries(stepStartEvent.tools).map(([name, tool]) => ({
|
|
524
|
+
name,
|
|
525
|
+
description: tool.description,
|
|
526
|
+
parameters: tool.parameters
|
|
527
|
+
})) : void 0,
|
|
528
|
+
toolChoice: stepStartEvent.toolChoice,
|
|
529
|
+
maxOutputTokens: state.settings.maxOutputTokens,
|
|
530
|
+
temperature: state.settings.temperature,
|
|
531
|
+
topP: state.settings.topP,
|
|
532
|
+
topK: state.settings.topK,
|
|
533
|
+
presencePenalty: state.settings.presencePenalty,
|
|
534
|
+
frequencyPenalty: state.settings.frequencyPenalty,
|
|
535
|
+
seed: state.settings.seed
|
|
536
|
+
}),
|
|
537
|
+
provider_options: stepStartEvent.providerOptions ? JSON.stringify(stepStartEvent.providerOptions) : null
|
|
538
|
+
});
|
|
539
|
+
},
|
|
540
|
+
onObjectStepStart: async (event) => {
|
|
541
|
+
const stepStartEvent = event;
|
|
542
|
+
const state = callStates.get(stepStartEvent.callId);
|
|
543
|
+
if (!state) return;
|
|
544
|
+
const stepId = crypto.randomUUID();
|
|
545
|
+
const startTime = Date.now();
|
|
546
|
+
const stepState = {
|
|
547
|
+
stepId,
|
|
548
|
+
startTime
|
|
549
|
+
};
|
|
550
|
+
state.stepStates.set(stepStartEvent.stepNumber, stepState);
|
|
551
|
+
activeSteps2.set(stepId, stepState);
|
|
552
|
+
await createStep({
|
|
553
|
+
id: stepId,
|
|
554
|
+
run_id: state.runId,
|
|
555
|
+
step_number: stepStartEvent.stepNumber + 1,
|
|
556
|
+
type: state.operationType,
|
|
557
|
+
model_id: stepStartEvent.modelId,
|
|
558
|
+
provider: stepStartEvent.provider ?? null,
|
|
559
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
560
|
+
input: JSON.stringify({
|
|
561
|
+
prompt: stepStartEvent.promptMessages,
|
|
562
|
+
maxOutputTokens: state.settings.maxOutputTokens,
|
|
563
|
+
temperature: state.settings.temperature,
|
|
564
|
+
topP: state.settings.topP,
|
|
565
|
+
topK: state.settings.topK,
|
|
566
|
+
presencePenalty: state.settings.presencePenalty,
|
|
567
|
+
frequencyPenalty: state.settings.frequencyPenalty,
|
|
568
|
+
seed: state.settings.seed
|
|
569
|
+
}),
|
|
570
|
+
provider_options: stepStartEvent.providerOptions ? JSON.stringify(stepStartEvent.providerOptions) : null
|
|
571
|
+
});
|
|
572
|
+
},
|
|
573
|
+
onStepEnd: async (event) => {
|
|
574
|
+
const stepResult = event;
|
|
575
|
+
const state = callStates.get(stepResult.callId);
|
|
576
|
+
if (!state) return;
|
|
577
|
+
const stepState = state.stepStates.get(stepResult.stepNumber);
|
|
578
|
+
if (!stepState) return;
|
|
579
|
+
activeSteps2.delete(stepState.stepId);
|
|
580
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
581
|
+
const output = {
|
|
582
|
+
content: stepResult.content,
|
|
583
|
+
finishReason: stepResult.finishReason,
|
|
584
|
+
response: {
|
|
585
|
+
id: stepResult.response.id,
|
|
586
|
+
modelId: stepResult.response.modelId,
|
|
587
|
+
timestamp: stepResult.response.timestamp,
|
|
588
|
+
messages: stepResult.response.messages
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
await updateStepResult(stepState.stepId, {
|
|
592
|
+
duration_ms: durationMs,
|
|
593
|
+
output: JSON.stringify(output),
|
|
594
|
+
usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
|
|
595
|
+
error: null,
|
|
596
|
+
raw_request: stepResult.request?.body ? JSON.stringify(stepResult.request.body) : null,
|
|
597
|
+
raw_response: stepResult.response?.body ? JSON.stringify(stepResult.response.body) : null,
|
|
598
|
+
raw_chunks: null
|
|
599
|
+
});
|
|
600
|
+
state.stepStates.delete(stepResult.stepNumber);
|
|
601
|
+
},
|
|
602
|
+
onObjectStepEnd: async (event) => {
|
|
603
|
+
const stepResult = event;
|
|
604
|
+
const state = callStates.get(stepResult.callId);
|
|
605
|
+
if (!state) return;
|
|
606
|
+
const stepState = state.stepStates.get(stepResult.stepNumber);
|
|
607
|
+
if (!stepState) return;
|
|
608
|
+
activeSteps2.delete(stepState.stepId);
|
|
609
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
610
|
+
const output = {
|
|
611
|
+
finishReason: stepResult.finishReason,
|
|
612
|
+
objectText: stepResult.objectText,
|
|
613
|
+
response: {
|
|
614
|
+
id: stepResult.response.id,
|
|
615
|
+
modelId: stepResult.response.modelId,
|
|
616
|
+
timestamp: stepResult.response.timestamp
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
await updateStepResult(stepState.stepId, {
|
|
620
|
+
duration_ms: durationMs,
|
|
621
|
+
output: JSON.stringify(output),
|
|
622
|
+
usage: stepResult.usage ? JSON.stringify(stepResult.usage) : null,
|
|
623
|
+
error: null,
|
|
624
|
+
raw_request: stepResult.request?.body ? JSON.stringify(stepResult.request.body) : null,
|
|
625
|
+
raw_response: stepResult.response?.body ? JSON.stringify(stepResult.response.body) : null
|
|
626
|
+
});
|
|
627
|
+
state.stepStates.delete(stepResult.stepNumber);
|
|
628
|
+
},
|
|
629
|
+
onEnd: async (event) => {
|
|
630
|
+
const endEvent = event;
|
|
631
|
+
callStates.delete(endEvent.callId);
|
|
632
|
+
},
|
|
633
|
+
onError: async (error) => {
|
|
634
|
+
const errorObj = error;
|
|
635
|
+
const callId = errorObj?.callId;
|
|
636
|
+
if (!callId) return;
|
|
637
|
+
const state = callStates.get(callId);
|
|
638
|
+
if (!state) return;
|
|
639
|
+
const cause = errorObj?.error ?? error;
|
|
640
|
+
const errorMessage = cause instanceof Error ? cause.message : String(cause);
|
|
641
|
+
for (const [, stepState] of state.stepStates) {
|
|
642
|
+
activeSteps2.delete(stepState.stepId);
|
|
643
|
+
const durationMs = Date.now() - stepState.startTime;
|
|
644
|
+
await updateStepResult(stepState.stepId, {
|
|
645
|
+
duration_ms: durationMs,
|
|
646
|
+
output: null,
|
|
647
|
+
usage: null,
|
|
648
|
+
error: errorMessage,
|
|
649
|
+
raw_request: null,
|
|
650
|
+
raw_response: null,
|
|
651
|
+
raw_chunks: null
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
callStates.delete(callId);
|
|
655
|
+
},
|
|
656
|
+
executeTool: async ({ callId, toolCallId, execute }) => {
|
|
657
|
+
toolContextMap.set(toolCallId, {
|
|
658
|
+
parentCallId: callId,
|
|
659
|
+
parentToolCallId: toolCallId
|
|
660
|
+
});
|
|
661
|
+
const previousToolCallId = currentToolCallId;
|
|
662
|
+
currentToolCallId = toolCallId;
|
|
663
|
+
try {
|
|
664
|
+
return await execute();
|
|
665
|
+
} finally {
|
|
666
|
+
currentToolCallId = previousToolCallId;
|
|
667
|
+
toolContextMap.delete(toolCallId);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
};
|
|
671
|
+
return integration;
|
|
672
|
+
}
|
|
382
673
|
export {
|
|
674
|
+
DevToolsTelemetry,
|
|
383
675
|
devToolsMiddleware
|
|
384
676
|
};
|
package/dist/viewer/server.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
3
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4
4
|
import { Hono } from "hono";
|
|
5
|
-
import { cors } from "hono/cors";
|
|
6
5
|
import { streamSSE } from "hono/streaming";
|
|
7
6
|
import path2 from "path";
|
|
8
7
|
import fs2 from "fs";
|
|
@@ -11,8 +10,11 @@ import { fileURLToPath } from "url";
|
|
|
11
10
|
// src/db.ts
|
|
12
11
|
import path from "path";
|
|
13
12
|
import fs from "fs";
|
|
14
|
-
var
|
|
15
|
-
var
|
|
13
|
+
var DEVTOOLS_DB_DIR = ".devtools";
|
|
14
|
+
var DEVTOOLS_DB_FILE = "generations.json";
|
|
15
|
+
var MAX_DB_BYTES = 100 * 1024 * 1024;
|
|
16
|
+
var DB_DIR = path.join(process.cwd(), DEVTOOLS_DB_DIR);
|
|
17
|
+
var DB_PATH = path.join(DB_DIR, DEVTOOLS_DB_FILE);
|
|
16
18
|
var DEVTOOLS_PORT = process.env.AI_SDK_DEVTOOLS_PORT ? parseInt(process.env.AI_SDK_DEVTOOLS_PORT) : 4983;
|
|
17
19
|
var notifyServer = (event) => {
|
|
18
20
|
notifyServerAsync(event);
|
|
@@ -22,7 +24,7 @@ var notifyServerAsync = async (event) => {
|
|
|
22
24
|
await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
|
|
23
25
|
method: "POST",
|
|
24
26
|
headers: { "Content-Type": "application/json" },
|
|
25
|
-
body: JSON.stringify({ event, timestamp: Date.now() })
|
|
27
|
+
body: JSON.stringify({ event, timestamp: Date.now(), dbPath: DB_PATH })
|
|
26
28
|
});
|
|
27
29
|
} catch {
|
|
28
30
|
}
|
|
@@ -74,7 +76,43 @@ var saveDb = (db) => {
|
|
|
74
76
|
dbCache = db;
|
|
75
77
|
writeDb(db);
|
|
76
78
|
};
|
|
77
|
-
var
|
|
79
|
+
var normalizeDevtoolsDbPath = (dbPath) => {
|
|
80
|
+
const resolvedPath = path.resolve(dbPath);
|
|
81
|
+
const dbDir = path.dirname(resolvedPath);
|
|
82
|
+
if (path.basename(resolvedPath) !== DEVTOOLS_DB_FILE || path.basename(dbDir) !== DEVTOOLS_DB_DIR) {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
return resolvedPath;
|
|
86
|
+
};
|
|
87
|
+
var validateRemoteDbPath = (dbPath) => {
|
|
88
|
+
if (typeof dbPath !== "string") {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
const normalizedPath = normalizeDevtoolsDbPath(dbPath);
|
|
92
|
+
if (!normalizedPath) {
|
|
93
|
+
return void 0;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const stats = fs.statSync(normalizedPath);
|
|
97
|
+
if (!stats.isFile() || stats.size > MAX_DB_BYTES) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
const realPath = fs.realpathSync(normalizedPath);
|
|
101
|
+
return normalizeDevtoolsDbPath(realPath);
|
|
102
|
+
} catch {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var reloadDb = async (remoteDbPath2) => {
|
|
107
|
+
const validatedRemoteDbPath = validateRemoteDbPath(remoteDbPath2);
|
|
108
|
+
if (validatedRemoteDbPath) {
|
|
109
|
+
try {
|
|
110
|
+
const content = fs.readFileSync(validatedRemoteDbPath, "utf-8");
|
|
111
|
+
dbCache = JSON.parse(content);
|
|
112
|
+
return;
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
78
116
|
dbCache = readDb();
|
|
79
117
|
};
|
|
80
118
|
var getRuns = async () => {
|
|
@@ -120,12 +158,39 @@ var devEnv = process.env.AI_SDK_DEVTOOLS_DEV;
|
|
|
120
158
|
var isDevMode = devEnv !== void 0 && devEnv !== "false" && devEnv !== "0";
|
|
121
159
|
var projectRoot = path2.resolve(__dirname, "../..");
|
|
122
160
|
var clientDir = path2.join(projectRoot, "dist/client");
|
|
161
|
+
var remoteDbPath;
|
|
162
|
+
var viewerPort = 4983;
|
|
123
163
|
var app = new Hono();
|
|
124
|
-
|
|
164
|
+
var getAllowedHosts = () => /* @__PURE__ */ new Set([
|
|
165
|
+
`localhost:${viewerPort}`,
|
|
166
|
+
`127.0.0.1:${viewerPort}`,
|
|
167
|
+
`[::1]:${viewerPort}`
|
|
168
|
+
]);
|
|
169
|
+
var getAllowedOrigins = () => /* @__PURE__ */ new Set([
|
|
170
|
+
`http://localhost:${viewerPort}`,
|
|
171
|
+
`http://127.0.0.1:${viewerPort}`,
|
|
172
|
+
`http://[::1]:${viewerPort}`,
|
|
173
|
+
"http://localhost:5173",
|
|
174
|
+
"http://127.0.0.1:5173",
|
|
175
|
+
"http://[::1]:5173"
|
|
176
|
+
]);
|
|
177
|
+
app.use("/api/*", async (c, next) => {
|
|
178
|
+
const host = c.req.header("host");
|
|
179
|
+
if (!host || !getAllowedHosts().has(host)) {
|
|
180
|
+
return c.text("Forbidden", 403);
|
|
181
|
+
}
|
|
182
|
+
const origin = c.req.header("origin");
|
|
183
|
+
if (origin && !getAllowedOrigins().has(origin)) {
|
|
184
|
+
return c.text("Forbidden", 403);
|
|
185
|
+
}
|
|
186
|
+
await next();
|
|
187
|
+
});
|
|
125
188
|
app.get("/api/runs", async (c) => {
|
|
189
|
+
await reloadDb(remoteDbPath);
|
|
126
190
|
const runs = await getRuns();
|
|
191
|
+
const rootRuns = runs.filter((r) => !r.parent_run_id);
|
|
127
192
|
const runsWithMeta = await Promise.all(
|
|
128
|
-
|
|
193
|
+
rootRuns.map(async (run) => {
|
|
129
194
|
const steps = await getStepsForRun(run.id);
|
|
130
195
|
let firstMessage = "No user message";
|
|
131
196
|
let hasError = false;
|
|
@@ -159,14 +224,35 @@ app.get("/api/runs", async (c) => {
|
|
|
159
224
|
return c.json(runsWithMeta);
|
|
160
225
|
});
|
|
161
226
|
app.get("/api/runs/:id", async (c) => {
|
|
227
|
+
await reloadDb(remoteDbPath);
|
|
162
228
|
const data = await getRunWithSteps(c.req.param("id"));
|
|
163
229
|
if (!data) {
|
|
164
230
|
return c.json({ error: "Run not found" }, 404);
|
|
165
231
|
}
|
|
166
232
|
const isInProgress = data.steps.some((s) => s.duration_ms === null && !s.error);
|
|
233
|
+
const allRuns = await getRuns();
|
|
234
|
+
async function getDescendantRuns(parentRunId) {
|
|
235
|
+
const children = allRuns.filter((r) => r.parent_run_id === parentRunId);
|
|
236
|
+
return Promise.all(
|
|
237
|
+
children.map(async (childRun) => {
|
|
238
|
+
const childSteps = await getStepsForRun(childRun.id);
|
|
239
|
+
const childIsInProgress = childSteps.some(
|
|
240
|
+
(s) => s.duration_ms === null && !s.error
|
|
241
|
+
);
|
|
242
|
+
const grandchildren = await getDescendantRuns(childRun.id);
|
|
243
|
+
return {
|
|
244
|
+
run: { ...childRun, isInProgress: childIsInProgress },
|
|
245
|
+
steps: childSteps,
|
|
246
|
+
childRuns: grandchildren
|
|
247
|
+
};
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
const childRuns = await getDescendantRuns(data.run.id);
|
|
167
252
|
return c.json({
|
|
168
253
|
run: { ...data.run, isInProgress },
|
|
169
|
-
steps: data.steps
|
|
254
|
+
steps: data.steps,
|
|
255
|
+
childRuns
|
|
170
256
|
});
|
|
171
257
|
});
|
|
172
258
|
app.post("/api/clear", async (c) => {
|
|
@@ -224,7 +310,14 @@ app.get("/api/events", (c) => {
|
|
|
224
310
|
});
|
|
225
311
|
app.post("/api/notify", async (c) => {
|
|
226
312
|
const body = await c.req.json();
|
|
227
|
-
|
|
313
|
+
if (body.dbPath !== void 0) {
|
|
314
|
+
const validatedDbPath = validateRemoteDbPath(body.dbPath);
|
|
315
|
+
if (!validatedDbPath) {
|
|
316
|
+
return c.text("Invalid dbPath", 400);
|
|
317
|
+
}
|
|
318
|
+
remoteDbPath = validatedDbPath;
|
|
319
|
+
}
|
|
320
|
+
await reloadDb(remoteDbPath);
|
|
228
321
|
broadcastToClients("update", body);
|
|
229
322
|
return c.json({ success: true });
|
|
230
323
|
});
|
|
@@ -269,10 +362,12 @@ app.get("*", async (c) => {
|
|
|
269
362
|
}
|
|
270
363
|
});
|
|
271
364
|
var startViewer = (port = 4983) => {
|
|
365
|
+
viewerPort = port;
|
|
272
366
|
const server = serve(
|
|
273
367
|
{
|
|
274
368
|
fetch: app.fetch,
|
|
275
|
-
port
|
|
369
|
+
port,
|
|
370
|
+
hostname: "localhost"
|
|
276
371
|
},
|
|
277
372
|
() => {
|
|
278
373
|
if (isDevMode) {
|
|
@@ -309,5 +404,6 @@ if (isDirectRun) {
|
|
|
309
404
|
startViewer(port);
|
|
310
405
|
}
|
|
311
406
|
export {
|
|
407
|
+
app,
|
|
312
408
|
startViewer
|
|
313
409
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/devtools",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.31",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -25,38 +25,60 @@
|
|
|
25
25
|
"bin"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@hono/node-server": "^1.
|
|
29
|
-
"hono": "^4.
|
|
30
|
-
"@ai-sdk/provider": "4.0.0-beta.
|
|
28
|
+
"@hono/node-server": "^1.19.14",
|
|
29
|
+
"hono": "^4.12.18",
|
|
30
|
+
"@ai-sdk/provider": "4.0.0-beta.19"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
34
34
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
35
35
|
"@radix-ui/react-slot": "^1.2.4",
|
|
36
36
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
37
|
-
"@tailwindcss/vite": "^4.
|
|
38
|
-
"@types/node": "
|
|
39
|
-
"@types/react": "^18",
|
|
40
|
-
"@types/react-dom": "^18",
|
|
41
|
-
"@vitejs/plugin-react": "^4.
|
|
37
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
38
|
+
"@types/node": "22.19.19",
|
|
39
|
+
"@types/react": "^18.3.28",
|
|
40
|
+
"@types/react-dom": "^18.3.7",
|
|
41
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
42
42
|
"class-variance-authority": "^0.7.1",
|
|
43
43
|
"clsx": "^2.1.1",
|
|
44
|
-
"concurrently": "^9.1
|
|
45
|
-
"dotenv": "^17.2
|
|
44
|
+
"concurrently": "^9.2.1",
|
|
45
|
+
"dotenv": "^17.4.2",
|
|
46
46
|
"lucide-react": "^0.556.0",
|
|
47
|
-
"react": "^
|
|
48
|
-
"react-dom": "^
|
|
49
|
-
"tailwind-merge": "^3.
|
|
50
|
-
"tailwindcss": "^4.
|
|
51
|
-
"tsup": "^8",
|
|
52
|
-
"tsx": "^4.
|
|
47
|
+
"react": "^19.2.6",
|
|
48
|
+
"react-dom": "^19.2.6",
|
|
49
|
+
"tailwind-merge": "^3.6.0",
|
|
50
|
+
"tailwindcss": "^4.3.0",
|
|
51
|
+
"tsup": "^8.5.1",
|
|
52
|
+
"tsx": "^4.22.0",
|
|
53
53
|
"tw-animate-css": "^1.4.0",
|
|
54
54
|
"typescript": "5.8.3",
|
|
55
55
|
"vaul": "^1.1.2",
|
|
56
|
-
"vite": "^6.
|
|
56
|
+
"vite": "^6.4.2",
|
|
57
|
+
"vitest": "^4.1.6",
|
|
57
58
|
"zod": "3.25.76",
|
|
58
|
-
"ai": "7.0.0-beta.
|
|
59
|
+
"ai": "7.0.0-beta.177"
|
|
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": ">=22"
|
|
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
|
}
|