@andreprado/agentkit 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -77
  2. package/docs/guides/add-channel.md +14 -92
  3. package/docs/guides/add-knowledge.md +0 -21
  4. package/docs/guides/add-tool.md +5 -11
  5. package/docs/guides/channel-security.md +3 -207
  6. package/docs/guides/connect-discord.md +7 -172
  7. package/docs/guides/connect-slack.md +6 -121
  8. package/docs/guides/connect-telegram.md +6 -165
  9. package/docs/guides/connect-whatsapp-evolution.md +6 -116
  10. package/docs/guides/connect-whatsapp-uazapi.md +6 -134
  11. package/docs/guides/connect-whatsapp-zapster.md +6 -202
  12. package/docs/guides/create-agent.md +5 -14
  13. package/docs/guides/debug-channel.md +4 -156
  14. package/docs/guides/improve-local.md +13 -0
  15. package/docs/guides/local-only-migration.md +35 -0
  16. package/docs/guides/replay-local-traces.md +11 -0
  17. package/docs/guides/run-evals.md +2 -4
  18. package/docs/guides/security-rules.md +5 -154
  19. package/docs/guides/use-jev.md +3 -6
  20. package/docs/guides/use-provider.md +5 -6
  21. package/docs/guides/write-feedback.md +10 -0
  22. package/docs/llms-full.txt +27 -448
  23. package/docs/llms.txt +8 -44
  24. package/package.json +3 -5
  25. package/src/cli/commands/channels.ts +8 -1613
  26. package/src/cli/commands/feedback.ts +8 -86
  27. package/src/cli/commands/provider.ts +29 -11
  28. package/src/cli/constants.ts +0 -3
  29. package/src/cli/flags.ts +0 -28
  30. package/src/cli/help.ts +16 -92
  31. package/src/cli/index.ts +15 -1091
  32. package/src/index.ts +6 -158
  33. package/src/providers/codex-auth.ts +16 -2
  34. package/src/providers/pi.ts +36 -19
  35. package/src/runtime/channels/discord.ts +2 -2
  36. package/src/runtime/chat.ts +5 -3
  37. package/src/runtime/config.ts +14 -148
  38. package/src/runtime/database.ts +2 -2
  39. package/src/runtime/dev-server.ts +8 -8
  40. package/src/runtime/env.ts +11 -0
  41. package/src/runtime/improve.ts +2 -262
  42. package/src/runtime/inspect.ts +13 -73
  43. package/src/runtime/knowledge/ingest.ts +1 -1
  44. package/src/runtime/knowledge/tool.ts +16 -2
  45. package/src/runtime/knowledge/vector.ts +1 -1
  46. package/src/runtime/tool-runner.ts +5 -3
  47. package/src/runtime/tools.ts +10 -14
  48. package/src/storage/sqlite.ts +11 -32
  49. package/src/templates/blank.ts +15 -102
  50. package/src/templates/common.ts +60 -0
  51. package/src/templates/dentista.ts +7 -74
  52. package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
  53. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
  54. package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
  55. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
  56. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
  57. package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
  58. package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
  59. package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
  60. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
  61. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
  62. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
  63. package/src/templates/skills/agentkit-database/SKILL.md +2 -4
  64. package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
  65. package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
  66. package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
  67. package/src/templates/skills/agentkit-provider/SKILL.md +1 -2
  68. package/src/templates/skills/agentkit-security/SKILL.md +1 -3
  69. package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
  70. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
  71. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
  72. package/src/templates/support.ts +8 -92
  73. package/docs/guides/add-managed-composio.md +0 -165
  74. package/docs/guides/improve-from-production.md +0 -151
  75. package/docs/guides/prepare-deploy.md +0 -227
  76. package/docs/guides/replay-production-traces.md +0 -72
  77. package/docs/guides/send-feedback.md +0 -135
  78. package/src/cli/cloud-client.ts +0 -377
  79. package/src/cli/deploy-chat-ui.ts +0 -606
  80. package/src/cli/deploy-readiness.ts +0 -561
  81. package/src/cloud/artifact.ts +0 -139
  82. package/src/cloud/client.ts +0 -80
  83. package/src/cloud/contracts.ts +0 -63
  84. package/src/cloud/index.ts +0 -3
  85. package/src/runtime/build.ts +0 -43
  86. package/src/runtime/core/deploy-state.ts +0 -54
  87. package/src/runtime/core/manifest.ts +0 -283
  88. package/src/runtime/core/targets.ts +0 -133
  89. package/src/runtime/deploy-readiness.ts +0 -135
  90. package/src/runtime/deploy.ts +0 -1
  91. package/src/runtime/integrations/composio.ts +0 -425
  92. package/src/runtime/targets/cloudflare/build.ts +0 -3319
  93. package/src/runtime/targets/container/build.ts +0 -146
  94. package/src/runtime/targets/container/server.ts +0 -33
  95. package/src/runtime/targets/vps/deploy.ts +0 -223
  96. package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
  97. package/src/templates/skills/agentkit-integrations/SKILL.md +0 -98
@@ -1,606 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
3
- import { join, relative, resolve } from "node:path";
4
-
5
- import { findAgentCapsuleRoot } from "../runtime/config";
6
- import { readLocalDeployStateIfExists } from "../runtime/core/deploy-state";
7
- import { AgentKitError, isAgentKitError } from "../runtime/errors";
8
- import { defaultDeployChatAccessTokenName, defaultDeployChatAccessTokenPath } from "./constants";
9
-
10
- export async function startDeployChatUiServer(
11
- cwd: string,
12
- options: { port: number; tokenFile?: string; autoPort?: boolean },
13
- ): Promise<{
14
- url: string;
15
- deployUrl: string;
16
- tokenSource: string;
17
- port: number;
18
- portConflict: { port: number } | null;
19
- stop(): Promise<void>;
20
- }> {
21
- const root = await findAgentCapsuleRoot(cwd);
22
- const state = await readLocalDeployStateIfExists(root);
23
-
24
- if (!state?.url) {
25
- throw new Error("No hosted deploy URL found. Run agentkit deploy first.");
26
- }
27
-
28
- const tokenPath = options.tokenFile
29
- ? resolve(root, options.tokenFile)
30
- : join(root, defaultDeployChatAccessTokenPath);
31
- const hostname = "localhost";
32
- const deployUrl = state.url;
33
- const start = await listenForDeployChatUiServer({
34
- hostname,
35
- requestedPort: options.port,
36
- autoPort: options.autoPort === true,
37
- create: () => createDeployChatUiHttpServer({ root, tokenPath, hostname, requestedPort: options.port, deployUrl }),
38
- });
39
-
40
- return {
41
- url: `http://${hostname}:${start.port}`,
42
- deployUrl,
43
- tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
44
- port: start.port,
45
- portConflict: start.portConflict,
46
- stop: () =>
47
- new Promise<void>((resolvePromise, reject) => {
48
- start.server.close((error) => (error ? reject(error) : resolvePromise()));
49
- }),
50
- };
51
- }
52
-
53
- function createDeployChatUiHttpServer(input: {
54
- root: string;
55
- tokenPath: string;
56
- hostname: string;
57
- requestedPort: number;
58
- deployUrl: string;
59
- }): Server {
60
- const { root, tokenPath, hostname, requestedPort, deployUrl } = input;
61
- const server = createServer(async (request, response) => {
62
- try {
63
- const requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? `${hostname}:${requestedPort}`}`);
64
-
65
- if (request.method === "GET" && requestUrl.pathname === "/") {
66
- sendHtml(response, renderDeployChatUi({ deployUrl }));
67
- return;
68
- }
69
-
70
- if (request.method === "GET" && requestUrl.pathname === "/health") {
71
- sendJson(response, 200, { status: "ok", target: "deploy-chat-ui", deployUrl });
72
- return;
73
- }
74
-
75
- if (request.method === "GET" && requestUrl.pathname === "/_agentkit") {
76
- sendJson(response, 200, {
77
- deployUrl,
78
- access: "proxied",
79
- tokenSource: process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN ? "AGENTKIT_DEPLOY_ACCESS_TOKEN" : relativePath(root, tokenPath),
80
- });
81
- return;
82
- }
83
-
84
- if (request.method === "POST" && (requestUrl.pathname === "/v1/chat" || requestUrl.pathname === "/chat")) {
85
- const token = await readDeployAccessTokenForUi(tokenPath);
86
- const body = await readIncomingBody(request);
87
- const contentTypeHeader = request.headers["content-type"];
88
- const contentType = Array.isArray(contentTypeHeader)
89
- ? contentTypeHeader[0] ?? "application/json"
90
- : contentTypeHeader ?? "application/json";
91
- const upstream = await fetch(new URL("/v1/chat", deployUrl).href, {
92
- method: "POST",
93
- headers: {
94
- "Authorization": `Bearer ${token}`,
95
- "Content-Type": contentType,
96
- },
97
- body,
98
- });
99
- const text = await upstream.text();
100
-
101
- response.writeHead(upstream.status, {
102
- "content-type": upstream.headers.get("content-type") ?? "application/json",
103
- });
104
- response.end(text);
105
- return;
106
- }
107
-
108
- if (
109
- request.method === "GET" &&
110
- (requestUrl.pathname === "/v1/conversations" ||
111
- /^\/v1\/conversations\/[^/]+(?:\/trace)?$/.test(requestUrl.pathname))
112
- ) {
113
- const token = await readDeployAccessTokenForUi(tokenPath);
114
- const upstream = await fetch(new URL(requestUrl.pathname, deployUrl).href, {
115
- method: "GET",
116
- headers: {
117
- Authorization: `Bearer ${token}`,
118
- },
119
- });
120
- const text = await upstream.text();
121
-
122
- response.writeHead(upstream.status, {
123
- "content-type": upstream.headers.get("content-type") ?? "application/json",
124
- });
125
- response.end(text);
126
- return;
127
- }
128
-
129
- sendJson(response, 404, { error: { code: "not_found", message: "Route not found." } });
130
- } catch (error) {
131
- sendJson(response, 500, {
132
- error: {
133
- code: isAgentKitError(error) ? error.code : "deploy_chat_ui_error",
134
- message: error instanceof Error ? error.message : String(error),
135
- },
136
- });
137
- }
138
- });
139
-
140
- return server;
141
- }
142
-
143
- async function listenForDeployChatUiServer(input: {
144
- hostname: string;
145
- requestedPort: number;
146
- autoPort: boolean;
147
- create: () => Server;
148
- }): Promise<{ server: Server; port: number; portConflict: { port: number } | null }> {
149
- let firstConflict: { port: number } | null = null;
150
- const lastPort = input.autoPort ? input.requestedPort + 50 : input.requestedPort;
151
-
152
- for (let port = input.requestedPort; port <= lastPort; port += 1) {
153
- const server = input.create();
154
-
155
- try {
156
- await listen(server, input.hostname, port);
157
- return { server, port, portConflict: port === input.requestedPort ? null : firstConflict };
158
- } catch (error) {
159
- await closeServerAfterListenError(server);
160
-
161
- if (!input.autoPort || !isAddressInUseError(error)) {
162
- throw error;
163
- }
164
-
165
- firstConflict ??= { port };
166
- }
167
- }
168
-
169
- throw new AgentKitError(
170
- "runtime_error",
171
- `Could not allocate a local AgentKit hosted chat UI port after ${input.requestedPort}-${lastPort}.`,
172
- );
173
- }
174
-
175
- function listen(server: Server, hostname: string, port: number): Promise<void> {
176
- return new Promise((resolvePromise, reject) => {
177
- server.once("error", reject);
178
- server.listen(port, hostname, () => {
179
- server.off("error", reject);
180
- resolvePromise();
181
- });
182
- });
183
- }
184
-
185
- async function closeServerAfterListenError(server: Server): Promise<void> {
186
- await new Promise<void>((resolvePromise) => {
187
- server.close(() => resolvePromise());
188
- });
189
- }
190
-
191
- function isAddressInUseError(error: unknown): boolean {
192
- return Boolean(error && typeof error === "object" && "code" in error && error.code === "EADDRINUSE");
193
- }
194
-
195
- export async function readDeployAccessTokenForUi(tokenPath: string): Promise<string> {
196
- if (process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN) {
197
- return process.env.AGENTKIT_DEPLOY_ACCESS_TOKEN;
198
- }
199
-
200
- let payload: unknown;
201
-
202
- try {
203
- payload = JSON.parse(await readFile(tokenPath, "utf8"));
204
- } catch {
205
- throw new AgentKitError(
206
- "deploy_access_token_missing",
207
- `Could not read a clean deploy access token file at ${tokenPath}. Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
208
- );
209
- }
210
-
211
- const token =
212
- payload && typeof payload === "object"
213
- ? (payload as { access_token?: { token?: unknown }; token?: unknown; accessToken?: unknown }).access_token?.token ??
214
- (payload as { token?: unknown }).token ??
215
- (payload as { accessToken?: unknown }).accessToken
216
- : undefined;
217
-
218
- if (typeof token !== "string" || token.trim().length === 0) {
219
- throw new AgentKitError(
220
- "deploy_access_token_invalid",
221
- `Deploy access token file ${tokenPath} does not contain access_token.token. Recreate it with: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`,
222
- );
223
- }
224
-
225
- return token.trim();
226
- }
227
-
228
- function readIncomingBody(request: IncomingMessage): Promise<string> {
229
- return new Promise((resolvePromise, reject) => {
230
- let body = "";
231
- request.setEncoding("utf8");
232
- request.on("data", (chunk) => {
233
- body += chunk;
234
- });
235
- request.on("end", () => resolvePromise(body));
236
- request.on("error", reject);
237
- });
238
- }
239
-
240
- function sendJson(response: ServerResponse, status: number, body: unknown): void {
241
- response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
242
- response.end(JSON.stringify(body, null, 2));
243
- }
244
-
245
- function sendHtml(response: ServerResponse, body: string): void {
246
- response.writeHead(200, { "content-type": "text/html; charset=utf-8" });
247
- response.end(body);
248
- }
249
-
250
- function renderDeployChatUi(input: { deployUrl: string }): string {
251
- const deployUrl = escapeHtml(input.deployUrl);
252
-
253
- return `<!doctype html>
254
- <html lang="en">
255
- <head>
256
- <meta charset="utf-8">
257
- <meta name="viewport" content="width=device-width, initial-scale=1">
258
- <title>AgentKit Hosted Chat</title>
259
- <style>
260
- :root {
261
- color-scheme: light;
262
- --bg: #f6f7f5;
263
- --panel: #ffffff;
264
- --ink: #182022;
265
- --muted: #667175;
266
- --line: #d9ded9;
267
- --accent: #176b5d;
268
- --danger: #9f2d2d;
269
- }
270
-
271
- * { box-sizing: border-box; }
272
-
273
- body {
274
- margin: 0;
275
- min-height: 100svh;
276
- background: var(--bg);
277
- color: var(--ink);
278
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
279
- }
280
-
281
- main {
282
- min-height: 100svh;
283
- display: grid;
284
- grid-template-rows: auto 1fr auto;
285
- background: var(--panel);
286
- }
287
-
288
- header {
289
- border-bottom: 1px solid var(--line);
290
- padding: 18px 22px;
291
- display: grid;
292
- gap: 10px;
293
- }
294
-
295
- h1 {
296
- margin: 0 0 4px;
297
- font-size: 20px;
298
- line-height: 1.2;
299
- }
300
-
301
- .topline {
302
- display: flex;
303
- align-items: start;
304
- justify-content: space-between;
305
- gap: 12px;
306
- }
307
-
308
- code {
309
- color: var(--muted);
310
- font-size: 12px;
311
- overflow-wrap: anywhere;
312
- }
313
-
314
- .meta {
315
- display: flex;
316
- flex-wrap: wrap;
317
- gap: 8px 14px;
318
- color: var(--muted);
319
- font-size: 12px;
320
- }
321
-
322
- #messages {
323
- padding: 22px;
324
- overflow: auto;
325
- display: flex;
326
- flex-direction: column;
327
- gap: 12px;
328
- }
329
-
330
- .message {
331
- max-width: 780px;
332
- display: grid;
333
- gap: 4px;
334
- }
335
-
336
- .message.user {
337
- align-self: end;
338
- }
339
-
340
- .role {
341
- color: var(--muted);
342
- font-size: 12px;
343
- font-weight: 700;
344
- text-transform: uppercase;
345
- }
346
-
347
- .bubble {
348
- border: 1px solid var(--line);
349
- border-radius: 8px;
350
- padding: 12px 14px;
351
- white-space: pre-wrap;
352
- line-height: 1.45;
353
- background: #fbfbfa;
354
- }
355
-
356
- .user .bubble {
357
- border-color: #b8d8ce;
358
- background: #e8f5f0;
359
- }
360
-
361
- .error .bubble {
362
- border-color: #e1b1b1;
363
- color: var(--danger);
364
- background: #fff6f6;
365
- }
366
-
367
- #trace {
368
- border-top: 1px solid var(--line);
369
- padding: 12px 22px;
370
- display: grid;
371
- gap: 8px;
372
- background: #fbfbfa;
373
- }
374
-
375
- .tool-call {
376
- border: 1px solid var(--line);
377
- border-radius: 8px;
378
- padding: 10px 12px;
379
- display: grid;
380
- gap: 6px;
381
- font-size: 12px;
382
- }
383
-
384
- .tool-head {
385
- display: flex;
386
- flex-wrap: wrap;
387
- gap: 8px;
388
- align-items: center;
389
- color: var(--muted);
390
- font-weight: 700;
391
- }
392
-
393
- .tool-call pre {
394
- margin: 0;
395
- max-height: 180px;
396
- overflow: auto;
397
- white-space: pre-wrap;
398
- }
399
-
400
- form {
401
- border-top: 1px solid var(--line);
402
- padding: 14px;
403
- display: grid;
404
- grid-template-columns: 1fr auto;
405
- gap: 10px;
406
- }
407
-
408
- textarea {
409
- min-height: 52px;
410
- max-height: 180px;
411
- resize: vertical;
412
- border: 1px solid var(--line);
413
- border-radius: 8px;
414
- padding: 12px;
415
- color: var(--ink);
416
- font: inherit;
417
- }
418
-
419
- button {
420
- min-width: 96px;
421
- border: 0;
422
- border-radius: 8px;
423
- padding: 0 18px;
424
- color: white;
425
- background: var(--accent);
426
- font: inherit;
427
- font-weight: 700;
428
- cursor: pointer;
429
- }
430
-
431
- button.secondary {
432
- min-height: 34px;
433
- color: var(--ink);
434
- background: #eef1ee;
435
- border: 1px solid var(--line);
436
- }
437
-
438
- button:disabled {
439
- cursor: wait;
440
- opacity: 0.65;
441
- }
442
-
443
- @media (max-width: 640px) {
444
- form {
445
- grid-template-columns: 1fr;
446
- }
447
-
448
- button {
449
- min-height: 44px;
450
- }
451
- }
452
- </style>
453
- </head>
454
- <body>
455
- <main>
456
- <header>
457
- <div class="topline">
458
- <div>
459
- <h1>AgentKit Hosted Chat</h1>
460
- <code>${deployUrl}</code>
461
- </div>
462
- <button class="secondary" id="new-conversation" type="button">New conversation</button>
463
- </div>
464
- <div class="meta">
465
- <span>Conversation: <code id="conversation-id">none</code></span>
466
- <span>Tool calls: <code id="tool-count">0</code></span>
467
- </div>
468
- </header>
469
- <section id="messages" aria-live="polite"></section>
470
- <section id="trace" aria-live="polite"></section>
471
- <form id="form">
472
- <textarea id="message" name="message" placeholder="Type a message" required autofocus></textarea>
473
- <button id="send" type="submit">Send</button>
474
- </form>
475
- </main>
476
- <script>
477
- const form = document.querySelector("#form");
478
- const textarea = document.querySelector("#message");
479
- const send = document.querySelector("#send");
480
- const messages = document.querySelector("#messages");
481
- const trace = document.querySelector("#trace");
482
- const conversationLabel = document.querySelector("#conversation-id");
483
- const toolCount = document.querySelector("#tool-count");
484
- const newConversation = document.querySelector("#new-conversation");
485
- let conversationId = null;
486
-
487
- function addMessage(role, content, className = "") {
488
- const wrapper = document.createElement("article");
489
- wrapper.className = ["message", role, className].filter(Boolean).join(" ");
490
- const label = document.createElement("div");
491
- label.className = "role";
492
- label.textContent = role;
493
- const bubble = document.createElement("div");
494
- bubble.className = "bubble";
495
- bubble.textContent = content;
496
- wrapper.append(label, bubble);
497
- messages.append(wrapper);
498
- messages.scrollTop = messages.scrollHeight;
499
- }
500
-
501
- function setConversationId(value) {
502
- conversationId = value || null;
503
- conversationLabel.textContent = conversationId || "none";
504
- }
505
-
506
- function renderTrace(payload) {
507
- const calls = [];
508
- for (const run of payload.runs || []) {
509
- for (const call of run.toolCalls || []) {
510
- calls.push(call);
511
- }
512
- }
513
-
514
- toolCount.textContent = String(calls.length);
515
- trace.innerHTML = "";
516
-
517
- for (const call of calls.slice(-8)) {
518
- const item = document.createElement("article");
519
- item.className = "tool-call";
520
- const head = document.createElement("div");
521
- head.className = "tool-head";
522
- head.textContent = [call.name, call.status, call.errorMessage].filter(Boolean).join(" · ");
523
- const input = document.createElement("pre");
524
- input.textContent = JSON.stringify(call.input || {}, null, 2);
525
- item.append(head, input);
526
- trace.append(item);
527
- }
528
- }
529
-
530
- async function refreshTrace() {
531
- if (!conversationId) {
532
- toolCount.textContent = "0";
533
- trace.innerHTML = "";
534
- return;
535
- }
536
-
537
- const response = await fetch("/v1/conversations/" + encodeURIComponent(conversationId) + "/trace");
538
- const payload = await response.json();
539
- if (response.ok) renderTrace(payload);
540
- }
541
-
542
- newConversation.addEventListener("click", () => {
543
- setConversationId(null);
544
- messages.innerHTML = "";
545
- trace.innerHTML = "";
546
- toolCount.textContent = "0";
547
- textarea.focus();
548
- });
549
-
550
- textarea.addEventListener("keydown", (event) => {
551
- if (event.key !== "Enter" || event.shiftKey || event.isComposing) return;
552
- event.preventDefault();
553
- if (send.disabled) return;
554
- form.requestSubmit();
555
- });
556
-
557
- form.addEventListener("submit", async (event) => {
558
- event.preventDefault();
559
- const message = textarea.value.trim();
560
- if (!message) return;
561
-
562
- addMessage("user", message);
563
- textarea.value = "";
564
- send.disabled = true;
565
-
566
- try {
567
- const response = await fetch("/v1/chat", {
568
- method: "POST",
569
- headers: { "content-type": "application/json" },
570
- body: JSON.stringify({ message, conversationId }),
571
- });
572
- const payload = await response.json();
573
-
574
- if (!response.ok) {
575
- throw new Error(payload.error?.message || "Request failed");
576
- }
577
-
578
- setConversationId(payload.conversationId || conversationId);
579
- addMessage("assistant", payload.message?.content || "");
580
- await refreshTrace();
581
- } catch (error) {
582
- addMessage("error", error instanceof Error ? error.message : String(error), "error");
583
- await refreshTrace().catch(() => {});
584
- } finally {
585
- send.disabled = false;
586
- textarea.focus();
587
- }
588
- });
589
- </script>
590
- </body>
591
- </html>`;
592
- }
593
-
594
- function escapeHtml(value: string): string {
595
- return value
596
- .replace(/&/g, "&amp;")
597
- .replace(/</g, "&lt;")
598
- .replace(/>/g, "&gt;")
599
- .replace(/"/g, "&quot;")
600
- .replace(/'/g, "&#39;");
601
- }
602
-
603
- function relativePath(root: string, path: string): string {
604
- const output = relative(root, path);
605
- return output.startsWith("..") ? path : output || ".";
606
- }