@andreprado/agentkit 0.1.1 → 0.2.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 (95) 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 +0 -3
  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 +2 -4
  25. package/src/cli/commands/channels.ts +8 -1613
  26. package/src/cli/commands/feedback.ts +8 -86
  27. package/src/cli/constants.ts +0 -3
  28. package/src/cli/flags.ts +0 -28
  29. package/src/cli/help.ts +16 -92
  30. package/src/cli/index.ts +15 -1091
  31. package/src/index.ts +6 -158
  32. package/src/providers/pi.ts +2 -0
  33. package/src/runtime/channels/discord.ts +2 -2
  34. package/src/runtime/chat.ts +5 -3
  35. package/src/runtime/config.ts +14 -148
  36. package/src/runtime/database.ts +2 -2
  37. package/src/runtime/dev-server.ts +8 -8
  38. package/src/runtime/env.ts +11 -0
  39. package/src/runtime/improve.ts +2 -262
  40. package/src/runtime/inspect.ts +13 -73
  41. package/src/runtime/knowledge/ingest.ts +1 -1
  42. package/src/runtime/knowledge/tool.ts +16 -2
  43. package/src/runtime/knowledge/vector.ts +1 -1
  44. package/src/runtime/tool-runner.ts +5 -3
  45. package/src/runtime/tools.ts +10 -14
  46. package/src/storage/sqlite.ts +11 -32
  47. package/src/templates/blank.ts +15 -102
  48. package/src/templates/common.ts +60 -0
  49. package/src/templates/dentista.ts +7 -74
  50. package/src/templates/skills/agentkit-capsule/SKILL.md +5 -7
  51. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -3
  52. package/src/templates/skills/agentkit-channels/SKILL.md +6 -119
  53. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +0 -9
  54. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +1 -64
  55. package/src/templates/skills/agentkit-channels/references/discord.md +2 -92
  56. package/src/templates/skills/agentkit-channels/references/slack.md +2 -55
  57. package/src/templates/skills/agentkit-channels/references/telegram.md +2 -71
  58. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +2 -56
  59. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +2 -53
  60. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +2 -70
  61. package/src/templates/skills/agentkit-database/SKILL.md +2 -4
  62. package/src/templates/skills/agentkit-evals/SKILL.md +1 -1
  63. package/src/templates/skills/agentkit-improve/SKILL.md +6 -85
  64. package/src/templates/skills/agentkit-improve/references/trace-packets.md +1 -1
  65. package/src/templates/skills/agentkit-provider/SKILL.md +0 -1
  66. package/src/templates/skills/agentkit-security/SKILL.md +1 -3
  67. package/src/templates/skills/agentkit-tools/SKILL.md +1 -1
  68. package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +1 -2
  69. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +5 -11
  70. package/src/templates/support.ts +8 -92
  71. package/docs/guides/add-managed-composio.md +0 -165
  72. package/docs/guides/improve-from-production.md +0 -151
  73. package/docs/guides/prepare-deploy.md +0 -227
  74. package/docs/guides/replay-production-traces.md +0 -72
  75. package/docs/guides/send-feedback.md +0 -135
  76. package/src/cli/cloud-client.ts +0 -377
  77. package/src/cli/deploy-chat-ui.ts +0 -606
  78. package/src/cli/deploy-readiness.ts +0 -561
  79. package/src/cloud/artifact.ts +0 -139
  80. package/src/cloud/client.ts +0 -80
  81. package/src/cloud/contracts.ts +0 -63
  82. package/src/cloud/index.ts +0 -3
  83. package/src/runtime/build.ts +0 -43
  84. package/src/runtime/core/deploy-state.ts +0 -54
  85. package/src/runtime/core/manifest.ts +0 -283
  86. package/src/runtime/core/targets.ts +0 -133
  87. package/src/runtime/deploy-readiness.ts +0 -135
  88. package/src/runtime/deploy.ts +0 -1
  89. package/src/runtime/integrations/composio.ts +0 -425
  90. package/src/runtime/targets/cloudflare/build.ts +0 -3319
  91. package/src/runtime/targets/container/build.ts +0 -146
  92. package/src/runtime/targets/container/server.ts +0 -33
  93. package/src/runtime/targets/vps/deploy.ts +0 -223
  94. package/src/templates/skills/agentkit-deploy/SKILL.md +0 -52
  95. 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
- }