@firstlovecenter/ai-chat 0.6.1 → 0.8.0
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/CHANGELOG.md +50 -0
- package/dist/drizzle/index.cjs +14 -0
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.d.cts +18 -1
- package/dist/drizzle/index.d.ts +18 -1
- package/dist/drizzle/index.js +14 -0
- package/dist/drizzle/index.js.map +1 -1
- package/dist/prisma/index.cjs +6 -0
- package/dist/prisma/index.cjs.map +1 -1
- package/dist/prisma/index.d.cts +4 -1
- package/dist/prisma/index.d.ts +4 -1
- package/dist/prisma/index.js +6 -0
- package/dist/prisma/index.js.map +1 -1
- package/dist/server/index.cjs +169 -12
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +27 -3
- package/dist/server/index.d.ts +27 -3
- package/dist/server/index.js +169 -12
- package/dist/server/index.js.map +1 -1
- package/dist/{types-CQntnyDJ.d.cts → types-BnwUkqKb.d.cts} +8 -0
- package/dist/{types-CQntnyDJ.d.ts → types-BnwUkqKb.d.ts} +8 -0
- package/dist/ui/index.cjs +137 -98
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +15 -2
- package/dist/ui/index.d.ts +15 -2
- package/dist/ui/index.js +137 -98
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/chat-models.prisma +5 -0
package/dist/server/index.cjs
CHANGED
|
@@ -25,7 +25,10 @@ async function runAgent(input) {
|
|
|
25
25
|
const maxOutputTokens = input.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
26
26
|
const transcript = [];
|
|
27
27
|
transcript.push({ kind: "user", text: input.question });
|
|
28
|
-
const messages = [
|
|
28
|
+
const messages = [
|
|
29
|
+
...input.priorMessages ?? [],
|
|
30
|
+
{ role: "user", text: input.question }
|
|
31
|
+
];
|
|
29
32
|
const system = input.systemBlocks;
|
|
30
33
|
const toolSchemas = Object.values(input.tools).map((t) => t.schema);
|
|
31
34
|
let toolCallCount = 0;
|
|
@@ -218,11 +221,28 @@ function toAnthropicMessages(messages) {
|
|
|
218
221
|
const out = [];
|
|
219
222
|
for (const msg of messages) {
|
|
220
223
|
if (msg.role === "user") {
|
|
221
|
-
|
|
224
|
+
if (msg.cached) {
|
|
225
|
+
out.push({
|
|
226
|
+
role: "user",
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: "text",
|
|
230
|
+
text: msg.text,
|
|
231
|
+
cache_control: { type: "ephemeral" }
|
|
232
|
+
}
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
out.push({ role: "user", content: msg.text });
|
|
237
|
+
}
|
|
222
238
|
} else if (msg.role === "assistant") {
|
|
223
239
|
const blocks = [];
|
|
224
240
|
if (msg.text) {
|
|
225
|
-
|
|
241
|
+
const textBlock = { type: "text", text: msg.text };
|
|
242
|
+
if (msg.cached && msg.toolCalls.length === 0) {
|
|
243
|
+
textBlock.cache_control = { type: "ephemeral" };
|
|
244
|
+
}
|
|
245
|
+
blocks.push(textBlock);
|
|
226
246
|
}
|
|
227
247
|
for (const tc of msg.toolCalls) {
|
|
228
248
|
blocks.push({
|
|
@@ -574,6 +594,87 @@ var toolProviders = [
|
|
|
574
594
|
function getToolProvider(id) {
|
|
575
595
|
return toolProviders.find((p) => p.id === id);
|
|
576
596
|
}
|
|
597
|
+
|
|
598
|
+
// src/server/history.ts
|
|
599
|
+
var DEFAULT_MAX_HISTORY_PAIRS = 20;
|
|
600
|
+
var DEFAULT_MAX_TEXT_CHARS = 4e3;
|
|
601
|
+
function historyToNormalizedMessages(rows, opts = {}) {
|
|
602
|
+
const maxPairs = opts.maxPairs ?? DEFAULT_MAX_HISTORY_PAIRS;
|
|
603
|
+
const maxTextChars = opts.maxTextChars ?? DEFAULT_MAX_TEXT_CHARS;
|
|
604
|
+
const pairs = [];
|
|
605
|
+
let i = 0;
|
|
606
|
+
while (i < rows.length) {
|
|
607
|
+
const row = rows[i];
|
|
608
|
+
if (row.role !== "user" || !row.question) {
|
|
609
|
+
i += 1;
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const next = rows[i + 1];
|
|
613
|
+
if (next?.role !== "assistant") {
|
|
614
|
+
i += 1;
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const assistantText = truncate(
|
|
618
|
+
assistantMessageToText(next),
|
|
619
|
+
maxTextChars
|
|
620
|
+
);
|
|
621
|
+
if (assistantText) {
|
|
622
|
+
pairs.push([
|
|
623
|
+
{ role: "user", text: truncate(row.question, maxTextChars) },
|
|
624
|
+
{ role: "assistant", text: assistantText, toolCalls: [] }
|
|
625
|
+
]);
|
|
626
|
+
}
|
|
627
|
+
i += 2;
|
|
628
|
+
}
|
|
629
|
+
const kept = maxPairs > 0 ? pairs.slice(-maxPairs) : pairs;
|
|
630
|
+
return kept.flat();
|
|
631
|
+
}
|
|
632
|
+
function truncate(text, max) {
|
|
633
|
+
if (max <= 0 || text.length <= max) return text;
|
|
634
|
+
return text.slice(0, max);
|
|
635
|
+
}
|
|
636
|
+
function assistantMessageToText(row) {
|
|
637
|
+
if (row.errorJson) return "";
|
|
638
|
+
const proseText = proseToText(row.prose);
|
|
639
|
+
if (proseText) return proseText;
|
|
640
|
+
const blockText = blocksToText(row.blocks);
|
|
641
|
+
return blockText;
|
|
642
|
+
}
|
|
643
|
+
function proseToText(prose) {
|
|
644
|
+
if (!prose || typeof prose !== "object") return "";
|
|
645
|
+
const entries = Object.entries(prose).map(([k, v]) => [Number(k), typeof v === "string" ? v : ""]).filter(([k, v]) => Number.isFinite(k) && v.length > 0).sort(([a], [b]) => a - b);
|
|
646
|
+
return entries.map(([, v]) => v).join("\n\n").trim();
|
|
647
|
+
}
|
|
648
|
+
function blocksToText(blocks) {
|
|
649
|
+
if (!Array.isArray(blocks)) return "";
|
|
650
|
+
const parts = [];
|
|
651
|
+
for (const raw of blocks) {
|
|
652
|
+
if (!raw || typeof raw !== "object") continue;
|
|
653
|
+
const b = raw;
|
|
654
|
+
switch (b.kind) {
|
|
655
|
+
case "paragraph_brief": {
|
|
656
|
+
const facts = (b.key_facts ?? []).filter((f) => f && f.trim());
|
|
657
|
+
if (b.topic) parts.push(b.topic);
|
|
658
|
+
if (facts.length) parts.push(facts.join("\n"));
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
case "list": {
|
|
662
|
+
const items = (b.items ?? []).filter((s) => s && s.trim());
|
|
663
|
+
if (b.title) parts.push(b.title);
|
|
664
|
+
if (items.length) parts.push(items.map((s) => `- ${s}`).join("\n"));
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
case "chart":
|
|
668
|
+
case "table":
|
|
669
|
+
if (b.title) parts.push(`[${b.title}]`);
|
|
670
|
+
break;
|
|
671
|
+
case "callout":
|
|
672
|
+
if (b.text) parts.push(b.text);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return parts.join("\n\n").trim();
|
|
677
|
+
}
|
|
577
678
|
function vertexHost2(location) {
|
|
578
679
|
return location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
579
680
|
}
|
|
@@ -914,13 +1015,25 @@ function createAgentCustomRoutes(ctx) {
|
|
|
914
1015
|
const rawChatSessionId = body?.chatSessionId;
|
|
915
1016
|
const incomingChatSessionId = typeof rawChatSessionId === "number" && Number.isInteger(rawChatSessionId) ? rawChatSessionId : null;
|
|
916
1017
|
const aiSettings = await persistence.getAiSettings();
|
|
1018
|
+
const effectiveProjectId = aiSettings.gcpProjectId ?? vertex.projectId;
|
|
917
1019
|
let chatSessionId;
|
|
1020
|
+
let priorMessages = [];
|
|
918
1021
|
if (incomingChatSessionId !== null) {
|
|
919
1022
|
const owned = await persistence.getSession(incomingChatSessionId, userId);
|
|
920
1023
|
if (!owned) {
|
|
921
1024
|
return jsonError(404, "NOT_FOUND", "Chat session not found.");
|
|
922
1025
|
}
|
|
923
1026
|
chatSessionId = owned.id;
|
|
1027
|
+
const stored = await persistence.listMessagesForSession(chatSessionId, userId);
|
|
1028
|
+
priorMessages = historyToNormalizedMessages(stored);
|
|
1029
|
+
if (priorMessages.length > 0) {
|
|
1030
|
+
const last = priorMessages[priorMessages.length - 1];
|
|
1031
|
+
if (last.role === "assistant" && last.toolCalls.length === 0) {
|
|
1032
|
+
priorMessages[priorMessages.length - 1] = { ...last, cached: true };
|
|
1033
|
+
} else if (last.role === "user") {
|
|
1034
|
+
priorMessages[priorMessages.length - 1] = { ...last, cached: true };
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
924
1037
|
} else {
|
|
925
1038
|
const created = await persistence.createSession({
|
|
926
1039
|
userId,
|
|
@@ -957,7 +1070,7 @@ function createAgentCustomRoutes(ctx) {
|
|
|
957
1070
|
}
|
|
958
1071
|
const provider = def.createProvider({
|
|
959
1072
|
auth: vertex.auth,
|
|
960
|
-
projectId:
|
|
1073
|
+
projectId: effectiveProjectId,
|
|
961
1074
|
defaultLocation: vertex.defaultLocation,
|
|
962
1075
|
modelIds: vertex.modelIds,
|
|
963
1076
|
location: aiSettings.gcpLocation
|
|
@@ -1008,6 +1121,7 @@ data: ${JSON.stringify(data)}
|
|
|
1008
1121
|
send("meta", { chatSessionId, scopeLabel });
|
|
1009
1122
|
const agentResult = await runAgent({
|
|
1010
1123
|
question,
|
|
1124
|
+
priorMessages,
|
|
1011
1125
|
ctx: toolContext,
|
|
1012
1126
|
tools: tools.tools,
|
|
1013
1127
|
systemBlocks,
|
|
@@ -1035,7 +1149,7 @@ data: ${JSON.stringify(data)}
|
|
|
1035
1149
|
}
|
|
1036
1150
|
for await (const token of narratorFn({
|
|
1037
1151
|
auth: vertex.auth,
|
|
1038
|
-
projectId:
|
|
1152
|
+
projectId: effectiveProjectId,
|
|
1039
1153
|
location: aiSettings.gcpLocation,
|
|
1040
1154
|
modelId: narratorModelId,
|
|
1041
1155
|
maxTokens: aiSettings.maxOutputTokens,
|
|
@@ -1226,7 +1340,17 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1226
1340
|
if (short) return short;
|
|
1227
1341
|
}
|
|
1228
1342
|
const body = await req.json().catch(() => null);
|
|
1229
|
-
|
|
1343
|
+
let question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
1344
|
+
if (!question && Array.isArray(body?.messages)) {
|
|
1345
|
+
const msgs = body.messages;
|
|
1346
|
+
for (let i = msgs.length - 1; i >= 0; i -= 1) {
|
|
1347
|
+
const m = msgs[i];
|
|
1348
|
+
if (m && m.role === "user" && typeof m.content === "string") {
|
|
1349
|
+
question = m.content.trim();
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1230
1354
|
if (!question) {
|
|
1231
1355
|
return jsonError2(
|
|
1232
1356
|
400,
|
|
@@ -1239,7 +1363,9 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1239
1363
|
const rawModel = body?.model;
|
|
1240
1364
|
const requestedModel = typeof rawModel === "string" && VALID_MODELS.has(rawModel) ? rawModel : null;
|
|
1241
1365
|
const aiSettings = await persistence.getAiSettings();
|
|
1366
|
+
const effectiveProjectId = aiSettings.gcpProjectId ?? vertex.projectId;
|
|
1242
1367
|
let chatSessionId;
|
|
1368
|
+
let priorMessages = [];
|
|
1243
1369
|
if (incomingChatSessionId !== null) {
|
|
1244
1370
|
const owned = await persistence.getSession(
|
|
1245
1371
|
incomingChatSessionId,
|
|
@@ -1249,6 +1375,11 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1249
1375
|
return jsonError2(404, "NOT_FOUND", "Chat session not found.");
|
|
1250
1376
|
}
|
|
1251
1377
|
chatSessionId = owned.id;
|
|
1378
|
+
const stored = await persistence.listMessagesForSession(
|
|
1379
|
+
chatSessionId,
|
|
1380
|
+
userId
|
|
1381
|
+
);
|
|
1382
|
+
priorMessages = historyToNormalizedMessages(stored);
|
|
1252
1383
|
} else {
|
|
1253
1384
|
const created = await persistence.createSession({
|
|
1254
1385
|
userId,
|
|
@@ -1310,18 +1441,24 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1310
1441
|
});
|
|
1311
1442
|
const system = systemBlocks.map((b) => b.text).join("\n\n");
|
|
1312
1443
|
const model = provider === "claude" ? anthropic.createVertexAnthropic({
|
|
1313
|
-
project:
|
|
1444
|
+
project: effectiveProjectId,
|
|
1314
1445
|
location: vertex.defaultLocation,
|
|
1315
1446
|
googleAuthOptions: {}
|
|
1316
1447
|
})(vertex.modelIds.claude) : googleVertex.createVertex({
|
|
1317
|
-
project:
|
|
1448
|
+
project: effectiveProjectId,
|
|
1318
1449
|
location: aiSettings.gcpLocation,
|
|
1319
1450
|
googleAuthOptions: {}
|
|
1320
1451
|
})(vertex.modelIds.gemini);
|
|
1452
|
+
const priorCoreMessages = priorMessages.filter(
|
|
1453
|
+
(m) => m.role === "user" || m.role === "assistant"
|
|
1454
|
+
).map((m) => ({ role: m.role, content: m.text }));
|
|
1321
1455
|
const result = ai.streamText({
|
|
1322
1456
|
model,
|
|
1323
1457
|
system,
|
|
1324
|
-
messages: [
|
|
1458
|
+
messages: [
|
|
1459
|
+
...priorCoreMessages,
|
|
1460
|
+
{ role: "user", content: question }
|
|
1461
|
+
],
|
|
1325
1462
|
tools: vercelTools,
|
|
1326
1463
|
maxSteps: 12,
|
|
1327
1464
|
maxTokens: aiSettings.maxOutputTokens,
|
|
@@ -1615,6 +1752,7 @@ function createChatSessionsRoutes(ctx) {
|
|
|
1615
1752
|
var VALID_LOCATIONS = ["us-east5", "global"];
|
|
1616
1753
|
var MIN_MAX_OUTPUT_TOKENS = 256;
|
|
1617
1754
|
var MAX_MAX_OUTPUT_TOKENS = 64e3;
|
|
1755
|
+
var GCP_PROJECT_ID_REGEX = /^[a-z][-a-z0-9]{4,28}[a-z0-9]$/;
|
|
1618
1756
|
function isStringRecord(v) {
|
|
1619
1757
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1620
1758
|
}
|
|
@@ -1631,6 +1769,7 @@ function toWire(settings) {
|
|
|
1631
1769
|
chat_interface: settings.chatInterface,
|
|
1632
1770
|
max_output_tokens: settings.maxOutputTokens,
|
|
1633
1771
|
role_prompt: settings.rolePrompt,
|
|
1772
|
+
gcp_project_id: settings.gcpProjectId,
|
|
1634
1773
|
updated_at: settings.updatedAt ? settings.updatedAt.toISOString() : null,
|
|
1635
1774
|
updated_by_user_id: settings.updatedByUserId
|
|
1636
1775
|
};
|
|
@@ -1728,11 +1867,28 @@ function createAdminSettingsRoutes(ctx) {
|
|
|
1728
1867
|
return jsonResponse({ error: "invalid_role_prompt" }, 400);
|
|
1729
1868
|
}
|
|
1730
1869
|
}
|
|
1731
|
-
if (
|
|
1870
|
+
if ("gcp_project_id" in body) {
|
|
1871
|
+
const v = body.gcp_project_id;
|
|
1872
|
+
if (v === null) {
|
|
1873
|
+
patch.gcpProjectId = null;
|
|
1874
|
+
} else if (typeof v === "string") {
|
|
1875
|
+
const trimmed = v.trim();
|
|
1876
|
+
if (trimmed === "") {
|
|
1877
|
+
patch.gcpProjectId = null;
|
|
1878
|
+
} else if (trimmed.length > 64 || !GCP_PROJECT_ID_REGEX.test(trimmed)) {
|
|
1879
|
+
return jsonResponse({ error: "invalid_gcp_project_id" }, 400);
|
|
1880
|
+
} else {
|
|
1881
|
+
patch.gcpProjectId = trimmed;
|
|
1882
|
+
}
|
|
1883
|
+
} else {
|
|
1884
|
+
return jsonResponse({ error: "invalid_gcp_project_id" }, 400);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
if (patch.toolProvider === void 0 && patch.gcpLocation === void 0 && patch.chatInterface === void 0 && patch.maxOutputTokens === void 0 && !("rolePrompt" in patch) && !("gcpProjectId" in patch)) {
|
|
1732
1888
|
return jsonResponse(
|
|
1733
1889
|
{
|
|
1734
1890
|
error: "empty_patch",
|
|
1735
|
-
message: "Body must set at least one of tool_provider, gcp_location, chat_interface, max_output_tokens, role_prompt."
|
|
1891
|
+
message: "Body must set at least one of tool_provider, gcp_location, chat_interface, max_output_tokens, role_prompt, gcp_project_id."
|
|
1736
1892
|
},
|
|
1737
1893
|
400
|
|
1738
1894
|
);
|
|
@@ -1793,9 +1949,10 @@ function configureAiChat(opts) {
|
|
|
1793
1949
|
`Unknown tool provider '${providerId ?? settings.toolProvider}'. Registered: ${toolProviders2.map((p) => p.id).join(", ")}.`
|
|
1794
1950
|
);
|
|
1795
1951
|
}
|
|
1952
|
+
const effectiveProjectId = settings.gcpProjectId ?? opts.vertex.projectId;
|
|
1796
1953
|
const provider = def.createProvider({
|
|
1797
1954
|
auth: opts.vertex.auth,
|
|
1798
|
-
projectId:
|
|
1955
|
+
projectId: effectiveProjectId,
|
|
1799
1956
|
defaultLocation: opts.vertex.defaultLocation,
|
|
1800
1957
|
modelIds: opts.vertex.modelIds,
|
|
1801
1958
|
location: location ?? settings.gcpLocation
|