@ai-sdk/devtools 1.0.25 ā 1.0.27
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/index.d.ts +15 -13
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +717 -739
- package/dist/index.js.map +1 -0
- package/dist/viewer/server.js +297 -352
- package/package.json +5 -5
- package/src/integration.ts +3 -1
package/dist/viewer/server.js
CHANGED
|
@@ -1,335 +1,302 @@
|
|
|
1
|
-
// src/viewer/server.ts
|
|
2
1
|
import { serve } from "@hono/node-server";
|
|
3
2
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4
3
|
import { Hono } from "hono";
|
|
5
4
|
import { streamSSE } from "hono/streaming";
|
|
6
|
-
import path2 from "path";
|
|
7
|
-
import fs2 from "fs";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
|
-
|
|
10
|
-
// src/db.ts
|
|
11
5
|
import path from "path";
|
|
12
6
|
import fs from "fs";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import path$1 from "node:path";
|
|
9
|
+
import fs$1 from "node:fs";
|
|
10
|
+
//#region src/db.ts
|
|
11
|
+
const DEVTOOLS_DB_DIR = ".devtools";
|
|
12
|
+
const DEVTOOLS_DB_FILE = "generations.json";
|
|
13
|
+
const MAX_DB_BYTES = 104857600;
|
|
14
|
+
const DB_DIR = path$1.join(process.cwd(), DEVTOOLS_DB_DIR);
|
|
15
|
+
const DB_PATH = path$1.join(DB_DIR, DEVTOOLS_DB_FILE);
|
|
16
|
+
const DEVTOOLS_PORT = process.env.AI_SDK_DEVTOOLS_PORT ? parseInt(process.env.AI_SDK_DEVTOOLS_PORT) : 4983;
|
|
17
|
+
/**
|
|
18
|
+
* Notify the devtools server that data has changed.
|
|
19
|
+
* Fire-and-forget: doesn't block, ignores errors if server isn't running.
|
|
20
|
+
*/
|
|
21
|
+
const notifyServer = (event) => {
|
|
22
|
+
notifyServerAsync(event);
|
|
21
23
|
};
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Notify the devtools server and wait for the request to complete.
|
|
26
|
+
* Used during process cleanup to ensure notifications are sent before exit.
|
|
27
|
+
*/
|
|
28
|
+
const notifyServerAsync = async (event) => {
|
|
29
|
+
try {
|
|
30
|
+
await fetch(`http://localhost:${DEVTOOLS_PORT}/api/notify`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
event,
|
|
35
|
+
timestamp: Date.now(),
|
|
36
|
+
dbPath: DB_PATH
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
} catch {}
|
|
31
40
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
` : `${content}
|
|
45
|
-
.devtools
|
|
46
|
-
`;
|
|
47
|
-
fs.writeFileSync(gitignorePath, newContent);
|
|
48
|
-
}
|
|
41
|
+
/**
|
|
42
|
+
* Ensure .devtools is in .gitignore.
|
|
43
|
+
* Only writes if .gitignore exists and doesn't already contain .devtools.
|
|
44
|
+
*/
|
|
45
|
+
const ensureGitignore = () => {
|
|
46
|
+
const gitignorePath = path$1.join(process.cwd(), ".gitignore");
|
|
47
|
+
if (!fs$1.existsSync(gitignorePath)) return;
|
|
48
|
+
const content = fs$1.readFileSync(gitignorePath, "utf-8");
|
|
49
|
+
if (!content.split("\n").some((line) => line.trim() === ".devtools" || line.trim() === ".devtools/")) {
|
|
50
|
+
const newContent = content.endsWith("\n") ? `${content}.devtools\n` : `${content}\n.devtools\n`;
|
|
51
|
+
fs$1.writeFileSync(gitignorePath, newContent);
|
|
52
|
+
}
|
|
49
53
|
};
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const readDb = () => {
|
|
55
|
+
try {
|
|
56
|
+
if (fs$1.existsSync(DB_PATH)) {
|
|
57
|
+
const content = fs$1.readFileSync(DB_PATH, "utf-8");
|
|
58
|
+
return JSON.parse(content);
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
return {
|
|
62
|
+
runs: [],
|
|
63
|
+
steps: []
|
|
64
|
+
};
|
|
59
65
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
|
|
66
|
+
const writeDb = (db) => {
|
|
67
|
+
if (!fs$1.existsSync(DB_DIR)) {
|
|
68
|
+
fs$1.mkdirSync(DB_DIR, { recursive: true });
|
|
69
|
+
ensureGitignore();
|
|
70
|
+
}
|
|
71
|
+
fs$1.writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
|
|
67
72
|
};
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
return dbCache;
|
|
73
|
+
let dbCache = null;
|
|
74
|
+
const getDb = () => {
|
|
75
|
+
if (!dbCache) dbCache = readDb();
|
|
76
|
+
return dbCache;
|
|
74
77
|
};
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
const saveDb = (db) => {
|
|
79
|
+
dbCache = db;
|
|
80
|
+
writeDb(db);
|
|
78
81
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
return resolvedPath;
|
|
82
|
+
const normalizeDevtoolsDbPath = (dbPath) => {
|
|
83
|
+
const resolvedPath = path$1.resolve(dbPath);
|
|
84
|
+
const dbDir = path$1.dirname(resolvedPath);
|
|
85
|
+
if (path$1.basename(resolvedPath) !== DEVTOOLS_DB_FILE || path$1.basename(dbDir) !== DEVTOOLS_DB_DIR) return;
|
|
86
|
+
return resolvedPath;
|
|
86
87
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
const realPath = fs.realpathSync(normalizedPath);
|
|
101
|
-
return normalizeDevtoolsDbPath(realPath);
|
|
102
|
-
} catch {
|
|
103
|
-
return void 0;
|
|
104
|
-
}
|
|
88
|
+
const validateRemoteDbPath = (dbPath) => {
|
|
89
|
+
if (typeof dbPath !== "string") return;
|
|
90
|
+
const normalizedPath = normalizeDevtoolsDbPath(dbPath);
|
|
91
|
+
if (!normalizedPath) return;
|
|
92
|
+
try {
|
|
93
|
+
const stats = fs$1.statSync(normalizedPath);
|
|
94
|
+
if (!stats.isFile() || stats.size > MAX_DB_BYTES) return;
|
|
95
|
+
const realPath = fs$1.realpathSync(normalizedPath);
|
|
96
|
+
return normalizeDevtoolsDbPath(realPath);
|
|
97
|
+
} catch {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
105
100
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Reload the database from disk.
|
|
103
|
+
* Used by the viewer server to pick up changes made by the middleware/integration.
|
|
104
|
+
* When a valid remote dbPath is provided (from a notify POST), reads from that
|
|
105
|
+
* path instead of the local CWD-based path, so the viewer works regardless of
|
|
106
|
+
* where it was started.
|
|
107
|
+
*/
|
|
108
|
+
const reloadDb = async (remoteDbPath) => {
|
|
109
|
+
const validatedRemoteDbPath = validateRemoteDbPath(remoteDbPath);
|
|
110
|
+
if (validatedRemoteDbPath) try {
|
|
111
|
+
const content = fs$1.readFileSync(validatedRemoteDbPath, "utf-8");
|
|
112
|
+
dbCache = JSON.parse(content);
|
|
113
|
+
return;
|
|
114
|
+
} catch {}
|
|
115
|
+
dbCache = readDb();
|
|
117
116
|
};
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return [...db.runs].sort(
|
|
121
|
-
(a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime()
|
|
122
|
-
);
|
|
117
|
+
const getRuns = async () => {
|
|
118
|
+
return [...getDb().runs].sort((a, b) => new Date(b.started_at).getTime() - new Date(a.started_at).getTime());
|
|
123
119
|
};
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return db.steps.filter((s) => s.run_id === runId).sort((a, b) => a.step_number - b.step_number);
|
|
120
|
+
const getStepsForRun = async (runId) => {
|
|
121
|
+
return getDb().steps.filter((s) => s.run_id === runId).sort((a, b) => a.step_number - b.step_number);
|
|
127
122
|
};
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
123
|
+
const getRunWithSteps = async (runId) => {
|
|
124
|
+
const run = getDb().runs.find((r) => r.id === runId);
|
|
125
|
+
if (!run) return null;
|
|
126
|
+
return {
|
|
127
|
+
run,
|
|
128
|
+
steps: await getStepsForRun(runId)
|
|
129
|
+
};
|
|
134
130
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
const clearDatabase = async () => {
|
|
132
|
+
saveDb({
|
|
133
|
+
runs: [],
|
|
134
|
+
steps: []
|
|
135
|
+
});
|
|
136
|
+
notifyServer("clear");
|
|
139
137
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
client.controller.enqueue(message);
|
|
151
|
-
} catch {
|
|
152
|
-
sseClients.delete(client);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/viewer/server.ts
|
|
140
|
+
const sseClients = /* @__PURE__ */ new Set();
|
|
141
|
+
const broadcastToClients = (event, data) => {
|
|
142
|
+
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
143
|
+
for (const client of sseClients) try {
|
|
144
|
+
client.controller.enqueue(message);
|
|
145
|
+
} catch {
|
|
146
|
+
sseClients.delete(client);
|
|
147
|
+
}
|
|
155
148
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
149
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
150
|
+
const devEnv = process.env.AI_SDK_DEVTOOLS_DEV;
|
|
151
|
+
const isDevMode = devEnv !== void 0 && devEnv !== "false" && devEnv !== "0";
|
|
152
|
+
const projectRoot = path.resolve(__dirname, "../..");
|
|
153
|
+
const clientDir = path.join(projectRoot, "dist/client");
|
|
154
|
+
let remoteDbPath;
|
|
155
|
+
let viewerPort = 4983;
|
|
156
|
+
const app = new Hono();
|
|
157
|
+
const getAllowedHosts = () => /* @__PURE__ */ new Set([
|
|
158
|
+
`localhost:${viewerPort}`,
|
|
159
|
+
`127.0.0.1:${viewerPort}`,
|
|
160
|
+
`[::1]:${viewerPort}`
|
|
168
161
|
]);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
162
|
+
const getAllowedOrigins = () => /* @__PURE__ */ new Set([
|
|
163
|
+
`http://localhost:${viewerPort}`,
|
|
164
|
+
`http://127.0.0.1:${viewerPort}`,
|
|
165
|
+
`http://[::1]:${viewerPort}`,
|
|
166
|
+
"http://localhost:5173",
|
|
167
|
+
"http://127.0.0.1:5173",
|
|
168
|
+
"http://[::1]:5173"
|
|
176
169
|
]);
|
|
177
170
|
app.use("/api/*", async (c, next) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (origin && !getAllowedOrigins().has(origin)) {
|
|
184
|
-
return c.text("Forbidden", 403);
|
|
185
|
-
}
|
|
186
|
-
await next();
|
|
171
|
+
const host = c.req.header("host");
|
|
172
|
+
if (!host || !getAllowedHosts().has(host)) return c.text("Forbidden", 403);
|
|
173
|
+
const origin = c.req.header("origin");
|
|
174
|
+
if (origin && !getAllowedOrigins().has(origin)) return c.text("Forbidden", 403);
|
|
175
|
+
await next();
|
|
187
176
|
});
|
|
188
177
|
app.get("/api/runs", async (c) => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
hasError,
|
|
219
|
-
isInProgress,
|
|
220
|
-
type: firstStep?.type
|
|
221
|
-
};
|
|
222
|
-
})
|
|
223
|
-
);
|
|
224
|
-
return c.json(runsWithMeta);
|
|
178
|
+
await reloadDb(remoteDbPath);
|
|
179
|
+
const rootRuns = (await getRuns()).filter((r) => !r.parent_run_id);
|
|
180
|
+
const runsWithMeta = await Promise.all(rootRuns.map(async (run) => {
|
|
181
|
+
const steps = await getStepsForRun(run.id);
|
|
182
|
+
let firstMessage = "No user message";
|
|
183
|
+
let hasError = false;
|
|
184
|
+
let isInProgress = false;
|
|
185
|
+
const firstStep = steps[0];
|
|
186
|
+
if (firstStep) {
|
|
187
|
+
try {
|
|
188
|
+
const userMsg = JSON.parse(firstStep.input)?.prompt?.findLast((m) => m.role === "user");
|
|
189
|
+
if (userMsg) {
|
|
190
|
+
const content = typeof userMsg.content === "string" ? userMsg.content : userMsg.content?.[0]?.text || "";
|
|
191
|
+
firstMessage = content.slice(0, 60) + (content.length > 60 ? "..." : "");
|
|
192
|
+
}
|
|
193
|
+
} catch {}
|
|
194
|
+
hasError = steps.some((s) => s.error);
|
|
195
|
+
isInProgress = steps.some((s) => s.duration_ms === null && !s.error);
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
...run,
|
|
199
|
+
stepCount: steps.length,
|
|
200
|
+
firstMessage,
|
|
201
|
+
hasError,
|
|
202
|
+
isInProgress,
|
|
203
|
+
type: firstStep?.type
|
|
204
|
+
};
|
|
205
|
+
}));
|
|
206
|
+
return c.json(runsWithMeta);
|
|
225
207
|
});
|
|
226
208
|
app.get("/api/runs/:id", async (c) => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
209
|
+
await reloadDb(remoteDbPath);
|
|
210
|
+
const data = await getRunWithSteps(c.req.param("id"));
|
|
211
|
+
if (!data) return c.json({ error: "Run not found" }, 404);
|
|
212
|
+
const isInProgress = data.steps.some((s) => s.duration_ms === null && !s.error);
|
|
213
|
+
const allRuns = await getRuns();
|
|
214
|
+
async function getDescendantRuns(parentRunId) {
|
|
215
|
+
const children = allRuns.filter((r) => r.parent_run_id === parentRunId);
|
|
216
|
+
return Promise.all(children.map(async (childRun) => {
|
|
217
|
+
const childSteps = await getStepsForRun(childRun.id);
|
|
218
|
+
const childIsInProgress = childSteps.some((s) => s.duration_ms === null && !s.error);
|
|
219
|
+
const grandchildren = await getDescendantRuns(childRun.id);
|
|
220
|
+
return {
|
|
221
|
+
run: {
|
|
222
|
+
...childRun,
|
|
223
|
+
isInProgress: childIsInProgress
|
|
224
|
+
},
|
|
225
|
+
steps: childSteps,
|
|
226
|
+
childRuns: grandchildren
|
|
227
|
+
};
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
const childRuns = await getDescendantRuns(data.run.id);
|
|
231
|
+
return c.json({
|
|
232
|
+
run: {
|
|
233
|
+
...data.run,
|
|
234
|
+
isInProgress
|
|
235
|
+
},
|
|
236
|
+
steps: data.steps,
|
|
237
|
+
childRuns
|
|
238
|
+
});
|
|
257
239
|
});
|
|
258
240
|
app.post("/api/clear", async (c) => {
|
|
259
|
-
|
|
260
|
-
|
|
241
|
+
await clearDatabase();
|
|
242
|
+
return c.json({ success: true });
|
|
261
243
|
});
|
|
262
244
|
app.get("/api/events", (c) => {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
await stream.sleep(1e3);
|
|
304
|
-
}
|
|
305
|
-
} finally {
|
|
306
|
-
clearInterval(heartbeat);
|
|
307
|
-
sseClients.delete(client);
|
|
308
|
-
}
|
|
309
|
-
});
|
|
245
|
+
return streamSSE(c, async (stream) => {
|
|
246
|
+
const clientId = crypto.randomUUID();
|
|
247
|
+
const client = {
|
|
248
|
+
id: clientId,
|
|
249
|
+
controller: null
|
|
250
|
+
};
|
|
251
|
+
await stream.writeSSE({
|
|
252
|
+
event: "connected",
|
|
253
|
+
data: JSON.stringify({ clientId })
|
|
254
|
+
});
|
|
255
|
+
const originalWrite = stream.writeSSE.bind(stream);
|
|
256
|
+
client.controller = { enqueue: (message) => {
|
|
257
|
+
const lines = message.split("\n");
|
|
258
|
+
let event = "message";
|
|
259
|
+
let data = "";
|
|
260
|
+
for (const line of lines) if (line.startsWith("event: ")) event = line.slice(7);
|
|
261
|
+
else if (line.startsWith("data: ")) data = line.slice(6);
|
|
262
|
+
originalWrite({
|
|
263
|
+
event,
|
|
264
|
+
data
|
|
265
|
+
}).catch(() => {});
|
|
266
|
+
} };
|
|
267
|
+
sseClients.add(client);
|
|
268
|
+
const heartbeat = setInterval(async () => {
|
|
269
|
+
try {
|
|
270
|
+
await stream.writeSSE({
|
|
271
|
+
event: "heartbeat",
|
|
272
|
+
data: JSON.stringify({ time: Date.now() })
|
|
273
|
+
});
|
|
274
|
+
} catch {
|
|
275
|
+
clearInterval(heartbeat);
|
|
276
|
+
}
|
|
277
|
+
}, 3e4);
|
|
278
|
+
try {
|
|
279
|
+
while (true) await stream.sleep(1e3);
|
|
280
|
+
} finally {
|
|
281
|
+
clearInterval(heartbeat);
|
|
282
|
+
sseClients.delete(client);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
310
285
|
});
|
|
311
286
|
app.post("/api/notify", async (c) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
broadcastToClients("update", body);
|
|
322
|
-
return c.json({ success: true });
|
|
287
|
+
const body = await c.req.json();
|
|
288
|
+
if (body.dbPath !== void 0) {
|
|
289
|
+
const validatedDbPath = validateRemoteDbPath(body.dbPath);
|
|
290
|
+
if (!validatedDbPath) return c.text("Invalid dbPath", 400);
|
|
291
|
+
remoteDbPath = validatedDbPath;
|
|
292
|
+
}
|
|
293
|
+
await reloadDb(remoteDbPath);
|
|
294
|
+
broadcastToClients("update", body);
|
|
295
|
+
return c.json({ success: true });
|
|
323
296
|
});
|
|
324
|
-
app.use(
|
|
325
|
-
"/assets/*",
|
|
326
|
-
serveStatic({
|
|
327
|
-
root: clientDir.replace(/\/+$/, "")
|
|
328
|
-
})
|
|
329
|
-
);
|
|
297
|
+
app.use("/assets/*", serveStatic({ root: clientDir.replace(/\/+$/, "") }));
|
|
330
298
|
app.get("*", async (c) => {
|
|
331
|
-
|
|
332
|
-
return c.html(`
|
|
299
|
+
if (isDevMode) return c.html(`
|
|
333
300
|
<!DOCTYPE html>
|
|
334
301
|
<html>
|
|
335
302
|
<head>
|
|
@@ -346,66 +313,44 @@ app.get("*", async (c) => {
|
|
|
346
313
|
<body>
|
|
347
314
|
<div class="container">
|
|
348
315
|
<h2>Development Mode</h2>
|
|
349
|
-
<a href="http://localhost:5173">Open DevTools UI
|
|
316
|
+
<a href="http://localhost:5173">Open DevTools UI ā</a>
|
|
350
317
|
<p>This port (4983) only serves the API in dev mode.</p>
|
|
351
318
|
</div>
|
|
352
319
|
</body>
|
|
353
320
|
</html>
|
|
354
321
|
`);
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
322
|
+
const indexPath = path.join(clientDir, "index.html");
|
|
323
|
+
try {
|
|
324
|
+
const html = fs.readFileSync(indexPath, "utf-8");
|
|
325
|
+
return c.html(html);
|
|
326
|
+
} catch {
|
|
327
|
+
return c.text("DevTools client not built. Run `pnpm build` first.", 500);
|
|
328
|
+
}
|
|
363
329
|
});
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
`
|
|
387
|
-
This likely means AI SDK DevTools is already running.`
|
|
388
|
-
);
|
|
389
|
-
console.error(` Open http://localhost:${port} in your browser.
|
|
390
|
-
`);
|
|
391
|
-
console.error(` To use a different port, set AI_SDK_DEVTOOLS_PORT:
|
|
392
|
-
`);
|
|
393
|
-
console.error(
|
|
394
|
-
` AI_SDK_DEVTOOLS_PORT=4984 npx @ai-sdk/devtools@latest
|
|
395
|
-
`
|
|
396
|
-
);
|
|
397
|
-
process.exit(1);
|
|
398
|
-
}
|
|
399
|
-
throw err;
|
|
400
|
-
});
|
|
401
|
-
};
|
|
402
|
-
var currentFile = fileURLToPath(import.meta.url);
|
|
403
|
-
var isDirectRun = process.argv[1] === currentFile || process.argv[1]?.endsWith("/server.ts") || process.argv[1]?.endsWith("/server.js");
|
|
404
|
-
if (isDirectRun) {
|
|
405
|
-
const port = process.env.AI_SDK_DEVTOOLS_PORT ? parseInt(process.env.AI_SDK_DEVTOOLS_PORT) : 4983;
|
|
406
|
-
startViewer(port);
|
|
407
|
-
}
|
|
408
|
-
export {
|
|
409
|
-
app,
|
|
410
|
-
startViewer
|
|
330
|
+
const startViewer = (port = 4983) => {
|
|
331
|
+
viewerPort = port;
|
|
332
|
+
serve({
|
|
333
|
+
fetch: app.fetch,
|
|
334
|
+
port,
|
|
335
|
+
hostname: "localhost"
|
|
336
|
+
}, () => {
|
|
337
|
+
if (isDevMode) {
|
|
338
|
+
console.log(`š AI SDK DevTools API running on port ${port}`);
|
|
339
|
+
console.log(` Open http://localhost:5173 for the dev UI`);
|
|
340
|
+
} else console.log(`š AI SDK DevTools running at http://localhost:${port}`);
|
|
341
|
+
}).on("error", (err) => {
|
|
342
|
+
if (err.code === "EADDRINUSE") {
|
|
343
|
+
console.error(`\nā Port ${port} is already in use.`);
|
|
344
|
+
console.error(`\n This likely means AI SDK DevTools is already running.`);
|
|
345
|
+
console.error(` Open http://localhost:${port} in your browser.\n`);
|
|
346
|
+
console.error(` To use a different port, set AI_SDK_DEVTOOLS_PORT:\n`);
|
|
347
|
+
console.error(` AI_SDK_DEVTOOLS_PORT=4984 npx @ai-sdk/devtools@latest\n`);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
throw err;
|
|
351
|
+
});
|
|
411
352
|
};
|
|
353
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
354
|
+
if (process.argv[1] === currentFile || process.argv[1]?.endsWith("/server.ts") || process.argv[1]?.endsWith("/server.js")) startViewer(process.env.AI_SDK_DEVTOOLS_PORT ? parseInt(process.env.AI_SDK_DEVTOOLS_PORT) : 4983);
|
|
355
|
+
//#endregion
|
|
356
|
+
export { app, startViewer };
|