@corsair-dev/studio 0.1.3 → 0.1.4
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 +267 -36
- package/dist/src/server/chat-store.d.ts +26 -13
- package/dist/src/server/chat-store.d.ts.map +1 -1
- package/dist/src/server/handlers/chat.d.ts.map +1 -1
- package/dist/src/server/handlers/chats.d.ts.map +1 -1
- package/dist/src/server/model-pricing.d.ts +13 -0
- package/dist/src/server/model-pricing.d.ts.map +1 -0
- package/dist/src/server/router.d.ts.map +1 -1
- package/dist/web/assets/index-C1yAU4zE.js +55 -0
- package/dist/web/assets/{index-DIfzUXpe.js.map → index-C1yAU4zE.js.map} +1 -1
- package/dist/web/assets/index-MIlMjQfJ.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +5 -5
- package/dist/web/assets/index-DIfzUXpe.js +0 -55
- package/dist/web/assets/index-D_y_obFS.css +0 -1
package/dist/server/index.js
CHANGED
|
@@ -349,6 +349,127 @@ import { z } from "zod";
|
|
|
349
349
|
// src/server/chat-store.ts
|
|
350
350
|
import Database from "better-sqlite3";
|
|
351
351
|
import { Kysely, SqliteDialect } from "kysely";
|
|
352
|
+
|
|
353
|
+
// src/server/model-pricing.ts
|
|
354
|
+
var MODEL_PRICES = {
|
|
355
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
356
|
+
"gpt-4o-2024-11-20": { input: 2.5, output: 10 },
|
|
357
|
+
"gpt-4o-2024-08-06": { input: 2.5, output: 10 },
|
|
358
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
359
|
+
"gpt-4-turbo": { input: 10, output: 30 },
|
|
360
|
+
"gpt-4": { input: 30, output: 60 },
|
|
361
|
+
"gpt-3.5-turbo": { input: 0.5, output: 1.5 },
|
|
362
|
+
"gpt-4.1": { input: 2, output: 8 },
|
|
363
|
+
"gpt-4.1-mini": { input: 0.4, output: 1.6 },
|
|
364
|
+
"gpt-4.1-nano": { input: 0.1, output: 0.4 },
|
|
365
|
+
"gpt-5": { input: 1.25, output: 10 },
|
|
366
|
+
"gpt-5-mini": { input: 0.25, output: 2 },
|
|
367
|
+
"gpt-5-nano": { input: 0.05, output: 0.4 },
|
|
368
|
+
o1: { input: 15, output: 60 },
|
|
369
|
+
"o1-mini": { input: 3, output: 12 },
|
|
370
|
+
"o1-preview": { input: 15, output: 60 },
|
|
371
|
+
"o3-mini": { input: 1.1, output: 4.4 },
|
|
372
|
+
o3: { input: 2, output: 8 },
|
|
373
|
+
"o4-mini": { input: 1.1, output: 4.4 },
|
|
374
|
+
"claude-opus-4-5": { input: 15, output: 75 },
|
|
375
|
+
"claude-opus-4-7": { input: 15, output: 75 },
|
|
376
|
+
"claude-opus-4-1": { input: 15, output: 75 },
|
|
377
|
+
"claude-sonnet-4-5": { input: 3, output: 15 },
|
|
378
|
+
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
379
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
380
|
+
"claude-haiku-4-5": { input: 1, output: 5 },
|
|
381
|
+
"claude-3-7-sonnet-latest": { input: 3, output: 15 },
|
|
382
|
+
"claude-3-5-sonnet-latest": { input: 3, output: 15 },
|
|
383
|
+
"claude-3-5-sonnet-20241022": { input: 3, output: 15 },
|
|
384
|
+
"claude-3-5-haiku-latest": { input: 0.8, output: 4 },
|
|
385
|
+
"claude-3-5-haiku-20241022": { input: 0.8, output: 4 },
|
|
386
|
+
"claude-3-opus-latest": { input: 15, output: 75 },
|
|
387
|
+
"claude-3-haiku-20240307": { input: 0.25, output: 1.25 },
|
|
388
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
389
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
390
|
+
"gemini-2.5-flash-lite": { input: 0.1, output: 0.4 },
|
|
391
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
392
|
+
"gemini-2.0-flash-001": { input: 0.1, output: 0.4 },
|
|
393
|
+
"gemini-2.0-flash-lite": { input: 0.075, output: 0.3 },
|
|
394
|
+
"gemini-2.0-pro-exp": { input: 1.25, output: 5 },
|
|
395
|
+
"gemini-1.5-pro": { input: 1.25, output: 5 },
|
|
396
|
+
"gemini-1.5-pro-latest": { input: 1.25, output: 5 },
|
|
397
|
+
"gemini-1.5-flash": { input: 0.075, output: 0.3 },
|
|
398
|
+
"gemini-1.5-flash-latest": { input: 0.075, output: 0.3 },
|
|
399
|
+
"gemini-1.5-flash-8b": { input: 0.0375, output: 0.15 },
|
|
400
|
+
"llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
|
|
401
|
+
"llama-3.1-70b-versatile": { input: 0.59, output: 0.79 },
|
|
402
|
+
"llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
|
|
403
|
+
"llama-3.2-1b-preview": { input: 0.04, output: 0.04 },
|
|
404
|
+
"llama-3.2-3b-preview": { input: 0.06, output: 0.06 },
|
|
405
|
+
"llama-3.2-11b-vision-preview": { input: 0.18, output: 0.18 },
|
|
406
|
+
"llama-3.2-90b-vision-preview": { input: 0.9, output: 0.9 },
|
|
407
|
+
"mixtral-8x7b-32768": { input: 0.24, output: 0.24 },
|
|
408
|
+
"gemma2-9b-it": { input: 0.2, output: 0.2 },
|
|
409
|
+
"deepseek-r1-distill-llama-70b": { input: 0.75, output: 0.99 },
|
|
410
|
+
"deepseek-chat": { input: 0.27, output: 1.1 },
|
|
411
|
+
"deepseek-reasoner": { input: 0.55, output: 2.19 },
|
|
412
|
+
"mistral-large-latest": { input: 2, output: 6 },
|
|
413
|
+
"mistral-small-latest": { input: 0.2, output: 0.6 },
|
|
414
|
+
"codestral-latest": { input: 0.3, output: 0.9 },
|
|
415
|
+
"grok-2": { input: 2, output: 10 },
|
|
416
|
+
"grok-2-mini": { input: 0.3, output: 0.5 },
|
|
417
|
+
"grok-3": { input: 3, output: 15 },
|
|
418
|
+
"grok-3-mini": { input: 0.3, output: 0.5 },
|
|
419
|
+
"grok-4": { input: 5, output: 25 }
|
|
420
|
+
};
|
|
421
|
+
function normalizeId(id) {
|
|
422
|
+
return id.toLowerCase().replace(/^models\//, "").replace(/^anthropic\//, "").replace(/^openai\//, "").replace(/^google\//, "").replace(/^groq\//, "").trim();
|
|
423
|
+
}
|
|
424
|
+
function candidateIds(modelId) {
|
|
425
|
+
const norm = normalizeId(modelId);
|
|
426
|
+
const variants = /* @__PURE__ */ new Set([modelId, norm]);
|
|
427
|
+
const stripDate = norm.replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
428
|
+
if (stripDate !== norm) variants.add(stripDate);
|
|
429
|
+
const stripVersion = norm.replace(/-(?:00\d|0?\d{2,3})$/, "");
|
|
430
|
+
if (stripVersion !== norm) variants.add(stripVersion);
|
|
431
|
+
const stripLatest = norm.replace(/-latest$/, "");
|
|
432
|
+
if (stripLatest !== norm) variants.add(stripLatest);
|
|
433
|
+
const stripPreview = norm.replace(/-preview$/, "").replace(/-exp$/, "");
|
|
434
|
+
if (stripPreview !== norm) variants.add(stripPreview);
|
|
435
|
+
return Array.from(variants);
|
|
436
|
+
}
|
|
437
|
+
var PRICES_NORMALIZED = (() => {
|
|
438
|
+
const map = {};
|
|
439
|
+
for (const [k, v] of Object.entries(MODEL_PRICES)) {
|
|
440
|
+
map[normalizeId(k)] = v;
|
|
441
|
+
}
|
|
442
|
+
return map;
|
|
443
|
+
})();
|
|
444
|
+
function getModelPrice(modelId) {
|
|
445
|
+
if (MODEL_PRICES[modelId]) return MODEL_PRICES[modelId];
|
|
446
|
+
for (const candidate of candidateIds(modelId)) {
|
|
447
|
+
const direct = PRICES_NORMALIZED[candidate];
|
|
448
|
+
if (direct) return direct;
|
|
449
|
+
}
|
|
450
|
+
const norm = normalizeId(modelId);
|
|
451
|
+
let bestMatch = null;
|
|
452
|
+
for (const [key, price] of Object.entries(PRICES_NORMALIZED)) {
|
|
453
|
+
if (norm.startsWith(key) || key.startsWith(norm)) {
|
|
454
|
+
if (!bestMatch || key.length > bestMatch.key.length) {
|
|
455
|
+
bestMatch = { key, price };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return bestMatch?.price ?? null;
|
|
460
|
+
}
|
|
461
|
+
function computeCost(modelId, usage) {
|
|
462
|
+
const price = getModelPrice(modelId);
|
|
463
|
+
if (!price) {
|
|
464
|
+
console.warn(`[corsair:pricing] no price entry for model "${modelId}"`);
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
const input = usage.inputTokens / 1e6 * price.input;
|
|
468
|
+
const output = usage.outputTokens / 1e6 * price.output;
|
|
469
|
+
return input + output;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/server/chat-store.ts
|
|
352
473
|
var sqlite = new Database(":memory:");
|
|
353
474
|
var db = new Kysely({
|
|
354
475
|
dialect: new SqliteDialect({ database: sqlite })
|
|
@@ -356,7 +477,7 @@ var db = new Kysely({
|
|
|
356
477
|
var schemaReady = initSchema();
|
|
357
478
|
async function initSchema() {
|
|
358
479
|
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();
|
|
480
|
+
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()).addColumn("usage", "text").execute();
|
|
360
481
|
}
|
|
361
482
|
var _seq = 0;
|
|
362
483
|
function getTextFromBlocks(blocks) {
|
|
@@ -364,19 +485,81 @@ function getTextFromBlocks(blocks) {
|
|
|
364
485
|
(block) => block.type === "text"
|
|
365
486
|
).map((block) => block.content).join("");
|
|
366
487
|
}
|
|
488
|
+
function parseRawUsage(raw) {
|
|
489
|
+
if (!raw) return null;
|
|
490
|
+
try {
|
|
491
|
+
const parsed = JSON.parse(raw);
|
|
492
|
+
if (typeof parsed.model !== "string" || typeof parsed.inputTokens !== "number" || typeof parsed.outputTokens !== "number" || typeof parsed.totalTokens !== "number") {
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
model: parsed.model,
|
|
497
|
+
inputTokens: parsed.inputTokens,
|
|
498
|
+
outputTokens: parsed.outputTokens,
|
|
499
|
+
totalTokens: parsed.totalTokens
|
|
500
|
+
};
|
|
501
|
+
} catch {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function withCost(raw) {
|
|
506
|
+
return {
|
|
507
|
+
...raw,
|
|
508
|
+
cost: computeCost(raw.model, raw)
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function aggregateUsage(rows) {
|
|
512
|
+
const total = {
|
|
513
|
+
inputTokens: 0,
|
|
514
|
+
outputTokens: 0,
|
|
515
|
+
totalTokens: 0,
|
|
516
|
+
cost: 0,
|
|
517
|
+
hasUnknownCost: false
|
|
518
|
+
};
|
|
519
|
+
for (const u of rows) {
|
|
520
|
+
if (!u) continue;
|
|
521
|
+
total.inputTokens += u.inputTokens;
|
|
522
|
+
total.outputTokens += u.outputTokens;
|
|
523
|
+
total.totalTokens += u.totalTokens;
|
|
524
|
+
if (u.cost === null) {
|
|
525
|
+
total.hasUnknownCost = true;
|
|
526
|
+
} else {
|
|
527
|
+
total.cost += u.cost;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
return total;
|
|
531
|
+
}
|
|
367
532
|
async function createChat() {
|
|
368
533
|
await schemaReady;
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
534
|
+
const id = crypto.randomUUID();
|
|
535
|
+
const created_at = Date.now();
|
|
536
|
+
const title = "New chat";
|
|
537
|
+
await db.insertInto("chats").values({ id, title, created_at }).execute();
|
|
538
|
+
return {
|
|
539
|
+
id,
|
|
540
|
+
title,
|
|
541
|
+
created_at,
|
|
542
|
+
usage_total: aggregateUsage([])
|
|
373
543
|
};
|
|
374
|
-
await db.insertInto("chats").values(chat).execute();
|
|
375
|
-
return chat;
|
|
376
544
|
}
|
|
377
545
|
async function listChats() {
|
|
378
546
|
await schemaReady;
|
|
379
|
-
|
|
547
|
+
const chats = await db.selectFrom("chats").select(["id", "title", "created_at"]).orderBy("created_at", "desc").execute();
|
|
548
|
+
const messageRows = await db.selectFrom("chat_messages").select(["chat_id", "usage"]).execute();
|
|
549
|
+
const usageByChat = /* @__PURE__ */ new Map();
|
|
550
|
+
for (const row of messageRows) {
|
|
551
|
+
const raw = parseRawUsage(row.usage);
|
|
552
|
+
if (!raw) continue;
|
|
553
|
+
const list = usageByChat.get(row.chat_id) ?? [];
|
|
554
|
+
list.push(withCost(raw));
|
|
555
|
+
usageByChat.set(row.chat_id, list);
|
|
556
|
+
}
|
|
557
|
+
return chats.map((chat) => ({
|
|
558
|
+
id: chat.id,
|
|
559
|
+
title: chat.title,
|
|
560
|
+
created_at: chat.created_at,
|
|
561
|
+
usage_total: aggregateUsage(usageByChat.get(chat.id) ?? [])
|
|
562
|
+
}));
|
|
380
563
|
}
|
|
381
564
|
async function chatExists(chatId) {
|
|
382
565
|
await schemaReady;
|
|
@@ -385,24 +568,29 @@ async function chatExists(chatId) {
|
|
|
385
568
|
}
|
|
386
569
|
async function getMessages(chatId) {
|
|
387
570
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
571
|
+
const rows = await db.selectFrom("chat_messages").select(["id", "chat_id", "role", "blocks", "error", "usage"]).where("chat_id", "=", chatId).orderBy("seq", "asc").execute();
|
|
572
|
+
return rows.map((row) => {
|
|
573
|
+
const raw = parseRawUsage(row.usage);
|
|
574
|
+
return {
|
|
575
|
+
id: row.id,
|
|
576
|
+
chat_id: row.chat_id,
|
|
577
|
+
role: row.role,
|
|
578
|
+
blocks: JSON.parse(row.blocks),
|
|
579
|
+
error: row.error,
|
|
580
|
+
usage: raw ? withCost(raw) : null
|
|
581
|
+
};
|
|
582
|
+
});
|
|
396
583
|
}
|
|
397
|
-
async function appendMessage(chatId, id, role, blocks,
|
|
584
|
+
async function appendMessage(chatId, id, role, blocks, options = {}) {
|
|
398
585
|
await schemaReady;
|
|
399
586
|
await db.insertInto("chat_messages").values({
|
|
400
587
|
id,
|
|
401
588
|
chat_id: chatId,
|
|
402
589
|
role,
|
|
403
590
|
blocks: JSON.stringify(blocks),
|
|
404
|
-
error: error ?? null,
|
|
405
|
-
seq: ++_seq
|
|
591
|
+
error: options.error ?? null,
|
|
592
|
+
seq: ++_seq,
|
|
593
|
+
usage: options.usage ? JSON.stringify(options.usage) : null
|
|
406
594
|
}).execute();
|
|
407
595
|
if (role === "user") {
|
|
408
596
|
const row = await db.selectFrom("chats").select("title").where("id", "=", chatId).executeTakeFirst();
|
|
@@ -518,35 +706,36 @@ function buildAiTools(corsairClient) {
|
|
|
518
706
|
return tools;
|
|
519
707
|
}
|
|
520
708
|
async function resolveModel() {
|
|
521
|
-
const
|
|
709
|
+
const override = process.env.CORSAIR_CHAT_MODEL;
|
|
522
710
|
console.log(
|
|
523
711
|
"[corsair:chat] resolving model \u2014 OPENAI_API_KEY:",
|
|
524
712
|
!!process.env.OPENAI_API_KEY,
|
|
525
713
|
"| ANTHROPIC_API_KEY:",
|
|
526
714
|
!!process.env.ANTHROPIC_API_KEY,
|
|
527
715
|
"| CORSAIR_CHAT_MODEL:",
|
|
528
|
-
|
|
716
|
+
override ?? "(default)"
|
|
529
717
|
);
|
|
530
718
|
if (process.env.OPENAI_API_KEY) {
|
|
531
719
|
const { openai } = await import("@ai-sdk/openai");
|
|
532
|
-
|
|
533
|
-
|
|
720
|
+
const modelId = override ?? "gpt-4o-mini";
|
|
721
|
+
console.log("[corsair:chat] using openai:", modelId);
|
|
722
|
+
return { model: openai(modelId), modelId };
|
|
534
723
|
}
|
|
535
724
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
536
725
|
const { anthropic } = await import("@ai-sdk/anthropic");
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
);
|
|
541
|
-
return anthropic(model ?? "claude-sonnet-4-6");
|
|
726
|
+
const modelId = override ?? "claude-sonnet-4-6";
|
|
727
|
+
console.log("[corsair:chat] using anthropic:", modelId);
|
|
728
|
+
return { model: anthropic(modelId), modelId };
|
|
542
729
|
}
|
|
543
730
|
if (process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
|
|
544
731
|
const { google } = await import("@ai-sdk/google");
|
|
545
|
-
|
|
732
|
+
const modelId = override ?? "gemini-2.0-flash";
|
|
733
|
+
return { model: google(modelId), modelId };
|
|
546
734
|
}
|
|
547
735
|
if (process.env.GROQ_API_KEY) {
|
|
548
736
|
const { createGroq } = await import("@ai-sdk/groq");
|
|
549
|
-
|
|
737
|
+
const modelId = override ?? "llama-3.3-70b-versatile";
|
|
738
|
+
return { model: createGroq()(modelId), modelId };
|
|
550
739
|
}
|
|
551
740
|
throw new Error(
|
|
552
741
|
"No AI provider configured. Set one of: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, or GROQ_API_KEY."
|
|
@@ -558,20 +747,44 @@ function errorMessage(err) {
|
|
|
558
747
|
function isObject(value) {
|
|
559
748
|
return typeof value === "object" && value !== null;
|
|
560
749
|
}
|
|
750
|
+
function parseRawUsage2(raw) {
|
|
751
|
+
if (!isObject(raw)) return void 0;
|
|
752
|
+
const usage = {};
|
|
753
|
+
if (typeof raw.promptTokens === "number")
|
|
754
|
+
usage.promptTokens = raw.promptTokens;
|
|
755
|
+
if (typeof raw.completionTokens === "number")
|
|
756
|
+
usage.completionTokens = raw.completionTokens;
|
|
757
|
+
if (typeof raw.totalTokens === "number") usage.totalTokens = raw.totalTokens;
|
|
758
|
+
return usage;
|
|
759
|
+
}
|
|
561
760
|
function parseStreamPart(raw) {
|
|
562
761
|
if (!isObject(raw) || typeof raw.type !== "string") {
|
|
563
762
|
return null;
|
|
564
763
|
}
|
|
565
764
|
const textDelta = typeof raw.textDelta === "string" ? raw.textDelta : void 0;
|
|
566
765
|
const toolName = typeof raw.toolName === "string" ? raw.toolName : void 0;
|
|
766
|
+
let responseModelId;
|
|
767
|
+
if (isObject(raw.response) && typeof raw.response.modelId === "string") {
|
|
768
|
+
responseModelId = raw.response.modelId;
|
|
769
|
+
}
|
|
567
770
|
return {
|
|
568
771
|
type: raw.type,
|
|
569
772
|
textDelta,
|
|
570
773
|
toolName,
|
|
571
774
|
args: raw.args,
|
|
572
|
-
result: raw.result
|
|
775
|
+
result: raw.result,
|
|
776
|
+
usage: parseRawUsage2(raw.usage),
|
|
777
|
+
responseModelId
|
|
573
778
|
};
|
|
574
779
|
}
|
|
780
|
+
function buildRawMessageUsage(modelId, raw) {
|
|
781
|
+
if (!raw) return null;
|
|
782
|
+
const inputTokens = raw.promptTokens ?? 0;
|
|
783
|
+
const outputTokens = raw.completionTokens ?? 0;
|
|
784
|
+
const totalTokens = raw.totalTokens ?? inputTokens + outputTokens;
|
|
785
|
+
if (inputTokens === 0 && outputTokens === 0 && totalTokens === 0) return null;
|
|
786
|
+
return { model: modelId, inputTokens, outputTokens, totalTokens };
|
|
787
|
+
}
|
|
575
788
|
var chatHandler = async (ctx) => {
|
|
576
789
|
console.log("[corsair:chat] request received");
|
|
577
790
|
const body = await readJsonBody(ctx.req);
|
|
@@ -593,9 +806,9 @@ var chatHandler = async (ctx) => {
|
|
|
593
806
|
"| chatId:",
|
|
594
807
|
chatId ?? "(none)"
|
|
595
808
|
);
|
|
596
|
-
let
|
|
809
|
+
let resolved;
|
|
597
810
|
try {
|
|
598
|
-
|
|
811
|
+
resolved = await resolveModel();
|
|
599
812
|
} catch (err) {
|
|
600
813
|
const message = errorMessage(err);
|
|
601
814
|
console.error("[corsair:chat] model resolution failed:", message);
|
|
@@ -627,10 +840,11 @@ var chatHandler = async (ctx) => {
|
|
|
627
840
|
`);
|
|
628
841
|
};
|
|
629
842
|
const assistantBlocks = [];
|
|
843
|
+
let finalRawUsage = null;
|
|
630
844
|
console.log("[corsair:chat] starting stream");
|
|
631
845
|
try {
|
|
632
846
|
const result = streamText({
|
|
633
|
-
model,
|
|
847
|
+
model: resolved.model,
|
|
634
848
|
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
849
|
messages,
|
|
636
850
|
tools,
|
|
@@ -664,6 +878,18 @@ var chatHandler = async (ctx) => {
|
|
|
664
878
|
block.result = part.result;
|
|
665
879
|
}
|
|
666
880
|
} else if (part.type === "finish") {
|
|
881
|
+
const effectiveModelId = part.responseModelId ?? resolved.modelId;
|
|
882
|
+
const raw2 = buildRawMessageUsage(effectiveModelId, part.usage);
|
|
883
|
+
console.log(
|
|
884
|
+
"[corsair:chat] finish \u2014 model:",
|
|
885
|
+
effectiveModelId,
|
|
886
|
+
"| usage:",
|
|
887
|
+
part.usage
|
|
888
|
+
);
|
|
889
|
+
if (raw2) {
|
|
890
|
+
finalRawUsage = raw2;
|
|
891
|
+
send({ type: "usage", usage: withCost(raw2) });
|
|
892
|
+
}
|
|
667
893
|
send({ type: "done" });
|
|
668
894
|
}
|
|
669
895
|
}
|
|
@@ -678,7 +904,8 @@ var chatHandler = async (ctx) => {
|
|
|
678
904
|
chatId,
|
|
679
905
|
crypto.randomUUID(),
|
|
680
906
|
"assistant",
|
|
681
|
-
assistantBlocks
|
|
907
|
+
assistantBlocks,
|
|
908
|
+
{ usage: finalRawUsage ?? void 0 }
|
|
682
909
|
);
|
|
683
910
|
} catch (err) {
|
|
684
911
|
console.error("[corsair:chat] failed to save assistant message:", err);
|
|
@@ -1430,7 +1657,11 @@ var routes = [
|
|
|
1430
1657
|
{ method: "GET", path: "/api/db/permissions", handler: listPermissions },
|
|
1431
1658
|
{ method: "GET", path: "/api/chats", handler: listChatsHandler },
|
|
1432
1659
|
{ method: "POST", path: "/api/chats", handler: createChatHandler },
|
|
1433
|
-
{
|
|
1660
|
+
{
|
|
1661
|
+
method: "GET",
|
|
1662
|
+
path: "/api/chats/messages",
|
|
1663
|
+
handler: getChatMessagesHandler
|
|
1664
|
+
},
|
|
1434
1665
|
{ method: "POST", path: "/api/chat", handler: chatHandler }
|
|
1435
1666
|
];
|
|
1436
1667
|
async function handleApi(req, res, baseCtx) {
|
|
@@ -2,6 +2,7 @@ export type StoredChat = {
|
|
|
2
2
|
id: string;
|
|
3
3
|
title: string;
|
|
4
4
|
created_at: number;
|
|
5
|
+
usage_total: ChatUsageTotal;
|
|
5
6
|
};
|
|
6
7
|
export type StoredMsgBlock = {
|
|
7
8
|
type: 'text';
|
|
@@ -12,26 +13,38 @@ export type StoredMsgBlock = {
|
|
|
12
13
|
args: unknown;
|
|
13
14
|
result?: unknown;
|
|
14
15
|
};
|
|
16
|
+
export type RawMessageUsage = {
|
|
17
|
+
model: string;
|
|
18
|
+
inputTokens: number;
|
|
19
|
+
outputTokens: number;
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
};
|
|
22
|
+
export type MessageUsage = RawMessageUsage & {
|
|
23
|
+
cost: number | null;
|
|
24
|
+
};
|
|
25
|
+
export type ChatUsageTotal = {
|
|
26
|
+
inputTokens: number;
|
|
27
|
+
outputTokens: number;
|
|
28
|
+
totalTokens: number;
|
|
29
|
+
cost: number;
|
|
30
|
+
hasUnknownCost: boolean;
|
|
31
|
+
};
|
|
15
32
|
export type StoredMessage = {
|
|
16
33
|
id: string;
|
|
17
34
|
chat_id: string;
|
|
18
35
|
role: 'user' | 'assistant';
|
|
19
36
|
blocks: StoredMsgBlock[];
|
|
20
37
|
error: string | null;
|
|
38
|
+
usage: MessageUsage | null;
|
|
21
39
|
};
|
|
40
|
+
export declare function withCost(raw: RawMessageUsage): MessageUsage;
|
|
41
|
+
export declare function aggregateUsage(rows: (MessageUsage | null)[]): ChatUsageTotal;
|
|
22
42
|
export declare function createChat(): Promise<StoredChat>;
|
|
23
|
-
export declare function listChats(): Promise<
|
|
24
|
-
id: string;
|
|
25
|
-
title: string;
|
|
26
|
-
created_at: number;
|
|
27
|
-
}[]>;
|
|
43
|
+
export declare function listChats(): Promise<StoredChat[]>;
|
|
28
44
|
export declare function chatExists(chatId: string): Promise<boolean>;
|
|
29
|
-
export declare function getMessages(chatId: string): Promise<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
error: string | null;
|
|
35
|
-
}[]>;
|
|
36
|
-
export declare function appendMessage(chatId: string, id: string, role: 'user' | 'assistant', blocks: StoredMsgBlock[], error?: string): Promise<void>;
|
|
45
|
+
export declare function getMessages(chatId: string): Promise<StoredMessage[]>;
|
|
46
|
+
export declare function appendMessage(chatId: string, id: string, role: 'user' | 'assistant', blocks: StoredMsgBlock[], options?: {
|
|
47
|
+
error?: string;
|
|
48
|
+
usage?: RawMessageUsage;
|
|
49
|
+
}): Promise<void>;
|
|
37
50
|
//# sourceMappingURL=chat-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-store.d.ts","sourceRoot":"","sources":["../../../src/server/chat-store.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat-store.d.ts","sourceRoot":"","sources":["../../../src/server/chat-store.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,UAAU,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,cAAc,CAAC;CAC5B,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,eAAe,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG;IAC5C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,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;IACrB,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC3B,CAAC;AAsFF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,YAAY,CAK3D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,GAAG,cAAc,CAoB5E;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC,CActD;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CA6BvD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,oBAS9C;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAqB1E;AAED,wBAAsB,aAAa,CAClC,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GAAG,WAAW,EAC1B,MAAM,EAAE,cAAc,EAAE,EACxB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,eAAe,CAAA;CAAO,iBAoCzD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chat.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chat.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAuQ7C,eAAO,MAAM,WAAW,EAAE,SA6IzB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chats.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chats.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chats.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/chats.ts"],"names":[],"mappings":"AACA,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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ModelPrice = {
|
|
2
|
+
input: number;
|
|
3
|
+
output: number;
|
|
4
|
+
};
|
|
5
|
+
export declare const MODEL_PRICES: Record<string, ModelPrice>;
|
|
6
|
+
export declare function getModelPrice(modelId: string): ModelPrice | null;
|
|
7
|
+
export type TokenUsage = {
|
|
8
|
+
inputTokens: number;
|
|
9
|
+
outputTokens: number;
|
|
10
|
+
totalTokens: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function computeCost(modelId: string, usage: TokenUsage): number | null;
|
|
13
|
+
//# sourceMappingURL=model-pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-pricing.d.ts","sourceRoot":"","sources":["../../../src/server/model-pricing.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CACf,CAAC;AAMF,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAsEnD,CAAC;AA0CF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkBhE;AAED,MAAM,MAAM,UAAU,GAAG;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAS7E"}
|
|
@@ -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;AAyBjE,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,SAAS,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;AA8CrD,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"}
|