@adminforth/agent 1.37.0 → 1.38.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/agent/languageDetect.ts +0 -8
- package/agent/simpleAgent.ts +5 -5
- package/agent/systemPrompt.ts +35 -4
- package/agent/toolCallEvents.ts +31 -2
- package/agent/tools/apiTool.ts +1 -1
- package/agentResponseEvents.ts +197 -0
- package/apiBasedTools.ts +118 -284
- package/build.log +12 -2
- package/custom/ChatSurface.vue +31 -21
- package/custom/composables/agentAudio/agent-processing.mp3 +0 -0
- package/custom/composables/agentStore/constants.ts +8 -1
- package/custom/composables/agentStore/useAgentSessions.ts +85 -12
- package/custom/composables/useAgentAudio.ts +392 -0
- package/custom/composables/useAgentStore.ts +52 -5
- package/custom/conversation_area/ConversationArea.vue +1 -1
- package/custom/conversation_area/MessageRenderer.vue +12 -1
- package/custom/conversation_area/SystemMessageRenderer.vue +28 -0
- package/custom/conversation_area/TextRenderer.vue +4 -3
- package/custom/conversation_area/ToolRenderer.vue +1 -1
- package/custom/package.json +2 -1
- package/custom/pnpm-lock.yaml +29 -0
- package/custom/speech_recognition_frontend/AudioLines.vue +97 -0
- package/custom/speech_recognition_frontend/MicrophoneButon.vue +157 -0
- package/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +22 -0
- package/custom/speech_recognition_frontend/voiceActivityDetection.ts +151 -0
- package/custom/types.ts +52 -2
- package/dist/agent/languageDetect.js +0 -6
- package/dist/agent/simpleAgent.js +4 -3
- package/dist/agent/systemPrompt.js +24 -3
- package/dist/agent/toolCallEvents.js +24 -2
- package/dist/agent/tools/apiTool.js +1 -1
- package/dist/agentResponseEvents.js +141 -0
- package/dist/apiBasedTools.js +95 -211
- package/dist/custom/ChatSurface.vue +31 -21
- package/dist/custom/composables/agentAudio/agent-processing.mp3 +0 -0
- package/dist/custom/composables/agentStore/constants.ts +8 -1
- package/dist/custom/composables/agentStore/useAgentSessions.ts +85 -12
- package/dist/custom/composables/useAgentAudio.ts +392 -0
- package/dist/custom/composables/useAgentStore.ts +52 -5
- package/dist/custom/conversation_area/ConversationArea.vue +1 -1
- package/dist/custom/conversation_area/MessageRenderer.vue +12 -1
- package/dist/custom/conversation_area/SystemMessageRenderer.vue +28 -0
- package/dist/custom/conversation_area/TextRenderer.vue +4 -3
- package/dist/custom/conversation_area/ToolRenderer.vue +1 -1
- package/dist/custom/package.json +2 -1
- package/dist/custom/pnpm-lock.yaml +29 -0
- package/dist/custom/speech_recognition_frontend/AudioLines.vue +97 -0
- package/dist/custom/speech_recognition_frontend/MicrophoneButon.vue +157 -0
- package/dist/custom/speech_recognition_frontend/types/voice-activity-detection.d.ts +22 -0
- package/dist/custom/speech_recognition_frontend/voiceActivityDetection.ts +151 -0
- package/dist/custom/types.ts +52 -2
- package/dist/index.js +290 -400
- package/index.ts +318 -492
- package/package.json +3 -2
- package/types.ts +1 -1
package/index.ts
CHANGED
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
AdminUser,
|
|
3
|
-
AdminForthResource,
|
|
4
|
-
HttpExtra,
|
|
5
|
-
IAdminForth,
|
|
6
|
-
IHttpServer,
|
|
7
|
-
TextToSpeechInput,
|
|
8
|
-
} from "adminforth";
|
|
1
|
+
import type { AdminUser, AdminForthResource, IAdminForth, IHttpServer } from "adminforth";
|
|
9
2
|
|
|
10
3
|
import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
|
|
11
4
|
|
|
@@ -13,44 +6,24 @@ import type { PluginOptions } from './types.js';
|
|
|
13
6
|
import { randomUUID } from 'crypto';
|
|
14
7
|
import { HumanMessage, SystemMessage } from "langchain";
|
|
15
8
|
import { MemorySaver, type BaseCheckpointSaver } from "@langchain/langgraph";
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
callAgent,
|
|
19
|
-
type AgentChatModel,
|
|
20
|
-
} from "./agent/simpleAgent.js";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
|
|
21
11
|
import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
22
12
|
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
} from "./agent/
|
|
27
|
-
import {
|
|
28
|
-
prepareApiBasedTools as buildApiBasedTools,
|
|
29
|
-
} from './apiBasedTools.js';
|
|
30
|
-
import type { ApiBasedTool } from './apiBasedTools.js';
|
|
31
|
-
import {
|
|
32
|
-
appendCustomSystemPrompt,
|
|
33
|
-
buildAgentSystemPrompt,
|
|
34
|
-
DEFAULT_AGENT_SYSTEM_PROMPT,
|
|
35
|
-
} from "./agent/systemPrompt.js";
|
|
36
|
-
import { ALWAYS_AVAILABLE_API_TOOL_NAMES } from "./agent/tools/index.js";
|
|
13
|
+
import { detectUserLanguage } from "./agent/languageDetect.js";
|
|
14
|
+
import { prepareApiBasedTools as buildApiBasedTools } from './apiBasedTools.js';
|
|
15
|
+
import { createAgentEventStream } from "./agentResponseEvents.js";
|
|
16
|
+
import { appendCustomSystemPrompt, buildAgentSystemPrompt, buildAgentTurnSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
|
|
37
17
|
import type { ToolCallEvent } from "./agent/toolCallEvents.js";
|
|
38
18
|
import type { CurrentPageContext } from "./agent/tools/getUserLocation.js";
|
|
39
19
|
|
|
40
|
-
type
|
|
41
|
-
|
|
20
|
+
type MulterFile = {
|
|
21
|
+
buffer: Buffer;
|
|
22
|
+
originalname: string;
|
|
23
|
+
mimetype: string;
|
|
42
24
|
};
|
|
43
25
|
|
|
44
|
-
type
|
|
45
|
-
audioBase64: string;
|
|
46
|
-
filename: string;
|
|
47
|
-
mimeType: string;
|
|
48
|
-
prompt?: string;
|
|
49
|
-
sessionId?: string | null;
|
|
50
|
-
mode?: string | null;
|
|
51
|
-
timeZone?: string;
|
|
52
|
-
tts?: Omit<TextToSpeechInput, "text">;
|
|
53
|
-
};
|
|
26
|
+
type ExpressMulterRequest = { file?: MulterFile };
|
|
54
27
|
|
|
55
28
|
type AgentTurnRunInput = {
|
|
56
29
|
prompt: string;
|
|
@@ -59,102 +32,65 @@ type AgentTurnRunInput = {
|
|
|
59
32
|
modeName?: string | null;
|
|
60
33
|
userTimeZone: string;
|
|
61
34
|
currentPage?: CurrentPageContext;
|
|
35
|
+
abortSignal?: AbortSignal;
|
|
62
36
|
adminUser: AdminUser;
|
|
63
|
-
httpExtra: HttpExtra;
|
|
64
37
|
sequenceDebugCollector: ReturnType<typeof createSequenceDebugCollector>;
|
|
65
38
|
emitReasoningDelta?: (delta: string) => void;
|
|
66
39
|
emitTextDelta?: (delta: string) => void;
|
|
67
40
|
emitToolCallEvent?: (event: ToolCallEvent) => void;
|
|
68
41
|
};
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function formatAgentError(error: unknown) {
|
|
77
|
-
if (isAggregateErrorLike(error)) {
|
|
78
|
-
const nestedErrors = error.errors
|
|
79
|
-
.map((nestedError, index) => {
|
|
80
|
-
if (nestedError instanceof Error) {
|
|
81
|
-
return `${index + 1}. ${nestedError.stack ?? nestedError.message}`;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return `${index + 1}. ${String(nestedError)}`;
|
|
85
|
-
})
|
|
86
|
-
.join("\n");
|
|
87
|
-
|
|
88
|
-
return `${error.stack ?? error.message}\nNested errors:\n${nestedErrors}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (error instanceof Error) {
|
|
92
|
-
return error.stack ?? error.message;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return String(error);
|
|
96
|
-
}
|
|
43
|
+
type RunAndPersistAgentResponseInput =
|
|
44
|
+
Omit<AgentTurnRunInput, "turnId" | "sequenceDebugCollector"> & {
|
|
45
|
+
emitErrorResponse?: (response: string) => void;
|
|
46
|
+
failureLogMessage: string;
|
|
47
|
+
abortLogMessage: string;
|
|
48
|
+
};
|
|
97
49
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
50
|
+
const agentResponseBodySchema = z.object({
|
|
51
|
+
message: z.string(),
|
|
52
|
+
sessionId: z.string(),
|
|
53
|
+
mode: z.string().nullish(),
|
|
54
|
+
timeZone: z.string().optional(),
|
|
55
|
+
currentPage: z.custom<CurrentPageContext>().optional(),
|
|
56
|
+
}).strict();
|
|
101
57
|
|
|
102
|
-
|
|
103
|
-
return nestedErrors.join("\n");
|
|
104
|
-
}
|
|
58
|
+
const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({message: true})
|
|
105
59
|
|
|
106
|
-
|
|
107
|
-
|
|
60
|
+
const addSystemMessageBodySchema = z.object({
|
|
61
|
+
sessionId: z.string(),
|
|
62
|
+
systemMessage: z.string(),
|
|
63
|
+
}).strict();
|
|
108
64
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
65
|
+
const getSessionsBodySchema = z.object({
|
|
66
|
+
limit: z.number().optional(),
|
|
67
|
+
}).strict();
|
|
112
68
|
|
|
113
|
-
|
|
114
|
-
|
|
69
|
+
const sessionIdBodySchema = z.object({
|
|
70
|
+
sessionId: z.string(),
|
|
71
|
+
}).strict();
|
|
115
72
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
id: adminUser.pk,
|
|
120
|
-
email: dbUser[usernameField],
|
|
121
|
-
};
|
|
73
|
+
const createSessionBodySchema = z.object({
|
|
74
|
+
triggerMessage: z.string().optional(),
|
|
75
|
+
}).strict();
|
|
122
76
|
|
|
123
|
-
return [
|
|
124
|
-
"Current admin user context:",
|
|
125
|
-
JSON.stringify(adminUserContext, null, 2),
|
|
126
|
-
"Use this admin user email when the user asks to send information to themselves, the current admin, or the logged-in user.",
|
|
127
|
-
].join("\n");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function assertRequiredApiTool(
|
|
131
|
-
apiBasedTools: Record<string, ApiBasedTool>,
|
|
132
|
-
toolName: string,
|
|
133
|
-
) {
|
|
134
|
-
if (toolName in apiBasedTools) {
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const availableToolNames = Object.keys(apiBasedTools).sort().join(", ");
|
|
139
|
-
throw new Error(
|
|
140
|
-
`Required API tool "${toolName}" is missing from AdminForth Agent tools. Available tools: ${availableToolNames}`,
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
77
|
|
|
144
78
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
145
79
|
options: PluginOptions;
|
|
146
|
-
apiBasedTools: Record<string, ApiBasedTool> = {};
|
|
147
80
|
agentSystemPromptPromise: Promise<string>;
|
|
148
81
|
private checkpointer: BaseCheckpointSaver | null = null;
|
|
149
|
-
private
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
82
|
+
private parseBody<T>(
|
|
83
|
+
schema: z.ZodType<T>,
|
|
84
|
+
body: unknown,
|
|
85
|
+
response: { setStatus: (code: number, message?: string) => void },
|
|
86
|
+
): T | null {
|
|
87
|
+
const parsed = schema.safeParse(body ?? {});
|
|
88
|
+
if (!parsed.success) {
|
|
89
|
+
response.setStatus(422, parsed.error.message);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return parsed.data;
|
|
93
|
+
}
|
|
158
94
|
private async createNewTurn(sessionId: string, prompt: string, response?: string) {
|
|
159
95
|
const turnId = randomUUID();
|
|
160
96
|
const turnRecord = {
|
|
@@ -167,18 +103,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
167
103
|
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
168
104
|
}
|
|
169
105
|
|
|
170
|
-
private async updateTurn(turnId: string, updates: Record<string, unknown>) {
|
|
171
|
-
await this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, updates);
|
|
172
|
-
return {ok: true};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
private async updateSessionDate(sessionId: string) {
|
|
176
|
-
await this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
177
|
-
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
178
|
-
});
|
|
179
|
-
return {ok: true};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
106
|
private async getSessionTurns(sessionId: string) {
|
|
183
107
|
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
184
108
|
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)],
|
|
@@ -192,47 +116,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
192
116
|
}));
|
|
193
117
|
}
|
|
194
118
|
|
|
195
|
-
private async getModeModels(
|
|
196
|
-
mode: PluginOptions["modes"][number],
|
|
197
|
-
maxTokens: number,
|
|
198
|
-
) {
|
|
199
|
-
const cachedModels = this.modelsByModeName.get(mode.name);
|
|
200
|
-
|
|
201
|
-
if (cachedModels) {
|
|
202
|
-
return await cachedModels;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const modelsPromise = Promise.all([
|
|
206
|
-
createAgentChatModel({
|
|
207
|
-
adapter: mode.completionAdapter,
|
|
208
|
-
maxTokens,
|
|
209
|
-
purpose: "primary",
|
|
210
|
-
}),
|
|
211
|
-
createAgentChatModel({
|
|
212
|
-
adapter: mode.completionAdapter,
|
|
213
|
-
maxTokens,
|
|
214
|
-
purpose: "summary",
|
|
215
|
-
}),
|
|
216
|
-
]).then(([primaryModel, summaryModel]) => ({
|
|
217
|
-
model: primaryModel.model,
|
|
218
|
-
summaryModel: summaryModel.model,
|
|
219
|
-
modelMiddleware: primaryModel.middleware,
|
|
220
|
-
}));
|
|
221
|
-
|
|
222
|
-
this.modelsByModeName.set(mode.name, modelsPromise);
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
return await modelsPromise;
|
|
226
|
-
} catch (error) {
|
|
227
|
-
this.modelsByModeName.delete(mode.name);
|
|
228
|
-
throw error;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
119
|
private getCheckpointer() {
|
|
233
|
-
if (this.checkpointer)
|
|
234
|
-
return this.checkpointer;
|
|
235
|
-
}
|
|
120
|
+
if (this.checkpointer) return this.checkpointer;
|
|
236
121
|
|
|
237
122
|
this.checkpointer = this.options.checkpointResource
|
|
238
123
|
? new AdminForthCheckpointSaver(this.adminforth, this.options)
|
|
@@ -276,7 +161,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
276
161
|
hasAudioAdapter: Boolean(this.options.audioAdapter),
|
|
277
162
|
}
|
|
278
163
|
});
|
|
279
|
-
if (!this.
|
|
164
|
+
if (!this.options.sessionResource) {
|
|
280
165
|
throw new Error("sessionResource is required for AdminForthAgentPlugin");
|
|
281
166
|
}
|
|
282
167
|
}
|
|
@@ -296,31 +181,40 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
296
181
|
|
|
297
182
|
private async runAgentTurn(input: AgentTurnRunInput) {
|
|
298
183
|
let fullResponse = "";
|
|
299
|
-
const maxTokens = this.options.maxTokens ??
|
|
300
|
-
const selectedMode =
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
184
|
+
const maxTokens = this.options.maxTokens ?? 1000;
|
|
185
|
+
const selectedMode = this.options.modes.find((mode) => mode.name === input.modeName) ?? this.options.modes[0];
|
|
186
|
+
const [primaryModelSpec, summaryModelSpec] = await Promise.all([
|
|
187
|
+
createAgentChatModel({
|
|
188
|
+
adapter: selectedMode.completionAdapter,
|
|
189
|
+
maxTokens,
|
|
190
|
+
purpose: "primary",
|
|
191
|
+
}),
|
|
192
|
+
createAgentChatModel({
|
|
193
|
+
adapter: selectedMode.completionAdapter,
|
|
194
|
+
maxTokens,
|
|
195
|
+
purpose: "summary",
|
|
196
|
+
}),
|
|
197
|
+
]);
|
|
198
|
+
const model = primaryModelSpec.model;
|
|
199
|
+
const summaryModel = summaryModelSpec.model;
|
|
200
|
+
const modelMiddleware = primaryModelSpec.middleware;
|
|
201
|
+
|
|
305
202
|
const userLanguage = await detectUserLanguage(selectedMode.completionAdapter, input.prompt)
|
|
306
203
|
.catch((error) => {
|
|
307
|
-
logger.warn(`Failed to detect user language: ${error
|
|
204
|
+
logger.warn(`Failed to detect user language: ${error.message}`);
|
|
308
205
|
return null;
|
|
309
206
|
});
|
|
310
|
-
const systemPrompt =
|
|
311
|
-
await this.agentSystemPromptPromise,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
207
|
+
const systemPrompt = buildAgentTurnSystemPrompt({
|
|
208
|
+
agentSystemPrompt: await this.agentSystemPromptPromise,
|
|
209
|
+
adminUser: input.adminUser,
|
|
210
|
+
usernameField: this.adminforth.config.auth.usernameField,
|
|
211
|
+
userLanguage,
|
|
212
|
+
});
|
|
315
213
|
const apiBasedTools = buildApiBasedTools(
|
|
316
214
|
this.adminforth,
|
|
317
215
|
this.getInternalAgentResourceIds(),
|
|
318
216
|
);
|
|
319
|
-
|
|
320
|
-
assertRequiredApiTool(apiBasedTools, toolName);
|
|
321
|
-
}
|
|
322
|
-
assertRequiredApiTool(apiBasedTools, "update_record");
|
|
323
|
-
this.apiBasedTools = apiBasedTools;
|
|
217
|
+
|
|
324
218
|
const stream = await callAgent({
|
|
325
219
|
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
326
220
|
model,
|
|
@@ -338,8 +232,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
338
232
|
sessionId: input.sessionId,
|
|
339
233
|
turnId: input.turnId,
|
|
340
234
|
currentPage: input.currentPage,
|
|
341
|
-
httpExtra: input.httpExtra,
|
|
342
235
|
userTimeZone: input.userTimeZone,
|
|
236
|
+
abortSignal: input.abortSignal,
|
|
343
237
|
emitToolCallEvent: (event) => {
|
|
344
238
|
input.sequenceDebugCollector.handleToolCallEvent(event);
|
|
345
239
|
input.emitToolCallEvent?.(event);
|
|
@@ -389,11 +283,68 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
389
283
|
};
|
|
390
284
|
}
|
|
391
285
|
|
|
286
|
+
private async runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput) {
|
|
287
|
+
const turnId = await this.createNewTurn(input.sessionId, input.prompt);
|
|
288
|
+
await this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
|
|
289
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
290
|
+
});
|
|
291
|
+
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
292
|
+
let fullResponse = "";
|
|
293
|
+
let aborted = false;
|
|
294
|
+
let failed = false;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const agentResponse = await this.runAgentTurn({
|
|
298
|
+
prompt: input.prompt,
|
|
299
|
+
sessionId: input.sessionId,
|
|
300
|
+
turnId,
|
|
301
|
+
modeName: input.modeName,
|
|
302
|
+
userTimeZone: input.userTimeZone,
|
|
303
|
+
currentPage: input.currentPage,
|
|
304
|
+
abortSignal: input.abortSignal,
|
|
305
|
+
adminUser: input.adminUser,
|
|
306
|
+
sequenceDebugCollector,
|
|
307
|
+
emitToolCallEvent: input.emitToolCallEvent,
|
|
308
|
+
emitReasoningDelta: input.emitReasoningDelta,
|
|
309
|
+
emitTextDelta: input.emitTextDelta,
|
|
310
|
+
});
|
|
311
|
+
fullResponse = agentResponse.text;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (input.abortSignal?.aborted) {
|
|
314
|
+
aborted = true;
|
|
315
|
+
logger.info(input.abortLogMessage);
|
|
316
|
+
} else {
|
|
317
|
+
failed = true;
|
|
318
|
+
logger.error(`${input.failureLogMessage}:\n${error.message}`);
|
|
319
|
+
fullResponse = error.message;
|
|
320
|
+
input.emitErrorResponse?.(fullResponse);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
sequenceDebugCollector.flush();
|
|
325
|
+
const turnUpdates: Record<string, unknown> = {
|
|
326
|
+
[this.options.turnResource.responseField]: fullResponse,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
if (this.options.turnResource.debugField) {
|
|
330
|
+
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
await this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, turnUpdates);
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
text: fullResponse,
|
|
337
|
+
turnId,
|
|
338
|
+
aborted,
|
|
339
|
+
failed,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
392
343
|
setupEndpoints(server: IHttpServer) {
|
|
393
344
|
server.endpoint({
|
|
394
345
|
method: 'POST',
|
|
395
346
|
path: `/agent/get-placeholder-messages`,
|
|
396
|
-
handler: async ({
|
|
347
|
+
handler: async ({ headers, adminUser }) => {
|
|
397
348
|
if (!this.options.placeholderMessages) {
|
|
398
349
|
return {
|
|
399
350
|
messages: [],
|
|
@@ -402,14 +353,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
402
353
|
|
|
403
354
|
const messages = await this.options.placeholderMessages({
|
|
404
355
|
adminUser,
|
|
405
|
-
|
|
406
|
-
body,
|
|
407
|
-
query,
|
|
408
|
-
headers,
|
|
409
|
-
cookies,
|
|
410
|
-
requestUrl,
|
|
411
|
-
response,
|
|
412
|
-
},
|
|
356
|
+
headers,
|
|
413
357
|
});
|
|
414
358
|
|
|
415
359
|
return {
|
|
@@ -420,362 +364,242 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
420
364
|
server.endpoint({
|
|
421
365
|
method: 'POST',
|
|
422
366
|
path: `/agent/response`,
|
|
423
|
-
handler: async ({ body,
|
|
424
|
-
const
|
|
367
|
+
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
368
|
+
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
369
|
+
if (!data) return;
|
|
370
|
+
const stream = createAgentEventStream(_raw_express_res, {vercelAiUiMessageStream: true, closeActiveBlockOnToolStart: true});
|
|
425
371
|
const messageId = randomUUID();
|
|
426
|
-
const prompt = body.message;
|
|
427
|
-
const userTimeZone = (body.timeZone as string | undefined) ?? 'UTC';
|
|
428
|
-
const currentPage = (body as CurrentPageRequestBody).currentPage;
|
|
429
|
-
const sessionId = body.sessionId || adminUser?.pk || adminUser?.username || 'default';
|
|
430
|
-
const turnId = await this.createNewTurn(sessionId, prompt);
|
|
431
|
-
await this.updateSessionDate(sessionId);
|
|
432
|
-
let fullResponse = "";
|
|
433
|
-
let isStreamClosed = false;
|
|
434
|
-
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
435
|
-
|
|
436
|
-
res.writeHead(200, {
|
|
437
|
-
'Content-Type': 'text/event-stream',
|
|
438
|
-
'Cache-Control': 'no-cache',
|
|
439
|
-
'Connection': 'keep-alive',
|
|
440
|
-
'x-vercel-ai-ui-message-stream': 'v1',
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
const send = (obj: unknown) => {
|
|
444
|
-
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const emitToolCallEvent = (event: ToolCallEvent) => {
|
|
451
|
-
if (event.phase === "start") {
|
|
452
|
-
endActiveBlock();
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
send({
|
|
456
|
-
type: "data-tool-call",
|
|
457
|
-
data: event,
|
|
458
|
-
});
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
let activeBlock: { type: 'text' | 'reasoning'; id: string } | null = null;
|
|
462
|
-
|
|
463
|
-
const endActiveBlock = () => {
|
|
464
|
-
if (!activeBlock) {
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
send({
|
|
469
|
-
type: `${activeBlock.type}-end`,
|
|
470
|
-
id: activeBlock.id,
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
activeBlock = null;
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
const startBlock = (type: 'text' | 'reasoning') => {
|
|
477
|
-
if (activeBlock?.type === type) {
|
|
478
|
-
return activeBlock.id;
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
endActiveBlock();
|
|
482
|
-
|
|
483
|
-
const id = randomUUID();
|
|
484
|
-
activeBlock = { type, id };
|
|
485
|
-
|
|
486
|
-
send({
|
|
487
|
-
type: `${type}-start`,
|
|
488
|
-
id,
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
return id;
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
const endStream = () => {
|
|
495
|
-
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
496
|
-
return;
|
|
497
|
-
}
|
|
498
|
-
endActiveBlock();
|
|
499
|
-
|
|
500
|
-
send({
|
|
501
|
-
type: 'finish',
|
|
502
|
-
});
|
|
503
372
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
currentPage,
|
|
522
|
-
adminUser,
|
|
523
|
-
httpExtra: {
|
|
524
|
-
body,
|
|
525
|
-
query,
|
|
526
|
-
headers,
|
|
527
|
-
cookies,
|
|
528
|
-
requestUrl,
|
|
529
|
-
response,
|
|
530
|
-
},
|
|
531
|
-
sequenceDebugCollector,
|
|
532
|
-
emitToolCallEvent,
|
|
533
|
-
emitReasoningDelta: (reasoningDelta) => {
|
|
534
|
-
const reasoningId = startBlock('reasoning');
|
|
535
|
-
send({
|
|
536
|
-
type: 'reasoning-delta',
|
|
537
|
-
id: reasoningId,
|
|
538
|
-
delta: reasoningDelta,
|
|
539
|
-
});
|
|
540
|
-
},
|
|
541
|
-
emitTextDelta: (textDelta) => {
|
|
542
|
-
const textId = startBlock('text');
|
|
543
|
-
fullResponse += textDelta;
|
|
544
|
-
send({
|
|
545
|
-
type: 'text-delta',
|
|
546
|
-
id: textId,
|
|
547
|
-
delta: textDelta,
|
|
548
|
-
});
|
|
549
|
-
},
|
|
550
|
-
});
|
|
551
|
-
fullResponse = agentResponse.text;
|
|
552
|
-
} catch (error) {
|
|
553
|
-
logger.error(`Agent response streaming failed:\n${formatAgentError(error)}`);
|
|
554
|
-
sequenceDebugCollector.flush();
|
|
555
|
-
fullResponse = formatAgentResponseError(error);
|
|
556
|
-
const textId = startBlock('text');
|
|
557
|
-
send({
|
|
558
|
-
type: 'text-delta',
|
|
559
|
-
id: textId,
|
|
560
|
-
delta: fullResponse,
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
sequenceDebugCollector.flush();
|
|
564
|
-
const turnUpdates: Record<string, unknown> = {
|
|
565
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
566
|
-
};
|
|
567
|
-
|
|
568
|
-
if (this.options.turnResource.debugField) {
|
|
569
|
-
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
await this.updateTurn(turnId, turnUpdates);
|
|
573
|
-
endStream();
|
|
373
|
+
stream.start(messageId);
|
|
374
|
+
await this.runAndPersistAgentResponse({
|
|
375
|
+
prompt: data.message,
|
|
376
|
+
sessionId: data.sessionId,
|
|
377
|
+
modeName: data.mode,
|
|
378
|
+
userTimeZone: data.timeZone ?? 'UTC',
|
|
379
|
+
currentPage: data.currentPage,
|
|
380
|
+
abortSignal,
|
|
381
|
+
adminUser,
|
|
382
|
+
emitToolCallEvent: stream.toolCall,
|
|
383
|
+
emitReasoningDelta: stream.reasoningDelta,
|
|
384
|
+
emitTextDelta: stream.textDelta,
|
|
385
|
+
emitErrorResponse: stream.textDelta,
|
|
386
|
+
failureLogMessage: "Agent response streaming failed",
|
|
387
|
+
abortLogMessage: "Agent response streaming aborted by the client",
|
|
388
|
+
});
|
|
389
|
+
stream.end();
|
|
574
390
|
return null;
|
|
575
391
|
}
|
|
576
392
|
});
|
|
577
393
|
server.endpoint({
|
|
578
394
|
method: 'POST',
|
|
579
395
|
path: `/agent/speech-response`,
|
|
580
|
-
|
|
396
|
+
target: 'upload',
|
|
397
|
+
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
398
|
+
const req = _raw_express_req as ExpressMulterRequest;
|
|
581
399
|
const audioAdapter = this.options.audioAdapter;
|
|
582
400
|
if (!audioAdapter) {
|
|
583
401
|
response.setStatus(400, undefined);
|
|
584
|
-
return {
|
|
585
|
-
error: "Audio adapter is not configured for AdminForth Agent",
|
|
586
|
-
};
|
|
402
|
+
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
587
403
|
}
|
|
588
|
-
|
|
589
|
-
|
|
404
|
+
const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
405
|
+
if (!data) return;
|
|
406
|
+
if (!req.file) {
|
|
407
|
+
response.setStatus(400, undefined);
|
|
408
|
+
return { error: "Audio file is required" };
|
|
409
|
+
}
|
|
410
|
+
const stream = createAgentEventStream(_raw_express_res);
|
|
411
|
+
|
|
590
412
|
let transcription;
|
|
591
413
|
|
|
592
414
|
try {
|
|
593
415
|
transcription = await audioAdapter.transcribe({
|
|
594
|
-
buffer:
|
|
595
|
-
filename:
|
|
596
|
-
mimeType:
|
|
416
|
+
buffer: req.file.buffer,
|
|
417
|
+
filename: req.file.originalname,
|
|
418
|
+
mimeType: req.file.mimetype,
|
|
597
419
|
language: "auto",
|
|
598
|
-
prompt: speechBody.prompt,
|
|
599
420
|
});
|
|
600
421
|
} catch (error) {
|
|
601
|
-
logger.error(`Agent speech transcription failed:\n${
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
};
|
|
422
|
+
logger.error(`Agent speech transcription failed:\n${error.message}`);
|
|
423
|
+
stream.error("Speech transcription failed. Check server logs for details.");
|
|
424
|
+
stream.end();
|
|
425
|
+
return null;
|
|
606
426
|
}
|
|
607
427
|
|
|
608
428
|
const prompt = transcription.text;
|
|
609
429
|
if (!prompt) {
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
430
|
+
stream.error("Speech transcription is empty");
|
|
431
|
+
stream.end();
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
stream.transcript(transcription.text, transcription.language);
|
|
435
|
+
|
|
436
|
+
const sessionId = data.sessionId as string;
|
|
437
|
+
const currentPage = data.currentPage;
|
|
438
|
+
const agentResponse = await this.runAndPersistAgentResponse({
|
|
439
|
+
prompt,
|
|
440
|
+
sessionId,
|
|
441
|
+
modeName: data.mode,
|
|
442
|
+
userTimeZone: data.timeZone ?? 'UTC',
|
|
443
|
+
currentPage,
|
|
444
|
+
abortSignal,
|
|
445
|
+
adminUser,
|
|
446
|
+
emitToolCallEvent: stream.toolCall,
|
|
447
|
+
failureLogMessage: "Agent speech response failed",
|
|
448
|
+
abortLogMessage: "Agent speech response aborted by the client",
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
if (agentResponse.aborted) {
|
|
452
|
+
stream.end();
|
|
453
|
+
return null;
|
|
614
454
|
}
|
|
615
455
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
456
|
+
if (agentResponse.failed) {
|
|
457
|
+
stream.error(agentResponse.text);
|
|
458
|
+
stream.end();
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
621
461
|
|
|
622
462
|
try {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
modeName: speechBody.mode,
|
|
628
|
-
userTimeZone: speechBody.timeZone ?? 'UTC',
|
|
629
|
-
currentPage: speechBody.currentPage,
|
|
630
|
-
adminUser,
|
|
631
|
-
httpExtra: {
|
|
632
|
-
body,
|
|
633
|
-
query,
|
|
634
|
-
headers,
|
|
635
|
-
cookies,
|
|
636
|
-
requestUrl,
|
|
637
|
-
response,
|
|
463
|
+
stream.speechResponse(
|
|
464
|
+
{
|
|
465
|
+
text: transcription.text,
|
|
466
|
+
language: transcription.language,
|
|
638
467
|
},
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
fullResponse += textDelta;
|
|
468
|
+
{
|
|
469
|
+
text: agentResponse.text,
|
|
642
470
|
},
|
|
643
|
-
|
|
644
|
-
|
|
471
|
+
sessionId,
|
|
472
|
+
agentResponse.turnId,
|
|
473
|
+
);
|
|
645
474
|
const speech = await audioAdapter.synthesize({
|
|
646
|
-
text:
|
|
647
|
-
|
|
475
|
+
text: agentResponse.text,
|
|
476
|
+
stream: true,
|
|
477
|
+
streamFormat: "audio",
|
|
478
|
+
format: "mp3",
|
|
648
479
|
});
|
|
649
|
-
sequenceDebugCollector.flush();
|
|
650
|
-
const turnUpdates: Record<string, unknown> = {
|
|
651
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
652
|
-
};
|
|
653
480
|
|
|
654
|
-
|
|
655
|
-
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
656
|
-
}
|
|
481
|
+
stream.audioStart(speech.mimeType, speech.format);
|
|
657
482
|
|
|
658
|
-
|
|
483
|
+
const reader = speech.audioStream.getReader();
|
|
659
484
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
},
|
|
668
|
-
audio: {
|
|
669
|
-
base64: speech.audio.toString("base64"),
|
|
670
|
-
mimeType: speech.mimeType,
|
|
671
|
-
format: speech.format,
|
|
672
|
-
},
|
|
673
|
-
sessionId,
|
|
674
|
-
turnId,
|
|
675
|
-
};
|
|
676
|
-
} catch (error) {
|
|
677
|
-
logger.error(`Agent speech response failed:\n${formatAgentError(error)}`);
|
|
678
|
-
sequenceDebugCollector.flush();
|
|
679
|
-
fullResponse = formatAgentResponseError(error);
|
|
680
|
-
const turnUpdates: Record<string, unknown> = {
|
|
681
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
682
|
-
};
|
|
485
|
+
try {
|
|
486
|
+
while (true) {
|
|
487
|
+
const { value, done } = await reader.read();
|
|
488
|
+
|
|
489
|
+
if (done) {
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
683
492
|
|
|
684
|
-
|
|
685
|
-
|
|
493
|
+
stream.audioDelta(value);
|
|
494
|
+
}
|
|
495
|
+
} finally {
|
|
496
|
+
reader.releaseLock();
|
|
686
497
|
}
|
|
687
498
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return
|
|
691
|
-
|
|
692
|
-
|
|
499
|
+
stream.audioDone();
|
|
500
|
+
stream.end();
|
|
501
|
+
return null;
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (abortSignal.aborted) {
|
|
504
|
+
logger.info("Agent speech audio streaming aborted by the client");
|
|
505
|
+
} else {
|
|
506
|
+
logger.error(`Agent speech audio streaming failed:\n${error}`);
|
|
507
|
+
stream.error(error);
|
|
508
|
+
}
|
|
509
|
+
stream.end();
|
|
510
|
+
return null;
|
|
693
511
|
}
|
|
694
512
|
}
|
|
695
513
|
});
|
|
696
514
|
server.endpoint({
|
|
697
515
|
method: 'POST',
|
|
698
516
|
path: `/agent/get-sessions`,
|
|
699
|
-
handler: async ({body, adminUser }) => {
|
|
517
|
+
handler: async ({body, adminUser, response }) => {
|
|
518
|
+
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
519
|
+
if (!data) return;
|
|
700
520
|
const userId = adminUser.pk;
|
|
701
|
-
const limit =
|
|
702
|
-
const sessions = await this.adminforth.resource(this.
|
|
703
|
-
[Filters.EQ(this.
|
|
521
|
+
const limit = data.limit ?? 20;
|
|
522
|
+
const sessions = await this.adminforth.resource(this.options.sessionResource.resourceId).list(
|
|
523
|
+
[Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]
|
|
704
524
|
);
|
|
705
|
-
const sessionsToReturn = [];
|
|
706
|
-
for (const session of sessions) {
|
|
707
|
-
sessionsToReturn.push({
|
|
708
|
-
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
709
|
-
title: session[this.pluginOptions.sessionResource.titleField],
|
|
710
|
-
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
711
|
-
})
|
|
712
|
-
}
|
|
713
525
|
return {
|
|
714
|
-
sessions:
|
|
526
|
+
sessions: sessions.map((session) => ({
|
|
527
|
+
sessionId: session[this.options.sessionResource.idField],
|
|
528
|
+
title: session[this.options.sessionResource.titleField],
|
|
529
|
+
timestamp: session[this.options.sessionResource.createdAtField],
|
|
530
|
+
})),
|
|
715
531
|
};
|
|
716
532
|
}
|
|
717
533
|
});
|
|
718
534
|
server.endpoint({
|
|
719
535
|
method: 'POST',
|
|
720
536
|
path: `/agent/get-session-info`,
|
|
721
|
-
handler: async ({body, adminUser }) => {
|
|
537
|
+
handler: async ({body, adminUser, response }) => {
|
|
538
|
+
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
539
|
+
if (!parsedBody.success) {
|
|
540
|
+
response.setStatus(422, parsedBody.error.message);
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
722
543
|
const userId = adminUser.pk;
|
|
723
|
-
const sessionId =
|
|
724
|
-
const session = await this.adminforth.resource(this.
|
|
725
|
-
[Filters.EQ(this.
|
|
544
|
+
const sessionId = parsedBody.data.sessionId;
|
|
545
|
+
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
546
|
+
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
726
547
|
);
|
|
727
548
|
if (!session) {
|
|
728
549
|
return {
|
|
729
550
|
error: 'Session not found'
|
|
730
551
|
};
|
|
731
552
|
}
|
|
732
|
-
if (session[this.
|
|
553
|
+
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
733
554
|
return {
|
|
734
555
|
error: 'Unauthorized'
|
|
735
556
|
};
|
|
736
557
|
}
|
|
737
558
|
const turns = await this.getSessionTurns(sessionId);
|
|
738
|
-
const messagesToReturn = [];
|
|
739
|
-
for (const turn of turns) {
|
|
740
|
-
messagesToReturn.push({
|
|
741
|
-
text: turn.prompt,
|
|
742
|
-
role: 'user',
|
|
743
|
-
});
|
|
744
|
-
if (turn.response !== "not_finished") {
|
|
745
|
-
messagesToReturn.push({
|
|
746
|
-
text: turn.response,
|
|
747
|
-
role: 'assistant',
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
const sessionToReturn = {
|
|
752
|
-
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
753
|
-
title: session[this.pluginOptions.sessionResource.titleField],
|
|
754
|
-
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
755
|
-
messages: messagesToReturn
|
|
756
|
-
}
|
|
757
559
|
return {
|
|
758
|
-
session:
|
|
759
|
-
|
|
560
|
+
session: {
|
|
561
|
+
sessionId,
|
|
562
|
+
title: session[this.options.sessionResource.titleField],
|
|
563
|
+
timestamp: session[this.options.sessionResource.createdAtField],
|
|
564
|
+
messages: turns.flatMap(turn => {
|
|
565
|
+
const messages = [];
|
|
566
|
+
if (turn.prompt) {
|
|
567
|
+
messages.push({
|
|
568
|
+
text: turn.prompt,
|
|
569
|
+
role: 'user',
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
if (turn.response && turn.response !== "not_finished") {
|
|
573
|
+
messages.push({
|
|
574
|
+
text: turn.response,
|
|
575
|
+
role: 'assistant',
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return messages;
|
|
579
|
+
}),
|
|
580
|
+
},
|
|
581
|
+
};
|
|
760
582
|
}
|
|
761
583
|
});
|
|
762
584
|
server.endpoint({
|
|
763
585
|
method: 'POST',
|
|
764
586
|
path: `/agent/create-session`,
|
|
765
|
-
handler: async ({body, adminUser }) => {
|
|
766
|
-
const
|
|
587
|
+
handler: async ({body, adminUser, response }) => {
|
|
588
|
+
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
589
|
+
if (!data) return;
|
|
590
|
+
const triggerMessage = data.triggerMessage;
|
|
767
591
|
const userId = adminUser.pk;
|
|
768
|
-
const title = triggerMessage
|
|
592
|
+
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
769
593
|
const newSession = {
|
|
770
|
-
[this.
|
|
771
|
-
[this.
|
|
772
|
-
[this.
|
|
594
|
+
[this.options.sessionResource.idField]: randomUUID(),
|
|
595
|
+
[this.options.sessionResource.titleField]: title,
|
|
596
|
+
[this.options.sessionResource.askerIdField]: userId,
|
|
773
597
|
};
|
|
774
|
-
await this.adminforth.resource(this.
|
|
598
|
+
await this.adminforth.resource(this.options.sessionResource.resourceId).create(newSession);
|
|
775
599
|
return {
|
|
776
|
-
sessionId: newSession[this.
|
|
777
|
-
title: newSession[this.
|
|
778
|
-
timestamp: newSession[this.
|
|
600
|
+
sessionId: newSession[this.options.sessionResource.idField],
|
|
601
|
+
title: newSession[this.options.sessionResource.titleField],
|
|
602
|
+
timestamp: newSession[this.options.sessionResource.createdAtField],
|
|
779
603
|
messages: []
|
|
780
604
|
};
|
|
781
605
|
}
|
|
@@ -783,28 +607,30 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
783
607
|
server.endpoint({
|
|
784
608
|
method: 'POST',
|
|
785
609
|
path: `/agent/delete-session`,
|
|
786
|
-
handler: async ({body, adminUser }) => {
|
|
787
|
-
const
|
|
610
|
+
handler: async ({body, adminUser, response }) => {
|
|
611
|
+
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
612
|
+
if (!data) return;
|
|
613
|
+
const sessionId = data.sessionId;
|
|
788
614
|
const userId = adminUser.pk;
|
|
789
|
-
const session = await this.adminforth.resource(this.
|
|
790
|
-
[Filters.EQ(this.
|
|
615
|
+
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
616
|
+
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
791
617
|
);
|
|
792
618
|
if (!session) {
|
|
793
619
|
return {
|
|
794
620
|
error: 'Session not found'
|
|
795
621
|
};
|
|
796
622
|
}
|
|
797
|
-
if (session[this.
|
|
623
|
+
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
798
624
|
return {
|
|
799
625
|
error: 'Unauthorized'
|
|
800
626
|
};
|
|
801
627
|
}
|
|
802
|
-
await this.adminforth.resource(this.
|
|
803
|
-
const turns = await this.adminforth.resource(this.
|
|
804
|
-
[Filters.EQ(this.
|
|
628
|
+
await this.adminforth.resource(this.options.sessionResource.resourceId).delete(sessionId);
|
|
629
|
+
const turns = await this.adminforth.resource(this.options.turnResource.resourceId).list(
|
|
630
|
+
[Filters.EQ(this.options.turnResource.sessionIdField, sessionId)]
|
|
805
631
|
);
|
|
806
632
|
for (const turn of turns) {
|
|
807
|
-
await this.adminforth.resource(this.
|
|
633
|
+
await this.adminforth.resource(this.options.turnResource.resourceId).delete(turn[this.options.turnResource.idField]);
|
|
808
634
|
}
|
|
809
635
|
return {
|
|
810
636
|
ok: true
|
|
@@ -814,14 +640,14 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
814
640
|
server.endpoint({
|
|
815
641
|
method: 'POST',
|
|
816
642
|
path: `/agent/add-system-message-to-turns`,
|
|
817
|
-
handler: async ({body,
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
await this.createNewTurn(sessionId, systemMessage);
|
|
643
|
+
handler: async ({body, response }) => {
|
|
644
|
+
const data = this.parseBody(addSystemMessageBodySchema, body, response);
|
|
645
|
+
if (!data) return;
|
|
646
|
+
await this.createNewTurn(data.sessionId, data.systemMessage);
|
|
821
647
|
return {
|
|
822
648
|
ok: true
|
|
823
649
|
}
|
|
824
650
|
}
|
|
825
|
-
})
|
|
651
|
+
})
|
|
826
652
|
}
|
|
827
653
|
}
|