@adminforth/agent 1.45.1 → 1.47.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 CHANGED
@@ -7,7 +7,6 @@ custom/
7
7
  custom/ChatFooter.vue
8
8
  custom/ChatHeader.vue
9
9
  custom/ChatSurface.vue
10
- custom/ChatSurfaceSettings.vue
11
10
  custom/CustomAutoScrollContainer.vue
12
11
  custom/SessionsHistory.vue
13
12
  custom/chat.ts
@@ -63,5 +62,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
63
62
  custom/speech_recognition_frontend/types/
64
63
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
65
64
 
66
- sent 1,671,664 bytes received 936 bytes 3,345,200.00 bytes/sec
67
- total size is 1,667,436 speedup is 1.00
65
+ sent 1,667,665 bytes received 921 bytes 3,337,172.00 bytes/sec
66
+ total size is 1,663,512 speedup is 1.00
@@ -5,78 +5,46 @@ import type {
5
5
  ChatSurfaceIncomingMessage,
6
6
  IAdminForth,
7
7
  } from "adminforth";
8
- import { Filters } from "adminforth";
9
- import { randomUUID } from "crypto";
8
+ import { Filters, logger } from "adminforth";
10
9
  import type { AgentEventEmitter } from "./agentEvents.js";
11
- import type { HandleTurnInput } from "./agentTurnService.js";
12
- import type { PluginOptions } from "./types.js";
10
+ import type {
11
+ HandleTurnInput,
12
+ RunAndPersistAgentResponseInput,
13
+ RunAndPersistAgentResponseResult,
14
+ } from "./agentTurnService.js";
15
+ import { getErrorMessage, isAbortError } from "./errors.js";
13
16
  import type { AgentSessionStore } from "./sessionStore.js";
17
+ import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
18
+ import type { PluginOptions } from "./types.js";
14
19
 
15
- type ChatSurfaceConnectAction = {
16
- type: "url";
17
- label: string;
18
- url: string;
19
- };
20
-
21
- export type ChatSurfaceAdapterWithConnectAction = ChatSurfaceAdapter & {
22
- createConnectAction?(input: {
23
- token: string;
24
- }): ChatSurfaceConnectAction | Promise<ChatSurfaceConnectAction>;
20
+ type ChatSurfaceIncomingMessageWithAudio = ChatSurfaceIncomingMessage & {
21
+ audio?: {
22
+ buffer: Buffer;
23
+ filename: string;
24
+ mimeType: string;
25
+ };
25
26
  };
26
27
 
27
- type ChatSurfaceLinkTokenPayload = {
28
- surface: string;
29
- adminUserId: AdminUser["pk"];
30
- expiresAt: number;
28
+ type ChatSurfaceEventSinkWithAudio = ChatSurfaceEventSink & {
29
+ emit(event: Parameters<ChatSurfaceEventSink["emit"]>[0] | {
30
+ type: "audio";
31
+ audio: Buffer;
32
+ filename: string;
33
+ mimeType: string;
34
+ }): void | Promise<void>;
31
35
  };
32
36
 
33
- const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
34
- const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
35
-
36
37
  export class ChatSurfaceService {
37
- private linkTokens = new Map<string, ChatSurfaceLinkTokenPayload>();
38
-
39
38
  constructor(
40
39
  private getAdminforth: () => IAdminForth,
41
40
  private options: PluginOptions,
42
41
  private sessionStore: AgentSessionStore,
43
42
  private handleTurn: (input: HandleTurnInput) => Promise<unknown>,
43
+ private runAndPersistAgentResponse: (
44
+ input: RunAndPersistAgentResponseInput,
45
+ ) => Promise<RunAndPersistAgentResponseResult>,
44
46
  ) {}
45
47
 
46
- getConnectActionAdapters() {
47
- return (this.options.chatSurfaceAdapters ?? [])
48
- .map((adapter) => adapter as ChatSurfaceAdapterWithConnectAction)
49
- .filter((adapter) => adapter.createConnectAction);
50
- }
51
-
52
- createLinkToken(surface: string, adminUser: AdminUser) {
53
- for (const [token, payload] of this.linkTokens) {
54
- if (payload.expiresAt <= Date.now()) {
55
- this.linkTokens.delete(token);
56
- }
57
- }
58
-
59
- const token = randomUUID();
60
- this.linkTokens.set(token, {
61
- surface,
62
- adminUserId: adminUser.pk,
63
- expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
64
- });
65
-
66
- return token;
67
- }
68
-
69
- private consumeLinkToken(surface: string, token: string) {
70
- const payload = this.linkTokens.get(token);
71
- this.linkTokens.delete(token);
72
-
73
- if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
74
- return null;
75
- }
76
-
77
- return payload;
78
- }
79
-
80
48
  private createEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
81
49
  return async (event) => {
82
50
  if (event.type === "text-delta") {
@@ -108,39 +76,200 @@ export class ChatSurfaceService {
108
76
  incoming: ChatSurfaceIncomingMessage,
109
77
  sink: ChatSurfaceEventSink,
110
78
  ) {
111
- if (typeof incoming.metadata?.startPayload !== "string") {
79
+ if (incoming.metadata?.isStartCommand !== true) {
112
80
  return false;
113
81
  }
114
82
 
115
- const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
116
- if (!payload) {
83
+ await sink.emit({
84
+ type: "done",
85
+ text: `Open AdminForth and connect your ${incoming.surface} account from Connected Accounts settings.`,
86
+ });
87
+ return true;
88
+ }
89
+
90
+ private async handleAudioMessage(
91
+ incoming: ChatSurfaceIncomingMessageWithAudio,
92
+ sink: ChatSurfaceEventSinkWithAudio,
93
+ adminUser: AdminUser,
94
+ ) {
95
+ const audioAdapter = this.options.audioAdapter;
96
+ if (!audioAdapter) {
117
97
  await sink.emit({
118
98
  type: "error",
119
- message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
99
+ message: "Audio adapter is not configured for AdminForth Agent.",
120
100
  });
121
- return true;
101
+ return;
102
+ }
103
+
104
+ let transcription;
105
+
106
+ try {
107
+ transcription = await audioAdapter.transcribe({
108
+ buffer: incoming.audio!.buffer,
109
+ filename: incoming.audio!.filename,
110
+ mimeType: incoming.audio!.mimeType,
111
+ language: "auto",
112
+ });
113
+ } catch (error) {
114
+ if (isAbortError(error)) {
115
+ logger.info(`Agent ${incoming.surface} surface speech transcription aborted`);
116
+ return;
117
+ }
118
+
119
+ logger.error(`Agent ${incoming.surface} surface speech transcription failed:\n${getErrorMessage(error)}`);
120
+ await sink.emit({
121
+ type: "error",
122
+ message: "Speech transcription failed. Check server logs for details.",
123
+ });
124
+ return;
125
+ }
126
+
127
+ if (!transcription.text) {
128
+ await sink.emit({
129
+ type: "error",
130
+ message: "Speech transcription is empty",
131
+ });
132
+ return;
133
+ }
134
+
135
+ const agentResponse = await this.handleAgentSurfaceResponse(
136
+ incoming,
137
+ sink,
138
+ adminUser,
139
+ transcription.text,
140
+ { emitDone: false },
141
+ );
142
+
143
+ if (!agentResponse || agentResponse.aborted || agentResponse.failed) {
144
+ return;
122
145
  }
123
- const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
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!;
128
- const adminUserRecord = await adminforth.resource(authResourceId).get([
129
- Filters.EQ(primaryKeyField, payload.adminUserId),
130
- ]);
131
146
 
132
- await adminforth.resource(authResourceId).update(payload.adminUserId, {
133
- [externalUserIdField]: {
134
- ...(adminUserRecord[externalUserIdField] ?? {}),
135
- [incoming.surface]: incoming.externalUserId,
136
- },
137
- });
138
147
  await sink.emit({
139
148
  type: "done",
140
- text: `${incoming.surface} account connected to AdminForth.`,
149
+ text: agentResponse.text,
141
150
  });
142
151
 
143
- return true;
152
+ try {
153
+ const speech = await audioAdapter.synthesize({
154
+ text: sanitizeSpeechText(agentResponse.text),
155
+ stream: false,
156
+ format: "opus",
157
+ });
158
+
159
+ await sink.emit({
160
+ type: "audio",
161
+ audio: speech.audio,
162
+ filename: "agent-response.ogg",
163
+ mimeType: speech.mimeType,
164
+ });
165
+ } catch (error) {
166
+ if (isAbortError(error)) {
167
+ logger.info(`Agent ${incoming.surface} surface speech synthesis aborted`);
168
+ return;
169
+ }
170
+
171
+ logger.error(`Agent ${incoming.surface} surface speech synthesis failed:\n${getErrorMessage(error)}`);
172
+ await sink.emit({
173
+ type: "error",
174
+ message: getErrorMessage(error),
175
+ });
176
+ }
177
+ }
178
+
179
+ private async handleAgentSurfaceResponse(
180
+ incoming: ChatSurfaceIncomingMessage,
181
+ sink: ChatSurfaceEventSink,
182
+ adminUser: AdminUser,
183
+ prompt: string,
184
+ options?: { emitDone?: boolean },
185
+ ) {
186
+ const emitDone = options?.emitDone ?? true;
187
+ const sessionId = await this.sessionStore.getOrCreateChatSurfaceSession(
188
+ { ...incoming, prompt },
189
+ adminUser,
190
+ );
191
+
192
+ if (emitDone) {
193
+ await this.handleTurn({
194
+ prompt,
195
+ sessionId,
196
+ modeName: incoming.modeName,
197
+ userTimeZone: incoming.userTimeZone ?? "UTC",
198
+ adminUser,
199
+ emit: this.createEventEmitter(sink),
200
+ failureLogMessage: `Agent ${incoming.surface} surface response failed`,
201
+ abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
202
+ });
203
+ return null;
204
+ }
205
+
206
+ const agentResponse = await this.runAndPersistAgentResponse({
207
+ prompt,
208
+ sessionId,
209
+ modeName: incoming.modeName,
210
+ userTimeZone: incoming.userTimeZone ?? "UTC",
211
+ adminUser,
212
+ emit: this.createEventEmitter(sink),
213
+ failureLogMessage: `Agent ${incoming.surface} surface response failed`,
214
+ abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
215
+ });
216
+
217
+ if (agentResponse.failed) {
218
+ await sink.emit({
219
+ type: "error",
220
+ message: agentResponse.text,
221
+ });
222
+ }
223
+
224
+ return agentResponse;
225
+ }
226
+
227
+ private async getAdminUserRecordForChatSurface(
228
+ adapter: ChatSurfaceAdapter,
229
+ incoming: ChatSurfaceIncomingMessage,
230
+ ) {
231
+ const adminforth = this.getAdminforth();
232
+ const authResourceId = adminforth.config.auth!.usersResourceId!;
233
+ const externalIdentityResource = this.options.chatExternalIdentityResource;
234
+ if (!externalIdentityResource) {
235
+ return null;
236
+ }
237
+
238
+ const surfaceIdentityConfig = externalIdentityResource.surfaces[adapter.name];
239
+ if (!surfaceIdentityConfig) {
240
+ return null;
241
+ }
242
+
243
+ const providerField = externalIdentityResource.providerField ?? 'provider';
244
+ const subjectField = externalIdentityResource.subjectField ?? 'subject';
245
+ const adminUserIdField = externalIdentityResource.adminUserIdField ?? 'adminUserId';
246
+ const externalUserIdField = externalIdentityResource.externalUserIdField ?? 'externalUserId';
247
+ const identityFilters = [
248
+ Filters.EQ(providerField, surfaceIdentityConfig.provider),
249
+ Filters.EQ(externalUserIdField, incoming.externalUserId),
250
+ ];
251
+ const identities = await adminforth.resource(externalIdentityResource.resourceId).list(identityFilters);
252
+ const identity = identities.find((identity) => {
253
+ if (String(identity[externalUserIdField]) === incoming.externalUserId) {
254
+ return true;
255
+ }
256
+
257
+ if (String(identity[subjectField]) === incoming.externalUserId) {
258
+ return true;
259
+ }
260
+
261
+ return false;
262
+ });
263
+
264
+ if (!identity) {
265
+ return null;
266
+ }
267
+
268
+ const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
269
+ const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
270
+ return adminforth.resource(authResourceId).get([
271
+ Filters.EQ(primaryKeyField, identity[adminUserIdField]),
272
+ ]);
144
273
  }
145
274
 
146
275
  async handleMessage(
@@ -156,10 +285,7 @@ export class ChatSurfaceService {
156
285
  const authResourceId = adminforth.config.auth!.usersResourceId!;
157
286
  const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
158
287
  const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
159
- const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
160
- const adminUserRecord = (
161
- await adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))
162
- ).find((user) => user[externalUserIdField]?.[adapter.name] === incoming.externalUserId);
288
+ const adminUserRecord = await this.getAdminUserRecordForChatSurface(adapter, incoming);
163
289
 
164
290
  if (!adminUserRecord) {
165
291
  await sink.emit({
@@ -175,15 +301,12 @@ export class ChatSurfaceService {
175
301
  dbUser: adminUserRecord,
176
302
  };
177
303
 
178
- await this.handleTurn({
179
- prompt: incoming.prompt,
180
- sessionId: await this.sessionStore.getOrCreateChatSurfaceSession(incoming, adminUser),
181
- modeName: incoming.modeName,
182
- userTimeZone: incoming.userTimeZone ?? "UTC",
183
- adminUser,
184
- emit: this.createEventEmitter(sink),
185
- failureLogMessage: `Agent ${incoming.surface} surface response failed`,
186
- abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
187
- });
304
+ const incomingWithAudio = incoming as ChatSurfaceIncomingMessageWithAudio;
305
+ if (incomingWithAudio.audio) {
306
+ await this.handleAudioMessage(incomingWithAudio, sink as ChatSurfaceEventSinkWithAudio, adminUser);
307
+ return;
308
+ }
309
+
310
+ await this.handleAgentSurfaceResponse(incoming, sink, adminUser, incoming.prompt);
188
311
  }
189
312
  }
@@ -1,29 +1,18 @@
1
- import type { AdminUser, ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
2
- import type { HandleTurnInput } from "./agentTurnService.js";
3
- import type { PluginOptions } from "./types.js";
1
+ import type { ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
2
+ import type { HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "./agentTurnService.js";
4
3
  import type { AgentSessionStore } from "./sessionStore.js";
5
- type ChatSurfaceConnectAction = {
6
- type: "url";
7
- label: string;
8
- url: string;
9
- };
10
- export type ChatSurfaceAdapterWithConnectAction = ChatSurfaceAdapter & {
11
- createConnectAction?(input: {
12
- token: string;
13
- }): ChatSurfaceConnectAction | Promise<ChatSurfaceConnectAction>;
14
- };
4
+ import type { PluginOptions } from "./types.js";
15
5
  export declare class ChatSurfaceService {
16
6
  private getAdminforth;
17
7
  private options;
18
8
  private sessionStore;
19
9
  private handleTurn;
20
- private linkTokens;
21
- constructor(getAdminforth: () => IAdminForth, options: PluginOptions, sessionStore: AgentSessionStore, handleTurn: (input: HandleTurnInput) => Promise<unknown>);
22
- getConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
23
- createLinkToken(surface: string, adminUser: AdminUser): `${string}-${string}-${string}-${string}-${string}`;
24
- private consumeLinkToken;
10
+ private runAndPersistAgentResponse;
11
+ constructor(getAdminforth: () => IAdminForth, options: PluginOptions, sessionStore: AgentSessionStore, handleTurn: (input: HandleTurnInput) => Promise<unknown>, runAndPersistAgentResponse: (input: RunAndPersistAgentResponseInput) => Promise<RunAndPersistAgentResponseResult>);
25
12
  private createEventEmitter;
26
13
  private handleLink;
14
+ private handleAudioMessage;
15
+ private handleAgentSurfaceResponse;
16
+ private getAdminUserRecordForChatSurface;
27
17
  handleMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
28
18
  }
29
- export {};
@@ -7,45 +7,16 @@ 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";
11
- import { randomUUID } from "crypto";
12
- const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
13
- const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
10
+ import { Filters, logger } from "adminforth";
11
+ import { getErrorMessage, isAbortError } from "./errors.js";
12
+ import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
14
13
  export class ChatSurfaceService {
15
- constructor(getAdminforth, options, sessionStore, handleTurn) {
14
+ constructor(getAdminforth, options, sessionStore, handleTurn, runAndPersistAgentResponse) {
16
15
  this.getAdminforth = getAdminforth;
17
16
  this.options = options;
18
17
  this.sessionStore = sessionStore;
19
18
  this.handleTurn = handleTurn;
20
- this.linkTokens = new Map();
21
- }
22
- getConnectActionAdapters() {
23
- var _a;
24
- return ((_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : [])
25
- .map((adapter) => adapter)
26
- .filter((adapter) => adapter.createConnectAction);
27
- }
28
- createLinkToken(surface, adminUser) {
29
- for (const [token, payload] of this.linkTokens) {
30
- if (payload.expiresAt <= Date.now()) {
31
- this.linkTokens.delete(token);
32
- }
33
- }
34
- const token = randomUUID();
35
- this.linkTokens.set(token, {
36
- surface,
37
- adminUserId: adminUser.pk,
38
- expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
39
- });
40
- return token;
41
- }
42
- consumeLinkToken(surface, token) {
43
- const payload = this.linkTokens.get(token);
44
- this.linkTokens.delete(token);
45
- if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
46
- return null;
47
- }
48
- return payload;
19
+ this.runAndPersistAgentResponse = runAndPersistAgentResponse;
49
20
  }
50
21
  createEventEmitter(sink) {
51
22
  return (event) => __awaiter(this, void 0, void 0, function* () {
@@ -73,39 +44,169 @@ export class ChatSurfaceService {
73
44
  }
74
45
  handleLink(incoming, sink) {
75
46
  return __awaiter(this, void 0, void 0, function* () {
76
- var _a, _b, _c;
77
- if (typeof ((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.startPayload) !== "string") {
47
+ var _a;
48
+ if (((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.isStartCommand) !== true) {
78
49
  return false;
79
50
  }
80
- const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
81
- if (!payload) {
51
+ yield sink.emit({
52
+ type: "done",
53
+ text: `Open AdminForth and connect your ${incoming.surface} account from Connected Accounts settings.`,
54
+ });
55
+ return true;
56
+ });
57
+ }
58
+ handleAudioMessage(incoming, sink, adminUser) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const audioAdapter = this.options.audioAdapter;
61
+ if (!audioAdapter) {
62
+ yield sink.emit({
63
+ type: "error",
64
+ message: "Audio adapter is not configured for AdminForth Agent.",
65
+ });
66
+ return;
67
+ }
68
+ let transcription;
69
+ try {
70
+ transcription = yield audioAdapter.transcribe({
71
+ buffer: incoming.audio.buffer,
72
+ filename: incoming.audio.filename,
73
+ mimeType: incoming.audio.mimeType,
74
+ language: "auto",
75
+ });
76
+ }
77
+ catch (error) {
78
+ if (isAbortError(error)) {
79
+ logger.info(`Agent ${incoming.surface} surface speech transcription aborted`);
80
+ return;
81
+ }
82
+ logger.error(`Agent ${incoming.surface} surface speech transcription failed:\n${getErrorMessage(error)}`);
83
+ yield sink.emit({
84
+ type: "error",
85
+ message: "Speech transcription failed. Check server logs for details.",
86
+ });
87
+ return;
88
+ }
89
+ if (!transcription.text) {
90
+ yield sink.emit({
91
+ type: "error",
92
+ message: "Speech transcription is empty",
93
+ });
94
+ return;
95
+ }
96
+ const agentResponse = yield this.handleAgentSurfaceResponse(incoming, sink, adminUser, transcription.text, { emitDone: false });
97
+ if (!agentResponse || agentResponse.aborted || agentResponse.failed) {
98
+ return;
99
+ }
100
+ yield sink.emit({
101
+ type: "done",
102
+ text: agentResponse.text,
103
+ });
104
+ try {
105
+ const speech = yield audioAdapter.synthesize({
106
+ text: sanitizeSpeechText(agentResponse.text),
107
+ stream: false,
108
+ format: "opus",
109
+ });
110
+ yield sink.emit({
111
+ type: "audio",
112
+ audio: speech.audio,
113
+ filename: "agent-response.ogg",
114
+ mimeType: speech.mimeType,
115
+ });
116
+ }
117
+ catch (error) {
118
+ if (isAbortError(error)) {
119
+ logger.info(`Agent ${incoming.surface} surface speech synthesis aborted`);
120
+ return;
121
+ }
122
+ logger.error(`Agent ${incoming.surface} surface speech synthesis failed:\n${getErrorMessage(error)}`);
82
123
  yield sink.emit({
83
124
  type: "error",
84
- message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
125
+ message: getErrorMessage(error),
85
126
  });
86
- return true;
87
127
  }
88
- const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
128
+ });
129
+ }
130
+ handleAgentSurfaceResponse(incoming, sink, adminUser, prompt, options) {
131
+ return __awaiter(this, void 0, void 0, function* () {
132
+ var _a, _b, _c;
133
+ const emitDone = (_a = options === null || options === void 0 ? void 0 : options.emitDone) !== null && _a !== void 0 ? _a : true;
134
+ const sessionId = yield this.sessionStore.getOrCreateChatSurfaceSession(Object.assign(Object.assign({}, incoming), { prompt }), adminUser);
135
+ if (emitDone) {
136
+ yield this.handleTurn({
137
+ prompt,
138
+ sessionId,
139
+ modeName: incoming.modeName,
140
+ userTimeZone: (_b = incoming.userTimeZone) !== null && _b !== void 0 ? _b : "UTC",
141
+ adminUser,
142
+ emit: this.createEventEmitter(sink),
143
+ failureLogMessage: `Agent ${incoming.surface} surface response failed`,
144
+ abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
145
+ });
146
+ return null;
147
+ }
148
+ const agentResponse = yield this.runAndPersistAgentResponse({
149
+ prompt,
150
+ sessionId,
151
+ modeName: incoming.modeName,
152
+ userTimeZone: (_c = incoming.userTimeZone) !== null && _c !== void 0 ? _c : "UTC",
153
+ adminUser,
154
+ emit: this.createEventEmitter(sink),
155
+ failureLogMessage: `Agent ${incoming.surface} surface response failed`,
156
+ abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
157
+ });
158
+ if (agentResponse.failed) {
159
+ yield sink.emit({
160
+ type: "error",
161
+ message: agentResponse.text,
162
+ });
163
+ }
164
+ return agentResponse;
165
+ });
166
+ }
167
+ getAdminUserRecordForChatSurface(adapter, incoming) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ var _a, _b, _c, _d;
89
170
  const adminforth = this.getAdminforth();
90
171
  const authResourceId = adminforth.config.auth.usersResourceId;
172
+ const externalIdentityResource = this.options.chatExternalIdentityResource;
173
+ if (!externalIdentityResource) {
174
+ return null;
175
+ }
176
+ const surfaceIdentityConfig = externalIdentityResource.surfaces[adapter.name];
177
+ if (!surfaceIdentityConfig) {
178
+ return null;
179
+ }
180
+ const providerField = (_a = externalIdentityResource.providerField) !== null && _a !== void 0 ? _a : 'provider';
181
+ const subjectField = (_b = externalIdentityResource.subjectField) !== null && _b !== void 0 ? _b : 'subject';
182
+ const adminUserIdField = (_c = externalIdentityResource.adminUserIdField) !== null && _c !== void 0 ? _c : 'adminUserId';
183
+ const externalUserIdField = (_d = externalIdentityResource.externalUserIdField) !== null && _d !== void 0 ? _d : 'externalUserId';
184
+ const identityFilters = [
185
+ Filters.EQ(providerField, surfaceIdentityConfig.provider),
186
+ Filters.EQ(externalUserIdField, incoming.externalUserId),
187
+ ];
188
+ const identities = yield adminforth.resource(externalIdentityResource.resourceId).list(identityFilters);
189
+ const identity = identities.find((identity) => {
190
+ if (String(identity[externalUserIdField]) === incoming.externalUserId) {
191
+ return true;
192
+ }
193
+ if (String(identity[subjectField]) === incoming.externalUserId) {
194
+ return true;
195
+ }
196
+ return false;
197
+ });
198
+ if (!identity) {
199
+ return null;
200
+ }
91
201
  const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
92
202
  const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
93
- const adminUserRecord = yield adminforth.resource(authResourceId).get([
94
- Filters.EQ(primaryKeyField, payload.adminUserId),
203
+ return adminforth.resource(authResourceId).get([
204
+ Filters.EQ(primaryKeyField, identity[adminUserIdField]),
95
205
  ]);
96
- yield adminforth.resource(authResourceId).update(payload.adminUserId, {
97
- [externalUserIdField]: Object.assign(Object.assign({}, ((_c = adminUserRecord[externalUserIdField]) !== null && _c !== void 0 ? _c : {})), { [incoming.surface]: incoming.externalUserId }),
98
- });
99
- yield sink.emit({
100
- type: "done",
101
- text: `${incoming.surface} account connected to AdminForth.`,
102
- });
103
- return true;
104
206
  });
105
207
  }
106
208
  handleMessage(adapter, incoming, sink) {
107
209
  return __awaiter(this, void 0, void 0, function* () {
108
- var _a, _b;
109
210
  if (yield this.handleLink(incoming, sink)) {
110
211
  return;
111
212
  }
@@ -113,8 +214,7 @@ export class ChatSurfaceService {
113
214
  const authResourceId = adminforth.config.auth.usersResourceId;
114
215
  const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
115
216
  const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
116
- const externalUserIdField = (_a = this.options.chatExternalIdsField) !== null && _a !== void 0 ? _a : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
117
- 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; });
217
+ const adminUserRecord = yield this.getAdminUserRecordForChatSurface(adapter, incoming);
118
218
  if (!adminUserRecord) {
119
219
  yield sink.emit({
120
220
  type: "error",
@@ -127,16 +227,12 @@ export class ChatSurfaceService {
127
227
  username: adminUserRecord[adminforth.config.auth.usernameField],
128
228
  dbUser: adminUserRecord,
129
229
  };
130
- yield this.handleTurn({
131
- prompt: incoming.prompt,
132
- sessionId: yield this.sessionStore.getOrCreateChatSurfaceSession(incoming, adminUser),
133
- modeName: incoming.modeName,
134
- userTimeZone: (_b = incoming.userTimeZone) !== null && _b !== void 0 ? _b : "UTC",
135
- adminUser,
136
- emit: this.createEventEmitter(sink),
137
- failureLogMessage: `Agent ${incoming.surface} surface response failed`,
138
- abortLogMessage: `Agent ${incoming.surface} surface response aborted`,
139
- });
230
+ const incomingWithAudio = incoming;
231
+ if (incomingWithAudio.audio) {
232
+ yield this.handleAudioMessage(incomingWithAudio, sink, adminUser);
233
+ return;
234
+ }
235
+ yield this.handleAgentSurfaceResponse(incoming, sink, adminUser, incoming.prompt);
140
236
  });
141
237
  }
142
238
  }