@firstlovecenter/ai-chat 0.2.2 → 0.5.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 +38 -0
- package/dist/drizzle/index.d.cts +2 -1
- package/dist/drizzle/index.d.ts +2 -1
- package/dist/prisma/index.d.cts +2 -1
- package/dist/prisma/index.d.ts +2 -1
- package/dist/server/index.cjs +345 -4
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +40 -3
- package/dist/server/index.d.ts +40 -3
- package/dist/server/index.js +345 -4
- package/dist/server/index.js.map +1 -1
- package/dist/{types-DNwFvL-C.d.cts → types-CDKxdzQc.d.cts} +9 -0
- package/dist/{types-DNwFvL-C.d.ts → types-CDKxdzQc.d.ts} +9 -0
- package/dist/ui/index.cjs +968 -88
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.cts +24 -12
- package/dist/ui/index.d.ts +24 -12
- package/dist/ui/index.js +966 -89
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
package/dist/server/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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-CDKxdzQc.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-CDKxdzQc.cjs';
|
|
3
3
|
import { GoogleAuth } from 'google-auth-library';
|
|
4
4
|
export { GoogleAuth } from 'google-auth-library';
|
|
5
|
+
import 'zod';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Provider-agnostic tool-calling abstraction.
|
|
@@ -302,6 +303,34 @@ declare function createAgentCustomRoutes<S>(ctx: AgentCustomRouteCtx<S>): {
|
|
|
302
303
|
POST: (req: Request) => Promise<Response>;
|
|
303
304
|
};
|
|
304
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Streaming-route hooks for the Vercel AI SDK chat.
|
|
308
|
+
*
|
|
309
|
+
* Identical to `AgentCustomHooks` — the lifecycle (pre-auth → auth →
|
|
310
|
+
* post-auth → resolve session → onSessionStart → stream → onSessionEnd)
|
|
311
|
+
* is the same. We re-alias rather than introducing a new type so consumers
|
|
312
|
+
* can plug a single hook bag into both routes.
|
|
313
|
+
*/
|
|
314
|
+
type AgentVercelHooks$1<S> = AgentCustomHooks$1<S>;
|
|
315
|
+
type AgentVercelRouteCtx<S> = {
|
|
316
|
+
persistence: PersistencePort;
|
|
317
|
+
auth: AuthPort<S>;
|
|
318
|
+
scope: ScopePort<S>;
|
|
319
|
+
tools: ToolsPort;
|
|
320
|
+
vertex: VertexPort;
|
|
321
|
+
logger?: LoggerPort;
|
|
322
|
+
/**
|
|
323
|
+
* Optional lifecycle hooks. See `AgentCustomHooks` for the available
|
|
324
|
+
* extension points (shutdown gating, rate limiting, per-request
|
|
325
|
+
* resource setup/teardown).
|
|
326
|
+
*/
|
|
327
|
+
hooks?: AgentVercelHooks$1<S>;
|
|
328
|
+
};
|
|
329
|
+
declare function createAgentVercelRoutes<S>(ctx: AgentVercelRouteCtx<S>): {
|
|
330
|
+
/** Next.js-compatible POST handler. */
|
|
331
|
+
POST: (req: Request) => Promise<Response>;
|
|
332
|
+
};
|
|
333
|
+
|
|
305
334
|
/**
|
|
306
335
|
* `chat-sessions` route factory — host-agnostic CRUD for chat sessions.
|
|
307
336
|
*
|
|
@@ -501,6 +530,7 @@ type AiChatRuntime<S = unknown> = {
|
|
|
501
530
|
}) => Promise<AgentResult>;
|
|
502
531
|
routes: {
|
|
503
532
|
agentCustom: ReturnType<typeof createAgentCustomRoutes<S>>;
|
|
533
|
+
agentVercel: ReturnType<typeof createAgentVercelRoutes<S>>;
|
|
504
534
|
chatSessions: ReturnType<typeof createChatSessionsRoutes<S>>;
|
|
505
535
|
adminSettings: ReturnType<typeof createAdminSettingsRoutes<S>>;
|
|
506
536
|
};
|
|
@@ -513,5 +543,12 @@ declare function configureAiChat<S = unknown>(opts: ConfigureAiChatOpts<S>): AiC
|
|
|
513
543
|
|
|
514
544
|
type RouteHooks<S> = RouteHooks$1<S>;
|
|
515
545
|
type AgentCustomHooks<S> = AgentCustomHooks$1<S>;
|
|
546
|
+
/**
|
|
547
|
+
* Alias of `AgentCustomHooks` — the Vercel AI SDK chat reuses the same
|
|
548
|
+
* lifecycle (pre-auth → auth → post-auth → resolve session → onSessionStart
|
|
549
|
+
* → stream → onSessionEnd). Re-exported for callers that prefer the more
|
|
550
|
+
* specific name.
|
|
551
|
+
*/
|
|
552
|
+
type AgentVercelHooks<S> = AgentVercelHooks$1<S>;
|
|
516
553
|
|
|
517
|
-
export { type AgentCustomHooks, type AgentInput, type AgentResult, type AiChatRuntime, AuthPort, BUILTIN_CHAT_INTERFACE_IDS, type ChatInterfaceRegistryEntry, type ConfigureAiChatOpts, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MAX_TOOL_TURNS, LoggerPort, PersistencePort, PresentPayload, type ProviderInitOpts, type RouteHooks, ScopePort, SystemBlock, ToolContext, ToolDefinition, type ToolProviderDef, ToolSchema, ToolsPort, type TranscriptEntry, VertexPort, configureAiChat, getToolProvider, runAgent, toolProviders };
|
|
554
|
+
export { type AgentCustomHooks, type AgentInput, type AgentResult, type AgentVercelHooks, type AiChatRuntime, AuthPort, BUILTIN_CHAT_INTERFACE_IDS, type ChatInterfaceRegistryEntry, type ConfigureAiChatOpts, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MAX_TOOL_TURNS, LoggerPort, PersistencePort, PresentPayload, type ProviderInitOpts, type RouteHooks, ScopePort, SystemBlock, ToolContext, ToolDefinition, type ToolProviderDef, ToolSchema, ToolsPort, type TranscriptEntry, VertexPort, configureAiChat, getToolProvider, runAgent, toolProviders };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
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-CDKxdzQc.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-CDKxdzQc.js';
|
|
3
3
|
import { GoogleAuth } from 'google-auth-library';
|
|
4
4
|
export { GoogleAuth } from 'google-auth-library';
|
|
5
|
+
import 'zod';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Provider-agnostic tool-calling abstraction.
|
|
@@ -302,6 +303,34 @@ declare function createAgentCustomRoutes<S>(ctx: AgentCustomRouteCtx<S>): {
|
|
|
302
303
|
POST: (req: Request) => Promise<Response>;
|
|
303
304
|
};
|
|
304
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Streaming-route hooks for the Vercel AI SDK chat.
|
|
308
|
+
*
|
|
309
|
+
* Identical to `AgentCustomHooks` — the lifecycle (pre-auth → auth →
|
|
310
|
+
* post-auth → resolve session → onSessionStart → stream → onSessionEnd)
|
|
311
|
+
* is the same. We re-alias rather than introducing a new type so consumers
|
|
312
|
+
* can plug a single hook bag into both routes.
|
|
313
|
+
*/
|
|
314
|
+
type AgentVercelHooks$1<S> = AgentCustomHooks$1<S>;
|
|
315
|
+
type AgentVercelRouteCtx<S> = {
|
|
316
|
+
persistence: PersistencePort;
|
|
317
|
+
auth: AuthPort<S>;
|
|
318
|
+
scope: ScopePort<S>;
|
|
319
|
+
tools: ToolsPort;
|
|
320
|
+
vertex: VertexPort;
|
|
321
|
+
logger?: LoggerPort;
|
|
322
|
+
/**
|
|
323
|
+
* Optional lifecycle hooks. See `AgentCustomHooks` for the available
|
|
324
|
+
* extension points (shutdown gating, rate limiting, per-request
|
|
325
|
+
* resource setup/teardown).
|
|
326
|
+
*/
|
|
327
|
+
hooks?: AgentVercelHooks$1<S>;
|
|
328
|
+
};
|
|
329
|
+
declare function createAgentVercelRoutes<S>(ctx: AgentVercelRouteCtx<S>): {
|
|
330
|
+
/** Next.js-compatible POST handler. */
|
|
331
|
+
POST: (req: Request) => Promise<Response>;
|
|
332
|
+
};
|
|
333
|
+
|
|
305
334
|
/**
|
|
306
335
|
* `chat-sessions` route factory — host-agnostic CRUD for chat sessions.
|
|
307
336
|
*
|
|
@@ -501,6 +530,7 @@ type AiChatRuntime<S = unknown> = {
|
|
|
501
530
|
}) => Promise<AgentResult>;
|
|
502
531
|
routes: {
|
|
503
532
|
agentCustom: ReturnType<typeof createAgentCustomRoutes<S>>;
|
|
533
|
+
agentVercel: ReturnType<typeof createAgentVercelRoutes<S>>;
|
|
504
534
|
chatSessions: ReturnType<typeof createChatSessionsRoutes<S>>;
|
|
505
535
|
adminSettings: ReturnType<typeof createAdminSettingsRoutes<S>>;
|
|
506
536
|
};
|
|
@@ -513,5 +543,12 @@ declare function configureAiChat<S = unknown>(opts: ConfigureAiChatOpts<S>): AiC
|
|
|
513
543
|
|
|
514
544
|
type RouteHooks<S> = RouteHooks$1<S>;
|
|
515
545
|
type AgentCustomHooks<S> = AgentCustomHooks$1<S>;
|
|
546
|
+
/**
|
|
547
|
+
* Alias of `AgentCustomHooks` — the Vercel AI SDK chat reuses the same
|
|
548
|
+
* lifecycle (pre-auth → auth → post-auth → resolve session → onSessionStart
|
|
549
|
+
* → stream → onSessionEnd). Re-exported for callers that prefer the more
|
|
550
|
+
* specific name.
|
|
551
|
+
*/
|
|
552
|
+
type AgentVercelHooks<S> = AgentVercelHooks$1<S>;
|
|
516
553
|
|
|
517
|
-
export { type AgentCustomHooks, type AgentInput, type AgentResult, type AiChatRuntime, AuthPort, BUILTIN_CHAT_INTERFACE_IDS, type ChatInterfaceRegistryEntry, type ConfigureAiChatOpts, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MAX_TOOL_TURNS, LoggerPort, PersistencePort, PresentPayload, type ProviderInitOpts, type RouteHooks, ScopePort, SystemBlock, ToolContext, ToolDefinition, type ToolProviderDef, ToolSchema, ToolsPort, type TranscriptEntry, VertexPort, configureAiChat, getToolProvider, runAgent, toolProviders };
|
|
554
|
+
export { type AgentCustomHooks, type AgentInput, type AgentResult, type AgentVercelHooks, type AiChatRuntime, AuthPort, BUILTIN_CHAT_INTERFACE_IDS, type ChatInterfaceRegistryEntry, type ConfigureAiChatOpts, DEFAULT_MAX_OUTPUT_TOKENS, DEFAULT_MAX_TOOL_TURNS, LoggerPort, PersistencePort, PresentPayload, type ProviderInitOpts, type RouteHooks, ScopePort, SystemBlock, ToolContext, ToolDefinition, type ToolProviderDef, ToolSchema, ToolsPort, type TranscriptEntry, VertexPort, configureAiChat, getToolProvider, runAgent, toolProviders };
|
package/dist/server/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { AnthropicVertex } from '@anthropic-ai/vertex-sdk';
|
|
2
2
|
import { randomUUID } from 'crypto';
|
|
3
|
+
import { StreamData, streamText, tool } from 'ai';
|
|
4
|
+
import { createVertex } from '@ai-sdk/google-vertex';
|
|
5
|
+
import { createVertexAnthropic } from '@ai-sdk/google-vertex/anthropic';
|
|
3
6
|
export { GoogleAuth } from 'google-auth-library';
|
|
4
7
|
|
|
5
8
|
// src/server/tools/types.ts
|
|
@@ -70,8 +73,8 @@ async function runAgent(input) {
|
|
|
70
73
|
const toolResults = [];
|
|
71
74
|
for (const tc of response.toolCalls) {
|
|
72
75
|
transcript.push({ kind: "tool_use", name: tc.name, input: tc.input });
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
76
|
+
const tool2 = input.tools[tc.name];
|
|
77
|
+
if (!tool2) {
|
|
75
78
|
const errResult = {
|
|
76
79
|
ok: false,
|
|
77
80
|
error: { code: "UNKNOWN_TOOL", message: `Unknown tool: ${tc.name}` }
|
|
@@ -85,7 +88,7 @@ async function runAgent(input) {
|
|
|
85
88
|
});
|
|
86
89
|
continue;
|
|
87
90
|
}
|
|
88
|
-
const result = await
|
|
91
|
+
const result = await tool2.execute(tc.input, {
|
|
89
92
|
...input.ctx,
|
|
90
93
|
toolCallCount
|
|
91
94
|
});
|
|
@@ -128,8 +131,46 @@ async function runAgent(input) {
|
|
|
128
131
|
transcript
|
|
129
132
|
};
|
|
130
133
|
}
|
|
134
|
+
if (allBlocksEmpty(presentPayload.blocks)) {
|
|
135
|
+
const lastAssistantText = [...transcript].reverse().find((e) => e.kind === "assistant_text");
|
|
136
|
+
const text = lastAssistantText?.text.trim();
|
|
137
|
+
if (text) {
|
|
138
|
+
const topic = input.question.length > 80 ? input.question.slice(0, 77) + "..." : input.question;
|
|
139
|
+
presentPayload = {
|
|
140
|
+
blocks: [
|
|
141
|
+
{
|
|
142
|
+
kind: "paragraph_brief",
|
|
143
|
+
topic,
|
|
144
|
+
key_facts: [text]
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
raw_numbers: presentPayload.raw_numbers ?? {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
131
151
|
return { ok: true, structured: presentPayload, toolCallCount, transcript };
|
|
132
152
|
}
|
|
153
|
+
function allBlocksEmpty(blocks) {
|
|
154
|
+
if (blocks.length === 0) return true;
|
|
155
|
+
return blocks.every((b) => {
|
|
156
|
+
if (b.kind === "paragraph_brief") {
|
|
157
|
+
return b.key_facts.length === 0 || b.key_facts.every((f) => !f.trim());
|
|
158
|
+
}
|
|
159
|
+
if (b.kind === "list") {
|
|
160
|
+
return b.items.length === 0 || b.items.every((i) => !i.trim());
|
|
161
|
+
}
|
|
162
|
+
if (b.kind === "callout") {
|
|
163
|
+
return !b.text.trim();
|
|
164
|
+
}
|
|
165
|
+
if (b.kind === "chart") {
|
|
166
|
+
return b.data.length === 0;
|
|
167
|
+
}
|
|
168
|
+
if (b.kind === "table") {
|
|
169
|
+
return b.rows.length === 0;
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
133
174
|
var ClaudeToolProvider = class {
|
|
134
175
|
id = "claude";
|
|
135
176
|
client;
|
|
@@ -1097,6 +1138,297 @@ data: {}
|
|
|
1097
1138
|
}
|
|
1098
1139
|
};
|
|
1099
1140
|
}
|
|
1141
|
+
function buildVercelTools(tools, ctx, data, onPresent) {
|
|
1142
|
+
const result = {};
|
|
1143
|
+
let toolCallCount = 0;
|
|
1144
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
1145
|
+
if (!def.zodSchema) {
|
|
1146
|
+
throw new Error(
|
|
1147
|
+
`Tool '${name}' has no zodSchema; required for the Vercel AI SDK chat. Add a Zod schema to the tool definition (or remove it from the registry if the host only uses the custom SSE chat).`
|
|
1148
|
+
);
|
|
1149
|
+
}
|
|
1150
|
+
if (name === TERMINAL_TOOL_NAME) {
|
|
1151
|
+
result[name] = tool({
|
|
1152
|
+
description: def.schema.description,
|
|
1153
|
+
// The Zod schema doubles as the runtime parameter validator the SDK
|
|
1154
|
+
// hands the model. We accept whatever Zod shape the host registered;
|
|
1155
|
+
// the SDK uses it to validate the tool-call arguments before dispatch.
|
|
1156
|
+
parameters: def.zodSchema,
|
|
1157
|
+
execute: async (input) => {
|
|
1158
|
+
if (toolCallCount < 2) {
|
|
1159
|
+
return {
|
|
1160
|
+
error: {
|
|
1161
|
+
code: "SELF_VERIFY_REQUIRED",
|
|
1162
|
+
message: "Per FR-8.3 you must run at least one CROSS-CHECK tool call (a different metric, a different period, or a run_sql sanity-check) before present. Make that extra call now, then call present again."
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
const payload = input;
|
|
1167
|
+
for (let i = 0; i < payload.blocks.length; i++) {
|
|
1168
|
+
data.append({
|
|
1169
|
+
type: "block",
|
|
1170
|
+
value: { index: i, ...payload.blocks[i] }
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
onPresent(payload);
|
|
1174
|
+
return { ok: true };
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
result[name] = tool({
|
|
1180
|
+
description: def.schema.description,
|
|
1181
|
+
parameters: def.zodSchema,
|
|
1182
|
+
execute: async (input) => {
|
|
1183
|
+
const res = await def.execute(input, { ...ctx, toolCallCount });
|
|
1184
|
+
toolCallCount += 1;
|
|
1185
|
+
if (res.ok) return res.data;
|
|
1186
|
+
return { error: res.error };
|
|
1187
|
+
}
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
return result;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/server/routes/agent-vercel.ts
|
|
1194
|
+
var VALID_MODELS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
1195
|
+
function jsonError2(status, code, message) {
|
|
1196
|
+
return new Response(JSON.stringify({ error: { code, message } }), {
|
|
1197
|
+
status,
|
|
1198
|
+
headers: { "Content-Type": "application/json" }
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
function defaultGenerateSessionId2() {
|
|
1202
|
+
return randomUUID().replace(/-/g, "").slice(0, 16);
|
|
1203
|
+
}
|
|
1204
|
+
function createAgentVercelRoutes(ctx) {
|
|
1205
|
+
const { persistence, auth, scope, tools, vertex, logger, hooks } = ctx;
|
|
1206
|
+
return {
|
|
1207
|
+
/** Next.js-compatible POST handler. */
|
|
1208
|
+
POST: async (req) => {
|
|
1209
|
+
if (hooks?.onRequest) {
|
|
1210
|
+
const short = await hooks.onRequest(req);
|
|
1211
|
+
if (short) return short;
|
|
1212
|
+
}
|
|
1213
|
+
const authResult = await auth.requireAuth(req);
|
|
1214
|
+
if (!authResult.ok) return authResult.response;
|
|
1215
|
+
const { scope: callerScope, userId } = authResult;
|
|
1216
|
+
if (hooks?.onAuthenticated) {
|
|
1217
|
+
const short = await hooks.onAuthenticated({
|
|
1218
|
+
req,
|
|
1219
|
+
scope: callerScope,
|
|
1220
|
+
userId
|
|
1221
|
+
});
|
|
1222
|
+
if (short) return short;
|
|
1223
|
+
}
|
|
1224
|
+
const body = await req.json().catch(() => null);
|
|
1225
|
+
const question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
1226
|
+
if (!question) {
|
|
1227
|
+
return jsonError2(
|
|
1228
|
+
400,
|
|
1229
|
+
"VALIDATION_FAILED",
|
|
1230
|
+
"question must be a non-empty string."
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
const rawChatSessionId = body?.chatSessionId;
|
|
1234
|
+
const incomingChatSessionId = typeof rawChatSessionId === "number" && Number.isInteger(rawChatSessionId) ? rawChatSessionId : null;
|
|
1235
|
+
const rawModel = body?.model;
|
|
1236
|
+
const requestedModel = typeof rawModel === "string" && VALID_MODELS.has(rawModel) ? rawModel : null;
|
|
1237
|
+
const aiSettings = await persistence.getAiSettings();
|
|
1238
|
+
let chatSessionId;
|
|
1239
|
+
if (incomingChatSessionId !== null) {
|
|
1240
|
+
const owned = await persistence.getSession(
|
|
1241
|
+
incomingChatSessionId,
|
|
1242
|
+
userId
|
|
1243
|
+
);
|
|
1244
|
+
if (!owned) {
|
|
1245
|
+
return jsonError2(404, "NOT_FOUND", "Chat session not found.");
|
|
1246
|
+
}
|
|
1247
|
+
chatSessionId = owned.id;
|
|
1248
|
+
} else {
|
|
1249
|
+
const created = await persistence.createSession({
|
|
1250
|
+
userId,
|
|
1251
|
+
title: question.slice(0, 200)
|
|
1252
|
+
});
|
|
1253
|
+
chatSessionId = created.id;
|
|
1254
|
+
}
|
|
1255
|
+
await persistence.appendMessage({
|
|
1256
|
+
sessionId: chatSessionId,
|
|
1257
|
+
role: "user",
|
|
1258
|
+
question
|
|
1259
|
+
});
|
|
1260
|
+
const sessionId = hooks?.generateSessionId ? await hooks.generateSessionId({
|
|
1261
|
+
scope: callerScope,
|
|
1262
|
+
userId,
|
|
1263
|
+
chatSessionId: incomingChatSessionId
|
|
1264
|
+
}) : defaultGenerateSessionId2();
|
|
1265
|
+
const scopeSummary = await scope.buildScopeSummary(callerScope);
|
|
1266
|
+
const scopeLabel = await scope.resolveScopeLabel(callerScope);
|
|
1267
|
+
const toolContext = {
|
|
1268
|
+
scope: callerScope,
|
|
1269
|
+
sessionId,
|
|
1270
|
+
scopeSummary,
|
|
1271
|
+
toolCallCount: 0
|
|
1272
|
+
};
|
|
1273
|
+
const systemBlocks = await tools.buildSystemBlocks(toolContext);
|
|
1274
|
+
const provider = requestedModel ?? aiSettings.toolProvider;
|
|
1275
|
+
if (!VALID_MODELS.has(provider)) {
|
|
1276
|
+
return jsonError2(
|
|
1277
|
+
400,
|
|
1278
|
+
"INVALID_PROVIDER",
|
|
1279
|
+
`Vercel chat only supports 'claude' or 'gemini'; got '${provider}'.`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
const data = new StreamData();
|
|
1283
|
+
let presentPayload = null;
|
|
1284
|
+
let persistedError = null;
|
|
1285
|
+
let sessionStarted = false;
|
|
1286
|
+
try {
|
|
1287
|
+
if (hooks?.onSessionStart) {
|
|
1288
|
+
await hooks.onSessionStart({
|
|
1289
|
+
scope: callerScope,
|
|
1290
|
+
sessionId,
|
|
1291
|
+
userId
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
sessionStarted = true;
|
|
1295
|
+
const vercelTools = buildVercelTools(
|
|
1296
|
+
tools.tools,
|
|
1297
|
+
toolContext,
|
|
1298
|
+
data,
|
|
1299
|
+
(p) => {
|
|
1300
|
+
presentPayload = p;
|
|
1301
|
+
}
|
|
1302
|
+
);
|
|
1303
|
+
data.append({
|
|
1304
|
+
type: "meta",
|
|
1305
|
+
value: { chatSessionId, scopeLabel }
|
|
1306
|
+
});
|
|
1307
|
+
const system = systemBlocks.map((b) => b.text).join("\n\n");
|
|
1308
|
+
const model = provider === "claude" ? createVertexAnthropic({
|
|
1309
|
+
project: vertex.projectId,
|
|
1310
|
+
location: vertex.defaultLocation,
|
|
1311
|
+
googleAuthOptions: {}
|
|
1312
|
+
})(vertex.modelIds.claude) : createVertex({
|
|
1313
|
+
project: vertex.projectId,
|
|
1314
|
+
location: aiSettings.gcpLocation,
|
|
1315
|
+
googleAuthOptions: {}
|
|
1316
|
+
})(vertex.modelIds.gemini);
|
|
1317
|
+
const result = streamText({
|
|
1318
|
+
model,
|
|
1319
|
+
system,
|
|
1320
|
+
messages: [{ role: "user", content: question }],
|
|
1321
|
+
tools: vercelTools,
|
|
1322
|
+
maxSteps: 12,
|
|
1323
|
+
maxTokens: 4096,
|
|
1324
|
+
onFinish: async ({ text }) => {
|
|
1325
|
+
try {
|
|
1326
|
+
let blocks = presentPayload?.blocks ?? [];
|
|
1327
|
+
const prose = {};
|
|
1328
|
+
const trimmed = (text ?? "").trim();
|
|
1329
|
+
if (presentPayload === null && trimmed) {
|
|
1330
|
+
const topic = question.length > 80 ? question.slice(0, 77) + "..." : question;
|
|
1331
|
+
const synthetic = {
|
|
1332
|
+
kind: "paragraph_brief",
|
|
1333
|
+
topic,
|
|
1334
|
+
key_facts: [trimmed]
|
|
1335
|
+
};
|
|
1336
|
+
blocks = [synthetic];
|
|
1337
|
+
prose[0] = trimmed;
|
|
1338
|
+
data.append({
|
|
1339
|
+
type: "block",
|
|
1340
|
+
value: { index: 0, ...synthetic }
|
|
1341
|
+
});
|
|
1342
|
+
} else if (text) {
|
|
1343
|
+
const firstPbIdx = blocks.findIndex(
|
|
1344
|
+
(b) => b.kind === "paragraph_brief"
|
|
1345
|
+
);
|
|
1346
|
+
if (firstPbIdx >= 0) prose[firstPbIdx] = text;
|
|
1347
|
+
}
|
|
1348
|
+
await persistence.appendMessage({
|
|
1349
|
+
sessionId: chatSessionId,
|
|
1350
|
+
role: "assistant",
|
|
1351
|
+
blocks: blocks.length ? blocks : null,
|
|
1352
|
+
prose: Object.keys(prose).length ? prose : null,
|
|
1353
|
+
errorJson: persistedError
|
|
1354
|
+
});
|
|
1355
|
+
} catch (err2) {
|
|
1356
|
+
logger?.warn?.(
|
|
1357
|
+
{
|
|
1358
|
+
chatSessionId,
|
|
1359
|
+
sessionId,
|
|
1360
|
+
err: err2.message
|
|
1361
|
+
},
|
|
1362
|
+
"[agent-vercel] failed to persist assistant turn"
|
|
1363
|
+
);
|
|
1364
|
+
} finally {
|
|
1365
|
+
try {
|
|
1366
|
+
await data.close();
|
|
1367
|
+
} catch {
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
return result.toDataStreamResponse({ data });
|
|
1373
|
+
} catch (e) {
|
|
1374
|
+
const message = e.message ?? "Internal error";
|
|
1375
|
+
persistedError = { code: "INTERNAL", message };
|
|
1376
|
+
logger?.error?.(
|
|
1377
|
+
{ chatSessionId, sessionId, err: message },
|
|
1378
|
+
"[agent-vercel] route errored"
|
|
1379
|
+
);
|
|
1380
|
+
try {
|
|
1381
|
+
data.append({
|
|
1382
|
+
type: "error",
|
|
1383
|
+
value: { code: "INTERNAL", message }
|
|
1384
|
+
});
|
|
1385
|
+
} catch {
|
|
1386
|
+
}
|
|
1387
|
+
try {
|
|
1388
|
+
await data.close();
|
|
1389
|
+
} catch {
|
|
1390
|
+
}
|
|
1391
|
+
try {
|
|
1392
|
+
await persistence.appendMessage({
|
|
1393
|
+
sessionId: chatSessionId,
|
|
1394
|
+
role: "assistant",
|
|
1395
|
+
blocks: null,
|
|
1396
|
+
prose: null,
|
|
1397
|
+
errorJson: persistedError
|
|
1398
|
+
});
|
|
1399
|
+
} catch (err2) {
|
|
1400
|
+
logger?.warn?.(
|
|
1401
|
+
{ chatSessionId, sessionId, err: err2.message },
|
|
1402
|
+
"[agent-vercel] failed to persist error turn"
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
return jsonError2(500, "INTERNAL", message);
|
|
1406
|
+
} finally {
|
|
1407
|
+
if (hooks?.onSessionEnd) {
|
|
1408
|
+
const cause = req.signal.aborted ? "abort" : persistedError ? "error" : "complete";
|
|
1409
|
+
try {
|
|
1410
|
+
await hooks.onSessionEnd({
|
|
1411
|
+
scope: callerScope,
|
|
1412
|
+
sessionId,
|
|
1413
|
+
userId,
|
|
1414
|
+
cause
|
|
1415
|
+
});
|
|
1416
|
+
} catch (err2) {
|
|
1417
|
+
logger?.warn?.(
|
|
1418
|
+
{
|
|
1419
|
+
chatSessionId,
|
|
1420
|
+
sessionId,
|
|
1421
|
+
sessionStarted,
|
|
1422
|
+
err: err2.message
|
|
1423
|
+
},
|
|
1424
|
+
"[agent-vercel] onSessionEnd hook failed"
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1100
1432
|
|
|
1101
1433
|
// src/server/routes/chat-sessions.ts
|
|
1102
1434
|
var DEFAULT_TITLE = "New chat";
|
|
@@ -1457,6 +1789,15 @@ function configureAiChat(opts) {
|
|
|
1457
1789
|
resolveNarratorId: opts.resolveNarratorId,
|
|
1458
1790
|
hooks: opts.hooks
|
|
1459
1791
|
});
|
|
1792
|
+
const agentVercel = createAgentVercelRoutes({
|
|
1793
|
+
persistence: opts.persistence,
|
|
1794
|
+
auth: opts.auth,
|
|
1795
|
+
scope: opts.scope,
|
|
1796
|
+
tools,
|
|
1797
|
+
vertex: opts.vertex,
|
|
1798
|
+
logger: opts.logger,
|
|
1799
|
+
hooks: opts.hooks
|
|
1800
|
+
});
|
|
1460
1801
|
const chatSessions = createChatSessionsRoutes({
|
|
1461
1802
|
persistence: opts.persistence,
|
|
1462
1803
|
auth: opts.auth,
|
|
@@ -1473,7 +1814,7 @@ function configureAiChat(opts) {
|
|
|
1473
1814
|
});
|
|
1474
1815
|
return {
|
|
1475
1816
|
runAgent: runAgentBound,
|
|
1476
|
-
routes: { agentCustom, chatSessions, adminSettings },
|
|
1817
|
+
routes: { agentCustom, agentVercel, chatSessions, adminSettings },
|
|
1477
1818
|
registries: { toolProviders: toolProviders2, chatInterfaces }
|
|
1478
1819
|
};
|
|
1479
1820
|
}
|