@firstlovecenter/ai-chat 0.2.3 → 0.6.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 +57 -0
- package/dist/drizzle/index.cjs +24 -0
- package/dist/drizzle/index.cjs.map +1 -1
- package/dist/drizzle/index.d.cts +36 -1
- package/dist/drizzle/index.d.ts +36 -1
- package/dist/drizzle/index.js +25 -1
- package/dist/drizzle/index.js.map +1 -1
- package/dist/prisma/index.cjs +7 -0
- package/dist/prisma/index.cjs.map +1 -1
- package/dist/prisma/index.d.cts +8 -1
- package/dist/prisma/index.d.ts +8 -1
- package/dist/prisma/index.js +7 -0
- package/dist/prisma/index.js.map +1 -1
- package/dist/server/index.cjs +353 -15
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +50 -4
- package/dist/server/index.d.ts +50 -4
- package/dist/server/index.js +353 -15
- package/dist/server/index.js.map +1 -1
- package/dist/{types-DNwFvL-C.d.cts → types-CQntnyDJ.d.cts} +24 -2
- package/dist/{types-DNwFvL-C.d.ts → types-CQntnyDJ.d.ts} +24 -2
- package/dist/ui/index.cjs +1024 -87
- 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 +1022 -88
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
- package/prisma/chat-models.prisma +7 -0
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-CQntnyDJ.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-CQntnyDJ.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
|
*
|
|
@@ -369,7 +398,7 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
369
398
|
/**
|
|
370
399
|
* `/api/admin/ai-settings` route factory — global AI configuration (super_admin only).
|
|
371
400
|
*
|
|
372
|
-
*
|
|
401
|
+
* Five patchable fields on the singleton settings row:
|
|
373
402
|
* - `tool_provider` — vendor that drives the agent tool loop. Validated
|
|
374
403
|
* against the registered `toolProviders` registry passed in via ctx.
|
|
375
404
|
* - `gcp_location` — the Vertex region every provider call hits. Stays
|
|
@@ -379,6 +408,15 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
379
408
|
* against the `chatInterfaces` registry passed in via ctx (the actual
|
|
380
409
|
* registry lives in `@firstlovecenter/ai-chat/ui` so the host wires it through;
|
|
381
410
|
* the route stays free of UI imports).
|
|
411
|
+
* - `max_output_tokens` — caps the agent loop's per-turn output AND each
|
|
412
|
+
* narrator's prose pass. Bounded `[256, 64000]` — anything below 256 can't
|
|
413
|
+
* fit a useful response, anything above 64000 exceeds the headroom of any
|
|
414
|
+
* model currently routed through Vertex.
|
|
415
|
+
* - `role_prompt` — admin-editable persona string. Empty string and the
|
|
416
|
+
* explicit JSON `null` both clear the override back to the host's static
|
|
417
|
+
* `configureAiChat({ rolePrompt })` fallback; we canonicalise an empty/
|
|
418
|
+
* whitespace-only string to `null` on write so the "no override" state has
|
|
419
|
+
* a single representation in storage.
|
|
382
420
|
*
|
|
383
421
|
* Wire format is snake_case to preserve byte-for-byte parity with the
|
|
384
422
|
* host route the package replaces — existing host UIs keep working
|
|
@@ -501,6 +539,7 @@ type AiChatRuntime<S = unknown> = {
|
|
|
501
539
|
}) => Promise<AgentResult>;
|
|
502
540
|
routes: {
|
|
503
541
|
agentCustom: ReturnType<typeof createAgentCustomRoutes<S>>;
|
|
542
|
+
agentVercel: ReturnType<typeof createAgentVercelRoutes<S>>;
|
|
504
543
|
chatSessions: ReturnType<typeof createChatSessionsRoutes<S>>;
|
|
505
544
|
adminSettings: ReturnType<typeof createAdminSettingsRoutes<S>>;
|
|
506
545
|
};
|
|
@@ -513,5 +552,12 @@ declare function configureAiChat<S = unknown>(opts: ConfigureAiChatOpts<S>): AiC
|
|
|
513
552
|
|
|
514
553
|
type RouteHooks<S> = RouteHooks$1<S>;
|
|
515
554
|
type AgentCustomHooks<S> = AgentCustomHooks$1<S>;
|
|
555
|
+
/**
|
|
556
|
+
* Alias of `AgentCustomHooks` — the Vercel AI SDK chat reuses the same
|
|
557
|
+
* lifecycle (pre-auth → auth → post-auth → resolve session → onSessionStart
|
|
558
|
+
* → stream → onSessionEnd). Re-exported for callers that prefer the more
|
|
559
|
+
* specific name.
|
|
560
|
+
*/
|
|
561
|
+
type AgentVercelHooks<S> = AgentVercelHooks$1<S>;
|
|
516
562
|
|
|
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 };
|
|
563
|
+
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-CQntnyDJ.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-CQntnyDJ.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
|
*
|
|
@@ -369,7 +398,7 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
369
398
|
/**
|
|
370
399
|
* `/api/admin/ai-settings` route factory — global AI configuration (super_admin only).
|
|
371
400
|
*
|
|
372
|
-
*
|
|
401
|
+
* Five patchable fields on the singleton settings row:
|
|
373
402
|
* - `tool_provider` — vendor that drives the agent tool loop. Validated
|
|
374
403
|
* against the registered `toolProviders` registry passed in via ctx.
|
|
375
404
|
* - `gcp_location` — the Vertex region every provider call hits. Stays
|
|
@@ -379,6 +408,15 @@ declare function createChatSessionsRoutes<S>(ctx: ChatSessionsRouteCtx<S>): {
|
|
|
379
408
|
* against the `chatInterfaces` registry passed in via ctx (the actual
|
|
380
409
|
* registry lives in `@firstlovecenter/ai-chat/ui` so the host wires it through;
|
|
381
410
|
* the route stays free of UI imports).
|
|
411
|
+
* - `max_output_tokens` — caps the agent loop's per-turn output AND each
|
|
412
|
+
* narrator's prose pass. Bounded `[256, 64000]` — anything below 256 can't
|
|
413
|
+
* fit a useful response, anything above 64000 exceeds the headroom of any
|
|
414
|
+
* model currently routed through Vertex.
|
|
415
|
+
* - `role_prompt` — admin-editable persona string. Empty string and the
|
|
416
|
+
* explicit JSON `null` both clear the override back to the host's static
|
|
417
|
+
* `configureAiChat({ rolePrompt })` fallback; we canonicalise an empty/
|
|
418
|
+
* whitespace-only string to `null` on write so the "no override" state has
|
|
419
|
+
* a single representation in storage.
|
|
382
420
|
*
|
|
383
421
|
* Wire format is snake_case to preserve byte-for-byte parity with the
|
|
384
422
|
* host route the package replaces — existing host UIs keep working
|
|
@@ -501,6 +539,7 @@ type AiChatRuntime<S = unknown> = {
|
|
|
501
539
|
}) => Promise<AgentResult>;
|
|
502
540
|
routes: {
|
|
503
541
|
agentCustom: ReturnType<typeof createAgentCustomRoutes<S>>;
|
|
542
|
+
agentVercel: ReturnType<typeof createAgentVercelRoutes<S>>;
|
|
504
543
|
chatSessions: ReturnType<typeof createChatSessionsRoutes<S>>;
|
|
505
544
|
adminSettings: ReturnType<typeof createAdminSettingsRoutes<S>>;
|
|
506
545
|
};
|
|
@@ -513,5 +552,12 @@ declare function configureAiChat<S = unknown>(opts: ConfigureAiChatOpts<S>): AiC
|
|
|
513
552
|
|
|
514
553
|
type RouteHooks<S> = RouteHooks$1<S>;
|
|
515
554
|
type AgentCustomHooks<S> = AgentCustomHooks$1<S>;
|
|
555
|
+
/**
|
|
556
|
+
* Alias of `AgentCustomHooks` — the Vercel AI SDK chat reuses the same
|
|
557
|
+
* lifecycle (pre-auth → auth → post-auth → resolve session → onSessionStart
|
|
558
|
+
* → stream → onSessionEnd). Re-exported for callers that prefer the more
|
|
559
|
+
* specific name.
|
|
560
|
+
*/
|
|
561
|
+
type AgentVercelHooks<S> = AgentVercelHooks$1<S>;
|
|
516
562
|
|
|
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 };
|
|
563
|
+
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
|
});
|
|
@@ -647,7 +650,7 @@ async function* streamClaudeNarration(opts) {
|
|
|
647
650
|
});
|
|
648
651
|
const stream = await client.messages.stream({
|
|
649
652
|
model: opts.modelId,
|
|
650
|
-
max_tokens:
|
|
653
|
+
max_tokens: opts.maxTokens,
|
|
651
654
|
system: NARRATIVE_SYSTEM,
|
|
652
655
|
messages: [{ role: "user", content: buildNarrativeUserMessage(opts.input) }]
|
|
653
656
|
});
|
|
@@ -707,7 +710,7 @@ async function* streamGeminiNarration(opts) {
|
|
|
707
710
|
parts: [{ text: buildNarrativeUserMessage(opts.input) }]
|
|
708
711
|
}
|
|
709
712
|
],
|
|
710
|
-
generationConfig: { maxOutputTokens:
|
|
713
|
+
generationConfig: { maxOutputTokens: opts.maxTokens, temperature: 0 }
|
|
711
714
|
})
|
|
712
715
|
});
|
|
713
716
|
if (!res.ok || !res.body) {
|
|
@@ -783,7 +786,7 @@ async function* streamGrokNarration(opts) {
|
|
|
783
786
|
},
|
|
784
787
|
body: JSON.stringify({
|
|
785
788
|
model: opts.modelId,
|
|
786
|
-
max_tokens:
|
|
789
|
+
max_tokens: opts.maxTokens,
|
|
787
790
|
stream: true,
|
|
788
791
|
messages: [
|
|
789
792
|
{ role: "system", content: NARRATIVE_SYSTEM3 },
|
|
@@ -1006,7 +1009,8 @@ data: ${JSON.stringify(data)}
|
|
|
1006
1009
|
ctx: toolContext,
|
|
1007
1010
|
tools: tools.tools,
|
|
1008
1011
|
systemBlocks,
|
|
1009
|
-
provider
|
|
1012
|
+
provider,
|
|
1013
|
+
maxOutputTokens: aiSettings.maxOutputTokens
|
|
1010
1014
|
});
|
|
1011
1015
|
if (!agentResult.ok) {
|
|
1012
1016
|
persistedError = agentResult.error;
|
|
@@ -1032,6 +1036,7 @@ data: ${JSON.stringify(data)}
|
|
|
1032
1036
|
projectId: vertex.projectId,
|
|
1033
1037
|
location: aiSettings.gcpLocation,
|
|
1034
1038
|
modelId: narratorModelId,
|
|
1039
|
+
maxTokens: aiSettings.maxOutputTokens,
|
|
1035
1040
|
input: {
|
|
1036
1041
|
question,
|
|
1037
1042
|
structured,
|
|
@@ -1135,6 +1140,297 @@ data: {}
|
|
|
1135
1140
|
}
|
|
1136
1141
|
};
|
|
1137
1142
|
}
|
|
1143
|
+
function buildVercelTools(tools, ctx, data, onPresent) {
|
|
1144
|
+
const result = {};
|
|
1145
|
+
let toolCallCount = 0;
|
|
1146
|
+
for (const [name, def] of Object.entries(tools)) {
|
|
1147
|
+
if (!def.zodSchema) {
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
`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).`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
if (name === TERMINAL_TOOL_NAME) {
|
|
1153
|
+
result[name] = tool({
|
|
1154
|
+
description: def.schema.description,
|
|
1155
|
+
// The Zod schema doubles as the runtime parameter validator the SDK
|
|
1156
|
+
// hands the model. We accept whatever Zod shape the host registered;
|
|
1157
|
+
// the SDK uses it to validate the tool-call arguments before dispatch.
|
|
1158
|
+
parameters: def.zodSchema,
|
|
1159
|
+
execute: async (input) => {
|
|
1160
|
+
if (toolCallCount < 2) {
|
|
1161
|
+
return {
|
|
1162
|
+
error: {
|
|
1163
|
+
code: "SELF_VERIFY_REQUIRED",
|
|
1164
|
+
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."
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
const payload = input;
|
|
1169
|
+
for (let i = 0; i < payload.blocks.length; i++) {
|
|
1170
|
+
data.append({
|
|
1171
|
+
type: "block",
|
|
1172
|
+
value: { index: i, ...payload.blocks[i] }
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
onPresent(payload);
|
|
1176
|
+
return { ok: true };
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
result[name] = tool({
|
|
1182
|
+
description: def.schema.description,
|
|
1183
|
+
parameters: def.zodSchema,
|
|
1184
|
+
execute: async (input) => {
|
|
1185
|
+
const res = await def.execute(input, { ...ctx, toolCallCount });
|
|
1186
|
+
toolCallCount += 1;
|
|
1187
|
+
if (res.ok) return res.data;
|
|
1188
|
+
return { error: res.error };
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
return result;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/server/routes/agent-vercel.ts
|
|
1196
|
+
var VALID_MODELS = /* @__PURE__ */ new Set(["claude", "gemini"]);
|
|
1197
|
+
function jsonError2(status, code, message) {
|
|
1198
|
+
return new Response(JSON.stringify({ error: { code, message } }), {
|
|
1199
|
+
status,
|
|
1200
|
+
headers: { "Content-Type": "application/json" }
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
function defaultGenerateSessionId2() {
|
|
1204
|
+
return randomUUID().replace(/-/g, "").slice(0, 16);
|
|
1205
|
+
}
|
|
1206
|
+
function createAgentVercelRoutes(ctx) {
|
|
1207
|
+
const { persistence, auth, scope, tools, vertex, logger, hooks } = ctx;
|
|
1208
|
+
return {
|
|
1209
|
+
/** Next.js-compatible POST handler. */
|
|
1210
|
+
POST: async (req) => {
|
|
1211
|
+
if (hooks?.onRequest) {
|
|
1212
|
+
const short = await hooks.onRequest(req);
|
|
1213
|
+
if (short) return short;
|
|
1214
|
+
}
|
|
1215
|
+
const authResult = await auth.requireAuth(req);
|
|
1216
|
+
if (!authResult.ok) return authResult.response;
|
|
1217
|
+
const { scope: callerScope, userId } = authResult;
|
|
1218
|
+
if (hooks?.onAuthenticated) {
|
|
1219
|
+
const short = await hooks.onAuthenticated({
|
|
1220
|
+
req,
|
|
1221
|
+
scope: callerScope,
|
|
1222
|
+
userId
|
|
1223
|
+
});
|
|
1224
|
+
if (short) return short;
|
|
1225
|
+
}
|
|
1226
|
+
const body = await req.json().catch(() => null);
|
|
1227
|
+
const question = typeof body?.question === "string" ? body.question.trim() : "";
|
|
1228
|
+
if (!question) {
|
|
1229
|
+
return jsonError2(
|
|
1230
|
+
400,
|
|
1231
|
+
"VALIDATION_FAILED",
|
|
1232
|
+
"question must be a non-empty string."
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const rawChatSessionId = body?.chatSessionId;
|
|
1236
|
+
const incomingChatSessionId = typeof rawChatSessionId === "number" && Number.isInteger(rawChatSessionId) ? rawChatSessionId : null;
|
|
1237
|
+
const rawModel = body?.model;
|
|
1238
|
+
const requestedModel = typeof rawModel === "string" && VALID_MODELS.has(rawModel) ? rawModel : null;
|
|
1239
|
+
const aiSettings = await persistence.getAiSettings();
|
|
1240
|
+
let chatSessionId;
|
|
1241
|
+
if (incomingChatSessionId !== null) {
|
|
1242
|
+
const owned = await persistence.getSession(
|
|
1243
|
+
incomingChatSessionId,
|
|
1244
|
+
userId
|
|
1245
|
+
);
|
|
1246
|
+
if (!owned) {
|
|
1247
|
+
return jsonError2(404, "NOT_FOUND", "Chat session not found.");
|
|
1248
|
+
}
|
|
1249
|
+
chatSessionId = owned.id;
|
|
1250
|
+
} else {
|
|
1251
|
+
const created = await persistence.createSession({
|
|
1252
|
+
userId,
|
|
1253
|
+
title: question.slice(0, 200)
|
|
1254
|
+
});
|
|
1255
|
+
chatSessionId = created.id;
|
|
1256
|
+
}
|
|
1257
|
+
await persistence.appendMessage({
|
|
1258
|
+
sessionId: chatSessionId,
|
|
1259
|
+
role: "user",
|
|
1260
|
+
question
|
|
1261
|
+
});
|
|
1262
|
+
const sessionId = hooks?.generateSessionId ? await hooks.generateSessionId({
|
|
1263
|
+
scope: callerScope,
|
|
1264
|
+
userId,
|
|
1265
|
+
chatSessionId: incomingChatSessionId
|
|
1266
|
+
}) : defaultGenerateSessionId2();
|
|
1267
|
+
const scopeSummary = await scope.buildScopeSummary(callerScope);
|
|
1268
|
+
const scopeLabel = await scope.resolveScopeLabel(callerScope);
|
|
1269
|
+
const toolContext = {
|
|
1270
|
+
scope: callerScope,
|
|
1271
|
+
sessionId,
|
|
1272
|
+
scopeSummary,
|
|
1273
|
+
toolCallCount: 0
|
|
1274
|
+
};
|
|
1275
|
+
const systemBlocks = await tools.buildSystemBlocks(toolContext);
|
|
1276
|
+
const provider = requestedModel ?? aiSettings.toolProvider;
|
|
1277
|
+
if (!VALID_MODELS.has(provider)) {
|
|
1278
|
+
return jsonError2(
|
|
1279
|
+
400,
|
|
1280
|
+
"INVALID_PROVIDER",
|
|
1281
|
+
`Vercel chat only supports 'claude' or 'gemini'; got '${provider}'.`
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
const data = new StreamData();
|
|
1285
|
+
let presentPayload = null;
|
|
1286
|
+
let persistedError = null;
|
|
1287
|
+
let sessionStarted = false;
|
|
1288
|
+
try {
|
|
1289
|
+
if (hooks?.onSessionStart) {
|
|
1290
|
+
await hooks.onSessionStart({
|
|
1291
|
+
scope: callerScope,
|
|
1292
|
+
sessionId,
|
|
1293
|
+
userId
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
sessionStarted = true;
|
|
1297
|
+
const vercelTools = buildVercelTools(
|
|
1298
|
+
tools.tools,
|
|
1299
|
+
toolContext,
|
|
1300
|
+
data,
|
|
1301
|
+
(p) => {
|
|
1302
|
+
presentPayload = p;
|
|
1303
|
+
}
|
|
1304
|
+
);
|
|
1305
|
+
data.append({
|
|
1306
|
+
type: "meta",
|
|
1307
|
+
value: { chatSessionId, scopeLabel }
|
|
1308
|
+
});
|
|
1309
|
+
const system = systemBlocks.map((b) => b.text).join("\n\n");
|
|
1310
|
+
const model = provider === "claude" ? createVertexAnthropic({
|
|
1311
|
+
project: vertex.projectId,
|
|
1312
|
+
location: vertex.defaultLocation,
|
|
1313
|
+
googleAuthOptions: {}
|
|
1314
|
+
})(vertex.modelIds.claude) : createVertex({
|
|
1315
|
+
project: vertex.projectId,
|
|
1316
|
+
location: aiSettings.gcpLocation,
|
|
1317
|
+
googleAuthOptions: {}
|
|
1318
|
+
})(vertex.modelIds.gemini);
|
|
1319
|
+
const result = streamText({
|
|
1320
|
+
model,
|
|
1321
|
+
system,
|
|
1322
|
+
messages: [{ role: "user", content: question }],
|
|
1323
|
+
tools: vercelTools,
|
|
1324
|
+
maxSteps: 12,
|
|
1325
|
+
maxTokens: aiSettings.maxOutputTokens,
|
|
1326
|
+
onFinish: async ({ text }) => {
|
|
1327
|
+
try {
|
|
1328
|
+
let blocks = presentPayload?.blocks ?? [];
|
|
1329
|
+
const prose = {};
|
|
1330
|
+
const trimmed = (text ?? "").trim();
|
|
1331
|
+
if (presentPayload === null && trimmed) {
|
|
1332
|
+
const topic = question.length > 80 ? question.slice(0, 77) + "..." : question;
|
|
1333
|
+
const synthetic = {
|
|
1334
|
+
kind: "paragraph_brief",
|
|
1335
|
+
topic,
|
|
1336
|
+
key_facts: [trimmed]
|
|
1337
|
+
};
|
|
1338
|
+
blocks = [synthetic];
|
|
1339
|
+
prose[0] = trimmed;
|
|
1340
|
+
data.append({
|
|
1341
|
+
type: "block",
|
|
1342
|
+
value: { index: 0, ...synthetic }
|
|
1343
|
+
});
|
|
1344
|
+
} else if (text) {
|
|
1345
|
+
const firstPbIdx = blocks.findIndex(
|
|
1346
|
+
(b) => b.kind === "paragraph_brief"
|
|
1347
|
+
);
|
|
1348
|
+
if (firstPbIdx >= 0) prose[firstPbIdx] = text;
|
|
1349
|
+
}
|
|
1350
|
+
await persistence.appendMessage({
|
|
1351
|
+
sessionId: chatSessionId,
|
|
1352
|
+
role: "assistant",
|
|
1353
|
+
blocks: blocks.length ? blocks : null,
|
|
1354
|
+
prose: Object.keys(prose).length ? prose : null,
|
|
1355
|
+
errorJson: persistedError
|
|
1356
|
+
});
|
|
1357
|
+
} catch (err2) {
|
|
1358
|
+
logger?.warn?.(
|
|
1359
|
+
{
|
|
1360
|
+
chatSessionId,
|
|
1361
|
+
sessionId,
|
|
1362
|
+
err: err2.message
|
|
1363
|
+
},
|
|
1364
|
+
"[agent-vercel] failed to persist assistant turn"
|
|
1365
|
+
);
|
|
1366
|
+
} finally {
|
|
1367
|
+
try {
|
|
1368
|
+
await data.close();
|
|
1369
|
+
} catch {
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
return result.toDataStreamResponse({ data });
|
|
1375
|
+
} catch (e) {
|
|
1376
|
+
const message = e.message ?? "Internal error";
|
|
1377
|
+
persistedError = { code: "INTERNAL", message };
|
|
1378
|
+
logger?.error?.(
|
|
1379
|
+
{ chatSessionId, sessionId, err: message },
|
|
1380
|
+
"[agent-vercel] route errored"
|
|
1381
|
+
);
|
|
1382
|
+
try {
|
|
1383
|
+
data.append({
|
|
1384
|
+
type: "error",
|
|
1385
|
+
value: { code: "INTERNAL", message }
|
|
1386
|
+
});
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
try {
|
|
1390
|
+
await data.close();
|
|
1391
|
+
} catch {
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
await persistence.appendMessage({
|
|
1395
|
+
sessionId: chatSessionId,
|
|
1396
|
+
role: "assistant",
|
|
1397
|
+
blocks: null,
|
|
1398
|
+
prose: null,
|
|
1399
|
+
errorJson: persistedError
|
|
1400
|
+
});
|
|
1401
|
+
} catch (err2) {
|
|
1402
|
+
logger?.warn?.(
|
|
1403
|
+
{ chatSessionId, sessionId, err: err2.message },
|
|
1404
|
+
"[agent-vercel] failed to persist error turn"
|
|
1405
|
+
);
|
|
1406
|
+
}
|
|
1407
|
+
return jsonError2(500, "INTERNAL", message);
|
|
1408
|
+
} finally {
|
|
1409
|
+
if (hooks?.onSessionEnd) {
|
|
1410
|
+
const cause = req.signal.aborted ? "abort" : persistedError ? "error" : "complete";
|
|
1411
|
+
try {
|
|
1412
|
+
await hooks.onSessionEnd({
|
|
1413
|
+
scope: callerScope,
|
|
1414
|
+
sessionId,
|
|
1415
|
+
userId,
|
|
1416
|
+
cause
|
|
1417
|
+
});
|
|
1418
|
+
} catch (err2) {
|
|
1419
|
+
logger?.warn?.(
|
|
1420
|
+
{
|
|
1421
|
+
chatSessionId,
|
|
1422
|
+
sessionId,
|
|
1423
|
+
sessionStarted,
|
|
1424
|
+
err: err2.message
|
|
1425
|
+
},
|
|
1426
|
+
"[agent-vercel] onSessionEnd hook failed"
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1138
1434
|
|
|
1139
1435
|
// src/server/routes/chat-sessions.ts
|
|
1140
1436
|
var DEFAULT_TITLE = "New chat";
|
|
@@ -1315,6 +1611,8 @@ function createChatSessionsRoutes(ctx) {
|
|
|
1315
1611
|
|
|
1316
1612
|
// src/server/routes/admin-settings.ts
|
|
1317
1613
|
var VALID_LOCATIONS = ["us-east5", "global"];
|
|
1614
|
+
var MIN_MAX_OUTPUT_TOKENS = 256;
|
|
1615
|
+
var MAX_MAX_OUTPUT_TOKENS = 64e3;
|
|
1318
1616
|
function isStringRecord(v) {
|
|
1319
1617
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1320
1618
|
}
|
|
@@ -1329,6 +1627,8 @@ function toWire(settings) {
|
|
|
1329
1627
|
tool_provider: settings.toolProvider,
|
|
1330
1628
|
gcp_location: settings.gcpLocation,
|
|
1331
1629
|
chat_interface: settings.chatInterface,
|
|
1630
|
+
max_output_tokens: settings.maxOutputTokens,
|
|
1631
|
+
role_prompt: settings.rolePrompt,
|
|
1332
1632
|
updated_at: settings.updatedAt ? settings.updatedAt.toISOString() : null,
|
|
1333
1633
|
updated_by_user_id: settings.updatedByUserId
|
|
1334
1634
|
};
|
|
@@ -1408,11 +1708,29 @@ function createAdminSettingsRoutes(ctx) {
|
|
|
1408
1708
|
}
|
|
1409
1709
|
patch.chatInterface = v;
|
|
1410
1710
|
}
|
|
1411
|
-
if (
|
|
1711
|
+
if ("max_output_tokens" in body) {
|
|
1712
|
+
const v = body.max_output_tokens;
|
|
1713
|
+
if (typeof v !== "number" || !Number.isInteger(v) || v < MIN_MAX_OUTPUT_TOKENS || v > MAX_MAX_OUTPUT_TOKENS) {
|
|
1714
|
+
return jsonResponse({ error: "invalid_max_output_tokens" }, 400);
|
|
1715
|
+
}
|
|
1716
|
+
patch.maxOutputTokens = v;
|
|
1717
|
+
}
|
|
1718
|
+
if ("role_prompt" in body) {
|
|
1719
|
+
const v = body.role_prompt;
|
|
1720
|
+
if (v === null) {
|
|
1721
|
+
patch.rolePrompt = null;
|
|
1722
|
+
} else if (typeof v === "string") {
|
|
1723
|
+
const trimmed = v.trim();
|
|
1724
|
+
patch.rolePrompt = trimmed === "" ? null : trimmed;
|
|
1725
|
+
} else {
|
|
1726
|
+
return jsonResponse({ error: "invalid_role_prompt" }, 400);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
if (patch.toolProvider === void 0 && patch.gcpLocation === void 0 && patch.chatInterface === void 0 && patch.maxOutputTokens === void 0 && !("rolePrompt" in patch)) {
|
|
1412
1730
|
return jsonResponse(
|
|
1413
1731
|
{
|
|
1414
1732
|
error: "empty_patch",
|
|
1415
|
-
message: "Body must set at least one of tool_provider, gcp_location, chat_interface."
|
|
1733
|
+
message: "Body must set at least one of tool_provider, gcp_location, chat_interface, max_output_tokens, role_prompt."
|
|
1416
1734
|
},
|
|
1417
1735
|
400
|
|
1418
1736
|
);
|
|
@@ -1437,16 +1755,27 @@ function configureAiChat(opts) {
|
|
|
1437
1755
|
];
|
|
1438
1756
|
const getProvider = (id) => toolProviders2.find((p) => p.id === id) ?? getToolProvider(id);
|
|
1439
1757
|
const chatInterfaces = opts.chatInterfaces ?? BUILTIN_CHAT_INTERFACE_IDS.map((id) => ({ id }));
|
|
1440
|
-
const
|
|
1758
|
+
const staticRolePrompt = opts.rolePrompt;
|
|
1759
|
+
const tools = {
|
|
1441
1760
|
tools: opts.tools.tools,
|
|
1442
1761
|
async buildSystemBlocks(ctx) {
|
|
1443
1762
|
const inner = await opts.tools.buildSystemBlocks(ctx);
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1763
|
+
let role = null;
|
|
1764
|
+
try {
|
|
1765
|
+
const settings = await opts.persistence.getAiSettings();
|
|
1766
|
+
if (settings.rolePrompt && settings.rolePrompt.trim()) {
|
|
1767
|
+
role = settings.rolePrompt;
|
|
1768
|
+
}
|
|
1769
|
+
} catch {
|
|
1770
|
+
}
|
|
1771
|
+
if (!role && staticRolePrompt) {
|
|
1772
|
+
const resolved = typeof staticRolePrompt === "function" ? await staticRolePrompt(ctx) : staticRolePrompt;
|
|
1773
|
+
if (resolved && resolved.trim()) role = resolved;
|
|
1774
|
+
}
|
|
1775
|
+
if (!role) return inner;
|
|
1447
1776
|
return [{ text: role, cached: true }, ...inner];
|
|
1448
1777
|
}
|
|
1449
|
-
}
|
|
1778
|
+
};
|
|
1450
1779
|
const runAgentBound = async ({
|
|
1451
1780
|
question,
|
|
1452
1781
|
ctx,
|
|
@@ -1495,6 +1824,15 @@ function configureAiChat(opts) {
|
|
|
1495
1824
|
resolveNarratorId: opts.resolveNarratorId,
|
|
1496
1825
|
hooks: opts.hooks
|
|
1497
1826
|
});
|
|
1827
|
+
const agentVercel = createAgentVercelRoutes({
|
|
1828
|
+
persistence: opts.persistence,
|
|
1829
|
+
auth: opts.auth,
|
|
1830
|
+
scope: opts.scope,
|
|
1831
|
+
tools,
|
|
1832
|
+
vertex: opts.vertex,
|
|
1833
|
+
logger: opts.logger,
|
|
1834
|
+
hooks: opts.hooks
|
|
1835
|
+
});
|
|
1498
1836
|
const chatSessions = createChatSessionsRoutes({
|
|
1499
1837
|
persistence: opts.persistence,
|
|
1500
1838
|
auth: opts.auth,
|
|
@@ -1511,7 +1849,7 @@ function configureAiChat(opts) {
|
|
|
1511
1849
|
});
|
|
1512
1850
|
return {
|
|
1513
1851
|
runAgent: runAgentBound,
|
|
1514
|
-
routes: { agentCustom, chatSessions, adminSettings },
|
|
1852
|
+
routes: { agentCustom, agentVercel, chatSessions, adminSettings },
|
|
1515
1853
|
registries: { toolProviders: toolProviders2, chatInterfaces }
|
|
1516
1854
|
};
|
|
1517
1855
|
}
|