@adminforth/agent 1.37.0 → 1.39.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 +13 -2
- package/custom/ChatSurface.vue +31 -21
- package/custom/composables/agentStore/constants.ts +8 -1
- package/custom/composables/agentStore/useAgentSessions.ts +85 -12
- package/custom/composables/useAgentAudio.ts +401 -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/public/agentAudio/agent-processing.mp3 +0 -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/agentStore/constants.ts +8 -1
- package/dist/custom/composables/agentStore/useAgentSessions.ts +85 -12
- package/dist/custom/composables/useAgentAudio.ts +401 -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/public/agentAudio/agent-processing.mp3 +0 -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/dist/index.js
CHANGED
|
@@ -18,68 +18,44 @@ import { AdminForthPlugin, logger, Filters, Sorts } from "adminforth";
|
|
|
18
18
|
import { randomUUID } from 'crypto';
|
|
19
19
|
import { HumanMessage, SystemMessage } from "langchain";
|
|
20
20
|
import { MemorySaver } from "@langchain/langgraph";
|
|
21
|
-
import {
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
import { createAgentChatModel, callAgent } from "./agent/simpleAgent.js";
|
|
22
23
|
import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
23
24
|
import { createSequenceDebugCollector } from "./agent/middleware/sequenceDebug.js";
|
|
24
|
-
import { detectUserLanguage
|
|
25
|
-
import { prepareApiBasedTools as buildApiBasedTools
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return
|
|
25
|
+
import { detectUserLanguage } from "./agent/languageDetect.js";
|
|
26
|
+
import { prepareApiBasedTools as buildApiBasedTools } from './apiBasedTools.js';
|
|
27
|
+
import { createAgentEventStream } from "./agentResponseEvents.js";
|
|
28
|
+
import { appendCustomSystemPrompt, buildAgentSystemPrompt, buildAgentTurnSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT } from "./agent/systemPrompt.js";
|
|
29
|
+
const agentResponseBodySchema = z.object({
|
|
30
|
+
message: z.string(),
|
|
31
|
+
sessionId: z.string(),
|
|
32
|
+
mode: z.string().nullish(),
|
|
33
|
+
timeZone: z.string().optional(),
|
|
34
|
+
currentPage: z.custom().optional(),
|
|
35
|
+
}).strict();
|
|
36
|
+
const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({ message: true });
|
|
37
|
+
const addSystemMessageBodySchema = z.object({
|
|
38
|
+
sessionId: z.string(),
|
|
39
|
+
systemMessage: z.string(),
|
|
40
|
+
}).strict();
|
|
41
|
+
const getSessionsBodySchema = z.object({
|
|
42
|
+
limit: z.number().optional(),
|
|
43
|
+
}).strict();
|
|
44
|
+
const sessionIdBodySchema = z.object({
|
|
45
|
+
sessionId: z.string(),
|
|
46
|
+
}).strict();
|
|
47
|
+
const createSessionBodySchema = z.object({
|
|
48
|
+
triggerMessage: z.string().optional(),
|
|
49
|
+
}).strict();
|
|
50
|
+
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
51
|
+
parseBody(schema, body, response) {
|
|
52
|
+
const parsed = schema.safeParse(body !== null && body !== void 0 ? body : {});
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
response.setStatus(422, parsed.error.message);
|
|
55
|
+
return null;
|
|
55
56
|
}
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
if (error instanceof Error) {
|
|
59
|
-
return error.toString();
|
|
57
|
+
return parsed.data;
|
|
60
58
|
}
|
|
61
|
-
return String(error);
|
|
62
|
-
}
|
|
63
|
-
function formatAdminUserPrompt(adminUser, usernameField) {
|
|
64
|
-
const dbUser = adminUser.dbUser;
|
|
65
|
-
const adminUserContext = {
|
|
66
|
-
id: adminUser.pk,
|
|
67
|
-
email: dbUser[usernameField],
|
|
68
|
-
};
|
|
69
|
-
return [
|
|
70
|
-
"Current admin user context:",
|
|
71
|
-
JSON.stringify(adminUserContext, null, 2),
|
|
72
|
-
"Use this admin user email when the user asks to send information to themselves, the current admin, or the logged-in user.",
|
|
73
|
-
].join("\n");
|
|
74
|
-
}
|
|
75
|
-
function assertRequiredApiTool(apiBasedTools, toolName) {
|
|
76
|
-
if (toolName in apiBasedTools) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const availableToolNames = Object.keys(apiBasedTools).sort().join(", ");
|
|
80
|
-
throw new Error(`Required API tool "${toolName}" is missing from AdminForth Agent tools. Available tools: ${availableToolNames}`);
|
|
81
|
-
}
|
|
82
|
-
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
83
59
|
createNewTurn(sessionId, prompt, response) {
|
|
84
60
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
61
|
const turnId = randomUUID();
|
|
@@ -93,20 +69,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
93
69
|
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
94
70
|
});
|
|
95
71
|
}
|
|
96
|
-
updateTurn(turnId, updates) {
|
|
97
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
98
|
-
yield this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, updates);
|
|
99
|
-
return { ok: true };
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
updateSessionDate(sessionId) {
|
|
103
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
104
|
-
yield this.adminforth.resource(this.options.sessionResource.resourceId).update(sessionId, {
|
|
105
|
-
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
106
|
-
});
|
|
107
|
-
return { ok: true };
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
72
|
getSessionTurns(sessionId) {
|
|
111
73
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
74
|
const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
|
|
@@ -116,42 +78,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
116
78
|
}));
|
|
117
79
|
});
|
|
118
80
|
}
|
|
119
|
-
getModeModels(mode, maxTokens) {
|
|
120
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
121
|
-
const cachedModels = this.modelsByModeName.get(mode.name);
|
|
122
|
-
if (cachedModels) {
|
|
123
|
-
return yield cachedModels;
|
|
124
|
-
}
|
|
125
|
-
const modelsPromise = Promise.all([
|
|
126
|
-
createAgentChatModel({
|
|
127
|
-
adapter: mode.completionAdapter,
|
|
128
|
-
maxTokens,
|
|
129
|
-
purpose: "primary",
|
|
130
|
-
}),
|
|
131
|
-
createAgentChatModel({
|
|
132
|
-
adapter: mode.completionAdapter,
|
|
133
|
-
maxTokens,
|
|
134
|
-
purpose: "summary",
|
|
135
|
-
}),
|
|
136
|
-
]).then(([primaryModel, summaryModel]) => ({
|
|
137
|
-
model: primaryModel.model,
|
|
138
|
-
summaryModel: summaryModel.model,
|
|
139
|
-
modelMiddleware: primaryModel.middleware,
|
|
140
|
-
}));
|
|
141
|
-
this.modelsByModeName.set(mode.name, modelsPromise);
|
|
142
|
-
try {
|
|
143
|
-
return yield modelsPromise;
|
|
144
|
-
}
|
|
145
|
-
catch (error) {
|
|
146
|
-
this.modelsByModeName.delete(mode.name);
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
81
|
getCheckpointer() {
|
|
152
|
-
if (this.checkpointer)
|
|
82
|
+
if (this.checkpointer)
|
|
153
83
|
return this.checkpointer;
|
|
154
|
-
}
|
|
155
84
|
this.checkpointer = this.options.checkpointResource
|
|
156
85
|
? new AdminForthCheckpointSaver(this.adminforth, this.options)
|
|
157
86
|
: new MemorySaver();
|
|
@@ -167,9 +96,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
167
96
|
}
|
|
168
97
|
constructor(options) {
|
|
169
98
|
super(options, import.meta.url);
|
|
170
|
-
this.apiBasedTools = {};
|
|
171
99
|
this.checkpointer = null;
|
|
172
|
-
this.modelsByModeName = new Map();
|
|
173
100
|
this.options = options;
|
|
174
101
|
this.agentSystemPromptPromise = Promise.resolve(appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt));
|
|
175
102
|
this.shouldHaveSingleInstancePerWholeApp = () => false;
|
|
@@ -197,7 +124,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
197
124
|
hasAudioAdapter: Boolean(this.options.audioAdapter),
|
|
198
125
|
}
|
|
199
126
|
});
|
|
200
|
-
if (!this.
|
|
127
|
+
if (!this.options.sessionResource) {
|
|
201
128
|
throw new Error("sessionResource is required for AdminForthAgentPlugin");
|
|
202
129
|
}
|
|
203
130
|
});
|
|
@@ -216,25 +143,35 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
216
143
|
var _a, e_1, _b, _c;
|
|
217
144
|
var _d, _e, _f, _g;
|
|
218
145
|
let fullResponse = "";
|
|
219
|
-
const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d :
|
|
146
|
+
const maxTokens = (_d = this.options.maxTokens) !== null && _d !== void 0 ? _d : 1000;
|
|
220
147
|
const selectedMode = (_e = this.options.modes.find((mode) => mode.name === input.modeName)) !== null && _e !== void 0 ? _e : this.options.modes[0];
|
|
221
|
-
const
|
|
148
|
+
const [primaryModelSpec, summaryModelSpec] = yield Promise.all([
|
|
149
|
+
createAgentChatModel({
|
|
150
|
+
adapter: selectedMode.completionAdapter,
|
|
151
|
+
maxTokens,
|
|
152
|
+
purpose: "primary",
|
|
153
|
+
}),
|
|
154
|
+
createAgentChatModel({
|
|
155
|
+
adapter: selectedMode.completionAdapter,
|
|
156
|
+
maxTokens,
|
|
157
|
+
purpose: "summary",
|
|
158
|
+
}),
|
|
159
|
+
]);
|
|
160
|
+
const model = primaryModelSpec.model;
|
|
161
|
+
const summaryModel = summaryModelSpec.model;
|
|
162
|
+
const modelMiddleware = primaryModelSpec.middleware;
|
|
222
163
|
const userLanguage = yield detectUserLanguage(selectedMode.completionAdapter, input.prompt)
|
|
223
164
|
.catch((error) => {
|
|
224
|
-
logger.warn(`Failed to detect user language: ${error
|
|
165
|
+
logger.warn(`Failed to detect user language: ${error.message}`);
|
|
225
166
|
return null;
|
|
226
167
|
});
|
|
227
|
-
const systemPrompt =
|
|
228
|
-
yield this.agentSystemPromptPromise,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
168
|
+
const systemPrompt = buildAgentTurnSystemPrompt({
|
|
169
|
+
agentSystemPrompt: yield this.agentSystemPromptPromise,
|
|
170
|
+
adminUser: input.adminUser,
|
|
171
|
+
usernameField: this.adminforth.config.auth.usernameField,
|
|
172
|
+
userLanguage,
|
|
173
|
+
});
|
|
232
174
|
const apiBasedTools = buildApiBasedTools(this.adminforth, this.getInternalAgentResourceIds());
|
|
233
|
-
for (const toolName of ALWAYS_AVAILABLE_API_TOOL_NAMES) {
|
|
234
|
-
assertRequiredApiTool(apiBasedTools, toolName);
|
|
235
|
-
}
|
|
236
|
-
assertRequiredApiTool(apiBasedTools, "update_record");
|
|
237
|
-
this.apiBasedTools = apiBasedTools;
|
|
238
175
|
const stream = yield callAgent({
|
|
239
176
|
name: `adminforth-agent-${this.pluginInstanceId}`,
|
|
240
177
|
model,
|
|
@@ -252,8 +189,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
252
189
|
sessionId: input.sessionId,
|
|
253
190
|
turnId: input.turnId,
|
|
254
191
|
currentPage: input.currentPage,
|
|
255
|
-
httpExtra: input.httpExtra,
|
|
256
192
|
userTimeZone: input.userTimeZone,
|
|
193
|
+
abortSignal: input.abortSignal,
|
|
257
194
|
emitToolCallEvent: (event) => {
|
|
258
195
|
var _a;
|
|
259
196
|
input.sequenceDebugCollector.handleToolCallEvent(event);
|
|
@@ -307,11 +244,67 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
307
244
|
};
|
|
308
245
|
});
|
|
309
246
|
}
|
|
247
|
+
runAndPersistAgentResponse(input) {
|
|
248
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
249
|
+
var _a, _b;
|
|
250
|
+
const turnId = yield this.createNewTurn(input.sessionId, input.prompt);
|
|
251
|
+
yield this.adminforth.resource(this.options.sessionResource.resourceId).update(input.sessionId, {
|
|
252
|
+
[this.options.sessionResource.createdAtField]: new Date().toISOString(),
|
|
253
|
+
});
|
|
254
|
+
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
255
|
+
let fullResponse = "";
|
|
256
|
+
let aborted = false;
|
|
257
|
+
let failed = false;
|
|
258
|
+
try {
|
|
259
|
+
const agentResponse = yield this.runAgentTurn({
|
|
260
|
+
prompt: input.prompt,
|
|
261
|
+
sessionId: input.sessionId,
|
|
262
|
+
turnId,
|
|
263
|
+
modeName: input.modeName,
|
|
264
|
+
userTimeZone: input.userTimeZone,
|
|
265
|
+
currentPage: input.currentPage,
|
|
266
|
+
abortSignal: input.abortSignal,
|
|
267
|
+
adminUser: input.adminUser,
|
|
268
|
+
sequenceDebugCollector,
|
|
269
|
+
emitToolCallEvent: input.emitToolCallEvent,
|
|
270
|
+
emitReasoningDelta: input.emitReasoningDelta,
|
|
271
|
+
emitTextDelta: input.emitTextDelta,
|
|
272
|
+
});
|
|
273
|
+
fullResponse = agentResponse.text;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if ((_a = input.abortSignal) === null || _a === void 0 ? void 0 : _a.aborted) {
|
|
277
|
+
aborted = true;
|
|
278
|
+
logger.info(input.abortLogMessage);
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
failed = true;
|
|
282
|
+
logger.error(`${input.failureLogMessage}:\n${error.message}`);
|
|
283
|
+
fullResponse = error.message;
|
|
284
|
+
(_b = input.emitErrorResponse) === null || _b === void 0 ? void 0 : _b.call(input, fullResponse);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
sequenceDebugCollector.flush();
|
|
288
|
+
const turnUpdates = {
|
|
289
|
+
[this.options.turnResource.responseField]: fullResponse,
|
|
290
|
+
};
|
|
291
|
+
if (this.options.turnResource.debugField) {
|
|
292
|
+
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
293
|
+
}
|
|
294
|
+
yield this.adminforth.resource(this.options.turnResource.resourceId).update(turnId, turnUpdates);
|
|
295
|
+
return {
|
|
296
|
+
text: fullResponse,
|
|
297
|
+
turnId,
|
|
298
|
+
aborted,
|
|
299
|
+
failed,
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
310
303
|
setupEndpoints(server) {
|
|
311
304
|
server.endpoint({
|
|
312
305
|
method: 'POST',
|
|
313
306
|
path: `/agent/get-placeholder-messages`,
|
|
314
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({
|
|
307
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ headers, adminUser }) {
|
|
315
308
|
if (!this.options.placeholderMessages) {
|
|
316
309
|
return {
|
|
317
310
|
messages: [],
|
|
@@ -319,14 +312,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
319
312
|
}
|
|
320
313
|
const messages = yield this.options.placeholderMessages({
|
|
321
314
|
adminUser,
|
|
322
|
-
|
|
323
|
-
body,
|
|
324
|
-
query,
|
|
325
|
-
headers,
|
|
326
|
-
cookies,
|
|
327
|
-
requestUrl,
|
|
328
|
-
response,
|
|
329
|
-
},
|
|
315
|
+
headers,
|
|
330
316
|
});
|
|
331
317
|
return {
|
|
332
318
|
messages,
|
|
@@ -336,330 +322,230 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
336
322
|
server.endpoint({
|
|
337
323
|
method: 'POST',
|
|
338
324
|
path: `/agent/response`,
|
|
339
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body,
|
|
325
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
|
|
340
326
|
var _b;
|
|
341
|
-
const
|
|
327
|
+
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
328
|
+
if (!data)
|
|
329
|
+
return;
|
|
330
|
+
const stream = createAgentEventStream(_raw_express_res, { vercelAiUiMessageStream: true, closeActiveBlockOnToolStart: true });
|
|
342
331
|
const messageId = randomUUID();
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
332
|
+
stream.start(messageId);
|
|
333
|
+
yield this.runAndPersistAgentResponse({
|
|
334
|
+
prompt: data.message,
|
|
335
|
+
sessionId: data.sessionId,
|
|
336
|
+
modeName: data.mode,
|
|
337
|
+
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
338
|
+
currentPage: data.currentPage,
|
|
339
|
+
abortSignal,
|
|
340
|
+
adminUser,
|
|
341
|
+
emitToolCallEvent: stream.toolCall,
|
|
342
|
+
emitReasoningDelta: stream.reasoningDelta,
|
|
343
|
+
emitTextDelta: stream.textDelta,
|
|
344
|
+
emitErrorResponse: stream.textDelta,
|
|
345
|
+
failureLogMessage: "Agent response streaming failed",
|
|
346
|
+
abortLogMessage: "Agent response streaming aborted by the client",
|
|
357
347
|
});
|
|
358
|
-
|
|
359
|
-
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
res.write(`data: ${JSON.stringify(obj)}\n\n`);
|
|
363
|
-
};
|
|
364
|
-
const emitToolCallEvent = (event) => {
|
|
365
|
-
if (event.phase === "start") {
|
|
366
|
-
endActiveBlock();
|
|
367
|
-
}
|
|
368
|
-
send({
|
|
369
|
-
type: "data-tool-call",
|
|
370
|
-
data: event,
|
|
371
|
-
});
|
|
372
|
-
};
|
|
373
|
-
let activeBlock = null;
|
|
374
|
-
const endActiveBlock = () => {
|
|
375
|
-
if (!activeBlock) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
send({
|
|
379
|
-
type: `${activeBlock.type}-end`,
|
|
380
|
-
id: activeBlock.id,
|
|
381
|
-
});
|
|
382
|
-
activeBlock = null;
|
|
383
|
-
};
|
|
384
|
-
const startBlock = (type) => {
|
|
385
|
-
if ((activeBlock === null || activeBlock === void 0 ? void 0 : activeBlock.type) === type) {
|
|
386
|
-
return activeBlock.id;
|
|
387
|
-
}
|
|
388
|
-
endActiveBlock();
|
|
389
|
-
const id = randomUUID();
|
|
390
|
-
activeBlock = { type, id };
|
|
391
|
-
send({
|
|
392
|
-
type: `${type}-start`,
|
|
393
|
-
id,
|
|
394
|
-
});
|
|
395
|
-
return id;
|
|
396
|
-
};
|
|
397
|
-
const endStream = () => {
|
|
398
|
-
if (isStreamClosed || res.writableEnded || res.destroyed) {
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
endActiveBlock();
|
|
402
|
-
send({
|
|
403
|
-
type: 'finish',
|
|
404
|
-
});
|
|
405
|
-
res.write(`data: [DONE]\n\n`);
|
|
406
|
-
isStreamClosed = true;
|
|
407
|
-
res.end();
|
|
408
|
-
};
|
|
409
|
-
try {
|
|
410
|
-
send({
|
|
411
|
-
type: 'start',
|
|
412
|
-
messageId,
|
|
413
|
-
});
|
|
414
|
-
const agentResponse = yield this.runAgentTurn({
|
|
415
|
-
prompt,
|
|
416
|
-
sessionId,
|
|
417
|
-
turnId,
|
|
418
|
-
modeName: body.mode,
|
|
419
|
-
userTimeZone,
|
|
420
|
-
currentPage,
|
|
421
|
-
adminUser,
|
|
422
|
-
httpExtra: {
|
|
423
|
-
body,
|
|
424
|
-
query,
|
|
425
|
-
headers,
|
|
426
|
-
cookies,
|
|
427
|
-
requestUrl,
|
|
428
|
-
response,
|
|
429
|
-
},
|
|
430
|
-
sequenceDebugCollector,
|
|
431
|
-
emitToolCallEvent,
|
|
432
|
-
emitReasoningDelta: (reasoningDelta) => {
|
|
433
|
-
const reasoningId = startBlock('reasoning');
|
|
434
|
-
send({
|
|
435
|
-
type: 'reasoning-delta',
|
|
436
|
-
id: reasoningId,
|
|
437
|
-
delta: reasoningDelta,
|
|
438
|
-
});
|
|
439
|
-
},
|
|
440
|
-
emitTextDelta: (textDelta) => {
|
|
441
|
-
const textId = startBlock('text');
|
|
442
|
-
fullResponse += textDelta;
|
|
443
|
-
send({
|
|
444
|
-
type: 'text-delta',
|
|
445
|
-
id: textId,
|
|
446
|
-
delta: textDelta,
|
|
447
|
-
});
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
fullResponse = agentResponse.text;
|
|
451
|
-
}
|
|
452
|
-
catch (error) {
|
|
453
|
-
logger.error(`Agent response streaming failed:\n${formatAgentError(error)}`);
|
|
454
|
-
sequenceDebugCollector.flush();
|
|
455
|
-
fullResponse = formatAgentResponseError(error);
|
|
456
|
-
const textId = startBlock('text');
|
|
457
|
-
send({
|
|
458
|
-
type: 'text-delta',
|
|
459
|
-
id: textId,
|
|
460
|
-
delta: fullResponse,
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
sequenceDebugCollector.flush();
|
|
464
|
-
const turnUpdates = {
|
|
465
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
466
|
-
};
|
|
467
|
-
if (this.options.turnResource.debugField) {
|
|
468
|
-
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
469
|
-
}
|
|
470
|
-
yield this.updateTurn(turnId, turnUpdates);
|
|
471
|
-
endStream();
|
|
348
|
+
stream.end();
|
|
472
349
|
return null;
|
|
473
350
|
})
|
|
474
351
|
});
|
|
475
352
|
server.endpoint({
|
|
476
353
|
method: 'POST',
|
|
477
354
|
path: `/agent/speech-response`,
|
|
478
|
-
|
|
355
|
+
target: 'upload',
|
|
356
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) {
|
|
479
357
|
var _b;
|
|
358
|
+
const req = _raw_express_req;
|
|
480
359
|
const audioAdapter = this.options.audioAdapter;
|
|
481
360
|
if (!audioAdapter) {
|
|
482
361
|
response.setStatus(400, undefined);
|
|
483
|
-
return {
|
|
484
|
-
error: "Audio adapter is not configured for AdminForth Agent",
|
|
485
|
-
};
|
|
362
|
+
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
486
363
|
}
|
|
487
|
-
const
|
|
364
|
+
const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
365
|
+
if (!data)
|
|
366
|
+
return;
|
|
367
|
+
if (!req.file) {
|
|
368
|
+
response.setStatus(400, undefined);
|
|
369
|
+
return { error: "Audio file is required" };
|
|
370
|
+
}
|
|
371
|
+
const stream = createAgentEventStream(_raw_express_res);
|
|
488
372
|
let transcription;
|
|
489
373
|
try {
|
|
490
374
|
transcription = yield audioAdapter.transcribe({
|
|
491
|
-
buffer:
|
|
492
|
-
filename:
|
|
493
|
-
mimeType:
|
|
375
|
+
buffer: req.file.buffer,
|
|
376
|
+
filename: req.file.originalname,
|
|
377
|
+
mimeType: req.file.mimetype,
|
|
494
378
|
language: "auto",
|
|
495
|
-
prompt: speechBody.prompt,
|
|
496
379
|
});
|
|
497
380
|
}
|
|
498
381
|
catch (error) {
|
|
499
|
-
logger.error(`Agent speech transcription failed:\n${
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
};
|
|
382
|
+
logger.error(`Agent speech transcription failed:\n${error.message}`);
|
|
383
|
+
stream.error("Speech transcription failed. Check server logs for details.");
|
|
384
|
+
stream.end();
|
|
385
|
+
return null;
|
|
504
386
|
}
|
|
505
387
|
const prompt = transcription.text;
|
|
506
388
|
if (!prompt) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
389
|
+
stream.error("Speech transcription is empty");
|
|
390
|
+
stream.end();
|
|
391
|
+
return null;
|
|
392
|
+
}
|
|
393
|
+
stream.transcript(transcription.text, transcription.language);
|
|
394
|
+
const sessionId = data.sessionId;
|
|
395
|
+
const currentPage = data.currentPage;
|
|
396
|
+
const agentResponse = yield this.runAndPersistAgentResponse({
|
|
397
|
+
prompt,
|
|
398
|
+
sessionId,
|
|
399
|
+
modeName: data.mode,
|
|
400
|
+
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
401
|
+
currentPage,
|
|
402
|
+
abortSignal,
|
|
403
|
+
adminUser,
|
|
404
|
+
emitToolCallEvent: stream.toolCall,
|
|
405
|
+
failureLogMessage: "Agent speech response failed",
|
|
406
|
+
abortLogMessage: "Agent speech response aborted by the client",
|
|
407
|
+
});
|
|
408
|
+
if (agentResponse.aborted) {
|
|
409
|
+
stream.end();
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
if (agentResponse.failed) {
|
|
413
|
+
stream.error(agentResponse.text);
|
|
414
|
+
stream.end();
|
|
415
|
+
return null;
|
|
511
416
|
}
|
|
512
|
-
const sessionId = speechBody.sessionId || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.pk) || (adminUser === null || adminUser === void 0 ? void 0 : adminUser.username) || 'default';
|
|
513
|
-
const turnId = yield this.createNewTurn(sessionId, prompt);
|
|
514
|
-
yield this.updateSessionDate(sessionId);
|
|
515
|
-
const sequenceDebugCollector = createSequenceDebugCollector();
|
|
516
|
-
let fullResponse = "";
|
|
517
417
|
try {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
headers,
|
|
530
|
-
cookies,
|
|
531
|
-
requestUrl,
|
|
532
|
-
response,
|
|
533
|
-
},
|
|
534
|
-
sequenceDebugCollector,
|
|
535
|
-
emitTextDelta: (textDelta) => {
|
|
536
|
-
fullResponse += textDelta;
|
|
537
|
-
},
|
|
418
|
+
stream.speechResponse({
|
|
419
|
+
text: transcription.text,
|
|
420
|
+
language: transcription.language,
|
|
421
|
+
}, {
|
|
422
|
+
text: agentResponse.text,
|
|
423
|
+
}, sessionId, agentResponse.turnId);
|
|
424
|
+
const speech = yield audioAdapter.synthesize({
|
|
425
|
+
text: agentResponse.text,
|
|
426
|
+
stream: true,
|
|
427
|
+
streamFormat: "audio",
|
|
428
|
+
format: "mp3",
|
|
538
429
|
});
|
|
539
|
-
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
430
|
+
stream.audioStart(speech.mimeType, speech.format);
|
|
431
|
+
const reader = speech.audioStream.getReader();
|
|
432
|
+
try {
|
|
433
|
+
while (true) {
|
|
434
|
+
const { value, done } = yield reader.read();
|
|
435
|
+
if (done) {
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
stream.audioDelta(value);
|
|
439
|
+
}
|
|
547
440
|
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
response: {
|
|
555
|
-
text: fullResponse,
|
|
556
|
-
},
|
|
557
|
-
audio: {
|
|
558
|
-
base64: speech.audio.toString("base64"),
|
|
559
|
-
mimeType: speech.mimeType,
|
|
560
|
-
format: speech.format,
|
|
561
|
-
},
|
|
562
|
-
sessionId,
|
|
563
|
-
turnId,
|
|
564
|
-
};
|
|
441
|
+
finally {
|
|
442
|
+
reader.releaseLock();
|
|
443
|
+
}
|
|
444
|
+
stream.audioDone();
|
|
445
|
+
stream.end();
|
|
446
|
+
return null;
|
|
565
447
|
}
|
|
566
448
|
catch (error) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
fullResponse = formatAgentResponseError(error);
|
|
570
|
-
const turnUpdates = {
|
|
571
|
-
[this.options.turnResource.responseField]: fullResponse,
|
|
572
|
-
};
|
|
573
|
-
if (this.options.turnResource.debugField) {
|
|
574
|
-
turnUpdates[this.options.turnResource.debugField] = sequenceDebugCollector.getHistory();
|
|
449
|
+
if (abortSignal.aborted) {
|
|
450
|
+
logger.info("Agent speech audio streaming aborted by the client");
|
|
575
451
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
452
|
+
else {
|
|
453
|
+
logger.error(`Agent speech audio streaming failed:\n${error}`);
|
|
454
|
+
stream.error(error);
|
|
455
|
+
}
|
|
456
|
+
stream.end();
|
|
457
|
+
return null;
|
|
581
458
|
}
|
|
582
459
|
})
|
|
583
460
|
});
|
|
584
461
|
server.endpoint({
|
|
585
462
|
method: 'POST',
|
|
586
463
|
path: `/agent/get-sessions`,
|
|
587
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
|
|
464
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
465
|
+
var _b;
|
|
466
|
+
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
467
|
+
if (!data)
|
|
468
|
+
return;
|
|
588
469
|
const userId = adminUser.pk;
|
|
589
|
-
const limit =
|
|
590
|
-
const sessions = yield this.adminforth.resource(this.
|
|
591
|
-
const sessionsToReturn = [];
|
|
592
|
-
for (const session of sessions) {
|
|
593
|
-
sessionsToReturn.push({
|
|
594
|
-
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
595
|
-
title: session[this.pluginOptions.sessionResource.titleField],
|
|
596
|
-
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
597
|
-
});
|
|
598
|
-
}
|
|
470
|
+
const limit = (_b = data.limit) !== null && _b !== void 0 ? _b : 20;
|
|
471
|
+
const sessions = yield this.adminforth.resource(this.options.sessionResource.resourceId).list([Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]);
|
|
599
472
|
return {
|
|
600
|
-
sessions:
|
|
473
|
+
sessions: sessions.map((session) => ({
|
|
474
|
+
sessionId: session[this.options.sessionResource.idField],
|
|
475
|
+
title: session[this.options.sessionResource.titleField],
|
|
476
|
+
timestamp: session[this.options.sessionResource.createdAtField],
|
|
477
|
+
})),
|
|
601
478
|
};
|
|
602
479
|
})
|
|
603
480
|
});
|
|
604
481
|
server.endpoint({
|
|
605
482
|
method: 'POST',
|
|
606
483
|
path: `/agent/get-session-info`,
|
|
607
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
|
|
484
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
485
|
+
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
486
|
+
if (!parsedBody.success) {
|
|
487
|
+
response.setStatus(422, parsedBody.error.message);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
608
490
|
const userId = adminUser.pk;
|
|
609
|
-
const sessionId =
|
|
610
|
-
const session = yield this.adminforth.resource(this.
|
|
491
|
+
const sessionId = parsedBody.data.sessionId;
|
|
492
|
+
const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
|
|
611
493
|
if (!session) {
|
|
612
494
|
return {
|
|
613
495
|
error: 'Session not found'
|
|
614
496
|
};
|
|
615
497
|
}
|
|
616
|
-
if (session[this.
|
|
498
|
+
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
617
499
|
return {
|
|
618
500
|
error: 'Unauthorized'
|
|
619
501
|
};
|
|
620
502
|
}
|
|
621
503
|
const turns = yield this.getSessionTurns(sessionId);
|
|
622
|
-
const messagesToReturn = [];
|
|
623
|
-
for (const turn of turns) {
|
|
624
|
-
messagesToReturn.push({
|
|
625
|
-
text: turn.prompt,
|
|
626
|
-
role: 'user',
|
|
627
|
-
});
|
|
628
|
-
if (turn.response !== "not_finished") {
|
|
629
|
-
messagesToReturn.push({
|
|
630
|
-
text: turn.response,
|
|
631
|
-
role: 'assistant',
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
const sessionToReturn = {
|
|
636
|
-
sessionId: session[this.pluginOptions.sessionResource.idField],
|
|
637
|
-
title: session[this.pluginOptions.sessionResource.titleField],
|
|
638
|
-
timestamp: session[this.pluginOptions.sessionResource.createdAtField],
|
|
639
|
-
messages: messagesToReturn
|
|
640
|
-
};
|
|
641
504
|
return {
|
|
642
|
-
session:
|
|
505
|
+
session: {
|
|
506
|
+
sessionId,
|
|
507
|
+
title: session[this.options.sessionResource.titleField],
|
|
508
|
+
timestamp: session[this.options.sessionResource.createdAtField],
|
|
509
|
+
messages: turns.flatMap(turn => {
|
|
510
|
+
const messages = [];
|
|
511
|
+
if (turn.prompt) {
|
|
512
|
+
messages.push({
|
|
513
|
+
text: turn.prompt,
|
|
514
|
+
role: 'user',
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
if (turn.response && turn.response !== "not_finished") {
|
|
518
|
+
messages.push({
|
|
519
|
+
text: turn.response,
|
|
520
|
+
role: 'assistant',
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
return messages;
|
|
524
|
+
}),
|
|
525
|
+
},
|
|
643
526
|
};
|
|
644
527
|
})
|
|
645
528
|
});
|
|
646
529
|
server.endpoint({
|
|
647
530
|
method: 'POST',
|
|
648
531
|
path: `/agent/create-session`,
|
|
649
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
|
|
650
|
-
const
|
|
532
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
533
|
+
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
534
|
+
if (!data)
|
|
535
|
+
return;
|
|
536
|
+
const triggerMessage = data.triggerMessage;
|
|
651
537
|
const userId = adminUser.pk;
|
|
652
|
-
const title = triggerMessage
|
|
538
|
+
const title = (triggerMessage === null || triggerMessage === void 0 ? void 0 : triggerMessage.slice(0, 40)) || "New Session";
|
|
653
539
|
const newSession = {
|
|
654
|
-
[this.
|
|
655
|
-
[this.
|
|
656
|
-
[this.
|
|
540
|
+
[this.options.sessionResource.idField]: randomUUID(),
|
|
541
|
+
[this.options.sessionResource.titleField]: title,
|
|
542
|
+
[this.options.sessionResource.askerIdField]: userId,
|
|
657
543
|
};
|
|
658
|
-
yield this.adminforth.resource(this.
|
|
544
|
+
yield this.adminforth.resource(this.options.sessionResource.resourceId).create(newSession);
|
|
659
545
|
return {
|
|
660
|
-
sessionId: newSession[this.
|
|
661
|
-
title: newSession[this.
|
|
662
|
-
timestamp: newSession[this.
|
|
546
|
+
sessionId: newSession[this.options.sessionResource.idField],
|
|
547
|
+
title: newSession[this.options.sessionResource.titleField],
|
|
548
|
+
timestamp: newSession[this.options.sessionResource.createdAtField],
|
|
663
549
|
messages: []
|
|
664
550
|
};
|
|
665
551
|
})
|
|
@@ -667,24 +553,27 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
667
553
|
server.endpoint({
|
|
668
554
|
method: 'POST',
|
|
669
555
|
path: `/agent/delete-session`,
|
|
670
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser }) {
|
|
671
|
-
const
|
|
556
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
557
|
+
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
558
|
+
if (!data)
|
|
559
|
+
return;
|
|
560
|
+
const sessionId = data.sessionId;
|
|
672
561
|
const userId = adminUser.pk;
|
|
673
|
-
const session = yield this.adminforth.resource(this.
|
|
562
|
+
const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
|
|
674
563
|
if (!session) {
|
|
675
564
|
return {
|
|
676
565
|
error: 'Session not found'
|
|
677
566
|
};
|
|
678
567
|
}
|
|
679
|
-
if (session[this.
|
|
568
|
+
if (session[this.options.sessionResource.askerIdField] !== userId) {
|
|
680
569
|
return {
|
|
681
570
|
error: 'Unauthorized'
|
|
682
571
|
};
|
|
683
572
|
}
|
|
684
|
-
yield this.adminforth.resource(this.
|
|
685
|
-
const turns = yield this.adminforth.resource(this.
|
|
573
|
+
yield this.adminforth.resource(this.options.sessionResource.resourceId).delete(sessionId);
|
|
574
|
+
const turns = yield this.adminforth.resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)]);
|
|
686
575
|
for (const turn of turns) {
|
|
687
|
-
yield this.adminforth.resource(this.
|
|
576
|
+
yield this.adminforth.resource(this.options.turnResource.resourceId).delete(turn[this.options.turnResource.idField]);
|
|
688
577
|
}
|
|
689
578
|
return {
|
|
690
579
|
ok: true
|
|
@@ -694,10 +583,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
694
583
|
server.endpoint({
|
|
695
584
|
method: 'POST',
|
|
696
585
|
path: `/agent/add-system-message-to-turns`,
|
|
697
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body,
|
|
698
|
-
const
|
|
699
|
-
|
|
700
|
-
|
|
586
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, response }) {
|
|
587
|
+
const data = this.parseBody(addSystemMessageBodySchema, body, response);
|
|
588
|
+
if (!data)
|
|
589
|
+
return;
|
|
590
|
+
yield this.createNewTurn(data.sessionId, data.systemMessage);
|
|
701
591
|
return {
|
|
702
592
|
ok: true
|
|
703
593
|
};
|