@adminforth/agent 1.45.0 → 1.46.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/agentTurnService.ts +526 -0
- package/build.log +1 -1
- package/chatSurfaceService.ts +370 -0
- package/dist/agentTurnService.d.ts +70 -0
- package/dist/agentTurnService.js +453 -0
- package/dist/chatSurfaceService.d.ts +32 -0
- package/dist/chatSurfaceService.js +265 -0
- 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 +4 -47
- package/dist/index.js +37 -917
- package/dist/sessionStore.d.ts +19 -0
- package/dist/sessionStore.js +83 -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 +48 -1053
- package/package.json +1 -1
- package/sessionStore.ts +94 -0
- package/agentResponseEvents.ts +0 -1
- package/dist/agentResponseEvents.d.ts +0 -1
- package/dist/agentResponseEvents.js +0 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Filters, logger } from "adminforth";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { getErrorMessage, isAbortError } from "./errors.js";
|
|
13
|
+
import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
|
|
14
|
+
const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
|
|
15
|
+
const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
|
|
16
|
+
export class ChatSurfaceService {
|
|
17
|
+
constructor(getAdminforth, options, sessionStore, handleTurn, runAndPersistAgentResponse) {
|
|
18
|
+
this.getAdminforth = getAdminforth;
|
|
19
|
+
this.options = options;
|
|
20
|
+
this.sessionStore = sessionStore;
|
|
21
|
+
this.handleTurn = handleTurn;
|
|
22
|
+
this.runAndPersistAgentResponse = runAndPersistAgentResponse;
|
|
23
|
+
this.linkTokens = new Map();
|
|
24
|
+
}
|
|
25
|
+
getConnectActionAdapters() {
|
|
26
|
+
var _a;
|
|
27
|
+
return ((_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : [])
|
|
28
|
+
.map((adapter) => adapter)
|
|
29
|
+
.filter((adapter) => adapter.createConnectAction);
|
|
30
|
+
}
|
|
31
|
+
createLinkToken(surface, adminUser) {
|
|
32
|
+
for (const [token, payload] of this.linkTokens) {
|
|
33
|
+
if (payload.expiresAt <= Date.now()) {
|
|
34
|
+
this.linkTokens.delete(token);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const token = randomUUID();
|
|
38
|
+
this.linkTokens.set(token, {
|
|
39
|
+
surface,
|
|
40
|
+
adminUserId: adminUser.pk,
|
|
41
|
+
expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
|
|
42
|
+
});
|
|
43
|
+
return token;
|
|
44
|
+
}
|
|
45
|
+
consumeLinkToken(surface, token) {
|
|
46
|
+
const payload = this.linkTokens.get(token);
|
|
47
|
+
this.linkTokens.delete(token);
|
|
48
|
+
if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return payload;
|
|
52
|
+
}
|
|
53
|
+
createEventEmitter(sink) {
|
|
54
|
+
return (event) => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
if (event.type === "text-delta") {
|
|
56
|
+
yield sink.emit({
|
|
57
|
+
type: "text_delta",
|
|
58
|
+
delta: event.delta,
|
|
59
|
+
});
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (event.type === "response") {
|
|
63
|
+
yield sink.emit({
|
|
64
|
+
type: "done",
|
|
65
|
+
text: event.text,
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (event.type === "error") {
|
|
70
|
+
yield sink.emit({
|
|
71
|
+
type: "error",
|
|
72
|
+
message: event.error,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
handleLink(incoming, sink) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
var _a, _b, _c, _d;
|
|
80
|
+
if (((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.isStartCommand) !== true) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
84
|
+
const adminforth = this.getAdminforth();
|
|
85
|
+
const authResourceId = adminforth.config.auth.usersResourceId;
|
|
86
|
+
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
|
|
87
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
|
|
88
|
+
const linkedAdminUserRecord = (yield adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))).find((user) => { var _a; return ((_a = user[externalUserIdField]) === null || _a === void 0 ? void 0 : _a[incoming.surface]) === incoming.externalUserId; });
|
|
89
|
+
if (linkedAdminUserRecord) {
|
|
90
|
+
yield sink.emit({
|
|
91
|
+
type: "done",
|
|
92
|
+
text: `${incoming.surface} account is already connected to AdminForth.`,
|
|
93
|
+
});
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (typeof ((_c = incoming.metadata) === null || _c === void 0 ? void 0 : _c.startPayload) !== "string") {
|
|
97
|
+
yield sink.emit({
|
|
98
|
+
type: "done",
|
|
99
|
+
text: `Open AdminForth and connect your ${incoming.surface} account from Chat Surfaces settings.`,
|
|
100
|
+
});
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
|
|
104
|
+
if (!payload) {
|
|
105
|
+
yield sink.emit({
|
|
106
|
+
type: "error",
|
|
107
|
+
message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
const adminUserRecord = yield adminforth.resource(authResourceId).get([
|
|
112
|
+
Filters.EQ(primaryKeyField, payload.adminUserId),
|
|
113
|
+
]);
|
|
114
|
+
yield adminforth.resource(authResourceId).update(payload.adminUserId, {
|
|
115
|
+
[externalUserIdField]: Object.assign(Object.assign({}, ((_d = adminUserRecord[externalUserIdField]) !== null && _d !== void 0 ? _d : {})), { [incoming.surface]: incoming.externalUserId }),
|
|
116
|
+
});
|
|
117
|
+
yield sink.emit({
|
|
118
|
+
type: "done",
|
|
119
|
+
text: `${incoming.surface} account connected to AdminForth.`,
|
|
120
|
+
});
|
|
121
|
+
return true;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
handleAudioMessage(incoming, sink, adminUser) {
|
|
125
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
126
|
+
const audioAdapter = this.options.audioAdapter;
|
|
127
|
+
if (!audioAdapter) {
|
|
128
|
+
yield sink.emit({
|
|
129
|
+
type: "error",
|
|
130
|
+
message: "Audio adapter is not configured for AdminForth Agent.",
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
let transcription;
|
|
135
|
+
try {
|
|
136
|
+
transcription = yield audioAdapter.transcribe({
|
|
137
|
+
buffer: incoming.audio.buffer,
|
|
138
|
+
filename: incoming.audio.filename,
|
|
139
|
+
mimeType: incoming.audio.mimeType,
|
|
140
|
+
language: "auto",
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (isAbortError(error)) {
|
|
145
|
+
logger.info(`Agent ${incoming.surface} surface speech transcription aborted`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
logger.error(`Agent ${incoming.surface} surface speech transcription failed:\n${getErrorMessage(error)}`);
|
|
149
|
+
yield sink.emit({
|
|
150
|
+
type: "error",
|
|
151
|
+
message: "Speech transcription failed. Check server logs for details.",
|
|
152
|
+
});
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (!transcription.text) {
|
|
156
|
+
yield sink.emit({
|
|
157
|
+
type: "error",
|
|
158
|
+
message: "Speech transcription is empty",
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const agentResponse = yield this.handleAgentSurfaceResponse(incoming, sink, adminUser, transcription.text, { emitDone: false });
|
|
163
|
+
if (!agentResponse || agentResponse.aborted || agentResponse.failed) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
yield sink.emit({
|
|
167
|
+
type: "done",
|
|
168
|
+
text: agentResponse.text,
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
const speech = yield audioAdapter.synthesize({
|
|
172
|
+
text: sanitizeSpeechText(agentResponse.text),
|
|
173
|
+
stream: false,
|
|
174
|
+
format: "opus",
|
|
175
|
+
});
|
|
176
|
+
yield sink.emit({
|
|
177
|
+
type: "audio",
|
|
178
|
+
audio: speech.audio,
|
|
179
|
+
filename: "agent-response.ogg",
|
|
180
|
+
mimeType: speech.mimeType,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (isAbortError(error)) {
|
|
185
|
+
logger.info(`Agent ${incoming.surface} surface speech synthesis aborted`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
logger.error(`Agent ${incoming.surface} surface speech synthesis failed:\n${getErrorMessage(error)}`);
|
|
189
|
+
yield sink.emit({
|
|
190
|
+
type: "error",
|
|
191
|
+
message: getErrorMessage(error),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
handleAgentSurfaceResponse(incoming, sink, adminUser, prompt, options) {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
var _a, _b, _c;
|
|
199
|
+
const emitDone = (_a = options === null || options === void 0 ? void 0 : options.emitDone) !== null && _a !== void 0 ? _a : true;
|
|
200
|
+
const sessionId = yield this.sessionStore.getOrCreateChatSurfaceSession(Object.assign(Object.assign({}, incoming), { prompt }), adminUser);
|
|
201
|
+
if (emitDone) {
|
|
202
|
+
yield this.handleTurn({
|
|
203
|
+
prompt,
|
|
204
|
+
sessionId,
|
|
205
|
+
modeName: incoming.modeName,
|
|
206
|
+
userTimeZone: (_b = incoming.userTimeZone) !== null && _b !== void 0 ? _b : "UTC",
|
|
207
|
+
adminUser,
|
|
208
|
+
emit: this.createEventEmitter(sink),
|
|
209
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
210
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
211
|
+
});
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
const agentResponse = yield this.runAndPersistAgentResponse({
|
|
215
|
+
prompt,
|
|
216
|
+
sessionId,
|
|
217
|
+
modeName: incoming.modeName,
|
|
218
|
+
userTimeZone: (_c = incoming.userTimeZone) !== null && _c !== void 0 ? _c : "UTC",
|
|
219
|
+
adminUser,
|
|
220
|
+
emit: this.createEventEmitter(sink),
|
|
221
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
222
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
223
|
+
});
|
|
224
|
+
if (agentResponse.failed) {
|
|
225
|
+
yield sink.emit({
|
|
226
|
+
type: "error",
|
|
227
|
+
message: agentResponse.text,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return agentResponse;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
handleMessage(adapter, incoming, sink) {
|
|
234
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
var _a;
|
|
236
|
+
if (yield this.handleLink(incoming, sink)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const adminforth = this.getAdminforth();
|
|
240
|
+
const authResourceId = adminforth.config.auth.usersResourceId;
|
|
241
|
+
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
|
|
242
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
|
|
243
|
+
const externalUserIdField = (_a = this.options.chatExternalIdsField) !== null && _a !== void 0 ? _a : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
244
|
+
const adminUserRecord = (yield adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))).find((user) => { var _a; return ((_a = user[externalUserIdField]) === null || _a === void 0 ? void 0 : _a[adapter.name]) === incoming.externalUserId; });
|
|
245
|
+
if (!adminUserRecord) {
|
|
246
|
+
yield sink.emit({
|
|
247
|
+
type: "error",
|
|
248
|
+
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
249
|
+
});
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const adminUser = {
|
|
253
|
+
pk: adminUserRecord[primaryKeyField],
|
|
254
|
+
username: adminUserRecord[adminforth.config.auth.usernameField],
|
|
255
|
+
dbUser: adminUserRecord,
|
|
256
|
+
};
|
|
257
|
+
const incomingWithAudio = incoming;
|
|
258
|
+
if (incomingWithAudio.audio) {
|
|
259
|
+
yield this.handleAudioMessage(incomingWithAudio, sink, adminUser);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
yield this.handleAgentSurfaceResponse(incoming, sink, adminUser, incoming.prompt);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
|
|
11
|
+
export function setupChatSurfaceEndpoints(ctx, server) {
|
|
12
|
+
var _a;
|
|
13
|
+
if (ctx.getChatSurfaceConnectActionAdapters().length) {
|
|
14
|
+
server.endpoint({
|
|
15
|
+
method: "POST",
|
|
16
|
+
path: "/agent/surfaces/connectable",
|
|
17
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
18
|
+
var _b, _c;
|
|
19
|
+
const externalUserIdField = (_b = ctx.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
20
|
+
const externalIds = (_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {};
|
|
21
|
+
return {
|
|
22
|
+
surfaces: ctx.getChatSurfaceConnectActionAdapters().map((adapter) => {
|
|
23
|
+
var _a;
|
|
24
|
+
return ({
|
|
25
|
+
name: adapter.name,
|
|
26
|
+
externalUserId: (_a = externalIds[adapter.name]) !== null && _a !== void 0 ? _a : null,
|
|
27
|
+
});
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (const adapter of (_a = ctx.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : []) {
|
|
34
|
+
const connectActionAdapter = adapter;
|
|
35
|
+
if (connectActionAdapter.createConnectAction) {
|
|
36
|
+
server.endpoint({
|
|
37
|
+
method: "POST",
|
|
38
|
+
path: `/agent/surface/${adapter.name}/connect-action`,
|
|
39
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
40
|
+
const token = ctx.createChatSurfaceLinkToken(adapter.name, adminUser);
|
|
41
|
+
const action = yield connectActionAdapter.createConnectAction({ token });
|
|
42
|
+
return {
|
|
43
|
+
action,
|
|
44
|
+
};
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
server.endpoint({
|
|
48
|
+
method: "POST",
|
|
49
|
+
path: `/agent/surface/${adapter.name}/disconnect`,
|
|
50
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
51
|
+
var _b, _c;
|
|
52
|
+
const externalUserIdField = (_b = ctx.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
53
|
+
const externalIds = Object.assign({}, ((_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {}));
|
|
54
|
+
delete externalIds[adapter.name];
|
|
55
|
+
yield ctx.adminforth.resource(ctx.adminforth.config.auth.usersResourceId).update(adminUser.pk, {
|
|
56
|
+
[externalUserIdField]: externalIds,
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
ok: true,
|
|
60
|
+
};
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
server.endpoint({
|
|
65
|
+
method: "POST",
|
|
66
|
+
noAuth: true,
|
|
67
|
+
path: `/agent/surface/${adapter.name}/webhook`,
|
|
68
|
+
handler: (endpointInput) => __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
var _a;
|
|
70
|
+
const surfaceContext = {
|
|
71
|
+
body: endpointInput.body,
|
|
72
|
+
headers: endpointInput.headers,
|
|
73
|
+
abortSignal: endpointInput.abortSignal,
|
|
74
|
+
rawRequest: endpointInput._raw_express_req,
|
|
75
|
+
rawResponse: endpointInput._raw_express_res,
|
|
76
|
+
};
|
|
77
|
+
const incoming = yield adapter.parseIncomingMessage(surfaceContext);
|
|
78
|
+
if (!incoming)
|
|
79
|
+
return { ok: true };
|
|
80
|
+
const sink = yield adapter.createEventSink(surfaceContext, incoming);
|
|
81
|
+
try {
|
|
82
|
+
yield ctx.handleChatSurfaceMessage(adapter, incoming, sink);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
yield ((_a = sink.close) === null || _a === void 0 ? void 0 : _a.call(sink));
|
|
86
|
+
}
|
|
87
|
+
return { ok: true };
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AdminUser, ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
|
|
2
|
+
import type { ZodType } from "zod";
|
|
3
|
+
import type { HandleSpeechTurnInput, HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../agentTurnService.js";
|
|
4
|
+
import type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
|
|
5
|
+
import type { PluginOptions } from "../types.js";
|
|
6
|
+
export type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
|
|
7
|
+
export type EndpointResponse = {
|
|
8
|
+
setStatus: (code: number, message: string) => void;
|
|
9
|
+
};
|
|
10
|
+
export type SessionTurn = {
|
|
11
|
+
prompt: string;
|
|
12
|
+
response: string;
|
|
13
|
+
};
|
|
14
|
+
export type AgentEndpointsContext = {
|
|
15
|
+
adminforth: IAdminForth;
|
|
16
|
+
options: PluginOptions;
|
|
17
|
+
parseBody<T>(schema: ZodType<T>, body: unknown, response: EndpointResponse): T | null;
|
|
18
|
+
handleTurn(input: HandleTurnInput): Promise<RunAndPersistAgentResponseResult>;
|
|
19
|
+
handleSpeechTurn(input: HandleSpeechTurnInput): Promise<RunAndPersistAgentResponseResult | null>;
|
|
20
|
+
runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput): Promise<RunAndPersistAgentResponseResult>;
|
|
21
|
+
getSessionTurns(sessionId: string): Promise<SessionTurn[]>;
|
|
22
|
+
createNewTurn(sessionId: string, prompt: string, response?: string): Promise<string>;
|
|
23
|
+
createSystemTurn(sessionId: string, systemMessage: string): Promise<string>;
|
|
24
|
+
getChatSurfaceConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
|
|
25
|
+
createChatSurfaceLinkToken(surface: string, adminUser: AdminUser): string;
|
|
26
|
+
handleChatSurfaceMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
|
|
27
|
+
};
|
|
28
|
+
export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "parseBody" | "handleTurn" | "handleSpeechTurn">;
|
|
29
|
+
export type SessionEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "parseBody" | "getSessionTurns" | "createNewTurn" | "createSystemTurn">;
|
|
30
|
+
export type ChatSurfaceEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "getChatSurfaceConnectActionAdapters" | "createChatSurfaceLinkToken" | "handleChatSurfaceMessage">;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
import { createSseEventEmitter } from "../surfaces/web-sse/createSseEventEmitter.js";
|
|
12
|
+
const agentResponseBodySchema = z.object({
|
|
13
|
+
message: z.string(),
|
|
14
|
+
sessionId: z.string(),
|
|
15
|
+
mode: z.string().nullish(),
|
|
16
|
+
timeZone: z.string().optional(),
|
|
17
|
+
currentPage: z.custom().optional(),
|
|
18
|
+
}).strict();
|
|
19
|
+
const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({ message: true });
|
|
20
|
+
export function setupCoreEndpoints(ctx, server) {
|
|
21
|
+
server.endpoint({
|
|
22
|
+
method: 'POST',
|
|
23
|
+
path: `/agent/get-placeholder-messages`,
|
|
24
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ headers, adminUser }) {
|
|
25
|
+
if (!ctx.options.placeholderMessages) {
|
|
26
|
+
return {
|
|
27
|
+
messages: [],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const messages = yield ctx.options.placeholderMessages({
|
|
31
|
+
adminUser: adminUser,
|
|
32
|
+
headers,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
messages,
|
|
36
|
+
};
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
server.endpoint({
|
|
40
|
+
method: 'POST',
|
|
41
|
+
path: `/agent/response`,
|
|
42
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
|
|
43
|
+
var _b;
|
|
44
|
+
const data = ctx.parseBody(agentResponseBodySchema, body, response);
|
|
45
|
+
if (!data)
|
|
46
|
+
return;
|
|
47
|
+
const emit = createSseEventEmitter(_raw_express_res, {
|
|
48
|
+
vercelAiUiMessageStream: true,
|
|
49
|
+
closeActiveBlockOnToolStart: true,
|
|
50
|
+
});
|
|
51
|
+
yield ctx.handleTurn({
|
|
52
|
+
prompt: data.message,
|
|
53
|
+
sessionId: data.sessionId,
|
|
54
|
+
modeName: data.mode,
|
|
55
|
+
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
56
|
+
currentPage: data.currentPage,
|
|
57
|
+
abortSignal,
|
|
58
|
+
adminUser: adminUser,
|
|
59
|
+
emit,
|
|
60
|
+
failureLogMessage: "Agent response streaming failed",
|
|
61
|
+
abortLogMessage: "Agent response streaming aborted by the client",
|
|
62
|
+
});
|
|
63
|
+
return null;
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
server.endpoint({
|
|
67
|
+
method: 'POST',
|
|
68
|
+
path: `/agent/speech-response`,
|
|
69
|
+
target: 'upload',
|
|
70
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) {
|
|
71
|
+
var _b;
|
|
72
|
+
const req = _raw_express_req;
|
|
73
|
+
const audioAdapter = ctx.options.audioAdapter;
|
|
74
|
+
if (!audioAdapter) {
|
|
75
|
+
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
76
|
+
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
77
|
+
}
|
|
78
|
+
const data = ctx.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
79
|
+
if (!data)
|
|
80
|
+
return;
|
|
81
|
+
if (!req.file) {
|
|
82
|
+
response.setStatus(400, "Audio file is required");
|
|
83
|
+
return { error: "Audio file is required" };
|
|
84
|
+
}
|
|
85
|
+
const emit = createSseEventEmitter(_raw_express_res);
|
|
86
|
+
yield ctx.handleSpeechTurn({
|
|
87
|
+
audioAdapter,
|
|
88
|
+
audio: {
|
|
89
|
+
buffer: req.file.buffer,
|
|
90
|
+
filename: req.file.originalname,
|
|
91
|
+
mimeType: req.file.mimetype,
|
|
92
|
+
},
|
|
93
|
+
sessionId: data.sessionId,
|
|
94
|
+
modeName: data.mode,
|
|
95
|
+
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
96
|
+
currentPage: data.currentPage,
|
|
97
|
+
abortSignal,
|
|
98
|
+
adminUser: adminUser,
|
|
99
|
+
emit,
|
|
100
|
+
failureLogMessage: "Agent speech response failed",
|
|
101
|
+
abortLogMessage: "Agent speech response aborted by the client",
|
|
102
|
+
});
|
|
103
|
+
return null;
|
|
104
|
+
})
|
|
105
|
+
});
|
|
106
|
+
}
|