@adminforth/agent 1.45.1 → 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/build.log +1 -1
- package/chatSurfaceService.ts +199 -18
- package/dist/chatSurfaceService.d.ts +5 -2
- package/dist/chatSurfaceService.js +144 -21
- package/dist/index.js +1 -1
- package/index.ts +1 -0
- package/package.json +1 -1
package/build.log
CHANGED
|
@@ -63,5 +63,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
|
|
|
63
63
|
custom/speech_recognition_frontend/types/
|
|
64
64
|
custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
|
|
65
65
|
|
|
66
|
-
sent 1,671,
|
|
66
|
+
sent 1,671,668 bytes received 940 bytes 3,345,216.00 bytes/sec
|
|
67
67
|
total size is 1,667,436 speedup is 1.00
|
package/chatSurfaceService.ts
CHANGED
|
@@ -5,12 +5,18 @@ import type {
|
|
|
5
5
|
ChatSurfaceIncomingMessage,
|
|
6
6
|
IAdminForth,
|
|
7
7
|
} from "adminforth";
|
|
8
|
-
import { Filters } from "adminforth";
|
|
8
|
+
import { Filters, logger } from "adminforth";
|
|
9
9
|
import { randomUUID } from "crypto";
|
|
10
10
|
import type { AgentEventEmitter } from "./agentEvents.js";
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
HandleTurnInput,
|
|
13
|
+
RunAndPersistAgentResponseInput,
|
|
14
|
+
RunAndPersistAgentResponseResult,
|
|
15
|
+
} from "./agentTurnService.js";
|
|
12
16
|
import type { PluginOptions } from "./types.js";
|
|
13
17
|
import type { AgentSessionStore } from "./sessionStore.js";
|
|
18
|
+
import { getErrorMessage, isAbortError } from "./errors.js";
|
|
19
|
+
import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
|
|
14
20
|
|
|
15
21
|
type ChatSurfaceConnectAction = {
|
|
16
22
|
type: "url";
|
|
@@ -18,6 +24,23 @@ type ChatSurfaceConnectAction = {
|
|
|
18
24
|
url: string;
|
|
19
25
|
};
|
|
20
26
|
|
|
27
|
+
type ChatSurfaceIncomingMessageWithAudio = ChatSurfaceIncomingMessage & {
|
|
28
|
+
audio?: {
|
|
29
|
+
buffer: Buffer;
|
|
30
|
+
filename: string;
|
|
31
|
+
mimeType: string;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type ChatSurfaceEventSinkWithAudio = ChatSurfaceEventSink & {
|
|
36
|
+
emit(event: Parameters<ChatSurfaceEventSink["emit"]>[0] | {
|
|
37
|
+
type: "audio";
|
|
38
|
+
audio: Buffer;
|
|
39
|
+
filename: string;
|
|
40
|
+
mimeType: string;
|
|
41
|
+
}): void | Promise<void>;
|
|
42
|
+
};
|
|
43
|
+
|
|
21
44
|
export type ChatSurfaceAdapterWithConnectAction = ChatSurfaceAdapter & {
|
|
22
45
|
createConnectAction?(input: {
|
|
23
46
|
token: string;
|
|
@@ -41,6 +64,9 @@ export class ChatSurfaceService {
|
|
|
41
64
|
private options: PluginOptions,
|
|
42
65
|
private sessionStore: AgentSessionStore,
|
|
43
66
|
private handleTurn: (input: HandleTurnInput) => Promise<unknown>,
|
|
67
|
+
private runAndPersistAgentResponse: (
|
|
68
|
+
input: RunAndPersistAgentResponseInput,
|
|
69
|
+
) => Promise<RunAndPersistAgentResponseResult>,
|
|
44
70
|
) {}
|
|
45
71
|
|
|
46
72
|
getConnectActionAdapters() {
|
|
@@ -108,10 +134,35 @@ export class ChatSurfaceService {
|
|
|
108
134
|
incoming: ChatSurfaceIncomingMessage,
|
|
109
135
|
sink: ChatSurfaceEventSink,
|
|
110
136
|
) {
|
|
111
|
-
if (
|
|
137
|
+
if (incoming.metadata?.isStartCommand !== true) {
|
|
112
138
|
return false;
|
|
113
139
|
}
|
|
114
140
|
|
|
141
|
+
const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
142
|
+
const adminforth = this.getAdminforth();
|
|
143
|
+
const authResourceId = adminforth.config.auth!.usersResourceId!;
|
|
144
|
+
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
|
|
145
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
|
|
146
|
+
const linkedAdminUserRecord = (
|
|
147
|
+
await adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))
|
|
148
|
+
).find((user) => user[externalUserIdField]?.[incoming.surface] === incoming.externalUserId);
|
|
149
|
+
|
|
150
|
+
if (linkedAdminUserRecord) {
|
|
151
|
+
await sink.emit({
|
|
152
|
+
type: "done",
|
|
153
|
+
text: `${incoming.surface} account is already connected to AdminForth.`,
|
|
154
|
+
});
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (typeof incoming.metadata?.startPayload !== "string") {
|
|
159
|
+
await sink.emit({
|
|
160
|
+
type: "done",
|
|
161
|
+
text: `Open AdminForth and connect your ${incoming.surface} account from Chat Surfaces settings.`,
|
|
162
|
+
});
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
115
166
|
const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
|
|
116
167
|
if (!payload) {
|
|
117
168
|
await sink.emit({
|
|
@@ -120,11 +171,7 @@ export class ChatSurfaceService {
|
|
|
120
171
|
});
|
|
121
172
|
return true;
|
|
122
173
|
}
|
|
123
|
-
|
|
124
|
-
const adminforth = this.getAdminforth();
|
|
125
|
-
const authResourceId = adminforth.config.auth!.usersResourceId!;
|
|
126
|
-
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
|
|
127
|
-
const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
|
|
174
|
+
|
|
128
175
|
const adminUserRecord = await adminforth.resource(authResourceId).get([
|
|
129
176
|
Filters.EQ(primaryKeyField, payload.adminUserId),
|
|
130
177
|
]);
|
|
@@ -143,6 +190,143 @@ export class ChatSurfaceService {
|
|
|
143
190
|
return true;
|
|
144
191
|
}
|
|
145
192
|
|
|
193
|
+
private async handleAudioMessage(
|
|
194
|
+
incoming: ChatSurfaceIncomingMessageWithAudio,
|
|
195
|
+
sink: ChatSurfaceEventSinkWithAudio,
|
|
196
|
+
adminUser: AdminUser,
|
|
197
|
+
) {
|
|
198
|
+
const audioAdapter = this.options.audioAdapter;
|
|
199
|
+
if (!audioAdapter) {
|
|
200
|
+
await sink.emit({
|
|
201
|
+
type: "error",
|
|
202
|
+
message: "Audio adapter is not configured for AdminForth Agent.",
|
|
203
|
+
});
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let transcription;
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
transcription = await audioAdapter.transcribe({
|
|
211
|
+
buffer: incoming.audio!.buffer,
|
|
212
|
+
filename: incoming.audio!.filename,
|
|
213
|
+
mimeType: incoming.audio!.mimeType,
|
|
214
|
+
language: "auto",
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (isAbortError(error)) {
|
|
218
|
+
logger.info(`Agent ${incoming.surface} surface speech transcription aborted`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
logger.error(`Agent ${incoming.surface} surface speech transcription failed:\n${getErrorMessage(error)}`);
|
|
223
|
+
await sink.emit({
|
|
224
|
+
type: "error",
|
|
225
|
+
message: "Speech transcription failed. Check server logs for details.",
|
|
226
|
+
});
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!transcription.text) {
|
|
231
|
+
await sink.emit({
|
|
232
|
+
type: "error",
|
|
233
|
+
message: "Speech transcription is empty",
|
|
234
|
+
});
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const agentResponse = await this.handleAgentSurfaceResponse(
|
|
239
|
+
incoming,
|
|
240
|
+
sink,
|
|
241
|
+
adminUser,
|
|
242
|
+
transcription.text,
|
|
243
|
+
{ emitDone: false },
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!agentResponse || agentResponse.aborted || agentResponse.failed) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
await sink.emit({
|
|
251
|
+
type: "done",
|
|
252
|
+
text: agentResponse.text,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const speech = await audioAdapter.synthesize({
|
|
257
|
+
text: sanitizeSpeechText(agentResponse.text),
|
|
258
|
+
stream: false,
|
|
259
|
+
format: "opus",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await sink.emit({
|
|
263
|
+
type: "audio",
|
|
264
|
+
audio: speech.audio,
|
|
265
|
+
filename: "agent-response.ogg",
|
|
266
|
+
mimeType: speech.mimeType,
|
|
267
|
+
});
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (isAbortError(error)) {
|
|
270
|
+
logger.info(`Agent ${incoming.surface} surface speech synthesis aborted`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
logger.error(`Agent ${incoming.surface} surface speech synthesis failed:\n${getErrorMessage(error)}`);
|
|
275
|
+
await sink.emit({
|
|
276
|
+
type: "error",
|
|
277
|
+
message: getErrorMessage(error),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async handleAgentSurfaceResponse(
|
|
283
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
284
|
+
sink: ChatSurfaceEventSink,
|
|
285
|
+
adminUser: AdminUser,
|
|
286
|
+
prompt: string,
|
|
287
|
+
options?: { emitDone?: boolean },
|
|
288
|
+
) {
|
|
289
|
+
const emitDone = options?.emitDone ?? true;
|
|
290
|
+
const sessionId = await this.sessionStore.getOrCreateChatSurfaceSession(
|
|
291
|
+
{ ...incoming, prompt },
|
|
292
|
+
adminUser,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (emitDone) {
|
|
296
|
+
await this.handleTurn({
|
|
297
|
+
prompt,
|
|
298
|
+
sessionId,
|
|
299
|
+
modeName: incoming.modeName,
|
|
300
|
+
userTimeZone: incoming.userTimeZone ?? "UTC",
|
|
301
|
+
adminUser,
|
|
302
|
+
emit: this.createEventEmitter(sink),
|
|
303
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
304
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
305
|
+
});
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const agentResponse = await this.runAndPersistAgentResponse({
|
|
310
|
+
prompt,
|
|
311
|
+
sessionId,
|
|
312
|
+
modeName: incoming.modeName,
|
|
313
|
+
userTimeZone: incoming.userTimeZone ?? "UTC",
|
|
314
|
+
adminUser,
|
|
315
|
+
emit: this.createEventEmitter(sink),
|
|
316
|
+
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
317
|
+
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
if (agentResponse.failed) {
|
|
321
|
+
await sink.emit({
|
|
322
|
+
type: "error",
|
|
323
|
+
message: agentResponse.text,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return agentResponse;
|
|
328
|
+
}
|
|
329
|
+
|
|
146
330
|
async handleMessage(
|
|
147
331
|
adapter: ChatSurfaceAdapter,
|
|
148
332
|
incoming: ChatSurfaceIncomingMessage,
|
|
@@ -175,15 +359,12 @@ export class ChatSurfaceService {
|
|
|
175
359
|
dbUser: adminUserRecord,
|
|
176
360
|
};
|
|
177
361
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
186
|
-
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
187
|
-
});
|
|
362
|
+
const incomingWithAudio = incoming as ChatSurfaceIncomingMessageWithAudio;
|
|
363
|
+
if (incomingWithAudio.audio) {
|
|
364
|
+
await this.handleAudioMessage(incomingWithAudio, sink as ChatSurfaceEventSinkWithAudio, adminUser);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
await this.handleAgentSurfaceResponse(incoming, sink, adminUser, incoming.prompt);
|
|
188
369
|
}
|
|
189
370
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdminUser, ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
|
|
2
|
-
import type { HandleTurnInput } from "./agentTurnService.js";
|
|
2
|
+
import type { HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "./agentTurnService.js";
|
|
3
3
|
import type { PluginOptions } from "./types.js";
|
|
4
4
|
import type { AgentSessionStore } from "./sessionStore.js";
|
|
5
5
|
type ChatSurfaceConnectAction = {
|
|
@@ -17,13 +17,16 @@ export declare class ChatSurfaceService {
|
|
|
17
17
|
private options;
|
|
18
18
|
private sessionStore;
|
|
19
19
|
private handleTurn;
|
|
20
|
+
private runAndPersistAgentResponse;
|
|
20
21
|
private linkTokens;
|
|
21
|
-
constructor(getAdminforth: () => IAdminForth, options: PluginOptions, sessionStore: AgentSessionStore, handleTurn: (input: HandleTurnInput) => Promise<unknown>);
|
|
22
|
+
constructor(getAdminforth: () => IAdminForth, options: PluginOptions, sessionStore: AgentSessionStore, handleTurn: (input: HandleTurnInput) => Promise<unknown>, runAndPersistAgentResponse: (input: RunAndPersistAgentResponseInput) => Promise<RunAndPersistAgentResponseResult>);
|
|
22
23
|
getConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
|
|
23
24
|
createLinkToken(surface: string, adminUser: AdminUser): `${string}-${string}-${string}-${string}-${string}`;
|
|
24
25
|
private consumeLinkToken;
|
|
25
26
|
private createEventEmitter;
|
|
26
27
|
private handleLink;
|
|
28
|
+
private handleAudioMessage;
|
|
29
|
+
private handleAgentSurfaceResponse;
|
|
27
30
|
handleMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
|
|
28
31
|
}
|
|
29
32
|
export {};
|
|
@@ -7,16 +7,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import { Filters } from "adminforth";
|
|
10
|
+
import { Filters, logger } from "adminforth";
|
|
11
11
|
import { randomUUID } from "crypto";
|
|
12
|
+
import { getErrorMessage, isAbortError } from "./errors.js";
|
|
13
|
+
import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
|
|
12
14
|
const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
|
|
13
15
|
const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
|
|
14
16
|
export class ChatSurfaceService {
|
|
15
|
-
constructor(getAdminforth, options, sessionStore, handleTurn) {
|
|
17
|
+
constructor(getAdminforth, options, sessionStore, handleTurn, runAndPersistAgentResponse) {
|
|
16
18
|
this.getAdminforth = getAdminforth;
|
|
17
19
|
this.options = options;
|
|
18
20
|
this.sessionStore = sessionStore;
|
|
19
21
|
this.handleTurn = handleTurn;
|
|
22
|
+
this.runAndPersistAgentResponse = runAndPersistAgentResponse;
|
|
20
23
|
this.linkTokens = new Map();
|
|
21
24
|
}
|
|
22
25
|
getConnectActionAdapters() {
|
|
@@ -73,10 +76,30 @@ export class ChatSurfaceService {
|
|
|
73
76
|
}
|
|
74
77
|
handleLink(incoming, sink) {
|
|
75
78
|
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
-
var _a, _b, _c;
|
|
77
|
-
if (
|
|
79
|
+
var _a, _b, _c, _d;
|
|
80
|
+
if (((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.isStartCommand) !== true) {
|
|
78
81
|
return false;
|
|
79
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
|
+
}
|
|
80
103
|
const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
|
|
81
104
|
if (!payload) {
|
|
82
105
|
yield sink.emit({
|
|
@@ -85,16 +108,11 @@ export class ChatSurfaceService {
|
|
|
85
108
|
});
|
|
86
109
|
return true;
|
|
87
110
|
}
|
|
88
|
-
const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
89
|
-
const adminforth = this.getAdminforth();
|
|
90
|
-
const authResourceId = adminforth.config.auth.usersResourceId;
|
|
91
|
-
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
|
|
92
|
-
const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
|
|
93
111
|
const adminUserRecord = yield adminforth.resource(authResourceId).get([
|
|
94
112
|
Filters.EQ(primaryKeyField, payload.adminUserId),
|
|
95
113
|
]);
|
|
96
114
|
yield adminforth.resource(authResourceId).update(payload.adminUserId, {
|
|
97
|
-
[externalUserIdField]: Object.assign(Object.assign({}, ((
|
|
115
|
+
[externalUserIdField]: Object.assign(Object.assign({}, ((_d = adminUserRecord[externalUserIdField]) !== null && _d !== void 0 ? _d : {})), { [incoming.surface]: incoming.externalUserId }),
|
|
98
116
|
});
|
|
99
117
|
yield sink.emit({
|
|
100
118
|
type: "done",
|
|
@@ -103,9 +121,118 @@ export class ChatSurfaceService {
|
|
|
103
121
|
return true;
|
|
104
122
|
});
|
|
105
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
|
+
}
|
|
106
233
|
handleMessage(adapter, incoming, sink) {
|
|
107
234
|
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
-
var _a
|
|
235
|
+
var _a;
|
|
109
236
|
if (yield this.handleLink(incoming, sink)) {
|
|
110
237
|
return;
|
|
111
238
|
}
|
|
@@ -127,16 +254,12 @@ export class ChatSurfaceService {
|
|
|
127
254
|
username: adminUserRecord[adminforth.config.auth.usernameField],
|
|
128
255
|
dbUser: adminUserRecord,
|
|
129
256
|
};
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
emit: this.createEventEmitter(sink),
|
|
137
|
-
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
138
|
-
abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
|
|
139
|
-
});
|
|
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);
|
|
140
263
|
});
|
|
141
264
|
}
|
|
142
265
|
}
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
57
57
|
getInternalAgentResourceIds: this.getInternalAgentResourceIds.bind(this),
|
|
58
58
|
getAgentSystemPrompt: () => this.agentSystemPromptPromise,
|
|
59
59
|
});
|
|
60
|
-
this.chatSurfaceService = new ChatSurfaceService(() => this.adminforth, this.options, this.sessionStore, this.agentTurnService.handleTurn.bind(this.agentTurnService));
|
|
60
|
+
this.chatSurfaceService = new ChatSurfaceService(() => this.adminforth, this.options, this.sessionStore, this.agentTurnService.handleTurn.bind(this.agentTurnService), this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService));
|
|
61
61
|
this.agentSystemPromptPromise = Promise.resolve(appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt));
|
|
62
62
|
this.shouldHaveSingleInstancePerWholeApp = () => false;
|
|
63
63
|
}
|
package/index.ts
CHANGED
|
@@ -77,6 +77,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
77
77
|
this.options,
|
|
78
78
|
this.sessionStore,
|
|
79
79
|
this.agentTurnService.handleTurn.bind(this.agentTurnService),
|
|
80
|
+
this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService),
|
|
80
81
|
);
|
|
81
82
|
this.agentSystemPromptPromise = Promise.resolve(
|
|
82
83
|
appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt),
|