@corsair-dev/studio 0.1.1 → 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 +399 -38
- 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-BdMocr9R.js +0 -52
- package/dist/web/assets/index-BdMocr9R.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 };
|
|
@@ -697,6 +1058,10 @@ var listPermissions = async (ctx) => {
|
|
|
697
1058
|
};
|
|
698
1059
|
|
|
699
1060
|
// src/server/handlers/operations.ts
|
|
1061
|
+
import {
|
|
1062
|
+
getSchema as getCorsairSchema,
|
|
1063
|
+
listOperations as listCorsairOperations
|
|
1064
|
+
} from "corsair";
|
|
700
1065
|
function navigateToEndpoint(client, path) {
|
|
701
1066
|
const parts = path.split(".");
|
|
702
1067
|
let current = client;
|
|
@@ -711,25 +1076,17 @@ var listOperations = async (ctx) => {
|
|
|
711
1076
|
const plugin = body.plugin ? String(body.plugin) : void 0;
|
|
712
1077
|
const type = body.type ? String(body.type) : void 0;
|
|
713
1078
|
const { instance } = await ctx.getCorsair();
|
|
714
|
-
const corsair = instance;
|
|
715
|
-
if (typeof corsair.list_operations !== "function") {
|
|
716
|
-
throw new Error("list_operations not available on this Corsair instance.");
|
|
717
|
-
}
|
|
718
1079
|
const opts = {};
|
|
719
1080
|
if (plugin) opts.plugin = plugin;
|
|
720
1081
|
if (type === "api" || type === "webhooks" || type === "db") opts.type = type;
|
|
721
|
-
return
|
|
1082
|
+
return listCorsairOperations(instance, opts);
|
|
722
1083
|
};
|
|
723
1084
|
var schemaForOperation = async (ctx) => {
|
|
724
1085
|
const body = await readJsonBody(ctx.req);
|
|
725
1086
|
const path = String(body.path ?? "");
|
|
726
1087
|
if (!path) throw new Error("Missing path.");
|
|
727
1088
|
const { instance } = await ctx.getCorsair();
|
|
728
|
-
const
|
|
729
|
-
if (typeof corsair.get_schema !== "function") {
|
|
730
|
-
throw new Error("get_schema not available on this Corsair instance.");
|
|
731
|
-
}
|
|
732
|
-
const schema = corsair.get_schema(path);
|
|
1089
|
+
const schema = getCorsairSchema(instance, path);
|
|
733
1090
|
return { schema };
|
|
734
1091
|
};
|
|
735
1092
|
var runOperation = async (ctx) => {
|
|
@@ -813,19 +1170,19 @@ var setupPlugin = async (ctx) => {
|
|
|
813
1170
|
if (!hasAuthConfig(internal, pluginId)) {
|
|
814
1171
|
throw new Error(`Plugin '${pluginId}' has no credential setup.`);
|
|
815
1172
|
}
|
|
816
|
-
const
|
|
1173
|
+
const db2 = asDb(internal.database);
|
|
817
1174
|
const now = /* @__PURE__ */ new Date();
|
|
818
|
-
let integration = await
|
|
1175
|
+
let integration = await db2.selectFrom("corsair_integrations").selectAll().where("name", "=", pluginId).executeTakeFirst();
|
|
819
1176
|
if (!integration) {
|
|
820
1177
|
const id = randomUUID();
|
|
821
|
-
await
|
|
1178
|
+
await db2.insertInto("corsair_integrations").values({
|
|
822
1179
|
id,
|
|
823
1180
|
name: pluginId,
|
|
824
1181
|
config: {},
|
|
825
1182
|
created_at: now,
|
|
826
1183
|
updated_at: now
|
|
827
1184
|
}).execute();
|
|
828
|
-
integration = await
|
|
1185
|
+
integration = await db2.selectFrom("corsair_integrations").selectAll().where("id", "=", id).executeTakeFirst();
|
|
829
1186
|
}
|
|
830
1187
|
if (!integration) {
|
|
831
1188
|
throw new Error(`Failed to create integration for '${pluginId}'.`);
|
|
@@ -834,9 +1191,9 @@ var setupPlugin = async (ctx) => {
|
|
|
834
1191
|
if (typeof integrationId !== "string") {
|
|
835
1192
|
throw new Error(`Invalid integration row for '${pluginId}'.`);
|
|
836
1193
|
}
|
|
837
|
-
let account = await
|
|
1194
|
+
let account = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
|
|
838
1195
|
if (!account) {
|
|
839
|
-
await
|
|
1196
|
+
await db2.insertInto("corsair_accounts").values({
|
|
840
1197
|
id: randomUUID(),
|
|
841
1198
|
tenant_id: tenantId,
|
|
842
1199
|
integration_id: integrationId,
|
|
@@ -844,7 +1201,7 @@ var setupPlugin = async (ctx) => {
|
|
|
844
1201
|
created_at: now,
|
|
845
1202
|
updated_at: now
|
|
846
1203
|
}).execute();
|
|
847
|
-
account = await
|
|
1204
|
+
account = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integrationId).executeTakeFirst();
|
|
848
1205
|
}
|
|
849
1206
|
const rootKeys = instance.keys ?? null;
|
|
850
1207
|
const integrationNamespace = rootKeys?.[pluginId] ?? null;
|
|
@@ -952,8 +1309,8 @@ var listTenants = async (ctx) => {
|
|
|
952
1309
|
return { tenants: ["default"] };
|
|
953
1310
|
}
|
|
954
1311
|
if (!internal.database) throw new Error("No database configured.");
|
|
955
|
-
const
|
|
956
|
-
const rows = await
|
|
1312
|
+
const db2 = asDb2(internal.database);
|
|
1313
|
+
const rows = await db2.selectFrom("corsair_accounts").selectAll().execute();
|
|
957
1314
|
const ids = /* @__PURE__ */ new Set(["default"]);
|
|
958
1315
|
for (const row of rows) {
|
|
959
1316
|
const tenantId = row.tenant_id;
|
|
@@ -977,13 +1334,13 @@ var createTenant = async (ctx) => {
|
|
|
977
1334
|
throw new Error("Multi-tenancy is not enabled for this Corsair instance.");
|
|
978
1335
|
}
|
|
979
1336
|
if (!internal.database) throw new Error("No database configured.");
|
|
980
|
-
const
|
|
1337
|
+
const db2 = asDb2(internal.database);
|
|
981
1338
|
const now = /* @__PURE__ */ new Date();
|
|
982
1339
|
const authPluginIds = getAuthPluginIds(internal);
|
|
983
1340
|
if (authPluginIds.length === 0) {
|
|
984
1341
|
return { ok: true, created: false };
|
|
985
1342
|
}
|
|
986
|
-
const integrations = await
|
|
1343
|
+
const integrations = await db2.selectFrom("corsair_integrations").selectAll().execute();
|
|
987
1344
|
const integrationByName = /* @__PURE__ */ new Map();
|
|
988
1345
|
for (const row of integrations) {
|
|
989
1346
|
const name = row.name;
|
|
@@ -999,10 +1356,10 @@ var createTenant = async (ctx) => {
|
|
|
999
1356
|
created_at: now,
|
|
1000
1357
|
updated_at: now
|
|
1001
1358
|
};
|
|
1002
|
-
await
|
|
1359
|
+
await db2.insertInto("corsair_integrations").values(row).execute();
|
|
1003
1360
|
integrationByName.set(pluginId, row);
|
|
1004
1361
|
}
|
|
1005
|
-
const existingAccounts = await
|
|
1362
|
+
const existingAccounts = await db2.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).execute();
|
|
1006
1363
|
const accountByIntegrationId = /* @__PURE__ */ new Map();
|
|
1007
1364
|
for (const row of existingAccounts) {
|
|
1008
1365
|
const integrationId = row.integration_id;
|
|
@@ -1020,7 +1377,7 @@ var createTenant = async (ctx) => {
|
|
|
1020
1377
|
if (!existing) {
|
|
1021
1378
|
createdAny = true;
|
|
1022
1379
|
pluginsMissingDek.add(pluginId);
|
|
1023
|
-
await
|
|
1380
|
+
await db2.insertInto("corsair_accounts").values({
|
|
1024
1381
|
id: randomUUID2(),
|
|
1025
1382
|
tenant_id: tenantId,
|
|
1026
1383
|
integration_id: integrationId,
|
|
@@ -1070,7 +1427,11 @@ var routes = [
|
|
|
1070
1427
|
{ method: "GET", path: "/api/db/tables", handler: listDbTables },
|
|
1071
1428
|
{ method: "POST", path: "/api/db/rows", handler: listDbRows },
|
|
1072
1429
|
{ method: "POST", path: "/api/db/entities/query", handler: queryEntityData },
|
|
1073
|
-
{ 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 }
|
|
1074
1435
|
];
|
|
1075
1436
|
async function handleApi(req, res, baseCtx) {
|
|
1076
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"}
|