@corsair-dev/studio 0.1.2 → 0.1.3

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.
@@ -341,6 +341,367 @@ var exchangeOAuth = async (ctx) => {
341
341
  return { ok: true };
342
342
  };
343
343
 
344
+ // src/server/handlers/chat.ts
345
+ import { buildCorsairToolDefs } from "@corsair-dev/mcp";
346
+ import { streamText, tool } from "ai";
347
+ import { z } from "zod";
348
+
349
+ // src/server/chat-store.ts
350
+ import Database from "better-sqlite3";
351
+ import { Kysely, SqliteDialect } from "kysely";
352
+ var sqlite = new Database(":memory:");
353
+ var db = new Kysely({
354
+ dialect: new SqliteDialect({ database: sqlite })
355
+ });
356
+ var schemaReady = initSchema();
357
+ async function initSchema() {
358
+ await db.schema.createTable("chats").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("title", "text", (col) => col.notNull()).addColumn("created_at", "integer", (col) => col.notNull()).execute();
359
+ await db.schema.createTable("chat_messages").ifNotExists().addColumn("id", "text", (col) => col.primaryKey()).addColumn("chat_id", "text", (col) => col.notNull()).addColumn("role", "text", (col) => col.notNull()).addColumn("blocks", "text", (col) => col.notNull()).addColumn("error", "text").addColumn("seq", "integer", (col) => col.notNull()).execute();
360
+ }
361
+ var _seq = 0;
362
+ function getTextFromBlocks(blocks) {
363
+ return blocks.filter(
364
+ (block) => block.type === "text"
365
+ ).map((block) => block.content).join("");
366
+ }
367
+ async function createChat() {
368
+ await schemaReady;
369
+ const chat = {
370
+ id: crypto.randomUUID(),
371
+ title: "New chat",
372
+ created_at: Date.now()
373
+ };
374
+ await db.insertInto("chats").values(chat).execute();
375
+ return chat;
376
+ }
377
+ async function listChats() {
378
+ await schemaReady;
379
+ return db.selectFrom("chats").select(["id", "title", "created_at"]).orderBy("created_at", "desc").execute();
380
+ }
381
+ async function chatExists(chatId) {
382
+ await schemaReady;
383
+ const row = await db.selectFrom("chats").select("id").where("id", "=", chatId).executeTakeFirst();
384
+ return !!row;
385
+ }
386
+ async function getMessages(chatId) {
387
+ await schemaReady;
388
+ const rows = await db.selectFrom("chat_messages").select(["id", "chat_id", "role", "blocks", "error"]).where("chat_id", "=", chatId).orderBy("seq", "asc").execute();
389
+ return rows.map((row) => ({
390
+ id: row.id,
391
+ chat_id: row.chat_id,
392
+ role: row.role,
393
+ blocks: JSON.parse(row.blocks),
394
+ error: row.error
395
+ }));
396
+ }
397
+ async function appendMessage(chatId, id, role, blocks, error) {
398
+ await schemaReady;
399
+ await db.insertInto("chat_messages").values({
400
+ id,
401
+ chat_id: chatId,
402
+ role,
403
+ blocks: JSON.stringify(blocks),
404
+ error: error ?? null,
405
+ seq: ++_seq
406
+ }).execute();
407
+ if (role === "user") {
408
+ const row = await db.selectFrom("chats").select("title").where("id", "=", chatId).executeTakeFirst();
409
+ if (row?.title === "New chat") {
410
+ const text = getTextFromBlocks(blocks);
411
+ const newTitle = text.slice(0, 60).trim();
412
+ if (newTitle) {
413
+ await db.updateTable("chats").set({ title: newTitle }).where("id", "=", chatId).execute();
414
+ }
415
+ }
416
+ }
417
+ }
418
+
419
+ // src/server/handlers/chat.ts
420
+ function isTextBlock(block) {
421
+ return block.type === "text";
422
+ }
423
+ function isCompletedToolBlock(block) {
424
+ return block.type === "tool" && block.result !== void 0;
425
+ }
426
+ function isPendingToolBlock(block) {
427
+ return block.type === "tool" && block.result === void 0;
428
+ }
429
+ function joinTextBlocks(blocks) {
430
+ return blocks.map((block) => block.content).join("");
431
+ }
432
+ function toToolArgs(args) {
433
+ if (args && typeof args === "object") {
434
+ const toolArgs = {};
435
+ for (const [key, value] of Object.entries(args)) {
436
+ toolArgs[key] = value;
437
+ }
438
+ return toolArgs;
439
+ }
440
+ return {};
441
+ }
442
+ function makeTextPart(block) {
443
+ return { type: "text", text: block.content };
444
+ }
445
+ function makeToolCallPart(toolCallId, block) {
446
+ return {
447
+ type: "tool-call",
448
+ toolCallId,
449
+ toolName: block.name,
450
+ args: toToolArgs(block.args)
451
+ };
452
+ }
453
+ function makeToolResultPart(toolCallId, block) {
454
+ return {
455
+ type: "tool-result",
456
+ toolCallId,
457
+ toolName: block.name,
458
+ result: block.result
459
+ };
460
+ }
461
+ function findPendingToolBlockIndex(blocks, toolName) {
462
+ return blocks.findLastIndex(
463
+ (block) => isPendingToolBlock(block) && block.name === toolName
464
+ );
465
+ }
466
+ function toAiMessages(stored) {
467
+ const result = [];
468
+ for (const msg of stored) {
469
+ const textBlocks = msg.blocks.filter(isTextBlock);
470
+ const text = joinTextBlocks(textBlocks);
471
+ if (msg.role === "user") {
472
+ result.push({ role: "user", content: text });
473
+ continue;
474
+ }
475
+ const toolBlocks = msg.blocks.filter(isCompletedToolBlock);
476
+ if (toolBlocks.length === 0) {
477
+ result.push({ role: "assistant", content: text });
478
+ continue;
479
+ }
480
+ const toolCallIds = toolBlocks.map(() => crypto.randomUUID());
481
+ const assistantParts = textBlocks.map(makeTextPart);
482
+ const toolParts = [];
483
+ for (const [index, block] of toolBlocks.entries()) {
484
+ const toolCallId = toolCallIds[index];
485
+ if (!toolCallId) {
486
+ continue;
487
+ }
488
+ assistantParts.push(makeToolCallPart(toolCallId, block));
489
+ toolParts.push(makeToolResultPart(toolCallId, block));
490
+ }
491
+ result.push({ role: "assistant", content: assistantParts });
492
+ result.push({ role: "tool", content: toolParts });
493
+ }
494
+ return result;
495
+ }
496
+ function buildAiTools(corsairClient) {
497
+ const defs = buildCorsairToolDefs({ corsair: corsairClient, setup: false });
498
+ const tools = {};
499
+ for (const def of defs) {
500
+ tools[def.name] = tool({
501
+ description: def.description,
502
+ parameters: z.object(def.shape),
503
+ execute: async (args) => {
504
+ const result = await def.handler(args);
505
+ const texts = result.content.filter((c) => c.type === "text");
506
+ if (result.isError) {
507
+ throw new Error(texts.map((c) => c.text).join("\n"));
508
+ }
509
+ const text = texts[0]?.text ?? "";
510
+ try {
511
+ return JSON.parse(text);
512
+ } catch {
513
+ return text;
514
+ }
515
+ }
516
+ });
517
+ }
518
+ return tools;
519
+ }
520
+ async function resolveModel() {
521
+ const model = process.env.CORSAIR_CHAT_MODEL;
522
+ console.log(
523
+ "[corsair:chat] resolving model \u2014 OPENAI_API_KEY:",
524
+ !!process.env.OPENAI_API_KEY,
525
+ "| ANTHROPIC_API_KEY:",
526
+ !!process.env.ANTHROPIC_API_KEY,
527
+ "| CORSAIR_CHAT_MODEL:",
528
+ model ?? "(default)"
529
+ );
530
+ if (process.env.OPENAI_API_KEY) {
531
+ const { openai } = await import("@ai-sdk/openai");
532
+ console.log("[corsair:chat] using openai:", model ?? "gpt-4o-mini");
533
+ return openai(model ?? "gpt-4o-mini");
534
+ }
535
+ if (process.env.ANTHROPIC_API_KEY) {
536
+ const { anthropic } = await import("@ai-sdk/anthropic");
537
+ console.log(
538
+ "[corsair:chat] using anthropic:",
539
+ model ?? "claude-sonnet-4-6"
540
+ );
541
+ return anthropic(model ?? "claude-sonnet-4-6");
542
+ }
543
+ if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
544
+ const { google } = await import("@ai-sdk/google");
545
+ return google(model ?? "gemini-2.0-flash");
546
+ }
547
+ if (process.env.GROQ_API_KEY) {
548
+ const { createGroq } = await import("@ai-sdk/groq");
549
+ return createGroq()(model ?? "llama-3.3-70b-versatile");
550
+ }
551
+ throw new Error(
552
+ "No AI provider configured. Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, or GROQ_API_KEY."
553
+ );
554
+ }
555
+ function errorMessage(err) {
556
+ return err instanceof Error ? err.message : String(err);
557
+ }
558
+ function isObject(value) {
559
+ return typeof value === "object" && value !== null;
560
+ }
561
+ function parseStreamPart(raw) {
562
+ if (!isObject(raw) || typeof raw.type !== "string") {
563
+ return null;
564
+ }
565
+ const textDelta = typeof raw.textDelta === "string" ? raw.textDelta : void 0;
566
+ const toolName = typeof raw.toolName === "string" ? raw.toolName : void 0;
567
+ return {
568
+ type: raw.type,
569
+ textDelta,
570
+ toolName,
571
+ args: raw.args,
572
+ result: raw.result
573
+ };
574
+ }
575
+ var chatHandler = async (ctx) => {
576
+ console.log("[corsair:chat] request received");
577
+ const body = await readJsonBody(ctx.req);
578
+ const tenant = body.tenant ? String(body.tenant) : void 0;
579
+ const chatId = body.chatId ? String(body.chatId) : void 0;
580
+ const newUserText = body.message ? String(body.message) : void 0;
581
+ const storedHistory = chatId && await chatExists(chatId) ? await getMessages(chatId) : [];
582
+ const messages = toAiMessages(storedHistory);
583
+ if (newUserText) {
584
+ messages.push({ role: "user", content: newUserText });
585
+ }
586
+ console.log(
587
+ "[corsair:chat] history:",
588
+ storedHistory.length,
589
+ "stored msgs \u2192",
590
+ messages.length,
591
+ "AI msgs | tenant:",
592
+ tenant ?? "(none)",
593
+ "| chatId:",
594
+ chatId ?? "(none)"
595
+ );
596
+ let model;
597
+ try {
598
+ model = await resolveModel();
599
+ } catch (err) {
600
+ const message = errorMessage(err);
601
+ console.error("[corsair:chat] model resolution failed:", message);
602
+ ctx.res.writeHead(400, { "content-type": "application/json" });
603
+ ctx.res.end(JSON.stringify({ error: message }));
604
+ return;
605
+ }
606
+ const handle = await ctx.getCorsair();
607
+ const client = handle.resolveClient(tenant);
608
+ const tools = buildAiTools(client);
609
+ const userMsgId = crypto.randomUUID();
610
+ if (chatId && await chatExists(chatId) && newUserText) {
611
+ try {
612
+ await appendMessage(chatId, userMsgId, "user", [
613
+ { type: "text", content: newUserText }
614
+ ]);
615
+ } catch (err) {
616
+ console.error("[corsair:chat] failed to save user message:", err);
617
+ }
618
+ }
619
+ ctx.res.writeHead(200, {
620
+ "content-type": "text/event-stream",
621
+ "cache-control": "no-cache",
622
+ connection: "keep-alive"
623
+ });
624
+ const send = (data) => {
625
+ ctx.res.write(`data: ${JSON.stringify(data)}
626
+
627
+ `);
628
+ };
629
+ const assistantBlocks = [];
630
+ console.log("[corsair:chat] starting stream");
631
+ try {
632
+ const result = streamText({
633
+ model,
634
+ system: "You are a helpful assistant with access to Corsair tools. Use the tools to answer questions about the user's integrations and data. Use list_operations to get the exact endpoint names.",
635
+ messages,
636
+ tools,
637
+ maxSteps: 10
638
+ });
639
+ for await (const raw of result.fullStream) {
640
+ const part = parseStreamPart(raw);
641
+ if (!part) {
642
+ continue;
643
+ }
644
+ if (part.type === "text-delta") {
645
+ send({ type: "text", text: part.textDelta });
646
+ const last = assistantBlocks[assistantBlocks.length - 1];
647
+ if (last?.type === "text") {
648
+ last.content += part.textDelta ?? "";
649
+ } else {
650
+ assistantBlocks.push({ type: "text", content: part.textDelta ?? "" });
651
+ }
652
+ } else if (part.type === "tool-call") {
653
+ send({ type: "tool-start", name: part.toolName, args: part.args });
654
+ assistantBlocks.push({
655
+ type: "tool",
656
+ name: part.toolName ?? "",
657
+ args: part.args
658
+ });
659
+ } else if (part.type === "tool-result") {
660
+ send({ type: "tool-end", name: part.toolName, result: part.result });
661
+ const idx = findPendingToolBlockIndex(assistantBlocks, part.toolName);
662
+ const block = idx >= 0 ? assistantBlocks[idx] : void 0;
663
+ if (block && isPendingToolBlock(block)) {
664
+ block.result = part.result;
665
+ }
666
+ } else if (part.type === "finish") {
667
+ send({ type: "done" });
668
+ }
669
+ }
670
+ } catch (err) {
671
+ const message = errorMessage(err);
672
+ console.error("[corsair:chat] stream error:", message);
673
+ send({ type: "error", message });
674
+ } finally {
675
+ if (chatId && await chatExists(chatId) && assistantBlocks.length > 0) {
676
+ try {
677
+ await appendMessage(
678
+ chatId,
679
+ crypto.randomUUID(),
680
+ "assistant",
681
+ assistantBlocks
682
+ );
683
+ } catch (err) {
684
+ console.error("[corsair:chat] failed to save assistant message:", err);
685
+ }
686
+ }
687
+ console.log("[corsair:chat] done");
688
+ ctx.res.end();
689
+ }
690
+ };
691
+
692
+ // src/server/handlers/chats.ts
693
+ var listChatsHandler = async () => {
694
+ return { chats: await listChats() };
695
+ };
696
+ var createChatHandler = async () => {
697
+ return { chat: await createChat() };
698
+ };
699
+ var getChatMessagesHandler = async (ctx) => {
700
+ const chatId = ctx.url.searchParams.get("chatId");
701
+ if (!chatId) throw new Error("chatId is required");
702
+ return { messages: await getMessages(chatId) };
703
+ };
704
+
344
705
  // src/server/handlers/credentials-internal.ts
345
706
  var BASE_FIELDS = {
346
707
  oauth_2: {
@@ -533,9 +894,9 @@ function getKyselyDb(database) {
533
894
  var listDbTables = async (ctx) => {
534
895
  const { internal } = await ctx.getCorsair();
535
896
  if (!internal.database) throw new Error("No database configured.");
536
- const db = getKyselyDb(internal.database);
537
- if (!db) throw new Error("Could not access kysely db handle.");
538
- const existing = await db.introspection.getTables();
897
+ const db2 = getKyselyDb(internal.database);
898
+ if (!db2) throw new Error("Could not access kysely db handle.");
899
+ const existing = await db2.introspection.getTables();
539
900
  const existingNames = new Set(existing.map((t) => t.name));
540
901
  return {
541
902
  core: CORE_TABLES.filter((t) => existingNames.has(t)),
@@ -551,14 +912,14 @@ var listDbRows = async (ctx) => {
551
912
  if (!table) throw new Error("Missing table.");
552
913
  const { internal } = await ctx.getCorsair();
553
914
  if (!internal.database) throw new Error("No database configured.");
554
- const db = getKyselyDb(internal.database);
555
- if (!db) throw new Error("Could not access kysely db handle.");
915
+ const db2 = getKyselyDb(internal.database);
916
+ if (!db2) throw new Error("Could not access kysely db handle.");
556
917
  let rows;
557
918
  try {
558
- const q = db.selectFrom(table).selectAll().limit(limit).offset(offset);
919
+ const q = db2.selectFrom(table).selectAll().limit(limit).offset(offset);
559
920
  rows = await q.orderBy("created_at", "desc").execute();
560
921
  } catch {
561
- rows = await db.selectFrom(table).selectAll().limit(limit).offset(offset).execute();
922
+ rows = await db2.selectFrom(table).selectAll().limit(limit).offset(offset).execute();
562
923
  }
563
924
  const safeRows = rows.map(redactSensitive);
564
925
  return { rows: safeRows, limit, offset };
@@ -580,9 +941,9 @@ var queryEntityData = async (ctx) => {
580
941
  if (!entity) throw new Error("Missing entity.");
581
942
  const { internal } = await ctx.getCorsair();
582
943
  if (!internal.database) throw new Error("No database configured.");
583
- const db = getKyselyDb(internal.database);
584
- if (!db) throw new Error("Could not access kysely db handle.");
585
- const base = db.selectFrom("corsair_entities as e").innerJoin("corsair_accounts as a", "a.id", "e.account_id").innerJoin("corsair_integrations as i", "i.id", "a.integration_id").where("a.tenant_id", "=", tenant).where("i.name", "=", integration).where("e.entity_type", "=", entity);
944
+ const db2 = getKyselyDb(internal.database);
945
+ if (!db2) throw new Error("Could not access kysely db handle.");
946
+ const base = db2.selectFrom("corsair_entities as e").innerJoin("corsair_accounts as a", "a.id", "e.account_id").innerJoin("corsair_integrations as i", "i.id", "a.integration_id").where("a.tenant_id", "=", tenant).where("i.name", "=", integration).where("e.entity_type", "=", entity);
586
947
  let rowsRaw = [];
587
948
  let hasMore = false;
588
949
  let total = 0;
@@ -682,14 +1043,14 @@ function collectPrimitiveValues(value) {
682
1043
  var listPermissions = async (ctx) => {
683
1044
  const { internal } = await ctx.getCorsair();
684
1045
  if (!internal.database) throw new Error("No database configured.");
685
- const db = getKyselyDb(internal.database);
686
- if (!db) throw new Error("Could not access kysely db handle.");
1046
+ const db2 = getKyselyDb(internal.database);
1047
+ if (!db2) throw new Error("Could not access kysely db handle.");
687
1048
  const limit = Math.min(
688
1049
  Math.max(Number(ctx.url.searchParams.get("limit") ?? 100), 1),
689
1050
  500
690
1051
  );
691
1052
  try {
692
- const rows = await db.selectFrom("corsair_permissions").selectAll().limit(limit).offset(0).orderBy("created_at", "desc").execute();
1053
+ const rows = await db2.selectFrom("corsair_permissions").selectAll().limit(limit).offset(0).orderBy("created_at", "desc").execute();
693
1054
  return { rows };
694
1055
  } catch (err) {
695
1056
  return { rows: [], note: err.message };
@@ -809,19 +1170,19 @@ var setupPlugin = async (ctx) => {
809
1170
  if (!hasAuthConfig(internal, pluginId)) {
810
1171
  throw new Error(`Plugin '${pluginId}' has no credential setup.`);
811
1172
  }
812
- const db = asDb(internal.database);
1173
+ const db2 = asDb(internal.database);
813
1174
  const now = /* @__PURE__ */ new Date();
814
- let integration = await db.selectFrom("corsair_integrations").selectAll().where("name", "=", pluginId).executeTakeFirst();
1175
+ let integration = await db2.selectFrom("corsair_integrations").selectAll().where("name", "=", pluginId).executeTakeFirst();
815
1176
  if (!integration) {
816
1177
  const id = randomUUID();
817
- await db.insertInto("corsair_integrations").values({
1178
+ await db2.insertInto("corsair_integrations").values({
818
1179
  id,
819
1180
  name: pluginId,
820
1181
  config: {},
821
1182
  created_at: now,
822
1183
  updated_at: now
823
1184
  }).execute();
824
- integration = await db.selectFrom("corsair_integrations").selectAll().where("id", "=", id).executeTakeFirst();
1185
+ integration = await db2.selectFrom("corsair_integrations").selectAll().where("id", "=", id).executeTakeFirst();
825
1186
  }
826
1187
  if (!integration) {
827
1188
  throw new Error(`Failed to create integration for '${pluginId}'.`);
@@ -830,9 +1191,9 @@ var setupPlugin = async (ctx) => {
830
1191
  if (typeof integrationId !== "string") {
831
1192
  throw new Error(`Invalid integration row for '${pluginId}'.`);
832
1193
  }
833
- let account = await db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
1194
+ let account = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
834
1195
  if (!account) {
835
- await db.insertInto("corsair_accounts").values({
1196
+ await db2.insertInto("corsair_accounts").values({
836
1197
  id: randomUUID(),
837
1198
  tenant_id: tenantId,
838
1199
  integration_id: integrationId,
@@ -840,7 +1201,7 @@ var setupPlugin = async (ctx) => {
840
1201
  created_at: now,
841
1202
  updated_at: now
842
1203
  }).execute();
843
- account = await db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
1204
+ account = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
844
1205
  }
845
1206
  const rootKeys = instance.keys ?? null;
846
1207
  const integrationNamespace = rootKeys?.[pluginId] ?? null;
@@ -948,8 +1309,8 @@ var listTenants = async (ctx) => {
948
1309
  return { tenants: ["default"] };
949
1310
  }
950
1311
  if (!internal.database) throw new Error("No database configured.");
951
- const db = asDb2(internal.database);
952
- const rows = await db.selectFrom("corsair_accounts").selectAll().execute();
1312
+ const db2 = asDb2(internal.database);
1313
+ const rows = await db2.selectFrom("corsair_accounts").selectAll().execute();
953
1314
  const ids = /* @__PURE__ */ new Set(["default"]);
954
1315
  for (const row of rows) {
955
1316
  const tenantId = row.tenant_id;
@@ -973,13 +1334,13 @@ var createTenant = async (ctx) => {
973
1334
  throw new Error("Multi-tenancy is not enabled for this Corsair instance.");
974
1335
  }
975
1336
  if (!internal.database) throw new Error("No database configured.");
976
- const db = asDb2(internal.database);
1337
+ const db2 = asDb2(internal.database);
977
1338
  const now = /* @__PURE__ */ new Date();
978
1339
  const authPluginIds = getAuthPluginIds(internal);
979
1340
  if (authPluginIds.length === 0) {
980
1341
  return { ok: true, created: false };
981
1342
  }
982
- const integrations = await db.selectFrom("corsair_integrations").selectAll().execute();
1343
+ const integrations = await db2.selectFrom("corsair_integrations").selectAll().execute();
983
1344
  const integrationByName = /* @__PURE__ */ new Map();
984
1345
  for (const row of integrations) {
985
1346
  const name = row.name;
@@ -995,10 +1356,10 @@ var createTenant = async (ctx) => {
995
1356
  created_at: now,
996
1357
  updated_at: now
997
1358
  };
998
- await db.insertInto("corsair_integrations").values(row).execute();
1359
+ await db2.insertInto("corsair_integrations").values(row).execute();
999
1360
  integrationByName.set(pluginId, row);
1000
1361
  }
1001
- const existingAccounts = await db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).execute();
1362
+ const existingAccounts = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).execute();
1002
1363
  const accountByIntegrationId = /* @__PURE__ */ new Map();
1003
1364
  for (const row of existingAccounts) {
1004
1365
  const integrationId = row.integration_id;
@@ -1016,7 +1377,7 @@ var createTenant = async (ctx) => {
1016
1377
  if (!existing) {
1017
1378
  createdAny = true;
1018
1379
  pluginsMissingDek.add(pluginId);
1019
- await db.insertInto("corsair_accounts").values({
1380
+ await db2.insertInto("corsair_accounts").values({
1020
1381
  id: randomUUID2(),
1021
1382
  tenant_id: tenantId,
1022
1383
  integration_id: integrationId,
@@ -1066,7 +1427,11 @@ var routes = [
1066
1427
  { method: "GET", path: "/api/db/tables", handler: listDbTables },
1067
1428
  { method: "POST", path: "/api/db/rows", handler: listDbRows },
1068
1429
  { method: "POST", path: "/api/db/entities/query", handler: queryEntityData },
1069
- { method: "GET", path: "/api/db/permissions", handler: listPermissions }
1430
+ { method: "GET", path: "/api/db/permissions", handler: listPermissions },
1431
+ { method: "GET", path: "/api/chats", handler: listChatsHandler },
1432
+ { method: "POST", path: "/api/chats", handler: createChatHandler },
1433
+ { method: "GET", path: "/api/chats/messages", handler: getChatMessagesHandler },
1434
+ { method: "POST", path: "/api/chat", handler: chatHandler }
1070
1435
  ];
1071
1436
  async function handleApi(req, res, baseCtx) {
1072
1437
  const url = new URL(req.url ?? "/", "http://localhost");
@@ -0,0 +1,37 @@
1
+ export type StoredChat = {
2
+ id: string;
3
+ title: string;
4
+ created_at: number;
5
+ };
6
+ export type StoredMsgBlock = {
7
+ type: 'text';
8
+ content: string;
9
+ } | {
10
+ type: 'tool';
11
+ name: string;
12
+ args: unknown;
13
+ result?: unknown;
14
+ };
15
+ export type StoredMessage = {
16
+ id: string;
17
+ chat_id: string;
18
+ role: 'user' | 'assistant';
19
+ blocks: StoredMsgBlock[];
20
+ error: string | null;
21
+ };
22
+ export declare function createChat(): Promise<StoredChat>;
23
+ export declare function listChats(): Promise<{
24
+ id: string;
25
+ title: string;
26
+ created_at: number;
27
+ }[]>;
28
+ export declare function chatExists(chatId: string): Promise<boolean>;
29
+ export declare function getMessages(chatId: string): Promise<{
30
+ id: string;
31
+ chat_id: string;
32
+ role: "user" | "assistant";
33
+ blocks: StoredMsgBlock[];
34
+ error: string | null;
35
+ }[]>;
36
+ export declare function appendMessage(chatId: string, id: string, role: 'user' | 'assistant', blocks: StoredMsgBlock[], error?: string): Promise<void>;
37
+ //# sourceMappingURL=chat-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-store.d.ts","sourceRoot":"","sources":["../../../src/server/chat-store.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,cAAc,GACvB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEnE,MAAM,MAAM,aAAa,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,MAAM,EAAE,cAAc,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AA4DF,wBAAsB,UAAU,wBAW/B;AAED,wBAAsB,SAAS;;;;KAQ9B;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,oBAS9C;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM;;;;YAcZ,cAAc,EAAE;;KAGnD;AAED,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GAAG,WAAW,EAC1B,MAAM,EAAE,cAAc,EAAE,EACxB,KAAK,CAAC,EAAE,MAAM,iBAmCd"}
@@ -0,0 +1,3 @@
1
+ import type { HandlerFn } from '../types.js';
2
+ export declare const chatHandler: HandlerFn;
3
+ //# sourceMappingURL=chat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chat.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA6N7C,eAAO,MAAM,WAAW,EAAE,SAgIzB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { HandlerFn } from '../types.js';
2
+ export declare const listChatsHandler: HandlerFn;
3
+ export declare const createChatHandler: HandlerFn;
4
+ export declare const getChatMessagesHandler: HandlerFn;
5
+ //# sourceMappingURL=chats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chats.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chats.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,eAAO,MAAM,gBAAgB,EAAE,SAE9B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,SAE/B,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,SAIpC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/operations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAsB1C,eAAO,MAAM,cAAc,EAAE,SAa5B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,SAQhC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,SA6B1B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,SA+BvB,CAAC"}
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/operations.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAiB1C,eAAO,MAAM,cAAc,EAAE,SAa5B,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,SAQhC,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,SA6B1B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,SA+BvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAmBjE,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,SAAS,CAAC;AAoCrD,wBAAsB,SAAS,CAC9B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,GAC9C,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAsB,YAAY,CACjC,GAAG,EAAE,eAAe,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBlC"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../../src/server/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAyBjE,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,SAAS,CAAC;AA0CrD,wBAAsB,SAAS,CAC9B,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,OAAO,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,GAC9C,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAsB,YAAY,CACjC,GAAG,EAAE,eAAe,GAClB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBlC"}