@adminforth/agent 1.46.0 → 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,668 bytes received 940 bytes 3,345,216.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
@@ -6,23 +6,16 @@ import type {
6
6
  IAdminForth,
7
7
  } from "adminforth";
8
8
  import { Filters, logger } from "adminforth";
9
- import { randomUUID } from "crypto";
10
9
  import type { AgentEventEmitter } from "./agentEvents.js";
11
10
  import type {
12
11
  HandleTurnInput,
13
12
  RunAndPersistAgentResponseInput,
14
13
  RunAndPersistAgentResponseResult,
15
14
  } from "./agentTurnService.js";
16
- import type { PluginOptions } from "./types.js";
17
- import type { AgentSessionStore } from "./sessionStore.js";
18
15
  import { getErrorMessage, isAbortError } from "./errors.js";
16
+ import type { AgentSessionStore } from "./sessionStore.js";
19
17
  import { sanitizeSpeechText } from "./sanitizeSpeechText.js";
20
-
21
- type ChatSurfaceConnectAction = {
22
- type: "url";
23
- label: string;
24
- url: string;
25
- };
18
+ import type { PluginOptions } from "./types.js";
26
19
 
27
20
  type ChatSurfaceIncomingMessageWithAudio = ChatSurfaceIncomingMessage & {
28
21
  audio?: {
@@ -41,24 +34,7 @@ type ChatSurfaceEventSinkWithAudio = ChatSurfaceEventSink & {
41
34
  }): void | Promise<void>;
42
35
  };
43
36
 
44
- export type ChatSurfaceAdapterWithConnectAction = ChatSurfaceAdapter & {
45
- createConnectAction?(input: {
46
- token: string;
47
- }): ChatSurfaceConnectAction | Promise<ChatSurfaceConnectAction>;
48
- };
49
-
50
- type ChatSurfaceLinkTokenPayload = {
51
- surface: string;
52
- adminUserId: AdminUser["pk"];
53
- expiresAt: number;
54
- };
55
-
56
- const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
57
- const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
58
-
59
37
  export class ChatSurfaceService {
60
- private linkTokens = new Map<string, ChatSurfaceLinkTokenPayload>();
61
-
62
38
  constructor(
63
39
  private getAdminforth: () => IAdminForth,
64
40
  private options: PluginOptions,
@@ -69,40 +45,6 @@ export class ChatSurfaceService {
69
45
  ) => Promise<RunAndPersistAgentResponseResult>,
70
46
  ) {}
71
47
 
72
- getConnectActionAdapters() {
73
- return (this.options.chatSurfaceAdapters ?? [])
74
- .map((adapter) => adapter as ChatSurfaceAdapterWithConnectAction)
75
- .filter((adapter) => adapter.createConnectAction);
76
- }
77
-
78
- createLinkToken(surface: string, adminUser: AdminUser) {
79
- for (const [token, payload] of this.linkTokens) {
80
- if (payload.expiresAt <= Date.now()) {
81
- this.linkTokens.delete(token);
82
- }
83
- }
84
-
85
- const token = randomUUID();
86
- this.linkTokens.set(token, {
87
- surface,
88
- adminUserId: adminUser.pk,
89
- expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
90
- });
91
-
92
- return token;
93
- }
94
-
95
- private consumeLinkToken(surface: string, token: string) {
96
- const payload = this.linkTokens.get(token);
97
- this.linkTokens.delete(token);
98
-
99
- if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
100
- return null;
101
- }
102
-
103
- return payload;
104
- }
105
-
106
48
  private createEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
107
49
  return async (event) => {
108
50
  if (event.type === "text-delta") {
@@ -138,55 +80,10 @@ export class ChatSurfaceService {
138
80
  return false;
139
81
  }
140
82
 
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
-
166
- const payload = this.consumeLinkToken(incoming.surface, incoming.metadata.startPayload);
167
- if (!payload) {
168
- await sink.emit({
169
- type: "error",
170
- message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
171
- });
172
- return true;
173
- }
174
-
175
- const adminUserRecord = await adminforth.resource(authResourceId).get([
176
- Filters.EQ(primaryKeyField, payload.adminUserId),
177
- ]);
178
-
179
- await adminforth.resource(authResourceId).update(payload.adminUserId, {
180
- [externalUserIdField]: {
181
- ...(adminUserRecord[externalUserIdField] ?? {}),
182
- [incoming.surface]: incoming.externalUserId,
183
- },
184
- });
185
83
  await sink.emit({
186
84
  type: "done",
187
- text: `${incoming.surface} account connected to AdminForth.`,
85
+ text: `Open AdminForth and connect your ${incoming.surface} account from Connected Accounts settings.`,
188
86
  });
189
-
190
87
  return true;
191
88
  }
192
89
 
@@ -327,6 +224,54 @@ export class ChatSurfaceService {
327
224
  return agentResponse;
328
225
  }
329
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
+ ]);
273
+ }
274
+
330
275
  async handleMessage(
331
276
  adapter: ChatSurfaceAdapter,
332
277
  incoming: ChatSurfaceIncomingMessage,
@@ -340,10 +285,7 @@ export class ChatSurfaceService {
340
285
  const authResourceId = adminforth.config.auth!.usersResourceId!;
341
286
  const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
342
287
  const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
343
- const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
344
- const adminUserRecord = (
345
- await adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))
346
- ).find((user) => user[externalUserIdField]?.[adapter.name] === incoming.externalUserId);
288
+ const adminUserRecord = await this.getAdminUserRecordForChatSurface(adapter, incoming);
347
289
 
348
290
  if (!adminUserRecord) {
349
291
  await sink.emit({
@@ -1,32 +1,18 @@
1
- import type { AdminUser, ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
1
+ import type { ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
2
2
  import type { HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "./agentTurnService.js";
3
- import type { PluginOptions } from "./types.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
10
  private runAndPersistAgentResponse;
21
- private linkTokens;
22
11
  constructor(getAdminforth: () => IAdminForth, options: PluginOptions, sessionStore: AgentSessionStore, handleTurn: (input: HandleTurnInput) => Promise<unknown>, runAndPersistAgentResponse: (input: RunAndPersistAgentResponseInput) => Promise<RunAndPersistAgentResponseResult>);
23
- getConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
24
- createLinkToken(surface: string, adminUser: AdminUser): `${string}-${string}-${string}-${string}-${string}`;
25
- private consumeLinkToken;
26
12
  private createEventEmitter;
27
13
  private handleLink;
28
14
  private handleAudioMessage;
29
15
  private handleAgentSurfaceResponse;
16
+ private getAdminUserRecordForChatSurface;
30
17
  handleMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
31
18
  }
32
- export {};
@@ -8,11 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { Filters, logger } from "adminforth";
11
- import { randomUUID } from "crypto";
12
11
  import { getErrorMessage, isAbortError } from "./errors.js";
13
12
  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
13
  export class ChatSurfaceService {
17
14
  constructor(getAdminforth, options, sessionStore, handleTurn, runAndPersistAgentResponse) {
18
15
  this.getAdminforth = getAdminforth;
@@ -20,35 +17,6 @@ export class ChatSurfaceService {
20
17
  this.sessionStore = sessionStore;
21
18
  this.handleTurn = handleTurn;
22
19
  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
20
  }
53
21
  createEventEmitter(sink) {
54
22
  return (event) => __awaiter(this, void 0, void 0, function* () {
@@ -76,47 +44,13 @@ export class ChatSurfaceService {
76
44
  }
77
45
  handleLink(incoming, sink) {
78
46
  return __awaiter(this, void 0, void 0, function* () {
79
- var _a, _b, _c, _d;
47
+ var _a;
80
48
  if (((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.isStartCommand) !== true) {
81
49
  return false;
82
50
  }
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
51
  yield sink.emit({
118
52
  type: "done",
119
- text: `${incoming.surface} account connected to AdminForth.`,
53
+ text: `Open AdminForth and connect your ${incoming.surface} account from Connected Accounts settings.`,
120
54
  });
121
55
  return true;
122
56
  });
@@ -230,9 +164,49 @@ export class ChatSurfaceService {
230
164
  return agentResponse;
231
165
  });
232
166
  }
167
+ getAdminUserRecordForChatSurface(adapter, incoming) {
168
+ return __awaiter(this, void 0, void 0, function* () {
169
+ var _a, _b, _c, _d;
170
+ const adminforth = this.getAdminforth();
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
+ }
201
+ const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
202
+ const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
203
+ return adminforth.resource(authResourceId).get([
204
+ Filters.EQ(primaryKeyField, identity[adminUserIdField]),
205
+ ]);
206
+ });
207
+ }
233
208
  handleMessage(adapter, incoming, sink) {
234
209
  return __awaiter(this, void 0, void 0, function* () {
235
- var _a;
236
210
  if (yield this.handleLink(incoming, sink)) {
237
211
  return;
238
212
  }
@@ -240,8 +214,7 @@ export class ChatSurfaceService {
240
214
  const authResourceId = adminforth.config.auth.usersResourceId;
241
215
  const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
242
216
  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; });
217
+ const adminUserRecord = yield this.getAdminUserRecordForChatSurface(adapter, incoming);
245
218
  if (!adminUserRecord) {
246
219
  yield sink.emit({
247
220
  type: "error",
@@ -1,9 +1,7 @@
1
- import type { AdminUser, ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
1
+ import type { ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
2
2
  import type { ZodType } from "zod";
3
3
  import type { HandleSpeechTurnInput, HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../agentTurnService.js";
4
- import type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
5
4
  import type { PluginOptions } from "../types.js";
6
- export type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
7
5
  export type EndpointResponse = {
8
6
  setStatus: (code: number, message: string) => void;
9
7
  };
@@ -21,10 +19,8 @@ export type AgentEndpointsContext = {
21
19
  getSessionTurns(sessionId: string): Promise<SessionTurn[]>;
22
20
  createNewTurn(sessionId: string, prompt: string, response?: string): Promise<string>;
23
21
  createSystemTurn(sessionId: string, systemMessage: string): Promise<string>;
24
- getChatSurfaceConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
25
- createChatSurfaceLinkToken(surface: string, adminUser: AdminUser): string;
26
22
  handleChatSurfaceMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
27
23
  };
28
24
  export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "parseBody" | "handleTurn" | "handleSpeechTurn">;
29
25
  export type SessionEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "parseBody" | "getSessionTurns" | "createNewTurn" | "createSystemTurn">;
30
- export type ChatSurfaceEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "getChatSurfaceConnectActionAdapters" | "createChatSurfaceLinkToken" | "handleChatSurfaceMessage">;
26
+ export type ChatSurfaceEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "handleChatSurfaceMessage">;
package/dist/index.d.ts CHANGED
@@ -9,7 +9,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
9
9
  private sessionStore;
10
10
  private agentTurnService;
11
11
  private chatSurfaceService;
12
- private chatSurfaceSettingsPageRegistered;
13
12
  private parseBody;
14
13
  private getCheckpointer;
15
14
  private getInternalAgentResourceIds;
package/dist/index.js CHANGED
@@ -13,7 +13,6 @@ import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
13
13
  import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT } from "./agent/systemPrompt.js";
14
14
  import { setupCoreEndpoints } from "./endpoints/core.js";
15
15
  import { setupSessionEndpoints } from "./endpoints/sessions.js";
16
- import { setupChatSurfaceEndpoints } from "./endpoints/chatSurfaces.js";
17
16
  import { AgentSessionStore } from "./sessionStore.js";
18
17
  import { ChatSurfaceService } from "./chatSurfaceService.js";
19
18
  import { AgentTurnService } from "./agentTurnService.js";
@@ -45,7 +44,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
45
44
  constructor(options) {
46
45
  super(options, import.meta.url);
47
46
  this.checkpointer = null;
48
- this.chatSurfaceSettingsPageRegistered = false;
49
47
  this.options = options;
50
48
  this.sessionStore = new AgentSessionStore(() => this.adminforth, this.options);
51
49
  this.agentTurnService = new AgentTurnService({
@@ -84,19 +82,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
84
82
  hasAudioAdapter: Boolean(this.options.audioAdapter),
85
83
  }
86
84
  });
87
- if (this.chatSurfaceService.getConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
88
- if (!this.adminforth.config.auth.userMenuSettingsPages) {
89
- this.adminforth.config.auth.userMenuSettingsPages = [];
90
- }
91
- this.adminforth.config.auth.userMenuSettingsPages.push({
92
- icon: "flowbite:link-outline",
93
- pageLabel: "Chat Surfaces",
94
- slug: "chat-surfaces",
95
- component: this.componentPath("ChatSurfaceSettings.vue"),
96
- isVisible: () => true,
97
- });
98
- this.chatSurfaceSettingsPageRegistered = true;
99
- }
100
85
  if (!this.adminforth.config.customization.customHeadItems) {
101
86
  this.adminforth.config.customization.customHeadItems = [];
102
87
  }
@@ -139,12 +124,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
139
124
  getSessionTurns: this.sessionStore.getSessionTurns.bind(this.sessionStore),
140
125
  createNewTurn: this.sessionStore.createNewTurn.bind(this.sessionStore),
141
126
  createSystemTurn: this.sessionStore.createSystemTurn.bind(this.sessionStore),
142
- getChatSurfaceConnectActionAdapters: this.chatSurfaceService.getConnectActionAdapters.bind(this.chatSurfaceService),
143
- createChatSurfaceLinkToken: this.chatSurfaceService.createLinkToken.bind(this.chatSurfaceService),
144
127
  handleChatSurfaceMessage: this.chatSurfaceService.handleMessage.bind(this.chatSurfaceService),
145
128
  };
146
129
  setupCoreEndpoints(endpointContext, server);
147
130
  setupSessionEndpoints(endpointContext, server);
148
- setupChatSurfaceEndpoints(endpointContext, server);
149
131
  }
150
132
  }
package/dist/types.d.ts CHANGED
@@ -91,8 +91,17 @@ export interface PluginOptions extends PluginsCommonOptions {
91
91
  */
92
92
  checkpointResource?: ICheckpointResource;
93
93
  /**
94
- * Optional field for storing external chat IDs.
94
+ * Optional resource configuration for resolving chat users through OAuth external identities.
95
95
  */
96
- chatExternalIdsField?: string;
96
+ chatExternalIdentityResource?: {
97
+ resourceId: string;
98
+ adminUserIdField?: string;
99
+ providerField?: string;
100
+ subjectField?: string;
101
+ externalUserIdField?: string;
102
+ surfaces: Record<string, {
103
+ provider: string;
104
+ }>;
105
+ };
97
106
  }
98
107
  export {};
@@ -1,5 +1,4 @@
1
1
  import type {
2
- AdminUser,
3
2
  ChatSurfaceAdapter,
4
3
  ChatSurfaceEventSink,
5
4
  ChatSurfaceIncomingMessage,
@@ -12,11 +11,8 @@ import type {
12
11
  RunAndPersistAgentResponseInput,
13
12
  RunAndPersistAgentResponseResult,
14
13
  } from "../agentTurnService.js";
15
- import type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
16
14
  import type { PluginOptions } from "../types.js";
17
15
 
18
- export type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
19
-
20
16
  export type EndpointResponse = {
21
17
  setStatus: (code: number, message: string) => void;
22
18
  };
@@ -36,8 +32,6 @@ export type AgentEndpointsContext = {
36
32
  getSessionTurns(sessionId: string): Promise<SessionTurn[]>;
37
33
  createNewTurn(sessionId: string, prompt: string, response?: string): Promise<string>;
38
34
  createSystemTurn(sessionId: string, systemMessage: string): Promise<string>;
39
- getChatSurfaceConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
40
- createChatSurfaceLinkToken(surface: string, adminUser: AdminUser): string;
41
35
  handleChatSurfaceMessage(
42
36
  adapter: ChatSurfaceAdapter,
43
37
  incoming: ChatSurfaceIncomingMessage,
@@ -60,7 +54,5 @@ export type ChatSurfaceEndpointsContext = Pick<
60
54
  AgentEndpointsContext,
61
55
  | "adminforth"
62
56
  | "options"
63
- | "getChatSurfaceConnectActionAdapters"
64
- | "createChatSurfaceLinkToken"
65
57
  | "handleChatSurfaceMessage"
66
58
  >;
package/index.ts CHANGED
@@ -13,7 +13,6 @@ import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
13
13
  import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
14
14
  import { setupCoreEndpoints } from "./endpoints/core.js";
15
15
  import { setupSessionEndpoints } from "./endpoints/sessions.js";
16
- import { setupChatSurfaceEndpoints } from "./endpoints/chatSurfaces.js";
17
16
  import type { AgentEndpointsContext } from "./endpoints/context.js";
18
17
  import { AgentSessionStore } from "./sessionStore.js";
19
18
  import { ChatSurfaceService } from "./chatSurfaceService.js";
@@ -28,7 +27,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
28
27
  private sessionStore: AgentSessionStore;
29
28
  private agentTurnService: AgentTurnService;
30
29
  private chatSurfaceService: ChatSurfaceService;
31
- private chatSurfaceSettingsPageRegistered = false;
32
30
  private parseBody<T>(
33
31
  schema: z.ZodType<T>,
34
32
  body: unknown,
@@ -103,19 +101,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
103
101
  hasAudioAdapter: Boolean(this.options.audioAdapter),
104
102
  }
105
103
  });
106
- if (this.chatSurfaceService.getConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
107
- if (!this.adminforth.config.auth!.userMenuSettingsPages) {
108
- this.adminforth.config.auth!.userMenuSettingsPages = [];
109
- }
110
- this.adminforth.config.auth!.userMenuSettingsPages.push({
111
- icon: "flowbite:link-outline",
112
- pageLabel: "Chat Surfaces",
113
- slug: "chat-surfaces",
114
- component: this.componentPath("ChatSurfaceSettings.vue"),
115
- isVisible: () => true,
116
- });
117
- this.chatSurfaceSettingsPageRegistered = true;
118
- }
119
104
  if (!this.adminforth.config.customization.customHeadItems) {
120
105
  this.adminforth.config.customization.customHeadItems = [];
121
106
  }
@@ -165,13 +150,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
165
150
  getSessionTurns: this.sessionStore.getSessionTurns.bind(this.sessionStore),
166
151
  createNewTurn: this.sessionStore.createNewTurn.bind(this.sessionStore),
167
152
  createSystemTurn: this.sessionStore.createSystemTurn.bind(this.sessionStore),
168
- getChatSurfaceConnectActionAdapters: this.chatSurfaceService.getConnectActionAdapters.bind(this.chatSurfaceService),
169
- createChatSurfaceLinkToken: this.chatSurfaceService.createLinkToken.bind(this.chatSurfaceService),
170
153
  handleChatSurfaceMessage: this.chatSurfaceService.handleMessage.bind(this.chatSurfaceService),
171
154
  } satisfies AgentEndpointsContext;
172
155
 
173
156
  setupCoreEndpoints(endpointContext, server);
174
157
  setupSessionEndpoints(endpointContext, server);
175
- setupChatSurfaceEndpoints(endpointContext, server);
176
158
  }
177
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.46.0",
3
+ "version": "1.47.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
package/types.ts CHANGED
@@ -112,7 +112,16 @@ export interface PluginOptions extends PluginsCommonOptions {
112
112
  checkpointResource?: ICheckpointResource;
113
113
 
114
114
  /**
115
- * Optional field for storing external chat IDs.
115
+ * Optional resource configuration for resolving chat users through OAuth external identities.
116
116
  */
117
- chatExternalIdsField?: string;
117
+ chatExternalIdentityResource?: {
118
+ resourceId: string;
119
+ adminUserIdField?: string;
120
+ providerField?: string;
121
+ subjectField?: string;
122
+ externalUserIdField?: string;
123
+ surfaces: Record<string, {
124
+ provider: string;
125
+ }>;
126
+ };
118
127
  }
@@ -1,125 +0,0 @@
1
- <template>
2
- <div class="flex flex-col justify-center mr-6 md:mr-12">
3
- <h2 class="flex items-start justify-start leading-none text-gray-800 dark:text-gray-50 text-3xl font-semibold">
4
- {{ $t('Chat Surfaces') }}
5
- </h2>
6
- <p class="text-sm mt-3">
7
- {{ $t('Connect external chat accounts to your AdminForth user') }}
8
- </p>
9
-
10
- <div class="mt-6 flex flex-wrap gap-4">
11
- <div
12
- v-for="surface in surfaces"
13
- :key="surface.name"
14
- class="flex flex-col w-full lg:w-72 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 shadow-sm"
15
- >
16
- <div class="flex items-center justify-between gap-3 mb-4">
17
- <div class="min-w-0">
18
- <p class="font-semibold text-gray-900 dark:text-white truncate">
19
- {{ formatSurfaceName(surface.name) }}
20
- </p>
21
- <p class="text-xs text-gray-500 dark:text-gray-400">
22
- {{ surface.externalUserId || $t('Not connected') }}
23
- </p>
24
- </div>
25
- <span
26
- class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
27
- :class="surface.externalUserId
28
- ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
29
- : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'"
30
- >
31
- {{ surface.externalUserId ? $t('Active') : $t('Inactive') }}
32
- </span>
33
- </div>
34
-
35
- <div class="grid gap-2 mt-auto" :class="surface.externalUserId ? 'grid-cols-2' : 'grid-cols-1'">
36
- <Button
37
- class="w-full"
38
- :disabled="isSurfaceBusy(surface.name)"
39
- :loader="connectingSurfaceName === surface.name"
40
- @click="connectSurface(surface.name)"
41
- >
42
- {{ surface.externalUserId ? $t('Reconnect') : $t('Connect') }}
43
- </Button>
44
- <Button
45
- v-if="surface.externalUserId"
46
- class="w-full"
47
- :disabled="isSurfaceBusy(surface.name)"
48
- :loader="disconnectingSurfaceName === surface.name"
49
- @click="disconnectSurface(surface.name)"
50
- >
51
- {{ $t('Disconnect') }}
52
- </Button>
53
- </div>
54
- </div>
55
- </div>
56
- </div>
57
- </template>
58
-
59
- <script setup lang="ts">
60
- import { onMounted, ref } from 'vue';
61
- import { Button } from '@/afcl';
62
- import { callAdminForthApi } from '@/utils';
63
-
64
- type ChatSurface = {
65
- name: string;
66
- externalUserId: string | null;
67
- };
68
-
69
- const surfaces = ref<ChatSurface[]>([]);
70
- const connectingSurfaceName = ref<string | null>(null);
71
- const disconnectingSurfaceName = ref<string | null>(null);
72
-
73
- onMounted(loadSurfaces);
74
-
75
- async function loadSurfaces() {
76
- const response = await callAdminForthApi({
77
- method: 'POST',
78
- path: '/agent/surfaces/connectable',
79
- body: {},
80
- });
81
-
82
- surfaces.value = response.surfaces;
83
- }
84
-
85
- async function connectSurface(surfaceName: string) {
86
- connectingSurfaceName.value = surfaceName;
87
-
88
- try {
89
- const response = await callAdminForthApi({
90
- method: 'POST',
91
- path: `/agent/surface/${surfaceName}/connect-action`,
92
- body: {},
93
- });
94
-
95
- if (response.action.type === 'url') {
96
- window.open(response.action.url, '_blank', 'noopener,noreferrer');
97
- }
98
- } finally {
99
- connectingSurfaceName.value = null;
100
- }
101
- }
102
-
103
- async function disconnectSurface(surfaceName: string) {
104
- disconnectingSurfaceName.value = surfaceName;
105
-
106
- try {
107
- await callAdminForthApi({
108
- method: 'POST',
109
- path: `/agent/surface/${surfaceName}/disconnect`,
110
- body: {},
111
- });
112
- await loadSurfaces();
113
- } finally {
114
- disconnectingSurfaceName.value = null;
115
- }
116
- }
117
-
118
- function isSurfaceBusy(surfaceName: string) {
119
- return connectingSurfaceName.value === surfaceName || disconnectingSurfaceName.value === surfaceName;
120
- }
121
-
122
- function formatSurfaceName(surfaceName: string) {
123
- return surfaceName.charAt(0).toUpperCase() + surfaceName.slice(1);
124
- }
125
- </script>
@@ -1,125 +0,0 @@
1
- <template>
2
- <div class="flex flex-col justify-center mr-6 md:mr-12">
3
- <h2 class="flex items-start justify-start leading-none text-gray-800 dark:text-gray-50 text-3xl font-semibold">
4
- {{ $t('Chat Surfaces') }}
5
- </h2>
6
- <p class="text-sm mt-3">
7
- {{ $t('Connect external chat accounts to your AdminForth user') }}
8
- </p>
9
-
10
- <div class="mt-6 flex flex-wrap gap-4">
11
- <div
12
- v-for="surface in surfaces"
13
- :key="surface.name"
14
- class="flex flex-col w-full lg:w-72 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl p-4 shadow-sm"
15
- >
16
- <div class="flex items-center justify-between gap-3 mb-4">
17
- <div class="min-w-0">
18
- <p class="font-semibold text-gray-900 dark:text-white truncate">
19
- {{ formatSurfaceName(surface.name) }}
20
- </p>
21
- <p class="text-xs text-gray-500 dark:text-gray-400">
22
- {{ surface.externalUserId || $t('Not connected') }}
23
- </p>
24
- </div>
25
- <span
26
- class="shrink-0 inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium"
27
- :class="surface.externalUserId
28
- ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
29
- : 'bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300'"
30
- >
31
- {{ surface.externalUserId ? $t('Active') : $t('Inactive') }}
32
- </span>
33
- </div>
34
-
35
- <div class="grid gap-2 mt-auto" :class="surface.externalUserId ? 'grid-cols-2' : 'grid-cols-1'">
36
- <Button
37
- class="w-full"
38
- :disabled="isSurfaceBusy(surface.name)"
39
- :loader="connectingSurfaceName === surface.name"
40
- @click="connectSurface(surface.name)"
41
- >
42
- {{ surface.externalUserId ? $t('Reconnect') : $t('Connect') }}
43
- </Button>
44
- <Button
45
- v-if="surface.externalUserId"
46
- class="w-full"
47
- :disabled="isSurfaceBusy(surface.name)"
48
- :loader="disconnectingSurfaceName === surface.name"
49
- @click="disconnectSurface(surface.name)"
50
- >
51
- {{ $t('Disconnect') }}
52
- </Button>
53
- </div>
54
- </div>
55
- </div>
56
- </div>
57
- </template>
58
-
59
- <script setup lang="ts">
60
- import { onMounted, ref } from 'vue';
61
- import { Button } from '@/afcl';
62
- import { callAdminForthApi } from '@/utils';
63
-
64
- type ChatSurface = {
65
- name: string;
66
- externalUserId: string | null;
67
- };
68
-
69
- const surfaces = ref<ChatSurface[]>([]);
70
- const connectingSurfaceName = ref<string | null>(null);
71
- const disconnectingSurfaceName = ref<string | null>(null);
72
-
73
- onMounted(loadSurfaces);
74
-
75
- async function loadSurfaces() {
76
- const response = await callAdminForthApi({
77
- method: 'POST',
78
- path: '/agent/surfaces/connectable',
79
- body: {},
80
- });
81
-
82
- surfaces.value = response.surfaces;
83
- }
84
-
85
- async function connectSurface(surfaceName: string) {
86
- connectingSurfaceName.value = surfaceName;
87
-
88
- try {
89
- const response = await callAdminForthApi({
90
- method: 'POST',
91
- path: `/agent/surface/${surfaceName}/connect-action`,
92
- body: {},
93
- });
94
-
95
- if (response.action.type === 'url') {
96
- window.open(response.action.url, '_blank', 'noopener,noreferrer');
97
- }
98
- } finally {
99
- connectingSurfaceName.value = null;
100
- }
101
- }
102
-
103
- async function disconnectSurface(surfaceName: string) {
104
- disconnectingSurfaceName.value = surfaceName;
105
-
106
- try {
107
- await callAdminForthApi({
108
- method: 'POST',
109
- path: `/agent/surface/${surfaceName}/disconnect`,
110
- body: {},
111
- });
112
- await loadSurfaces();
113
- } finally {
114
- disconnectingSurfaceName.value = null;
115
- }
116
- }
117
-
118
- function isSurfaceBusy(surfaceName: string) {
119
- return connectingSurfaceName.value === surfaceName || disconnectingSurfaceName.value === surfaceName;
120
- }
121
-
122
- function formatSurfaceName(surfaceName: string) {
123
- return surfaceName.charAt(0).toUpperCase() + surfaceName.slice(1);
124
- }
125
- </script>
@@ -1,3 +0,0 @@
1
- import type { IHttpServer } from "adminforth";
2
- import type { ChatSurfaceEndpointsContext } from "./context.js";
3
- export declare function setupChatSurfaceEndpoints(ctx: ChatSurfaceEndpointsContext, server: IHttpServer): void;
@@ -1,91 +0,0 @@
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
- }
@@ -1,93 +0,0 @@
1
- import type {
2
- IAdminForthEndpointHandlerInput,
3
- IHttpServer,
4
- } from "adminforth";
5
- import type { ChatSurfaceAdapterWithConnectAction, ChatSurfaceEndpointsContext } from "./context.js";
6
-
7
- const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
8
-
9
- export function setupChatSurfaceEndpoints(ctx: ChatSurfaceEndpointsContext, server: IHttpServer) {
10
- if (ctx.getChatSurfaceConnectActionAdapters().length) {
11
- server.endpoint({
12
- method: "POST",
13
- path: "/agent/surfaces/connectable",
14
- handler: async ({ adminUser }) => {
15
- const externalUserIdField = ctx.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
16
- const externalIds = adminUser!.dbUser[externalUserIdField] ?? {};
17
-
18
- return {
19
- surfaces: ctx.getChatSurfaceConnectActionAdapters().map((adapter) => ({
20
- name: adapter.name,
21
- externalUserId: externalIds[adapter.name] ?? null,
22
- })),
23
- };
24
- },
25
- });
26
- }
27
-
28
- for (const adapter of ctx.options.chatSurfaceAdapters ?? []) {
29
- const connectActionAdapter = adapter as ChatSurfaceAdapterWithConnectAction;
30
- if (connectActionAdapter.createConnectAction) {
31
- server.endpoint({
32
- method: "POST",
33
- path: `/agent/surface/${adapter.name}/connect-action`,
34
- handler: async ({ adminUser }) => {
35
- const token = ctx.createChatSurfaceLinkToken(adapter.name, adminUser!);
36
- const action = await connectActionAdapter.createConnectAction!({ token });
37
-
38
- return {
39
- action,
40
- };
41
- },
42
- });
43
- server.endpoint({
44
- method: "POST",
45
- path: `/agent/surface/${adapter.name}/disconnect`,
46
- handler: async ({ adminUser }) => {
47
- const externalUserIdField = ctx.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
48
- const externalIds = {
49
- ...(adminUser!.dbUser[externalUserIdField] ?? {}),
50
- };
51
-
52
- delete externalIds[adapter.name];
53
-
54
- await ctx.adminforth.resource(ctx.adminforth.config.auth!.usersResourceId!).update(adminUser!.pk, {
55
- [externalUserIdField]: externalIds,
56
- });
57
-
58
- return {
59
- ok: true,
60
- };
61
- },
62
- });
63
- }
64
-
65
- server.endpoint({
66
- method: "POST",
67
- noAuth: true,
68
- path: `/agent/surface/${adapter.name}/webhook`,
69
- handler: async (endpointInput: IAdminForthEndpointHandlerInput) => {
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 = await adapter.parseIncomingMessage(surfaceContext);
78
-
79
- if (!incoming) return { ok: true };
80
-
81
- const sink = await adapter.createEventSink(surfaceContext, incoming);
82
-
83
- try {
84
- await ctx.handleChatSurfaceMessage(adapter, incoming, sink);
85
- } finally {
86
- await sink.close?.();
87
- }
88
-
89
- return { ok: true };
90
- },
91
- });
92
- }
93
- }