@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.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SystemBlock, T as ToolSchema, a as ToolContext, b as ToolDefinition, P as PresentPayload, c as PersistencePort, A as AuthPort, d as ScopePort, e as ToolsPort, V as VertexPort, L as LoggerPort } from '../types-
|
|
2
|
-
export { f as AiSettings, g as AiSettingsPatch, h as AppendMessageInput, i as AuthFail, j as AuthOk, k as AuthResult, B as Block, C as ChartSpec, l as ChatMessage, m as ChatMessageRole, n as ChatSession, o as CreateSessionInput, p as ListSessionsOpts, q as TERMINAL_TOOL_NAME, r as ToolResult, s as err, t as ok } from '../types-
|
|
1
|
+
import { S as SystemBlock, T as ToolSchema, a as ToolContext, b as ToolDefinition, P as PresentPayload, c as PersistencePort, A as AuthPort, d as ScopePort, e as ToolsPort, V as VertexPort, L as LoggerPort } from '../types-BnwUkqKb.cjs';
|
|
2
|
+
export { f as AiSettings, g as AiSettingsPatch, h as AppendMessageInput, i as AuthFail, j as AuthOk, k as AuthResult, B as Block, C as ChartSpec, l as ChatMessage, m as ChatMessageRole, n as ChatSession, o as CreateSessionInput, p as ListSessionsOpts, q as TERMINAL_TOOL_NAME, r as ToolResult, s as err, t as ok } from '../types-BnwUkqKb.cjs';
|
|
3
3
|
import { GoogleAuth } from 'google-auth-library';
|
|
4
4
|
export { GoogleAuth } from 'google-auth-library';
|
|
5
5
|
import 'zod';
|
|
@@ -55,6 +55,14 @@ type NormalizedToolResult = {
|
|
|
55
55
|
type NormalizedMessage = {
|
|
56
56
|
role: 'user';
|
|
57
57
|
text: string;
|
|
58
|
+
/**
|
|
59
|
+
* Cache hint: when true, the producing route is asking the provider
|
|
60
|
+
* to mark this message's content with a cache breakpoint so the
|
|
61
|
+
* full prefix becomes cacheable on a subsequent request. Anthropic
|
|
62
|
+
* applies `cache_control: ephemeral`; Vertex Gemini ignores the
|
|
63
|
+
* flag (its prefix cache works automatically).
|
|
64
|
+
*/
|
|
65
|
+
cached?: boolean;
|
|
58
66
|
} | {
|
|
59
67
|
role: 'assistant';
|
|
60
68
|
/** Free text the model emitted (zero-or-more text blocks joined as-is). */
|
|
@@ -69,6 +77,8 @@ type NormalizedMessage = {
|
|
|
69
77
|
* thought_signature`. Other adapters can ignore.
|
|
70
78
|
*/
|
|
71
79
|
providerData?: unknown;
|
|
80
|
+
/** See `user.cached`. */
|
|
81
|
+
cached?: boolean;
|
|
72
82
|
} | {
|
|
73
83
|
role: 'tool';
|
|
74
84
|
results: NormalizedToolResult[];
|
|
@@ -155,6 +165,16 @@ type AgentInput<S = unknown> = {
|
|
|
155
165
|
systemBlocks: SystemBlock[];
|
|
156
166
|
/** Constructed ToolProvider — caller resolves the right one via toolProviders[id].createProvider({...}). */
|
|
157
167
|
provider: ToolProvider;
|
|
168
|
+
/**
|
|
169
|
+
* Conversation history to seed the prompt with, in chronological order.
|
|
170
|
+
* Hosts pass this to give the model memory across turns in a chat session
|
|
171
|
+
* (so a follow-up like "summarize that" resolves the antecedent). The
|
|
172
|
+
* route handler is responsible for fetching prior `chat_messages` and
|
|
173
|
+
* normalising them; see `historyToNormalizedMessages` in `./history`.
|
|
174
|
+
* Tool-call provenance is intentionally not replayed — assistant turns
|
|
175
|
+
* here should be plain text only.
|
|
176
|
+
*/
|
|
177
|
+
priorMessages?: NormalizedMessage[];
|
|
158
178
|
/** Optional caps. Default both. */
|
|
159
179
|
maxToolTurns?: number;
|
|
160
180
|
maxOutputTokens?: number;
|
|
@@ -398,7 +418,7 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
398
418
|
/**
|
|
399
419
|
* `/api/admin/ai-settings` route factory — global AI configuration (super_admin only).
|
|
400
420
|
*
|
|
401
|
-
*
|
|
421
|
+
* Six patchable fields on the singleton settings row:
|
|
402
422
|
* - `tool_provider` — vendor that drives the agent tool loop. Validated
|
|
403
423
|
* against the registered `toolProviders` registry passed in via ctx.
|
|
404
424
|
* - `gcp_location` — the Vertex region every provider call hits. Stays
|
|
@@ -417,6 +437,10 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
417
437
|
* `configureAiChat({ rolePrompt })` fallback; we canonicalise an empty/
|
|
418
438
|
* whitespace-only string to `null` on write so the "no override" state has
|
|
419
439
|
* a single representation in storage.
|
|
440
|
+
* - `gcp_project_id` — admin-editable GCP project override. Empty string and
|
|
441
|
+
* the explicit JSON `null` both clear the override back to the host's
|
|
442
|
+
* static `VertexPort.projectId`. Validated against GCP's project-id
|
|
443
|
+
* format (`[a-z][-a-z0-9]{4,28}[a-z0-9]`) when non-null.
|
|
420
444
|
*
|
|
421
445
|
* Wire format is snake_case to preserve byte-for-byte parity with the
|
|
422
446
|
* host route the package replaces — existing host UIs keep working
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SystemBlock, T as ToolSchema, a as ToolContext, b as ToolDefinition, P as PresentPayload, c as PersistencePort, A as AuthPort, d as ScopePort, e as ToolsPort, V as VertexPort, L as LoggerPort } from '../types-
|
|
2
|
-
export { f as AiSettings, g as AiSettingsPatch, h as AppendMessageInput, i as AuthFail, j as AuthOk, k as AuthResult, B as Block, C as ChartSpec, l as ChatMessage, m as ChatMessageRole, n as ChatSession, o as CreateSessionInput, p as ListSessionsOpts, q as TERMINAL_TOOL_NAME, r as ToolResult, s as err, t as ok } from '../types-
|
|
1
|
+
import { S as SystemBlock, T as ToolSchema, a as ToolContext, b as ToolDefinition, P as PresentPayload, c as PersistencePort, A as AuthPort, d as ScopePort, e as ToolsPort, V as VertexPort, L as LoggerPort } from '../types-BnwUkqKb.js';
|
|
2
|
+
export { f as AiSettings, g as AiSettingsPatch, h as AppendMessageInput, i as AuthFail, j as AuthOk, k as AuthResult, B as Block, C as ChartSpec, l as ChatMessage, m as ChatMessageRole, n as ChatSession, o as CreateSessionInput, p as ListSessionsOpts, q as TERMINAL_TOOL_NAME, r as ToolResult, s as err, t as ok } from '../types-BnwUkqKb.js';
|
|
3
3
|
import { GoogleAuth } from 'google-auth-library';
|
|
4
4
|
export { GoogleAuth } from 'google-auth-library';
|
|
5
5
|
import 'zod';
|
|
@@ -55,6 +55,14 @@ type NormalizedToolResult = {
|
|
|
55
55
|
type NormalizedMessage = {
|
|
56
56
|
role: 'user';
|
|
57
57
|
text: string;
|
|
58
|
+
/**
|
|
59
|
+
* Cache hint: when true, the producing route is asking the provider
|
|
60
|
+
* to mark this message's content with a cache breakpoint so the
|
|
61
|
+
* full prefix becomes cacheable on a subsequent request. Anthropic
|
|
62
|
+
* applies `cache_control: ephemeral`; Vertex Gemini ignores the
|
|
63
|
+
* flag (its prefix cache works automatically).
|
|
64
|
+
*/
|
|
65
|
+
cached?: boolean;
|
|
58
66
|
} | {
|
|
59
67
|
role: 'assistant';
|
|
60
68
|
/** Free text the model emitted (zero-or-more text blocks joined as-is). */
|
|
@@ -69,6 +77,8 @@ type NormalizedMessage = {
|
|
|
69
77
|
* thought_signature`. Other adapters can ignore.
|
|
70
78
|
*/
|
|
71
79
|
providerData?: unknown;
|
|
80
|
+
/** See `user.cached`. */
|
|
81
|
+
cached?: boolean;
|
|
72
82
|
} | {
|
|
73
83
|
role: 'tool';
|
|
74
84
|
results: NormalizedToolResult[];
|
|
@@ -155,6 +165,16 @@ type AgentInput<S = unknown> = {
|
|
|
155
165
|
systemBlocks: SystemBlock[];
|
|
156
166
|
/** Constructed ToolProvider — caller resolves the right one via toolProviders[id].createProvider({...}). */
|
|
157
167
|
provider: ToolProvider;
|
|
168
|
+
/**
|
|
169
|
+
* Conversation history to seed the prompt with, in chronological order.
|
|
170
|
+
* Hosts pass this to give the model memory across turns in a chat session
|
|
171
|
+
* (so a follow-up like "summarize that" resolves the antecedent). The
|
|
172
|
+
* route handler is responsible for fetching prior `chat_messages` and
|
|
173
|
+
* normalising them; see `historyToNormalizedMessages` in `./history`.
|
|
174
|
+
* Tool-call provenance is intentionally not replayed — assistant turns
|
|
175
|
+
* here should be plain text only.
|
|
176
|
+
*/
|
|
177
|
+
priorMessages?: NormalizedMessage[];
|
|
158
178
|
/** Optional caps. Default both. */
|
|
159
179
|
maxToolTurns?: number;
|
|
160
180
|
maxOutputTokens?: number;
|
|
@@ -398,7 +418,7 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
398
418
|
/**
|
|
399
419
|
* `/api/admin/ai-settings` route factory — global AI configuration (super_admin only).
|
|
400
420
|
*
|
|
401
|
-
*
|
|
421
|
+
* Six patchable fields on the singleton settings row:
|
|
402
422
|
* - `tool_provider` — vendor that drives the agent tool loop. Validated
|
|
403
423
|
* against the registered `toolProviders` registry passed in via ctx.
|
|
404
424
|
* - `gcp_location` — the Vertex region every provider call hits. Stays
|
|
@@ -417,6 +437,10 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
417
437
|
* `configureAiChat({ rolePrompt })` fallback; we canonicalise an empty/
|
|
418
438
|
* whitespace-only string to `null` on write so the "no override" state has
|
|
419
439
|
* a single representation in storage.
|
|
440
|
+
* - `gcp_project_id` — admin-editable GCP project override. Empty string and
|
|
441
|
+
* the explicit JSON `null` both clear the override back to the host's
|
|
442
|
+
* static `VertexPort.projectId`. Validated against GCP's project-id
|
|
443
|
+
* format (`[a-z][-a-z0-9]{4,28}[a-z0-9]`) when non-null.
|
|
420
444
|
*
|
|
421
445
|
* Wire format is snake_case to preserve byte-for-byte parity with the
|
|
422
446
|
* host route the package replaces — existing host UIs keep working
|
package/dist/server/index.js
CHANGED
|
@@ -23,7 +23,10 @@ async function runAgent(input) {
|
|
|
23
23
|
const maxOutputTokens = input.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
24
24
|
const transcript = [];
|
|
25
25
|
transcript.push({ kind: "user", text: input.question });
|
|
26
|
-
const messages = [
|
|
26
|
+
const messages = [
|
|
27
|
+
...input.priorMessages ?? [],
|
|
28
|
+
{ role: "user", text: input.question }
|
|
29
|
+
];
|
|
27
30
|
const system = input.systemBlocks;
|
|
28
31
|
const toolSchemas = Object.values(input.tools).map((t) => t.schema);
|
|
29
32
|
let toolCallCount = 0;
|
|
@@ -216,11 +219,28 @@ function toAnthropicMessages(messages) {
|
|
|
216
219
|
const out = [];
|
|
217
220
|
for (const msg of messages) {
|
|
218
221
|
if (msg.role === "user") {
|
|
219
|
-
|
|
222
|
+
if (msg.cached) {
|
|
223
|
+
out.push({
|
|
224
|
+
role: "user",
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: "text",
|
|
228
|
+
text: msg.text,
|
|
229
|
+
cache_control: { type: "ephemeral" }
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
out.push({ role: "user", content: msg.text });
|
|
235
|
+
}
|
|
220
236
|
} else if (msg.role === "assistant") {
|
|
221
237
|
const blocks = [];
|
|
222
238
|
if (msg.text) {
|
|
223
|
-
|
|
239
|
+
const textBlock = { type: "text", text: msg.text };
|
|
240
|
+
if (msg.cached && msg.toolCalls.length === 0) {
|
|
241
|
+
textBlock.cache_control = { type: "ephemeral" };
|
|
242
|
+
}
|
|
243
|
+
blocks.push(textBlock);
|
|
224
244
|
}
|
|
225
245
|
for (const tc of msg.toolCalls) {
|
|
226
246
|
blocks.push({
|
|
@@ -572,6 +592,87 @@ var toolProviders = [
|
|
|
572
592
|
function getToolProvider(id) {
|
|
573
593
|
return toolProviders.find((p) => p.id === id);
|
|
574
594
|
}
|
|
595
|
+
|
|
596
|
+
// src/server/history.ts
|
|
597
|
+
var DEFAULT_MAX_HISTORY_PAIRS = 20;
|
|
598
|
+
var DEFAULT_MAX_TEXT_CHARS = 4e3;
|
|
599
|
+
function historyToNormalizedMessages(rows, opts = {}) {
|
|
600
|
+
const maxPairs = opts.maxPairs ?? DEFAULT_MAX_HISTORY_PAIRS;
|
|
601
|
+
const maxTextChars = opts.maxTextChars ?? DEFAULT_MAX_TEXT_CHARS;
|
|
602
|
+
const pairs = [];
|
|
603
|
+
let i = 0;
|
|
604
|
+
while (i < rows.length) {
|
|
605
|
+
const row = rows[i];
|
|
606
|
+
if (row.role !== "user" || !row.question) {
|
|
607
|
+
i += 1;
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const next = rows[i + 1];
|
|
611
|
+
if (next?.role !== "assistant") {
|
|
612
|
+
i += 1;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
const assistantText = truncate(
|
|
616
|
+
assistantMessageToText(next),
|
|
617
|
+
maxTextChars
|
|
618
|
+
);
|
|
619
|
+
if (assistantText) {
|
|
620
|
+
pairs.push([
|
|
621
|
+
{ role: "user", text: truncate(row.question, maxTextChars) },
|
|
622
|
+
{ role: "assistant", text: assistantText, toolCalls: [] }
|
|
623
|
+
]);
|
|
624
|
+
}
|
|
625
|
+
i += 2;
|
|
626
|
+
}
|
|
627
|
+
const kept = maxPairs > 0 ? pairs.slice(-maxPairs) : pairs;
|
|
628
|
+
return kept.flat();
|
|
629
|
+
}
|
|
630
|
+
function truncate(text, max) {
|
|
631
|
+
if (max <= 0 || text.length <= max) return text;
|
|
632
|
+
return text.slice(0, max);
|
|
633
|
+
}
|
|
634
|
+
function assistantMessageToText(row) {
|
|
635
|
+
if (row.errorJson) return "";
|
|
636
|
+
const proseText = proseToText(row.prose);
|
|
637
|
+
if (proseText) return proseText;
|
|
638
|
+
const blockText = blocksToText(row.blocks);
|
|
639
|
+
return blockText;
|
|
640
|
+
}
|
|
641
|
+
function proseToText(prose) {
|
|
642
|
+
if (!prose || typeof prose !== "object") return "";
|
|
643
|
+
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);
|
|
644
|
+
return entries.map(([, v]) => v).join("\n\n").trim();
|
|
645
|
+
}
|
|
646
|
+
function blocksToText(blocks) {
|
|
647
|
+
if (!Array.isArray(blocks)) return "";
|
|
648
|
+
const parts = [];
|
|
649
|
+
for (const raw of blocks) {
|
|
650
|
+
if (!raw || typeof raw !== "object") continue;
|
|
651
|
+
const b = raw;
|
|
652
|
+
switch (b.kind) {
|
|
653
|
+
case "paragraph_brief": {
|
|
654
|
+
const facts = (b.key_facts ?? []).filter((f) => f && f.trim());
|
|
655
|
+
if (b.topic) parts.push(b.topic);
|
|
656
|
+
if (facts.length) parts.push(facts.join("\n"));
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
case "list": {
|
|
660
|
+
const items = (b.items ?? []).filter((s) => s && s.trim());
|
|
661
|
+
if (b.title) parts.push(b.title);
|
|
662
|
+
if (items.length) parts.push(items.map((s) => `- ${s}`).join("\n"));
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
665
|
+
case "chart":
|
|
666
|
+
case "table":
|
|
667
|
+
if (b.title) parts.push(`[${b.title}]`);
|
|
668
|
+
break;
|
|
669
|
+
case "callout":
|
|
670
|
+
if (b.text) parts.push(b.text);
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return parts.join("\n\n").trim();
|
|
675
|
+
}
|
|
575
676
|
function vertexHost2(location) {
|
|
576
677
|
return location === "global" ? "aiplatform.googleapis.com" : `${location}-aiplatform.googleapis.com`;
|
|
577
678
|
}
|
|
@@ -912,13 +1013,25 @@ function createAgentCustomRoutes(ctx) {
|
|
|
912
1013
|
const rawChatSessionId = body?.chatSessionId;
|
|
913
1014
|
const incomingChatSessionId = typeof rawChatSessionId === "number" && Number.isInteger(rawChatSessionId) ? rawChatSessionId : null;
|
|
914
1015
|
const aiSettings = await persistence.getAiSettings();
|
|
1016
|
+
const effectiveProjectId = aiSettings.gcpProjectId ?? vertex.projectId;
|
|
915
1017
|
let chatSessionId;
|
|
1018
|
+
let priorMessages = [];
|
|
916
1019
|
if (incomingChatSessionId !== null) {
|
|
917
1020
|
const owned = await persistence.getSession(incomingChatSessionId, userId);
|
|
918
1021
|
if (!owned) {
|
|
919
1022
|
return jsonError(404, "NOT_FOUND", "Chat session not found.");
|
|
920
1023
|
}
|
|
921
1024
|
chatSessionId = owned.id;
|
|
1025
|
+
const stored = await persistence.listMessagesForSession(chatSessionId, userId);
|
|
1026
|
+
priorMessages = historyToNormalizedMessages(stored);
|
|
1027
|
+
if (priorMessages.length > 0) {
|
|
1028
|
+
const last = priorMessages[priorMessages.length - 1];
|
|
1029
|
+
if (last.role === "assistant" && last.toolCalls.length === 0) {
|
|
1030
|
+
priorMessages[priorMessages.length - 1] = { ...last, cached: true };
|
|
1031
|
+
} else if (last.role === "user") {
|
|
1032
|
+
priorMessages[priorMessages.length - 1] = { ...last, cached: true };
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
922
1035
|
} else {
|
|
923
1036
|
const created = await persistence.createSession({
|
|
924
1037
|
userId,
|
|
@@ -955,7 +1068,7 @@ function createAgentCustomRoutes(ctx) {
|
|
|
955
1068
|
}
|
|
956
1069
|
const provider = def.createProvider({
|
|
957
1070
|
auth: vertex.auth,
|
|
958
|
-
projectId:
|
|
1071
|
+
projectId: effectiveProjectId,
|
|
959
1072
|
defaultLocation: vertex.defaultLocation,
|
|
960
1073
|
modelIds: vertex.modelIds,
|
|
961
1074
|
location: aiSettings.gcpLocation
|
|
@@ -1006,6 +1119,7 @@ data: ${JSON.stringify(data)}
|
|
|
1006
1119
|
send("meta", { chatSessionId, scopeLabel });
|
|
1007
1120
|
const agentResult = await runAgent({
|
|
1008
1121
|
question,
|
|
1122
|
+
priorMessages,
|
|
1009
1123
|
ctx: toolContext,
|
|
1010
1124
|
tools: tools.tools,
|
|
1011
1125
|
systemBlocks,
|
|
@@ -1033,7 +1147,7 @@ data: ${JSON.stringify(data)}
|
|
|
1033
1147
|
}
|
|
1034
1148
|
for await (const token of narratorFn({
|
|
1035
1149
|
auth: vertex.auth,
|
|
1036
|
-
projectId:
|
|
1150
|
+
projectId: effectiveProjectId,
|
|
1037
1151
|
location: aiSettings.gcpLocation,
|
|
1038
1152
|
modelId: narratorModelId,
|
|
1039
1153
|
maxTokens: aiSettings.maxOutputTokens,
|
|
@@ -1224,7 +1338,17 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1224
1338
|
if (short) return short;
|
|
1225
1339
|
}
|
|
1226
1340
|
const body = await req.json().catch(() => null);
|
|
1227
|
-
|
|
1341
|
+
let question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
1342
|
+
if (!question && Array.isArray(body?.messages)) {
|
|
1343
|
+
const msgs = body.messages;
|
|
1344
|
+
for (let i = msgs.length - 1; i >= 0; i -= 1) {
|
|
1345
|
+
const m = msgs[i];
|
|
1346
|
+
if (m && m.role === "user" && typeof m.content === "string") {
|
|
1347
|
+
question = m.content.trim();
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1228
1352
|
if (!question) {
|
|
1229
1353
|
return jsonError2(
|
|
1230
1354
|
400,
|
|
@@ -1237,7 +1361,9 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1237
1361
|
const rawModel = body?.model;
|
|
1238
1362
|
const requestedModel = typeof rawModel === "string" && VALID_MODELS.has(rawModel) ? rawModel : null;
|
|
1239
1363
|
const aiSettings = await persistence.getAiSettings();
|
|
1364
|
+
const effectiveProjectId = aiSettings.gcpProjectId ?? vertex.projectId;
|
|
1240
1365
|
let chatSessionId;
|
|
1366
|
+
let priorMessages = [];
|
|
1241
1367
|
if (incomingChatSessionId !== null) {
|
|
1242
1368
|
const owned = await persistence.getSession(
|
|
1243
1369
|
incomingChatSessionId,
|
|
@@ -1247,6 +1373,11 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1247
1373
|
return jsonError2(404, "NOT_FOUND", "Chat session not found.");
|
|
1248
1374
|
}
|
|
1249
1375
|
chatSessionId = owned.id;
|
|
1376
|
+
const stored = await persistence.listMessagesForSession(
|
|
1377
|
+
chatSessionId,
|
|
1378
|
+
userId
|
|
1379
|
+
);
|
|
1380
|
+
priorMessages = historyToNormalizedMessages(stored);
|
|
1250
1381
|
} else {
|
|
1251
1382
|
const created = await persistence.createSession({
|
|
1252
1383
|
userId,
|
|
@@ -1308,18 +1439,24 @@ function createAgentVercelRoutes(ctx) {
|
|
|
1308
1439
|
});
|
|
1309
1440
|
const system = systemBlocks.map((b) => b.text).join("\n\n");
|
|
1310
1441
|
const model = provider === "claude" ? createVertexAnthropic({
|
|
1311
|
-
project:
|
|
1442
|
+
project: effectiveProjectId,
|
|
1312
1443
|
location: vertex.defaultLocation,
|
|
1313
1444
|
googleAuthOptions: {}
|
|
1314
1445
|
})(vertex.modelIds.claude) : createVertex({
|
|
1315
|
-
project:
|
|
1446
|
+
project: effectiveProjectId,
|
|
1316
1447
|
location: aiSettings.gcpLocation,
|
|
1317
1448
|
googleAuthOptions: {}
|
|
1318
1449
|
})(vertex.modelIds.gemini);
|
|
1450
|
+
const priorCoreMessages = priorMessages.filter(
|
|
1451
|
+
(m) => m.role === "user" || m.role === "assistant"
|
|
1452
|
+
).map((m) => ({ role: m.role, content: m.text }));
|
|
1319
1453
|
const result = streamText({
|
|
1320
1454
|
model,
|
|
1321
1455
|
system,
|
|
1322
|
-
messages: [
|
|
1456
|
+
messages: [
|
|
1457
|
+
...priorCoreMessages,
|
|
1458
|
+
{ role: "user", content: question }
|
|
1459
|
+
],
|
|
1323
1460
|
tools: vercelTools,
|
|
1324
1461
|
maxSteps: 12,
|
|
1325
1462
|
maxTokens: aiSettings.maxOutputTokens,
|
|
@@ -1613,6 +1750,7 @@ function createChatSessionsRoutes(ctx) {
|
|
|
1613
1750
|
var VALID_LOCATIONS = ["us-east5", "global"];
|
|
1614
1751
|
var MIN_MAX_OUTPUT_TOKENS = 256;
|
|
1615
1752
|
var MAX_MAX_OUTPUT_TOKENS = 64e3;
|
|
1753
|
+
var GCP_PROJECT_ID_REGEX = /^[a-z][-a-z0-9]{4,28}[a-z0-9]$/;
|
|
1616
1754
|
function isStringRecord(v) {
|
|
1617
1755
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1618
1756
|
}
|
|
@@ -1629,6 +1767,7 @@ function toWire(settings) {
|
|
|
1629
1767
|
chat_interface: settings.chatInterface,
|
|
1630
1768
|
max_output_tokens: settings.maxOutputTokens,
|
|
1631
1769
|
role_prompt: settings.rolePrompt,
|
|
1770
|
+
gcp_project_id: settings.gcpProjectId,
|
|
1632
1771
|
updated_at: settings.updatedAt ? settings.updatedAt.toISOString() : null,
|
|
1633
1772
|
updated_by_user_id: settings.updatedByUserId
|
|
1634
1773
|
};
|
|
@@ -1726,11 +1865,28 @@ function createAdminSettingsRoutes(ctx) {
|
|
|
1726
1865
|
return jsonResponse({ error: "invalid_role_prompt" }, 400);
|
|
1727
1866
|
}
|
|
1728
1867
|
}
|
|
1729
|
-
if (
|
|
1868
|
+
if ("gcp_project_id" in body) {
|
|
1869
|
+
const v = body.gcp_project_id;
|
|
1870
|
+
if (v === null) {
|
|
1871
|
+
patch.gcpProjectId = null;
|
|
1872
|
+
} else if (typeof v === "string") {
|
|
1873
|
+
const trimmed = v.trim();
|
|
1874
|
+
if (trimmed === "") {
|
|
1875
|
+
patch.gcpProjectId = null;
|
|
1876
|
+
} else if (trimmed.length > 64 || !GCP_PROJECT_ID_REGEX.test(trimmed)) {
|
|
1877
|
+
return jsonResponse({ error: "invalid_gcp_project_id" }, 400);
|
|
1878
|
+
} else {
|
|
1879
|
+
patch.gcpProjectId = trimmed;
|
|
1880
|
+
}
|
|
1881
|
+
} else {
|
|
1882
|
+
return jsonResponse({ error: "invalid_gcp_project_id" }, 400);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (patch.toolProvider === void 0 && patch.gcpLocation === void 0 && patch.chatInterface === void 0 && patch.maxOutputTokens === void 0 && !("rolePrompt" in patch) && !("gcpProjectId" in patch)) {
|
|
1730
1886
|
return jsonResponse(
|
|
1731
1887
|
{
|
|
1732
1888
|
error: "empty_patch",
|
|
1733
|
-
message: "Body must set at least one of tool_provider, gcp_location, chat_interface, max_output_tokens, role_prompt."
|
|
1889
|
+
message: "Body must set at least one of tool_provider, gcp_location, chat_interface, max_output_tokens, role_prompt, gcp_project_id."
|
|
1734
1890
|
},
|
|
1735
1891
|
400
|
|
1736
1892
|
);
|
|
@@ -1791,9 +1947,10 @@ function configureAiChat(opts) {
|
|
|
1791
1947
|
`Unknown tool provider '${providerId ?? settings.toolProvider}'. Registered: ${toolProviders2.map((p) => p.id).join(", ")}.`
|
|
1792
1948
|
);
|
|
1793
1949
|
}
|
|
1950
|
+
const effectiveProjectId = settings.gcpProjectId ?? opts.vertex.projectId;
|
|
1794
1951
|
const provider = def.createProvider({
|
|
1795
1952
|
auth: opts.vertex.auth,
|
|
1796
|
-
projectId:
|
|
1953
|
+
projectId: effectiveProjectId,
|
|
1797
1954
|
defaultLocation: opts.vertex.defaultLocation,
|
|
1798
1955
|
modelIds: opts.vertex.modelIds,
|
|
1799
1956
|
location: location ?? settings.gcpLocation
|