Skip to content

Commit f566049

Browse files
committed
feat: remove deprecated chat surface settings and endpoints, and update context types for external identity resource
1 parent df9ffec commit f566049

6 files changed

Lines changed: 63 additions & 356 deletions

File tree

chatSurfaceService.ts

Lines changed: 52 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,16 @@ import type {
66
IAdminForth,
77
} from "adminforth";
88
import { Filters, logger } from "adminforth";
9-
import { randomUUID } from "crypto";
109
import type { AgentEventEmitter } from "./agentEvents.js";
1110
import type {
1211
HandleTurnInput,
1312
RunAndPersistAgentResponseInput,
1413
RunAndPersistAgentResponseResult,
1514
} from "./agentTurnService.js";
16-
import type { PluginOptions } from "./types.js";
17-
import type { AgentSessionStore } from "./sessionStore.js";
1815
import { getErrorMessage, isAbortError } from "./errors.js";
16+
import type { AgentSessionStore } from "./sessionStore.js";
1917
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";
2619

2720
type ChatSurfaceIncomingMessageWithAudio = ChatSurfaceIncomingMessage & {
2821
audio?: {
@@ -41,24 +34,7 @@ type ChatSurfaceEventSinkWithAudio = ChatSurfaceEventSink & {
4134
}): void | Promise<void>;
4235
};
4336

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-
5937
export class ChatSurfaceService {
60-
private linkTokens = new Map<string, ChatSurfaceLinkTokenPayload>();
61-
6238
constructor(
6339
private getAdminforth: () => IAdminForth,
6440
private options: PluginOptions,
@@ -69,40 +45,6 @@ export class ChatSurfaceService {
6945
) => Promise<RunAndPersistAgentResponseResult>,
7046
) {}
7147

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-
10648
private createEventEmitter(sink: ChatSurfaceEventSink): AgentEventEmitter {
10749
return async (event) => {
10850
if (event.type === "text-delta") {
@@ -138,55 +80,10 @@ export class ChatSurfaceService {
13880
return false;
13981
}
14082

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-
});
18583
await sink.emit({
18684
type: "done",
187-
text: `${incoming.surface} account connected to AdminForth.`,
85+
text: `Open AdminForth and connect your ${incoming.surface} account from Connected Accounts settings.`,
18886
});
189-
19087
return true;
19188
}
19289

@@ -327,6 +224,54 @@ export class ChatSurfaceService {
327224
return agentResponse;
328225
}
329226

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+
330275
async handleMessage(
331276
adapter: ChatSurfaceAdapter,
332277
incoming: ChatSurfaceIncomingMessage,
@@ -340,10 +285,7 @@ export class ChatSurfaceService {
340285
const authResourceId = adminforth.config.auth!.usersResourceId!;
341286
const authResource = adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
342287
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);
347289

348290
if (!adminUserRecord) {
349291
await sink.emit({

custom/ChatSurfaceSettings.vue

Lines changed: 0 additions & 125 deletions
This file was deleted.

0 commit comments

Comments
 (0)