@corsair-dev/studio 0.1.3 → 0.1.5

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.
@@ -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 chat = {
370
- id: crypto.randomUUID(),
371
- title: "New chat",
372
- created_at: Date.now()
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
- return db.selectFrom("chats").select(["id", "title", "created_at"]).orderBy("created_at", "desc").execute();
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
- id: row.id,
391
- chat_id: row.chat_id,
392
- role: row.role,
393
- blocks: JSON.parse(row.blocks),
394
- error: row.error
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, error) {
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 model = process.env.CORSAIR_CHAT_MODEL;
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
- model ?? "(default)"
716
+ override ?? "(default)"
529
717
  );
530
718
  if (process.env.OPENAI_API_KEY) {
531
719
  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");
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
- console.log(
538
- "[corsair:chat] using anthropic:",
539
- model ?? "claude-sonnet-4-6"
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
- return google(model ?? "gemini-2.0-flash");
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
- return createGroq()(model ?? "llama-3.3-70b-versatile");
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 model;
809
+ let resolved;
597
810
  try {
598
- model = await resolveModel();
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);
@@ -1060,6 +1287,7 @@ var listPermissions = async (ctx) => {
1060
1287
  // src/server/handlers/operations.ts
1061
1288
  import {
1062
1289
  getSchema as getCorsairSchema,
1290
+ getStructuredSchema as getCorsairStructuredSchema,
1063
1291
  listOperations as listCorsairOperations
1064
1292
  } from "corsair";
1065
1293
  function navigateToEndpoint(client, path) {
@@ -1089,6 +1317,17 @@ var schemaForOperation = async (ctx) => {
1089
1317
  const schema = getCorsairSchema(instance, path);
1090
1318
  return { schema };
1091
1319
  };
1320
+ var structuredSchemaForOperation = async (ctx) => {
1321
+ const body = await readJsonBody(ctx.req);
1322
+ const path = String(body.path ?? "");
1323
+ if (!path) throw new Error("Missing path.");
1324
+ const { instance } = await ctx.getCorsair();
1325
+ const structured = getCorsairStructuredSchema(
1326
+ instance,
1327
+ path
1328
+ );
1329
+ return { structured };
1330
+ };
1092
1331
  var runOperation = async (ctx) => {
1093
1332
  const body = await readJsonBody(ctx.req);
1094
1333
  const path = String(body.path ?? "");
@@ -1418,6 +1657,11 @@ var routes = [
1418
1657
  path: "/api/operations/schema",
1419
1658
  handler: schemaForOperation
1420
1659
  },
1660
+ {
1661
+ method: "POST",
1662
+ path: "/api/operations/structured-schema",
1663
+ handler: structuredSchemaForOperation
1664
+ },
1421
1665
  { method: "POST", path: "/api/operations/run", handler: runOperation },
1422
1666
  { method: "POST", path: "/api/operations/script", handler: runScript },
1423
1667
  { method: "POST", path: "/api/credentials/get", handler: getCredentials },
@@ -1430,7 +1674,11 @@ var routes = [
1430
1674
  { method: "GET", path: "/api/db/permissions", handler: listPermissions },
1431
1675
  { method: "GET", path: "/api/chats", handler: listChatsHandler },
1432
1676
  { method: "POST", path: "/api/chats", handler: createChatHandler },
1433
- { method: "GET", path: "/api/chats/messages", handler: getChatMessagesHandler },
1677
+ {
1678
+ method: "GET",
1679
+ path: "/api/chats/messages",
1680
+ handler: getChatMessagesHandler
1681
+ },
1434
1682
  { method: "POST", path: "/api/chat", handler: chatHandler }
1435
1683
  ];
1436
1684
  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
- 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>;
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":"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"}
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":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA6N7C,eAAO,MAAM,WAAW,EAAE,SAgIzB,CAAC"}
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":"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
+ {"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"}
@@ -1,6 +1,7 @@
1
1
  import type { HandlerFn } from '../types';
2
2
  export declare const listOperations: HandlerFn;
3
3
  export declare const schemaForOperation: HandlerFn;
4
+ export declare const structuredSchemaForOperation: HandlerFn;
4
5
  export declare const runOperation: HandlerFn;
5
6
  export declare const runScript: HandlerFn;
6
7
  //# sourceMappingURL=operations.d.ts.map
@@ -1 +1 @@
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
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../../../src/server/handlers/operations.ts"],"names":[],"mappings":"AAOA,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,4BAA4B,EAAE,SAW1C,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,SA6B1B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,SA+BvB,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;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"}
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;AA0BjE,OAAO,KAAK,EAAE,UAAU,EAAa,MAAM,SAAS,CAAC;AAmDrD,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"}
@@ -0,0 +1 @@
1
+ /*! tailwindcss v4.2.4 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-black:#000;--spacing:.25rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--animate-bounce:bounce 1s infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-bg:oklch(15% .01 260);--color-bg-elevated:oklch(19% .015 260);--color-bg-hover:oklch(23% .02 260);--color-border:oklch(28% .02 260);--color-border-strong:oklch(38% .02 260);--color-text:oklch(96% 0 0);--color-text-muted:oklch(68% .01 260);--color-text-subtle:oklch(52% .015 260);--color-accent:oklch(78% .16 70);--color-accent-dim:oklch(55% .12 70);--color-ok:oklch(78% .14 150);--color-warn:oklch(82% .15 85);--color-err:oklch(68% .2 25)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.col-span-full{grid-column:1/-1}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-auto{margin-top:auto}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-1{height:calc(var(--spacing) * 1)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-12{height:calc(var(--spacing) * 12)}.h-auto{height:auto}.h-full{height:100%}.max-h-48{max-height:calc(var(--spacing) * 48)}.max-h-\[200px\]{max-height:200px}.max-h-\[320px\]{max-height:320px}.max-h-\[480px\]{max-height:480px}.max-h-\[600px\]{max-height:600px}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-1{width:calc(var(--spacing) * 1)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-4{width:calc(var(--spacing) * 4)}.w-56{width:calc(var(--spacing) * 56)}.w-\[260px\]{width:260px}.w-full{width:100%}.max-w-\[75\%\]{max-width:75%}.max-w-\[85\%\]{max-width:85%}.max-w-full{max-width:100%}.max-w-xl{max-width:var(--container-xl)}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[220px\]{min-width:220px}.flex-1{flex:1}.flex-shrink-0,.shrink-0{flex-shrink:0}.animate-bounce{animation:var(--animate-bounce)}.animate-pulse{animation:var(--animate-pulse)}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-\[220px_240px_1fr\]{grid-template-columns:220px 240px 1fr}.grid-cols-\[320px_1fr\]{grid-template-columns:320px 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-6{gap:calc(var(--spacing) * 6)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-\[var\(--color-border\)\]>:not(:last-child)){border-color:var(--color-border)}:where(.divide-\[var\(--color-border\)\]\/70>:not(:last-child)){border-color:#232933b3}@supports (color:color-mix(in lab,red,red)){:where(.divide-\[var\(--color-border\)\]\/70>:not(:last-child)){border-color:color-mix(in oklab,var(--color-border) 70%,transparent)}}.self-stretch{align-self:stretch}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.rounded-br-sm{border-bottom-right-radius:var(--radius-sm)}.rounded-bl-sm{border-bottom-left-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-dashed{--tw-border-style:dashed;border-style:dashed}.border-\[var\(--color-accent\)\]\/30{border-color:#f7a2244d}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-accent\)\]\/30{border-color:color-mix(in oklab,var(--color-accent) 30%,transparent)}}.border-\[var\(--color-accent\)\]\/40{border-color:#f7a22466}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-accent\)\]\/40{border-color:color-mix(in oklab,var(--color-accent) 40%,transparent)}}.border-\[var\(--color-border\)\]{border-color:var(--color-border)}.border-\[var\(--color-err\)\]\/30{border-color:#fc58554d}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-err\)\]\/30{border-color:color-mix(in oklab,var(--color-err) 30%,transparent)}}.border-\[var\(--color-err\)\]\/40{border-color:#fc585566}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-err\)\]\/40{border-color:color-mix(in oklab,var(--color-err) 40%,transparent)}}.border-\[var\(--color-ok\)\]\/30{border-color:#6fd0874d}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-ok\)\]\/30{border-color:color-mix(in oklab,var(--color-ok) 30%,transparent)}}.border-\[var\(--color-warn\)\]\/30{border-color:#f0bb3b4d}@supports (color:color-mix(in lab,red,red)){.border-\[var\(--color-warn\)\]\/30{border-color:color-mix(in oklab,var(--color-warn) 30%,transparent)}}.border-transparent{border-color:#0000}.bg-\[var\(--color-accent\)\]{background-color:var(--color-accent)}.bg-\[var\(--color-accent\)\]\/10{background-color:#f7a2241a}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-accent\)\]\/10{background-color:color-mix(in oklab,var(--color-accent) 10%,transparent)}}.bg-\[var\(--color-accent\)\]\/15{background-color:#f7a22426}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-accent\)\]\/15{background-color:color-mix(in oklab,var(--color-accent) 15%,transparent)}}.bg-\[var\(--color-bg\)\]{background-color:var(--color-bg)}.bg-\[var\(--color-bg\)\]\/50{background-color:#090b0f80}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-bg\)\]\/50{background-color:color-mix(in oklab,var(--color-bg) 50%,transparent)}}.bg-\[var\(--color-bg-elevated\)\]{background-color:var(--color-bg-elevated)}.bg-\[var\(--color-bg-hover\)\]{background-color:var(--color-bg-hover)}.bg-\[var\(--color-err\)\]\/5{background-color:#fc58550d}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-err\)\]\/5{background-color:color-mix(in oklab,var(--color-err) 5%,transparent)}}.bg-\[var\(--color-err\)\]\/10{background-color:#fc58551a}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-err\)\]\/10{background-color:color-mix(in oklab,var(--color-err) 10%,transparent)}}.bg-\[var\(--color-ok\)\]{background-color:var(--color-ok)}.bg-\[var\(--color-ok\)\]\/10{background-color:#6fd0871a}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-ok\)\]\/10{background-color:color-mix(in oklab,var(--color-ok) 10%,transparent)}}.bg-\[var\(--color-warn\)\]{background-color:var(--color-warn)}.bg-\[var\(--color-warn\)\]\/10{background-color:#f0bb3b1a}@supports (color:color-mix(in lab,red,red)){.bg-\[var\(--color-warn\)\]\/10{background-color:color-mix(in oklab,var(--color-warn) 10%,transparent)}}.bg-current{background-color:currentColor}.bg-transparent{background-color:#0000}.p-0{padding:calc(var(--spacing) * 0)}.p-1{padding:calc(var(--spacing) * 1)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pl-1{padding-left:calc(var(--spacing) * 1)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[var\(--color-accent\)\]{color:var(--color-accent)}.text-\[var\(--color-err\)\]{color:var(--color-err)}.text-\[var\(--color-ok\)\]{color:var(--color-ok)}.text-\[var\(--color-text\)\]{color:var(--color-text)}.text-\[var\(--color-text-muted\)\]{color:var(--color-text-muted)}.text-\[var\(--color-text-subtle\)\]{color:var(--color-text-subtle)}.text-\[var\(--color-warn\)\]{color:var(--color-warn)}.text-black{color:var(--color-black)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.accent-\[var\(--color-accent\)\]{accent-color:var(--color-accent)}.opacity-70{opacity:.7}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.\[animation-delay\:-0\.3s\]{animation-delay:-.3s}.\[animation-delay\:-0\.15s\]{animation-delay:-.15s}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media(hover:hover){.hover\:border-\[var\(--color-border-strong\)\]:hover{border-color:var(--color-border-strong)}.hover\:bg-\[var\(--color-bg-hover\)\]:hover{background-color:var(--color-bg-hover)}.hover\:bg-\[var\(--color-bg-hover\)\]\/30:hover{background-color:#171d264d}@supports (color:color-mix(in lab,red,red)){.hover\:bg-\[var\(--color-bg-hover\)\]\/30:hover{background-color:color-mix(in oklab,var(--color-bg-hover) 30%,transparent)}}.hover\:bg-\[var\(--color-err\)\]\/10:hover{background-color:#fc58551a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-\[var\(--color-err\)\]\/10:hover{background-color:color-mix(in oklab,var(--color-err) 10%,transparent)}}.hover\:text-\[var\(--color-text\)\]:hover{color:var(--color-text)}.hover\:brightness-95:hover{--tw-brightness:brightness(95%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}}.focus\:border-\[var\(--color-accent-dim\)\]:focus{border-color:var(--color-accent-dim)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}@media(min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}}html,body,#root{height:100%;margin:0}body{background:var(--color-bg);color:var(--color-text);font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;font-size:14px;line-height:1.5}code,pre{font-family:JetBrains Mono,ui-monospace,SF Mono,Menlo,monospace;font-size:12.5px}*{scrollbar-width:thin;scrollbar-color:var(--color-border-strong) transparent}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--color-border-strong);border-radius:5px}::-webkit-scrollbar-track{background:0 0}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}