@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.
- package/dist/server/index.js +393 -28
- package/dist/src/server/chat-store.d.ts +37 -0
- package/dist/src/server/chat-store.d.ts.map +1 -0
- package/dist/src/server/handlers/chat.d.ts +3 -0
- package/dist/src/server/handlers/chat.d.ts.map +1 -0
- package/dist/src/server/handlers/chats.d.ts +5 -0
- package/dist/src/server/handlers/chats.d.ts.map +1 -0
- package/dist/src/server/handlers/operations.d.ts.map +1 -1
- package/dist/src/server/router.d.ts.map +1 -1
- package/dist/web/assets/index-DIfzUXpe.js +55 -0
- package/dist/web/assets/index-DIfzUXpe.js.map +1 -0
- package/dist/web/assets/index-D_y_obFS.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +13 -3
- package/dist/web/assets/index-Ck3_0vOi.js +0 -54
- package/dist/web/assets/index-Ck3_0vOi.js.map +0 -1
- package/dist/web/assets/index-D8r6TgYt.css +0 -1
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
537
|
-
if (!
|
|
538
|
-
const existing = await
|
|
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
|
|
555
|
-
if (!
|
|
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 =
|
|
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
|
|
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
|
|
584
|
-
if (!
|
|
585
|
-
const base =
|
|
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
|
|
686
|
-
if (!
|
|
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
|
|
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
|
|
1173
|
+
const db2 = asDb(internal.database);
|
|
813
1174
|
const now = /* @__PURE__ */ new Date();
|
|
814
|
-
let integration = await
|
|
1175
|
+
let integration = await db2.selectFrom("corsair_integrations").selectAll().where("name", "=", pluginId).executeTakeFirst();
|
|
815
1176
|
if (!integration) {
|
|
816
1177
|
const id = randomUUID();
|
|
817
|
-
await
|
|
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
|
|
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
|
|
1194
|
+
let account = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
|
|
834
1195
|
if (!account) {
|
|
835
|
-
await
|
|
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
|
|
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
|
|
952
|
-
const rows = await
|
|
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
|
|
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
|
|
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
|
|
1359
|
+
await db2.insertInto("corsair_integrations").values(row).execute();
|
|
999
1360
|
integrationByName.set(pluginId, row);
|
|
1000
1361
|
}
|
|
1001
|
-
const existingAccounts = await
|
|
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
|
|
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 @@
|
|
|
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 @@
|
|
|
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":"
|
|
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;
|
|
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"}
|