@adminforth/agent 1.44.2 → 1.45.1
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/agentTurnService.ts +526 -0
- package/build.log +3 -2
- package/chatSurfaceService.ts +189 -0
- package/custom/ChatSurfaceSettings.vue +125 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/agentTurnService.d.ts +70 -0
- package/dist/agentTurnService.js +453 -0
- package/dist/chatSurfaceService.d.ts +29 -0
- package/dist/chatSurfaceService.js +142 -0
- package/dist/custom/ChatSurfaceSettings.vue +125 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/endpoints/chatSurfaces.d.ts +3 -0
- package/dist/endpoints/chatSurfaces.js +91 -0
- package/dist/endpoints/context.d.ts +30 -0
- package/dist/endpoints/context.js +1 -0
- package/dist/endpoints/core.d.ts +3 -0
- package/dist/endpoints/core.js +106 -0
- package/dist/endpoints/sessions.d.ts +3 -0
- package/dist/endpoints/sessions.js +177 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +9 -0
- package/dist/index.d.ts +5 -42
- package/dist/index.js +50 -808
- package/dist/sessionStore.d.ts +19 -0
- package/dist/sessionStore.js +83 -0
- package/dist/types.d.ts +4 -0
- package/endpoints/chatSurfaces.ts +93 -0
- package/endpoints/context.ts +66 -0
- package/endpoints/core.ts +113 -0
- package/endpoints/sessions.ts +183 -0
- package/errors.ts +10 -0
- package/index.ts +60 -907
- package/package.json +2 -2
- package/sessionStore.ts +94 -0
- package/types.ts +5 -0
- package/agentResponseEvents.ts +0 -1
- package/dist/agentResponseEvents.d.ts +0 -1
- package/dist/agentResponseEvents.js +0 -1
package/index.ts
CHANGED
|
@@ -1,124 +1,34 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AdminUser,
|
|
3
2
|
AdminForthResource,
|
|
4
|
-
ChatSurfaceAdapter,
|
|
5
|
-
ChatSurfaceEventSink,
|
|
6
|
-
ChatSurfaceIncomingMessage,
|
|
7
3
|
IAdminForth,
|
|
8
4
|
IHttpServer,
|
|
9
5
|
} from "adminforth";
|
|
10
6
|
|
|
11
|
-
import { AdminForthPlugin
|
|
7
|
+
import { AdminForthPlugin } from "adminforth";
|
|
12
8
|
|
|
13
9
|
import type { PluginOptions } from './types.js';
|
|
14
|
-
import { randomUUID } from 'crypto';
|
|
15
|
-
import { HumanMessage, SystemMessage } from "langchain";
|
|
16
10
|
import { MemorySaver, type BaseCheckpointSaver } from "@langchain/langgraph";
|
|
17
11
|
import { z } from "zod";
|
|
18
|
-
import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
|
|
19
12
|
import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
13
|
+
import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
|
|
14
|
+
import { setupCoreEndpoints } from "./endpoints/core.js";
|
|
15
|
+
import { setupSessionEndpoints } from "./endpoints/sessions.js";
|
|
16
|
+
import { setupChatSurfaceEndpoints } from "./endpoints/chatSurfaces.js";
|
|
17
|
+
import type { AgentEndpointsContext } from "./endpoints/context.js";
|
|
18
|
+
import { AgentSessionStore } from "./sessionStore.js";
|
|
19
|
+
import { ChatSurfaceService } from "./chatSurfaceService.js";
|
|
20
|
+
import { AgentTurnService } from "./agentTurnService.js";
|
|
28
21
|
|
|
29
22
|
export type { AgentEvent, AgentEventEmitter } from "./agentEvents.js";
|
|
30
23
|
|
|
31
|
-
type MulterFile = {
|
|
32
|
-
buffer: Buffer;
|
|
33
|
-
originalname: string;
|
|
34
|
-
mimetype: string;
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
type ExpressMulterRequest = { file?: MulterFile };
|
|
38
|
-
|
|
39
|
-
type AgentTurnRunInput = {
|
|
40
|
-
prompt: string;
|
|
41
|
-
sessionId: string;
|
|
42
|
-
turnId: string;
|
|
43
|
-
previousUserMessages: PreviousUserMessage[];
|
|
44
|
-
modeName?: string | null;
|
|
45
|
-
userTimeZone: string;
|
|
46
|
-
currentPage?: CurrentPageContext;
|
|
47
|
-
abortSignal?: AbortSignal;
|
|
48
|
-
adminUser: AdminUser;
|
|
49
|
-
sequenceDebugCollector: ReturnType<typeof createSequenceDebugCollector>;
|
|
50
|
-
emit?: AgentEventEmitter;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
type RunAndPersistAgentResponseInput =
|
|
54
|
-
Omit<AgentTurnRunInput, "turnId" | "sequenceDebugCollector" | "previousUserMessages"> & {
|
|
55
|
-
failureLogMessage: string;
|
|
56
|
-
abortLogMessage: string;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type HandleTurnInput = Omit<RunAndPersistAgentResponseInput, "failureLogMessage" | "abortLogMessage"> & {
|
|
60
|
-
emit: AgentEventEmitter;
|
|
61
|
-
failureLogMessage?: string;
|
|
62
|
-
abortLogMessage?: string;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const agentResponseBodySchema = z.object({
|
|
66
|
-
message: z.string(),
|
|
67
|
-
sessionId: z.string(),
|
|
68
|
-
mode: z.string().nullish(),
|
|
69
|
-
timeZone: z.string().optional(),
|
|
70
|
-
currentPage: z.custom<CurrentPageContext>().optional(),
|
|
71
|
-
}).strict();
|
|
72
|
-
|
|
73
|
-
const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({message: true})
|
|
74
|
-
|
|
75
|
-
const addSystemMessageBodySchema = z.object({
|
|
76
|
-
sessionId: z.string(),
|
|
77
|
-
systemMessage: z.string(),
|
|
78
|
-
}).strict();
|
|
79
|
-
|
|
80
|
-
const getSessionsBodySchema = z.object({
|
|
81
|
-
limit: z.number().optional(),
|
|
82
|
-
}).strict();
|
|
83
|
-
|
|
84
|
-
const sessionIdBodySchema = z.object({
|
|
85
|
-
sessionId: z.string(),
|
|
86
|
-
}).strict();
|
|
87
|
-
|
|
88
|
-
const createSessionBodySchema = z.object({
|
|
89
|
-
triggerMessage: z.string().optional(),
|
|
90
|
-
}).strict();
|
|
91
|
-
|
|
92
|
-
const VEGA_LITE_FENCE_START = "```vega-lite";
|
|
93
|
-
const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
|
|
94
|
-
|
|
95
|
-
function isAbortError(error: unknown): boolean {
|
|
96
|
-
return (
|
|
97
|
-
error instanceof DOMException && error.name === "AbortError"
|
|
98
|
-
) || (
|
|
99
|
-
typeof error === "object" &&
|
|
100
|
-
error !== null &&
|
|
101
|
-
"name" in error &&
|
|
102
|
-
(error.name === "AbortError" || error.name === "APIUserAbortError")
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function getErrorMessage(error: unknown): string {
|
|
107
|
-
return error instanceof Error ? error.message : String(error);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function requireAdminUser(adminUser: AdminUser | undefined): AdminUser {
|
|
111
|
-
if (!adminUser) {
|
|
112
|
-
throw new Error("AdminForth Agent endpoint requires an authenticated admin user");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return adminUser;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
24
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
119
25
|
options: PluginOptions;
|
|
120
26
|
agentSystemPromptPromise: Promise<string>;
|
|
121
27
|
private checkpointer: BaseCheckpointSaver | null = null;
|
|
28
|
+
private sessionStore: AgentSessionStore;
|
|
29
|
+
private agentTurnService: AgentTurnService;
|
|
30
|
+
private chatSurfaceService: ChatSurfaceService;
|
|
31
|
+
private chatSurfaceSettingsPageRegistered = false;
|
|
122
32
|
private parseBody<T>(
|
|
123
33
|
schema: z.ZodType<T>,
|
|
124
34
|
body: unknown,
|
|
@@ -131,72 +41,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
131
41
|
}
|
|
132
42
|
return parsed.data;
|
|
133
43
|
}
|
|
134
|
-
private async createNewTurn(sessionId: string, prompt: string, response?: string) {
|
|
135
|
-
const turnId = randomUUID();
|
|
136
|
-
const turnRecord = {
|
|
137
|
-
[this.options.turnResource.idField]: turnId,
|
|
138
|
-
[this.options.turnResource.sessionIdField]: sessionId,
|
|
139
|
-
[this.options.turnResource.promptField]: prompt,
|
|
140
|
-
[this.options.turnResource.responseField]: response || "not_finished",
|
|
141
|
-
};
|
|
142
|
-
const newTurn = await this.adminforth.resource(this.options.turnResource.resourceId).create(turnRecord);
|
|
143
|
-
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
private async getSessionTurns(sessionId: string) {
|
|
147
|
-
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
148
|
-
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
149
|
-
undefined,
|
|
150
|
-
undefined,
|
|
151
|
-
[Sorts.ASC(this.options.turnResource.createdAtField)]
|
|
152
|
-
);
|
|
153
|
-
return turns.map(turn => ({
|
|
154
|
-
prompt: turn[this.options.turnResource.promptField],
|
|
155
|
-
response: turn[this.options.turnResource.responseField],
|
|
156
|
-
}));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
private async getPreviousUserMessages(sessionId: string) {
|
|
160
|
-
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
161
|
-
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
162
|
-
2,
|
|
163
|
-
undefined,
|
|
164
|
-
[Sorts.DESC(this.options.turnResource.createdAtField)]
|
|
165
|
-
);
|
|
166
|
-
return turns
|
|
167
|
-
.reverse()
|
|
168
|
-
.map((turn): PreviousUserMessage => ({
|
|
169
|
-
text: turn[this.options.turnResource.promptField],
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private getChatSurfaceSessionId(incoming: ChatSurfaceIncomingMessage) {
|
|
174
|
-
return `${incoming.surface}:${incoming.externalConversationId}`;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private async getOrCreateChatSurfaceSession(
|
|
178
|
-
incoming: ChatSurfaceIncomingMessage,
|
|
179
|
-
adminUser: AdminUser,
|
|
180
|
-
) {
|
|
181
|
-
const sessionId = this.getChatSurfaceSessionId(incoming);
|
|
182
|
-
const sessionResource = this.adminforth.resource(this.options.sessionResource.resourceId);
|
|
183
|
-
const session = await sessionResource.get(
|
|
184
|
-
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
if (session) {
|
|
188
|
-
return sessionId;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
await sessionResource.create({
|
|
192
|
-
[this.options.sessionResource.idField]: sessionId,
|
|
193
|
-
[this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
|
|
194
|
-
[this.options.sessionResource.askerIdField]: adminUser.pk,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
return sessionId;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
44
|
private getCheckpointer() {
|
|
201
45
|
if (this.checkpointer) return this.checkpointer;
|
|
202
46
|
|
|
@@ -218,6 +62,22 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
218
62
|
constructor(options: PluginOptions) {
|
|
219
63
|
super(options, import.meta.url);
|
|
220
64
|
this.options = options;
|
|
65
|
+
this.sessionStore = new AgentSessionStore(() => this.adminforth, this.options);
|
|
66
|
+
this.agentTurnService = new AgentTurnService({
|
|
67
|
+
getAdminforth: () => this.adminforth,
|
|
68
|
+
getPluginInstanceId: () => this.pluginInstanceId,
|
|
69
|
+
options: this.options,
|
|
70
|
+
sessionStore: this.sessionStore,
|
|
71
|
+
getCheckpointer: this.getCheckpointer.bind(this),
|
|
72
|
+
getInternalAgentResourceIds: this.getInternalAgentResourceIds.bind(this),
|
|
73
|
+
getAgentSystemPrompt: () => this.agentSystemPromptPromise,
|
|
74
|
+
});
|
|
75
|
+
this.chatSurfaceService = new ChatSurfaceService(
|
|
76
|
+
() => this.adminforth,
|
|
77
|
+
this.options,
|
|
78
|
+
this.sessionStore,
|
|
79
|
+
this.agentTurnService.handleTurn.bind(this.agentTurnService),
|
|
80
|
+
);
|
|
221
81
|
this.agentSystemPromptPromise = Promise.resolve(
|
|
222
82
|
appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt),
|
|
223
83
|
);
|
|
@@ -242,6 +102,19 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
242
102
|
hasAudioAdapter: Boolean(this.options.audioAdapter),
|
|
243
103
|
}
|
|
244
104
|
});
|
|
105
|
+
if (this.chatSurfaceService.getConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
|
|
106
|
+
if (!this.adminforth.config.auth!.userMenuSettingsPages) {
|
|
107
|
+
this.adminforth.config.auth!.userMenuSettingsPages = [];
|
|
108
|
+
}
|
|
109
|
+
this.adminforth.config.auth!.userMenuSettingsPages.push({
|
|
110
|
+
icon: "flowbite:link-outline",
|
|
111
|
+
pageLabel: "Chat Surfaces",
|
|
112
|
+
slug: "chat-surfaces",
|
|
113
|
+
component: this.componentPath("ChatSurfaceSettings.vue"),
|
|
114
|
+
isVisible: () => true,
|
|
115
|
+
});
|
|
116
|
+
this.chatSurfaceSettingsPageRegistered = true;
|
|
117
|
+
}
|
|
245
118
|
if (!this.adminforth.config.customization.customHeadItems) {
|
|
246
119
|
this.adminforth.config.customization.customHeadItems = [];
|
|
247
120
|
}
|
|
@@ -280,744 +153,24 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
280
153
|
return `single`;
|
|
281
154
|
}
|
|
282
155
|
|
|
283
|
-
private async runAgentTurn(input: AgentTurnRunInput) {
|
|
284
|
-
let fullResponse = "";
|
|
285
|
-
let bufferedTextDelta = "";
|
|
286
|
-
let isRenderingVegaLite = false;
|
|
287
|
-
const maxTokens = this.options.maxTokens ?? 1000;
|
|
288
|
-
const selectedMode = this.options.modes.find((mode) => mode.name === input.modeName) ?? this.options.modes[0];
|
|
289
|
-
const [primaryModelSpec, summaryModelSpec] = await Promise.all([
|
|
290
|
-
createAgentChatModel({
|
|
291
|
-
adapter: selectedMode.completionAdapter,
|
|
292
|
-
maxTokens,
|
|
293
|
-
purpose: "primary",
|
|
294
|
-
}),
|
|
295
|
-
createAgentChatModel({
|
|
296
|
-
adapter: selectedMode.completionAdapter,
|
|
297
|
-
maxTokens,
|
|
298
|
-
purpose: "summary",
|
|
299
|
-
}),
|
|
300
|
-
]);
|
|
301
|
-
const model = primaryModelSpec.model;
|
|
302
|
-
const summaryModel = summaryModelSpec.model;
|
|
303
|
-
const modelMiddleware = primaryModelSpec.middleware;
|
|
304
|
-
|
|
305
|
-
const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt, input.previousUserMessages)
|
|
306
|
-
.catch((error) => {
|
|
307
|
-
if (input.abortSignal?.aborted || isAbortError(error)) {
|
|
308
|
-
throw error;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
logger.warn(`Failed to detect user language: ${getErrorMessage(error)}`);
|
|
312
|
-
return null;
|
|
313
|
-
});
|
|
314
|
-
const systemPrompt = buildAgentTurnSystemPrompt({
|
|
315
|
-
agentSystemPrompt: await this.agentSystemPromptPromise,
|
|
316
|
-
adminUser: input.adminUser,
|
|
317
|
-
usernameField: this.adminforth.config.auth!.usernameField,
|
|
318
|
-
userLanguage,
|
|
319
|
-
});
|
|
320
|
-
const apiBasedTools = buildApiBasedTools(
|
|
321
|
-
this.adminforth,
|
|
322
|
-
this.getInternalAgentResourceIds(),
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const stream = await callAgent({
|
|
326
|
-
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
327
|
-
model,
|
|
328
|
-
summaryModel,
|
|
329
|
-
modelMiddleware,
|
|
330
|
-
checkpointer: this.getCheckpointer(),
|
|
331
|
-
messages: [
|
|
332
|
-
new SystemMessage(systemPrompt),
|
|
333
|
-
new HumanMessage(input.prompt),
|
|
334
|
-
],
|
|
335
|
-
adminUser: input.adminUser,
|
|
336
|
-
adminforth: this.adminforth,
|
|
337
|
-
apiBasedTools,
|
|
338
|
-
customComponentsDir: this.adminforth.config.customization.customComponentsDir ?? "custom",
|
|
339
|
-
sessionId: input.sessionId,
|
|
340
|
-
turnId: input.turnId,
|
|
341
|
-
currentPage: input.currentPage,
|
|
342
|
-
userTimeZone: input.userTimeZone,
|
|
343
|
-
abortSignal: input.abortSignal,
|
|
344
|
-
emitToolCallEvent: (event) => {
|
|
345
|
-
input.sequenceDebugCollector.handleToolCallEvent(event);
|
|
346
|
-
void input.emit?.({
|
|
347
|
-
type: "tool-call",
|
|
348
|
-
data: event,
|
|
349
|
-
});
|
|
350
|
-
},
|
|
351
|
-
sequenceDebugSink: input.sequenceDebugCollector,
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
for await (const rawChunk of stream as AsyncIterable<[any, any]>) {
|
|
355
|
-
if (input.abortSignal?.aborted) {
|
|
356
|
-
throw new DOMException("This operation was aborted", "AbortError");
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const [token, metadata] = rawChunk;
|
|
360
|
-
|
|
361
|
-
const nodeName =
|
|
362
|
-
typeof metadata?.langgraph_node === "string"
|
|
363
|
-
? metadata.langgraph_node
|
|
364
|
-
: "";
|
|
365
|
-
|
|
366
|
-
if (nodeName && !["model", "model_request"].includes(nodeName)) {
|
|
367
|
-
continue;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const blocks = Array.isArray(token?.contentBlocks)
|
|
371
|
-
? token.contentBlocks
|
|
372
|
-
: Array.isArray(token?.content)
|
|
373
|
-
? token.content
|
|
374
|
-
: [];
|
|
375
|
-
const reasoningDelta = blocks
|
|
376
|
-
.filter((b: any) => b?.type === "reasoning")
|
|
377
|
-
.map((b: any) => String(b.reasoning ?? ""))
|
|
378
|
-
.join("");
|
|
379
|
-
|
|
380
|
-
const textDelta = blocks
|
|
381
|
-
.filter((b: any) => b?.type === "text")
|
|
382
|
-
.map((b: any) => String(b.text ?? ""))
|
|
383
|
-
.join("");
|
|
384
|
-
|
|
385
|
-
if (reasoningDelta) {
|
|
386
|
-
await input.emit?.({
|
|
387
|
-
type: "reasoning-delta",
|
|
388
|
-
delta: reasoningDelta,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
if (textDelta) {
|
|
393
|
-
fullResponse += textDelta;
|
|
394
|
-
bufferedTextDelta += textDelta;
|
|
395
|
-
|
|
396
|
-
if (
|
|
397
|
-
bufferedTextDelta.includes(VEGA_LITE_FENCE_START) &&
|
|
398
|
-
!COMPLETE_VEGA_LITE_BLOCK_RE.test(bufferedTextDelta)
|
|
399
|
-
) {
|
|
400
|
-
if (!isRenderingVegaLite) {
|
|
401
|
-
isRenderingVegaLite = true;
|
|
402
|
-
await input.emit?.({
|
|
403
|
-
type: "rendering",
|
|
404
|
-
phase: "start",
|
|
405
|
-
label: "Rendering...",
|
|
406
|
-
});
|
|
407
|
-
}
|
|
408
|
-
continue;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (isRenderingVegaLite) {
|
|
412
|
-
isRenderingVegaLite = false;
|
|
413
|
-
await input.emit?.({
|
|
414
|
-
type: "rendering",
|
|
415
|
-
phase: "end",
|
|
416
|
-
label: "Rendering...",
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const streamableLength = bufferedTextDelta.includes(VEGA_LITE_FENCE_START)
|
|
421
|
-
? bufferedTextDelta.length
|
|
422
|
-
: bufferedTextDelta.length - getPartialVegaLiteFenceStartLength(bufferedTextDelta);
|
|
423
|
-
|
|
424
|
-
if (!streamableLength) {
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
await input.emit?.({
|
|
429
|
-
type: "text-delta",
|
|
430
|
-
delta: bufferedTextDelta.slice(0, streamableLength),
|
|
431
|
-
});
|
|
432
|
-
bufferedTextDelta = bufferedTextDelta.slice(streamableLength);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (isRenderingVegaLite) {
|
|
437
|
-
await input.emit?.({
|
|
438
|
-
type: "rendering",
|
|
439
|
-
phase: "end",
|
|
440
|
-
label: "Rendering...",
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
if (bufferedTextDelta) {
|
|
445
|
-
await input.emit?.({
|
|
446
|
-
type: "text-delta",
|
|
447
|
-
delta: bufferedTextDelta,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return {
|
|
452
|
-
text: fullResponse,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
private async runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput) {
|
|
457
|
-
const previousUserMessages = await this.getPreviousUserMessages(input.sessionId);
|
|
458
|
-
const turnId = await this.createNewTurn(input.sessionId, input.prompt);
|
|
459
|
-
await this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
|
|
460
|
-
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
461
|
-
});
|
|
462
|
-
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
463
|
-
let fullResponse = "";
|
|
464
|
-
let aborted = false;
|
|
465
|
-
let failed = false;
|
|
466
|
-
|
|
467
|
-
try {
|
|
468
|
-
const agentResponse = await this.runAgentTurn({
|
|
469
|
-
prompt: input.prompt,
|
|
470
|
-
sessionId: input.sessionId,
|
|
471
|
-
turnId,
|
|
472
|
-
previousUserMessages,
|
|
473
|
-
modeName: input.modeName,
|
|
474
|
-
userTimeZone: input.userTimeZone,
|
|
475
|
-
currentPage: input.currentPage,
|
|
476
|
-
abortSignal: input.abortSignal,
|
|
477
|
-
adminUser: input.adminUser,
|
|
478
|
-
sequenceDebugCollector,
|
|
479
|
-
emit: input.emit,
|
|
480
|
-
});
|
|
481
|
-
fullResponse = agentResponse.text;
|
|
482
|
-
} catch (error) {
|
|
483
|
-
if (input.abortSignal?.aborted || isAbortError(error)) {
|
|
484
|
-
aborted = true;
|
|
485
|
-
logger.info(input.abortLogMessage);
|
|
486
|
-
} else {
|
|
487
|
-
failed = true;
|
|
488
|
-
fullResponse = getErrorMessage(error);
|
|
489
|
-
logger.error(`${input.failureLogMessage}:\n${fullResponse}`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
sequenceDebugCollector.flush();
|
|
494
|
-
const turnUpdates: Record<string, unknown> = {
|
|
495
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
if (this.options.turnResource.debugField) {
|
|
499
|
-
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
await this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, turnUpdates);
|
|
503
|
-
|
|
504
|
-
return {
|
|
505
|
-
text: fullResponse,
|
|
506
|
-
turnId,
|
|
507
|
-
aborted,
|
|
508
|
-
failed,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
async handleTurn(input: HandleTurnInput) {
|
|
513
|
-
await input.emit({
|
|
514
|
-
type: "turn-started",
|
|
515
|
-
messageId: randomUUID(),
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
const agentResponse = await this.runAndPersistAgentResponse({
|
|
519
|
-
prompt: input.prompt,
|
|
520
|
-
sessionId: input.sessionId,
|
|
521
|
-
modeName: input.modeName,
|
|
522
|
-
userTimeZone: input.userTimeZone,
|
|
523
|
-
currentPage: input.currentPage,
|
|
524
|
-
abortSignal: input.abortSignal,
|
|
525
|
-
adminUser: input.adminUser,
|
|
526
|
-
emit: input.emit,
|
|
527
|
-
failureLogMessage: input.failureLogMessage ?? "Agent response failed",
|
|
528
|
-
abortLogMessage: input.abortLogMessage ?? "Agent response aborted",
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
if (agentResponse.failed) {
|
|
532
|
-
await input.emit({
|
|
533
|
-
type: "error",
|
|
534
|
-
error: agentResponse.text,
|
|
535
|
-
});
|
|
536
|
-
} else if (!agentResponse.aborted) {
|
|
537
|
-
await input.emit({
|
|
538
|
-
type: "response",
|
|
539
|
-
text: agentResponse.text,
|
|
540
|
-
sessionId: input.sessionId,
|
|
541
|
-
turnId: agentResponse.turnId,
|
|
542
|
-
});
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
await input.emit({
|
|
546
|
-
type: "finish",
|
|
547
|
-
});
|
|
548
|
-
|
|
549
|
-
return agentResponse;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
private createChatSurfaceEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
|
|
553
|
-
return async (event) => {
|
|
554
|
-
if (event.type === "text-delta") {
|
|
555
|
-
await sink.emit({
|
|
556
|
-
type: "text_delta",
|
|
557
|
-
delta: event.delta,
|
|
558
|
-
});
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
if (event.type === "response") {
|
|
563
|
-
await sink.emit({
|
|
564
|
-
type: "done",
|
|
565
|
-
text: event.text,
|
|
566
|
-
});
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
if (event.type === "error") {
|
|
571
|
-
await sink.emit({
|
|
572
|
-
type: "error",
|
|
573
|
-
message: event.error,
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
private async handleChatSurfaceMessage(
|
|
580
|
-
adapter: ChatSurfaceAdapter,
|
|
581
|
-
incoming: ChatSurfaceIncomingMessage,
|
|
582
|
-
sink: ChatSurfaceEventSink,
|
|
583
|
-
) {
|
|
584
|
-
const adminUser = await adapter.resolveAdminUser({
|
|
585
|
-
adminforth: this.adminforth,
|
|
586
|
-
incoming,
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
if (!adminUser) {
|
|
590
|
-
await sink.emit({
|
|
591
|
-
type: "error",
|
|
592
|
-
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
593
|
-
});
|
|
594
|
-
return;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
await this.handleTurn({
|
|
598
|
-
prompt: incoming.prompt,
|
|
599
|
-
sessionId: await this.getOrCreateChatSurfaceSession(incoming, adminUser),
|
|
600
|
-
modeName: incoming.modeName,
|
|
601
|
-
userTimeZone: incoming.userTimeZone ?? "UTC",
|
|
602
|
-
adminUser,
|
|
603
|
-
emit: this.createChatSurfaceEventEmitter(sink),
|
|
604
|
-
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
605
|
-
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
|
|
609
156
|
setupEndpoints(server: IHttpServer) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
try {
|
|
630
|
-
await this.handleChatSurfaceMessage(adapter, incoming, sink);
|
|
631
|
-
} finally {
|
|
632
|
-
await sink.close?.();
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return { ok: true };
|
|
636
|
-
},
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
server.endpoint({
|
|
641
|
-
method: 'POST',
|
|
642
|
-
path: `/agent/get-placeholder-messages`,
|
|
643
|
-
handler: async ({ headers, adminUser }) => {
|
|
644
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
645
|
-
|
|
646
|
-
if (!this.options.placeholderMessages) {
|
|
647
|
-
return {
|
|
648
|
-
messages: [],
|
|
649
|
-
};
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const messages = await this.options.placeholderMessages({
|
|
653
|
-
adminUser: currentAdminUser,
|
|
654
|
-
headers,
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
return {
|
|
658
|
-
messages,
|
|
659
|
-
};
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
server.endpoint({
|
|
663
|
-
method: 'POST',
|
|
664
|
-
path: `/agent/response`,
|
|
665
|
-
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
666
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
667
|
-
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
668
|
-
if (!data) return;
|
|
669
|
-
const emit = createSseEventEmitter(_raw_express_res, {
|
|
670
|
-
vercelAiUiMessageStream: true,
|
|
671
|
-
closeActiveBlockOnToolStart: true,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
await this.handleTurn({
|
|
675
|
-
prompt: data.message,
|
|
676
|
-
sessionId: data.sessionId,
|
|
677
|
-
modeName: data.mode,
|
|
678
|
-
userTimeZone: data.timeZone ?? 'UTC',
|
|
679
|
-
currentPage: data.currentPage,
|
|
680
|
-
abortSignal,
|
|
681
|
-
adminUser: currentAdminUser,
|
|
682
|
-
emit,
|
|
683
|
-
failureLogMessage: "Agent response streaming failed",
|
|
684
|
-
abortLogMessage: "Agent response streaming aborted by the client",
|
|
685
|
-
});
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
});
|
|
689
|
-
server.endpoint({
|
|
690
|
-
method: 'POST',
|
|
691
|
-
path: `/agent/speech-response`,
|
|
692
|
-
target: 'upload',
|
|
693
|
-
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
694
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
695
|
-
const req = _raw_express_req as ExpressMulterRequest;
|
|
696
|
-
const audioAdapter = this.options.audioAdapter;
|
|
697
|
-
if (!audioAdapter) {
|
|
698
|
-
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
699
|
-
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
700
|
-
}
|
|
701
|
-
const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
702
|
-
if (!data) return;
|
|
703
|
-
if (!req.file) {
|
|
704
|
-
response.setStatus(400, "Audio file is required");
|
|
705
|
-
return { error: "Audio file is required" };
|
|
706
|
-
}
|
|
707
|
-
const emit = createSseEventEmitter(_raw_express_res);
|
|
708
|
-
|
|
709
|
-
let transcription;
|
|
710
|
-
|
|
711
|
-
try {
|
|
712
|
-
transcription = await audioAdapter.transcribe({
|
|
713
|
-
buffer: req.file.buffer,
|
|
714
|
-
filename: req.file.originalname,
|
|
715
|
-
mimeType: req.file.mimetype,
|
|
716
|
-
language: "auto",
|
|
717
|
-
abortSignal,
|
|
718
|
-
});
|
|
719
|
-
} catch (error) {
|
|
720
|
-
if (abortSignal.aborted || isAbortError(error)) {
|
|
721
|
-
logger.info("Agent speech transcription aborted by the client");
|
|
722
|
-
await emit({ type: "finish" });
|
|
723
|
-
return null;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
logger.error(`Agent speech transcription failed:\n${getErrorMessage(error)}`);
|
|
727
|
-
await emit({
|
|
728
|
-
type: "error",
|
|
729
|
-
error: "Speech transcription failed. Check server logs for details.",
|
|
730
|
-
});
|
|
731
|
-
await emit({ type: "finish" });
|
|
732
|
-
return null;
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (abortSignal.aborted) {
|
|
736
|
-
await emit({ type: "finish" });
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const prompt = transcription.text;
|
|
741
|
-
if (!prompt) {
|
|
742
|
-
await emit({
|
|
743
|
-
type: "error",
|
|
744
|
-
error: "Speech transcription is empty",
|
|
745
|
-
});
|
|
746
|
-
await emit({ type: "finish" });
|
|
747
|
-
return null;
|
|
748
|
-
}
|
|
749
|
-
await emit({
|
|
750
|
-
type: "transcript",
|
|
751
|
-
text: transcription.text,
|
|
752
|
-
language: transcription.language,
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
const sessionId = data.sessionId as string;
|
|
756
|
-
const currentPage = data.currentPage;
|
|
757
|
-
const agentResponse = await this.runAndPersistAgentResponse({
|
|
758
|
-
prompt,
|
|
759
|
-
sessionId,
|
|
760
|
-
modeName: data.mode,
|
|
761
|
-
userTimeZone: data.timeZone ?? 'UTC',
|
|
762
|
-
currentPage,
|
|
763
|
-
abortSignal,
|
|
764
|
-
adminUser: currentAdminUser,
|
|
765
|
-
emit: async (event) => {
|
|
766
|
-
if (event.type === "tool-call") {
|
|
767
|
-
await emit(event);
|
|
768
|
-
}
|
|
769
|
-
},
|
|
770
|
-
failureLogMessage: "Agent speech response failed",
|
|
771
|
-
abortLogMessage: "Agent speech response aborted by the client",
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
if (agentResponse.aborted) {
|
|
775
|
-
await emit({ type: "finish" });
|
|
776
|
-
return null;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (agentResponse.failed) {
|
|
780
|
-
await emit({
|
|
781
|
-
type: "error",
|
|
782
|
-
error: agentResponse.text,
|
|
783
|
-
});
|
|
784
|
-
await emit({ type: "finish" });
|
|
785
|
-
return null;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
try {
|
|
789
|
-
await emit({
|
|
790
|
-
type: "speech-response",
|
|
791
|
-
transcript: {
|
|
792
|
-
text: transcription.text,
|
|
793
|
-
language: transcription.language,
|
|
794
|
-
},
|
|
795
|
-
response: {
|
|
796
|
-
text: agentResponse.text,
|
|
797
|
-
},
|
|
798
|
-
sessionId,
|
|
799
|
-
turnId: agentResponse.turnId,
|
|
800
|
-
});
|
|
801
|
-
const speech = await audioAdapter.synthesize({
|
|
802
|
-
text: sanitizeSpeechText(agentResponse.text),
|
|
803
|
-
stream: true,
|
|
804
|
-
streamFormat: "audio",
|
|
805
|
-
format: "pcm",
|
|
806
|
-
abortSignal,
|
|
807
|
-
});
|
|
808
|
-
|
|
809
|
-
await emit({
|
|
810
|
-
type: "audio-start",
|
|
811
|
-
mimeType: speech.mimeType,
|
|
812
|
-
format: speech.format,
|
|
813
|
-
sampleRate: 24000,
|
|
814
|
-
channelCount: 1,
|
|
815
|
-
bitsPerSample: 16,
|
|
816
|
-
});
|
|
817
|
-
|
|
818
|
-
const reader = speech.audioStream.getReader();
|
|
819
|
-
const cancelAudioStream = () => {
|
|
820
|
-
void reader.cancel().catch(() => undefined);
|
|
821
|
-
};
|
|
822
|
-
|
|
823
|
-
try {
|
|
824
|
-
abortSignal.addEventListener("abort", cancelAudioStream, { once: true });
|
|
825
|
-
|
|
826
|
-
while (true) {
|
|
827
|
-
if (abortSignal.aborted) {
|
|
828
|
-
await reader.cancel().catch(() => undefined);
|
|
829
|
-
break;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
const { value, done } = await reader.read();
|
|
833
|
-
|
|
834
|
-
if (done) {
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
if (abortSignal.aborted) {
|
|
839
|
-
break;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
await emit({
|
|
843
|
-
type: "audio-delta",
|
|
844
|
-
value,
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
} finally {
|
|
848
|
-
abortSignal.removeEventListener("abort", cancelAudioStream);
|
|
849
|
-
reader.releaseLock();
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
await emit({ type: "audio-done" });
|
|
853
|
-
await emit({ type: "finish" });
|
|
854
|
-
return null;
|
|
855
|
-
} catch (error) {
|
|
856
|
-
if (abortSignal.aborted || isAbortError(error)) {
|
|
857
|
-
logger.info("Agent speech audio streaming aborted by the client");
|
|
858
|
-
} else {
|
|
859
|
-
logger.error(`Agent speech audio streaming failed:\n${error}`);
|
|
860
|
-
await emit({
|
|
861
|
-
type: "error",
|
|
862
|
-
error: getErrorMessage(error),
|
|
863
|
-
});
|
|
864
|
-
}
|
|
865
|
-
await emit({ type: "finish" });
|
|
866
|
-
return null;
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
});
|
|
870
|
-
server.endpoint({
|
|
871
|
-
method: 'POST',
|
|
872
|
-
path: `/agent/get-sessions`,
|
|
873
|
-
handler: async ({body, adminUser, response }) => {
|
|
874
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
875
|
-
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
876
|
-
if (!data) return;
|
|
877
|
-
const userId = currentAdminUser.pk;
|
|
878
|
-
const limit = data.limit ?? 20;
|
|
879
|
-
const sessions = await this.adminforth.resource(this.options.sessionResource.resourceId).list(
|
|
880
|
-
[Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]
|
|
881
|
-
);
|
|
882
|
-
return {
|
|
883
|
-
sessions: sessions.map((session) => ({
|
|
884
|
-
sessionId: session[this.options.sessionResource.idField],
|
|
885
|
-
title: session[this.options.sessionResource.titleField],
|
|
886
|
-
timestamp: session[this.options.sessionResource.createdAtField],
|
|
887
|
-
})),
|
|
888
|
-
};
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
server.endpoint({
|
|
892
|
-
method: 'POST',
|
|
893
|
-
path: `/agent/get-session-info`,
|
|
894
|
-
handler: async ({body, adminUser, response }) => {
|
|
895
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
896
|
-
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
897
|
-
if (!parsedBody.success) {
|
|
898
|
-
response.setStatus(422, parsedBody.error.message);
|
|
899
|
-
return;
|
|
900
|
-
}
|
|
901
|
-
const userId = currentAdminUser.pk;
|
|
902
|
-
const sessionId = parsedBody.data.sessionId;
|
|
903
|
-
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
904
|
-
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
905
|
-
);
|
|
906
|
-
if (!session) {
|
|
907
|
-
return {
|
|
908
|
-
error: 'Session not found'
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
912
|
-
return {
|
|
913
|
-
error: 'Unauthorized'
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
const turns = await this.getSessionTurns(sessionId);
|
|
917
|
-
return {
|
|
918
|
-
session: {
|
|
919
|
-
sessionId,
|
|
920
|
-
title: session[this.options.sessionResource.titleField],
|
|
921
|
-
timestamp: session[this.options.sessionResource.createdAtField],
|
|
922
|
-
messages: turns.flatMap(turn => {
|
|
923
|
-
const messages: Array<{ text: string; role: 'user' | 'assistant' }> = [];
|
|
924
|
-
if (turn.prompt) {
|
|
925
|
-
messages.push({
|
|
926
|
-
text: turn.prompt,
|
|
927
|
-
role: 'user',
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
if (turn.response && turn.response !== "not_finished") {
|
|
931
|
-
messages.push({
|
|
932
|
-
text: turn.response,
|
|
933
|
-
role: 'assistant',
|
|
934
|
-
});
|
|
935
|
-
}
|
|
936
|
-
return messages;
|
|
937
|
-
}),
|
|
938
|
-
},
|
|
939
|
-
};
|
|
940
|
-
}
|
|
941
|
-
});
|
|
942
|
-
server.endpoint({
|
|
943
|
-
method: 'POST',
|
|
944
|
-
path: `/agent/create-session`,
|
|
945
|
-
handler: async ({body, adminUser, response }) => {
|
|
946
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
947
|
-
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
948
|
-
if (!data) return;
|
|
949
|
-
const triggerMessage = data.triggerMessage;
|
|
950
|
-
const userId = currentAdminUser.pk;
|
|
951
|
-
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
952
|
-
const newSession = {
|
|
953
|
-
[this.options.sessionResource.idField]: randomUUID(),
|
|
954
|
-
[this.options.sessionResource.titleField]: title,
|
|
955
|
-
[this.options.sessionResource.askerIdField]: userId,
|
|
956
|
-
};
|
|
957
|
-
await this.adminforth.resource(this.options.sessionResource.resourceId).create(newSession);
|
|
958
|
-
return {
|
|
959
|
-
sessionId: newSession[this.options.sessionResource.idField],
|
|
960
|
-
title: newSession[this.options.sessionResource.titleField],
|
|
961
|
-
timestamp: newSession[this.options.sessionResource.createdAtField],
|
|
962
|
-
messages: []
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
});
|
|
966
|
-
server.endpoint({
|
|
967
|
-
method: 'POST',
|
|
968
|
-
path: `/agent/delete-session`,
|
|
969
|
-
handler: async ({body, adminUser, response }) => {
|
|
970
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
971
|
-
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
972
|
-
if (!data) return;
|
|
973
|
-
const sessionId = data.sessionId;
|
|
974
|
-
const userId = currentAdminUser.pk;
|
|
975
|
-
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
976
|
-
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
977
|
-
);
|
|
978
|
-
if (!session) {
|
|
979
|
-
return {
|
|
980
|
-
error: 'Session not found'
|
|
981
|
-
};
|
|
982
|
-
}
|
|
983
|
-
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
984
|
-
return {
|
|
985
|
-
error: 'Unauthorized'
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
await this.adminforth.resource(this.options.sessionResource.resourceId).delete(sessionId);
|
|
989
|
-
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
990
|
-
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)]
|
|
991
|
-
);
|
|
992
|
-
for (const turn of turns) {
|
|
993
|
-
await this.adminforth.resource(this.options.turnResource.resourceId).delete(turn[this.options.turnResource.idField]);
|
|
994
|
-
}
|
|
995
|
-
return {
|
|
996
|
-
ok: true
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
}),
|
|
1000
|
-
server.endpoint({
|
|
1001
|
-
method: 'POST',
|
|
1002
|
-
path: `/agent/add-system-message-to-turns`,
|
|
1003
|
-
handler: async ({body, response }) => {
|
|
1004
|
-
const data = this.parseBody(addSystemMessageBodySchema, body, response);
|
|
1005
|
-
if (!data) return;
|
|
1006
|
-
await this.createNewTurn(data.sessionId, data.systemMessage);
|
|
1007
|
-
return {
|
|
1008
|
-
ok: true
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
})
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
function getPartialVegaLiteFenceStartLength(text: string): number {
|
|
1016
|
-
for (let length = Math.min(text.length, VEGA_LITE_FENCE_START.length - 1); length > 0; length -= 1) {
|
|
1017
|
-
if (VEGA_LITE_FENCE_START.startsWith(text.slice(-length))) {
|
|
1018
|
-
return length;
|
|
1019
|
-
}
|
|
157
|
+
const endpointContext = {
|
|
158
|
+
adminforth: this.adminforth,
|
|
159
|
+
options: this.options,
|
|
160
|
+
parseBody: this.parseBody.bind(this),
|
|
161
|
+
handleTurn: this.agentTurnService.handleTurn.bind(this.agentTurnService),
|
|
162
|
+
handleSpeechTurn: this.agentTurnService.handleSpeechTurn.bind(this.agentTurnService),
|
|
163
|
+
runAndPersistAgentResponse: this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService),
|
|
164
|
+
getSessionTurns: this.sessionStore.getSessionTurns.bind(this.sessionStore),
|
|
165
|
+
createNewTurn: this.sessionStore.createNewTurn.bind(this.sessionStore),
|
|
166
|
+
createSystemTurn: this.sessionStore.createSystemTurn.bind(this.sessionStore),
|
|
167
|
+
getChatSurfaceConnectActionAdapters: this.chatSurfaceService.getConnectActionAdapters.bind(this.chatSurfaceService),
|
|
168
|
+
createChatSurfaceLinkToken: this.chatSurfaceService.createLinkToken.bind(this.chatSurfaceService),
|
|
169
|
+
handleChatSurfaceMessage: this.chatSurfaceService.handleMessage.bind(this.chatSurfaceService),
|
|
170
|
+
} satisfies AgentEndpointsContext;
|
|
171
|
+
|
|
172
|
+
setupCoreEndpoints(endpointContext, server);
|
|
173
|
+
setupSessionEndpoints(endpointContext, server);
|
|
174
|
+
setupChatSurfaceEndpoints(endpointContext, server);
|
|
1020
175
|
}
|
|
1021
|
-
|
|
1022
|
-
return 0;
|
|
1023
176
|
}
|