@adminforth/agent 1.44.2 → 1.45.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 +3 -2
- package/custom/ChatSurfaceSettings.vue +125 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/custom/ChatSurfaceSettings.vue +125 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/index.d.ts +6 -0
- package/dist/index.js +149 -27
- package/dist/types.d.ts +4 -0
- package/index.ts +188 -29
- package/package.json +2 -2
- package/types.ts +5 -0
package/build.log
CHANGED
|
@@ -7,6 +7,7 @@ custom/
|
|
|
7
7
|
custom/ChatFooter.vue
|
|
8
8
|
custom/ChatHeader.vue
|
|
9
9
|
custom/ChatSurface.vue
|
|
10
|
+
custom/ChatSurfaceSettings.vue
|
|
10
11
|
custom/CustomAutoScrollContainer.vue
|
|
11
12
|
custom/SessionsHistory.vue
|
|
12
13
|
custom/chat.ts
|
|
@@ -62,5 +63,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
|
|
|
62
63
|
custom/speech_recognition_frontend/types/
|
|
63
64
|
custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
|
|
64
65
|
|
|
65
|
-
sent 1,
|
|
66
|
-
total size is 1,
|
|
66
|
+
sent 1,671,609 bytes received 940 bytes 3,345,098.00 bytes/sec
|
|
67
|
+
total size is 1,667,436 speedup is 1.00
|
|
@@ -0,0 +1,125 @@
|
|
|
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>
|
|
@@ -625,11 +625,11 @@ function sanitizeClassToken(value: string): string {
|
|
|
625
625
|
|
|
626
626
|
function escapeHtml(value: string): string {
|
|
627
627
|
return value
|
|
628
|
-
.
|
|
629
|
-
.
|
|
630
|
-
.
|
|
631
|
-
.
|
|
632
|
-
.
|
|
628
|
+
.replace(/&/g, '&')
|
|
629
|
+
.replace(/</g, '<')
|
|
630
|
+
.replace(/>/g, '>')
|
|
631
|
+
.replace(/"/g, '"')
|
|
632
|
+
.replace(/'/g, ''');
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
function escapeAttribute(value: string): string {
|
|
@@ -0,0 +1,125 @@
|
|
|
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>
|
|
@@ -625,11 +625,11 @@ function sanitizeClassToken(value: string): string {
|
|
|
625
625
|
|
|
626
626
|
function escapeHtml(value: string): string {
|
|
627
627
|
return value
|
|
628
|
-
.
|
|
629
|
-
.
|
|
630
|
-
.
|
|
631
|
-
.
|
|
632
|
-
.
|
|
628
|
+
.replace(/&/g, '&')
|
|
629
|
+
.replace(/</g, '<')
|
|
630
|
+
.replace(/>/g, '>')
|
|
631
|
+
.replace(/"/g, '"')
|
|
632
|
+
.replace(/'/g, ''');
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
function escapeAttribute(value: string): string {
|
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
32
32
|
options: PluginOptions;
|
|
33
33
|
agentSystemPromptPromise: Promise<string>;
|
|
34
34
|
private checkpointer;
|
|
35
|
+
private chatSurfaceLinkTokens;
|
|
36
|
+
private chatSurfaceSettingsPageRegistered;
|
|
35
37
|
private parseBody;
|
|
36
38
|
private createNewTurn;
|
|
37
39
|
private getSessionTurns;
|
|
@@ -40,6 +42,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
40
42
|
private getOrCreateChatSurfaceSession;
|
|
41
43
|
private getCheckpointer;
|
|
42
44
|
private getInternalAgentResourceIds;
|
|
45
|
+
private getChatSurfaceConnectActionAdapters;
|
|
43
46
|
constructor(options: PluginOptions);
|
|
44
47
|
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource): Promise<void>;
|
|
45
48
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource): void;
|
|
@@ -53,6 +56,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
53
56
|
failed: boolean;
|
|
54
57
|
}>;
|
|
55
58
|
private createChatSurfaceEventEmitter;
|
|
59
|
+
private createChatSurfaceLinkToken;
|
|
60
|
+
private consumeChatSurfaceLinkToken;
|
|
61
|
+
private handleChatSurfaceLink;
|
|
56
62
|
private handleChatSurfaceMessage;
|
|
57
63
|
setupEndpoints(server: IHttpServer): void;
|
|
58
64
|
}
|
package/dist/index.js
CHANGED
|
@@ -50,6 +50,8 @@ const createSessionBodySchema = z.object({
|
|
|
50
50
|
}).strict();
|
|
51
51
|
const VEGA_LITE_FENCE_START = "```vega-lite";
|
|
52
52
|
const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
|
|
53
|
+
const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
|
|
54
|
+
const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
|
|
53
55
|
function isAbortError(error) {
|
|
54
56
|
return (error instanceof DOMException && error.name === "AbortError") || (typeof error === "object" &&
|
|
55
57
|
error !== null &&
|
|
@@ -59,12 +61,6 @@ function isAbortError(error) {
|
|
|
59
61
|
function getErrorMessage(error) {
|
|
60
62
|
return error instanceof Error ? error.message : String(error);
|
|
61
63
|
}
|
|
62
|
-
function requireAdminUser(adminUser) {
|
|
63
|
-
if (!adminUser) {
|
|
64
|
-
throw new Error("AdminForth Agent endpoint requires an authenticated admin user");
|
|
65
|
-
}
|
|
66
|
-
return adminUser;
|
|
67
|
-
}
|
|
68
64
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
69
65
|
parseBody(schema, body, response) {
|
|
70
66
|
const parsed = schema.safeParse(body !== null && body !== void 0 ? body : {});
|
|
@@ -141,9 +137,17 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
141
137
|
(_a = this.options.checkpointResource) === null || _a === void 0 ? void 0 : _a.resourceId,
|
|
142
138
|
].filter((resourceId) => Boolean(resourceId));
|
|
143
139
|
}
|
|
140
|
+
getChatSurfaceConnectActionAdapters() {
|
|
141
|
+
var _a;
|
|
142
|
+
return ((_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : [])
|
|
143
|
+
.map((adapter) => adapter)
|
|
144
|
+
.filter((adapter) => adapter.createConnectAction);
|
|
145
|
+
}
|
|
144
146
|
constructor(options) {
|
|
145
147
|
super(options, import.meta.url);
|
|
146
148
|
this.checkpointer = null;
|
|
149
|
+
this.chatSurfaceLinkTokens = new Map();
|
|
150
|
+
this.chatSurfaceSettingsPageRegistered = false;
|
|
147
151
|
this.options = options;
|
|
148
152
|
this.agentSystemPromptPromise = Promise.resolve(appendCustomSystemPrompt(DEFAULT_AGENT_SYSTEM_PROMPT, this.options.systemPrompt));
|
|
149
153
|
this.shouldHaveSingleInstancePerWholeApp = () => false;
|
|
@@ -171,6 +175,19 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
171
175
|
hasAudioAdapter: Boolean(this.options.audioAdapter),
|
|
172
176
|
}
|
|
173
177
|
});
|
|
178
|
+
if (this.getChatSurfaceConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
|
|
179
|
+
if (!this.adminforth.config.auth.userMenuSettingsPages) {
|
|
180
|
+
this.adminforth.config.auth.userMenuSettingsPages = [];
|
|
181
|
+
}
|
|
182
|
+
this.adminforth.config.auth.userMenuSettingsPages.push({
|
|
183
|
+
icon: "flowbite:link-outline",
|
|
184
|
+
pageLabel: "Chat Surfaces",
|
|
185
|
+
slug: "chat-surfaces",
|
|
186
|
+
component: this.componentPath("ChatSurfaceSettings.vue"),
|
|
187
|
+
isVisible: () => true,
|
|
188
|
+
});
|
|
189
|
+
this.chatSurfaceSettingsPageRegistered = true;
|
|
190
|
+
}
|
|
174
191
|
if (!this.adminforth.config.customization.customHeadItems) {
|
|
175
192
|
this.adminforth.config.customization.customHeadItems = [];
|
|
176
193
|
}
|
|
@@ -485,25 +502,87 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
485
502
|
}
|
|
486
503
|
});
|
|
487
504
|
}
|
|
488
|
-
|
|
505
|
+
createChatSurfaceLinkToken(surface, adminUser) {
|
|
506
|
+
for (const [token, payload] of this.chatSurfaceLinkTokens) {
|
|
507
|
+
if (payload.expiresAt <= Date.now()) {
|
|
508
|
+
this.chatSurfaceLinkTokens.delete(token);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
const token = randomUUID();
|
|
512
|
+
this.chatSurfaceLinkTokens.set(token, {
|
|
513
|
+
surface,
|
|
514
|
+
adminUserId: adminUser.pk,
|
|
515
|
+
expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
|
|
516
|
+
});
|
|
517
|
+
return token;
|
|
518
|
+
}
|
|
519
|
+
consumeChatSurfaceLinkToken(surface, token) {
|
|
520
|
+
const payload = this.chatSurfaceLinkTokens.get(token);
|
|
521
|
+
this.chatSurfaceLinkTokens.delete(token);
|
|
522
|
+
if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
|
|
523
|
+
return null;
|
|
524
|
+
}
|
|
525
|
+
return payload;
|
|
526
|
+
}
|
|
527
|
+
handleChatSurfaceLink(incoming, sink) {
|
|
489
528
|
return __awaiter(this, void 0, void 0, function* () {
|
|
490
|
-
var _a;
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
529
|
+
var _a, _b, _c;
|
|
530
|
+
if (typeof ((_a = incoming.metadata) === null || _a === void 0 ? void 0 : _a.startPayload) !== "string") {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const payload = this.consumeChatSurfaceLinkToken(incoming.surface, incoming.metadata.startPayload);
|
|
534
|
+
if (!payload) {
|
|
535
|
+
yield sink.emit({
|
|
536
|
+
type: "error",
|
|
537
|
+
message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
|
|
538
|
+
});
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
542
|
+
const authResourceId = this.adminforth.config.auth.usersResourceId;
|
|
543
|
+
const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
|
|
544
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
|
|
545
|
+
const adminUserRecord = yield this.adminforth.resource(authResourceId).get([
|
|
546
|
+
Filters.EQ(primaryKeyField, payload.adminUserId),
|
|
547
|
+
]);
|
|
548
|
+
yield this.adminforth.resource(authResourceId).update(payload.adminUserId, {
|
|
549
|
+
[externalUserIdField]: Object.assign(Object.assign({}, ((_c = adminUserRecord[externalUserIdField]) !== null && _c !== void 0 ? _c : {})), { [incoming.surface]: incoming.externalUserId }),
|
|
550
|
+
});
|
|
551
|
+
yield sink.emit({
|
|
552
|
+
type: "done",
|
|
553
|
+
text: `${incoming.surface} account connected to AdminForth.`,
|
|
494
554
|
});
|
|
495
|
-
|
|
555
|
+
return true;
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
handleChatSurfaceMessage(adapter, incoming, sink) {
|
|
559
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
560
|
+
var _a, _b;
|
|
561
|
+
if (yield this.handleChatSurfaceLink(incoming, sink)) {
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const authResourceId = this.adminforth.config.auth.usersResourceId;
|
|
565
|
+
const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId);
|
|
566
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey).name;
|
|
567
|
+
const externalUserIdField = (_a = this.options.chatExternalIdsField) !== null && _a !== void 0 ? _a : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
568
|
+
const adminUserRecord = (yield this.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; });
|
|
569
|
+
if (!adminUserRecord) {
|
|
496
570
|
yield sink.emit({
|
|
497
571
|
type: "error",
|
|
498
572
|
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
499
573
|
});
|
|
500
574
|
return;
|
|
501
575
|
}
|
|
576
|
+
const adminUser = {
|
|
577
|
+
pk: adminUserRecord[primaryKeyField],
|
|
578
|
+
username: adminUserRecord[this.adminforth.config.auth.usernameField],
|
|
579
|
+
dbUser: adminUserRecord,
|
|
580
|
+
};
|
|
502
581
|
yield this.handleTurn({
|
|
503
582
|
prompt: incoming.prompt,
|
|
504
583
|
sessionId: yield this.getOrCreateChatSurfaceSession(incoming, adminUser),
|
|
505
584
|
modeName: incoming.modeName,
|
|
506
|
-
userTimeZone: (
|
|
585
|
+
userTimeZone: (_b = incoming.userTimeZone) !== null && _b !== void 0 ? _b : "UTC",
|
|
507
586
|
adminUser,
|
|
508
587
|
emit: this.createChatSurfaceEventEmitter(sink),
|
|
509
588
|
failureLogMessage: `Agent ${incoming.surface} surface response failed`,
|
|
@@ -513,7 +592,57 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
513
592
|
}
|
|
514
593
|
setupEndpoints(server) {
|
|
515
594
|
var _a;
|
|
595
|
+
if (this.getChatSurfaceConnectActionAdapters().length) {
|
|
596
|
+
server.endpoint({
|
|
597
|
+
method: "POST",
|
|
598
|
+
path: "/agent/surfaces/connectable",
|
|
599
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
600
|
+
var _b, _c;
|
|
601
|
+
const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
602
|
+
const externalIds = (_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {};
|
|
603
|
+
return {
|
|
604
|
+
surfaces: this.getChatSurfaceConnectActionAdapters().map((adapter) => {
|
|
605
|
+
var _a;
|
|
606
|
+
return ({
|
|
607
|
+
name: adapter.name,
|
|
608
|
+
externalUserId: (_a = externalIds[adapter.name]) !== null && _a !== void 0 ? _a : null,
|
|
609
|
+
});
|
|
610
|
+
}),
|
|
611
|
+
};
|
|
612
|
+
}),
|
|
613
|
+
});
|
|
614
|
+
}
|
|
516
615
|
for (const adapter of (_a = this.options.chatSurfaceAdapters) !== null && _a !== void 0 ? _a : []) {
|
|
616
|
+
const connectActionAdapter = adapter;
|
|
617
|
+
if (connectActionAdapter.createConnectAction) {
|
|
618
|
+
server.endpoint({
|
|
619
|
+
method: "POST",
|
|
620
|
+
path: `/agent/surface/${adapter.name}/connect-action`,
|
|
621
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
622
|
+
const token = this.createChatSurfaceLinkToken(adapter.name, adminUser);
|
|
623
|
+
const action = yield connectActionAdapter.createConnectAction({ token });
|
|
624
|
+
return {
|
|
625
|
+
action,
|
|
626
|
+
};
|
|
627
|
+
}),
|
|
628
|
+
});
|
|
629
|
+
server.endpoint({
|
|
630
|
+
method: "POST",
|
|
631
|
+
path: `/agent/surface/${adapter.name}/disconnect`,
|
|
632
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
|
|
633
|
+
var _b, _c;
|
|
634
|
+
const externalUserIdField = (_b = this.options.chatExternalIdsField) !== null && _b !== void 0 ? _b : DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
635
|
+
const externalIds = Object.assign({}, ((_c = adminUser.dbUser[externalUserIdField]) !== null && _c !== void 0 ? _c : {}));
|
|
636
|
+
delete externalIds[adapter.name];
|
|
637
|
+
yield this.adminforth.resource(this.adminforth.config.auth.usersResourceId).update(adminUser.pk, {
|
|
638
|
+
[externalUserIdField]: externalIds,
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
ok: true,
|
|
642
|
+
};
|
|
643
|
+
}),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
517
646
|
server.endpoint({
|
|
518
647
|
method: "POST",
|
|
519
648
|
noAuth: true,
|
|
@@ -545,14 +674,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
545
674
|
method: 'POST',
|
|
546
675
|
path: `/agent/get-placeholder-messages`,
|
|
547
676
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ headers, adminUser }) {
|
|
548
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
549
677
|
if (!this.options.placeholderMessages) {
|
|
550
678
|
return {
|
|
551
679
|
messages: [],
|
|
552
680
|
};
|
|
553
681
|
}
|
|
554
682
|
const messages = yield this.options.placeholderMessages({
|
|
555
|
-
adminUser:
|
|
683
|
+
adminUser: adminUser,
|
|
556
684
|
headers,
|
|
557
685
|
});
|
|
558
686
|
return {
|
|
@@ -565,7 +693,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
565
693
|
path: `/agent/response`,
|
|
566
694
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
|
|
567
695
|
var _b;
|
|
568
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
569
696
|
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
570
697
|
if (!data)
|
|
571
698
|
return;
|
|
@@ -580,7 +707,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
580
707
|
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
581
708
|
currentPage: data.currentPage,
|
|
582
709
|
abortSignal,
|
|
583
|
-
adminUser:
|
|
710
|
+
adminUser: adminUser,
|
|
584
711
|
emit,
|
|
585
712
|
failureLogMessage: "Agent response streaming failed",
|
|
586
713
|
abortLogMessage: "Agent response streaming aborted by the client",
|
|
@@ -594,7 +721,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
594
721
|
target: 'upload',
|
|
595
722
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) {
|
|
596
723
|
var _b;
|
|
597
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
598
724
|
const req = _raw_express_req;
|
|
599
725
|
const audioAdapter = this.options.audioAdapter;
|
|
600
726
|
if (!audioAdapter) {
|
|
@@ -660,7 +786,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
660
786
|
userTimeZone: (_b = data.timeZone) !== null && _b !== void 0 ? _b : 'UTC',
|
|
661
787
|
currentPage,
|
|
662
788
|
abortSignal,
|
|
663
|
-
adminUser:
|
|
789
|
+
adminUser: adminUser,
|
|
664
790
|
emit: (event) => __awaiter(this, void 0, void 0, function* () {
|
|
665
791
|
if (event.type === "tool-call") {
|
|
666
792
|
yield emit(event);
|
|
@@ -762,11 +888,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
762
888
|
path: `/agent/get-sessions`,
|
|
763
889
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
764
890
|
var _b;
|
|
765
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
766
891
|
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
767
892
|
if (!data)
|
|
768
893
|
return;
|
|
769
|
-
const userId =
|
|
894
|
+
const userId = adminUser.pk;
|
|
770
895
|
const limit = (_b = data.limit) !== null && _b !== void 0 ? _b : 20;
|
|
771
896
|
const sessions = yield this.adminforth.resource(this.options.sessionResource.resourceId).list([Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]);
|
|
772
897
|
return {
|
|
@@ -782,13 +907,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
782
907
|
method: 'POST',
|
|
783
908
|
path: `/agent/get-session-info`,
|
|
784
909
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
785
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
786
910
|
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
787
911
|
if (!parsedBody.success) {
|
|
788
912
|
response.setStatus(422, parsedBody.error.message);
|
|
789
913
|
return;
|
|
790
914
|
}
|
|
791
|
-
const userId =
|
|
915
|
+
const userId = adminUser.pk;
|
|
792
916
|
const sessionId = parsedBody.data.sessionId;
|
|
793
917
|
const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
|
|
794
918
|
if (!session) {
|
|
@@ -831,12 +955,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
831
955
|
method: 'POST',
|
|
832
956
|
path: `/agent/create-session`,
|
|
833
957
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
834
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
835
958
|
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
836
959
|
if (!data)
|
|
837
960
|
return;
|
|
838
961
|
const triggerMessage = data.triggerMessage;
|
|
839
|
-
const userId =
|
|
962
|
+
const userId = adminUser.pk;
|
|
840
963
|
const title = (triggerMessage === null || triggerMessage === void 0 ? void 0 : triggerMessage.slice(0, 40)) || "New Session";
|
|
841
964
|
const newSession = {
|
|
842
965
|
[this.options.sessionResource.idField]: randomUUID(),
|
|
@@ -856,12 +979,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
856
979
|
method: 'POST',
|
|
857
980
|
path: `/agent/delete-session`,
|
|
858
981
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
859
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
860
982
|
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
861
983
|
if (!data)
|
|
862
984
|
return;
|
|
863
985
|
const sessionId = data.sessionId;
|
|
864
|
-
const userId =
|
|
986
|
+
const userId = adminUser.pk;
|
|
865
987
|
const session = yield this.adminforth.resource(this.options.sessionResource.resourceId).get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
|
|
866
988
|
if (!session) {
|
|
867
989
|
return {
|
package/dist/types.d.ts
CHANGED
|
@@ -90,5 +90,9 @@ export interface PluginOptions extends PluginsCommonOptions {
|
|
|
90
90
|
* Falls back to an in-memory MemorySaver when omitted.
|
|
91
91
|
*/
|
|
92
92
|
checkpointResource?: ICheckpointResource;
|
|
93
|
+
/**
|
|
94
|
+
* Optional field for storing external chat IDs.
|
|
95
|
+
*/
|
|
96
|
+
chatExternalIdsField?: string;
|
|
93
97
|
}
|
|
94
98
|
export {};
|
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
ChatSurfaceAdapter,
|
|
5
5
|
ChatSurfaceEventSink,
|
|
6
6
|
ChatSurfaceIncomingMessage,
|
|
7
|
+
IAdminForthEndpointHandlerInput,
|
|
7
8
|
IAdminForth,
|
|
8
9
|
IHttpServer,
|
|
9
10
|
} from "adminforth";
|
|
@@ -36,6 +37,24 @@ type MulterFile = {
|
|
|
36
37
|
|
|
37
38
|
type ExpressMulterRequest = { file?: MulterFile };
|
|
38
39
|
|
|
40
|
+
type ChatSurfaceConnectAction = {
|
|
41
|
+
type: "url";
|
|
42
|
+
label: string;
|
|
43
|
+
url: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type ChatSurfaceAdapterWithConnectAction = ChatSurfaceAdapter & {
|
|
47
|
+
createConnectAction?(input: {
|
|
48
|
+
token: string;
|
|
49
|
+
}): ChatSurfaceConnectAction | Promise<ChatSurfaceConnectAction>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
type ChatSurfaceLinkTokenPayload = {
|
|
53
|
+
surface: string;
|
|
54
|
+
adminUserId: AdminUser["pk"];
|
|
55
|
+
expiresAt: number;
|
|
56
|
+
};
|
|
57
|
+
|
|
39
58
|
type AgentTurnRunInput = {
|
|
40
59
|
prompt: string;
|
|
41
60
|
sessionId: string;
|
|
@@ -91,6 +110,8 @@ const createSessionBodySchema = z.object({
|
|
|
91
110
|
|
|
92
111
|
const VEGA_LITE_FENCE_START = "```vega-lite";
|
|
93
112
|
const COMPLETE_VEGA_LITE_BLOCK_RE = /```vega-lite[\s\S]*?```/;
|
|
113
|
+
const DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD = "externalUserId";
|
|
114
|
+
const CHAT_SURFACE_LINK_TOKEN_TTL_MS = 60 * 1000;
|
|
94
115
|
|
|
95
116
|
function isAbortError(error: unknown): boolean {
|
|
96
117
|
return (
|
|
@@ -107,18 +128,12 @@ function getErrorMessage(error: unknown): string {
|
|
|
107
128
|
return error instanceof Error ? error.message : String(error);
|
|
108
129
|
}
|
|
109
130
|
|
|
110
|
-
function requireAdminUser(adminUser: AdminUser | undefined): AdminUser {
|
|
111
|
-
if (!adminUser) {
|
|
112
|
-
throw new Error("AdminForth Agent endpoint requires an authenticated admin user");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return adminUser;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
131
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
119
132
|
options: PluginOptions;
|
|
120
133
|
agentSystemPromptPromise: Promise<string>;
|
|
121
134
|
private checkpointer: BaseCheckpointSaver | null = null;
|
|
135
|
+
private chatSurfaceLinkTokens = new Map<string, ChatSurfaceLinkTokenPayload>();
|
|
136
|
+
private chatSurfaceSettingsPageRegistered = false;
|
|
122
137
|
private parseBody<T>(
|
|
123
138
|
schema: z.ZodType<T>,
|
|
124
139
|
body: unknown,
|
|
@@ -215,6 +230,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
215
230
|
].filter((resourceId): resourceId is string => Boolean(resourceId));
|
|
216
231
|
}
|
|
217
232
|
|
|
233
|
+
private getChatSurfaceConnectActionAdapters() {
|
|
234
|
+
return (this.options.chatSurfaceAdapters ?? [])
|
|
235
|
+
.map((adapter) => adapter as ChatSurfaceAdapterWithConnectAction)
|
|
236
|
+
.filter((adapter) => adapter.createConnectAction);
|
|
237
|
+
}
|
|
238
|
+
|
|
218
239
|
constructor(options: PluginOptions) {
|
|
219
240
|
super(options, import.meta.url);
|
|
220
241
|
this.options = options;
|
|
@@ -242,6 +263,19 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
242
263
|
hasAudioAdapter: Boolean(this.options.audioAdapter),
|
|
243
264
|
}
|
|
244
265
|
});
|
|
266
|
+
if (this.getChatSurfaceConnectActionAdapters().length && !this.chatSurfaceSettingsPageRegistered) {
|
|
267
|
+
if (!this.adminforth.config.auth!.userMenuSettingsPages) {
|
|
268
|
+
this.adminforth.config.auth!.userMenuSettingsPages = [];
|
|
269
|
+
}
|
|
270
|
+
this.adminforth.config.auth!.userMenuSettingsPages.push({
|
|
271
|
+
icon: "flowbite:link-outline",
|
|
272
|
+
pageLabel: "Chat Surfaces",
|
|
273
|
+
slug: "chat-surfaces",
|
|
274
|
+
component: this.componentPath("ChatSurfaceSettings.vue"),
|
|
275
|
+
isVisible: () => true,
|
|
276
|
+
});
|
|
277
|
+
this.chatSurfaceSettingsPageRegistered = true;
|
|
278
|
+
}
|
|
245
279
|
if (!this.adminforth.config.customization.customHeadItems) {
|
|
246
280
|
this.adminforth.config.customization.customHeadItems = [];
|
|
247
281
|
}
|
|
@@ -576,17 +610,90 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
576
610
|
};
|
|
577
611
|
}
|
|
578
612
|
|
|
613
|
+
private createChatSurfaceLinkToken(surface: string, adminUser: AdminUser) {
|
|
614
|
+
for (const [token, payload] of this.chatSurfaceLinkTokens) {
|
|
615
|
+
if (payload.expiresAt <= Date.now()) {
|
|
616
|
+
this.chatSurfaceLinkTokens.delete(token);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const token = randomUUID();
|
|
621
|
+
this.chatSurfaceLinkTokens.set(token, {
|
|
622
|
+
surface,
|
|
623
|
+
adminUserId: adminUser.pk,
|
|
624
|
+
expiresAt: Date.now() + CHAT_SURFACE_LINK_TOKEN_TTL_MS,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
return token;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private consumeChatSurfaceLinkToken(surface: string, token: string) {
|
|
631
|
+
const payload = this.chatSurfaceLinkTokens.get(token);
|
|
632
|
+
this.chatSurfaceLinkTokens.delete(token);
|
|
633
|
+
|
|
634
|
+
if (!payload || payload.surface !== surface || payload.expiresAt <= Date.now()) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return payload;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
private async handleChatSurfaceLink(
|
|
642
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
643
|
+
sink: ChatSurfaceEventSink,
|
|
644
|
+
) {
|
|
645
|
+
if (typeof incoming.metadata?.startPayload !== "string") {
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const payload = this.consumeChatSurfaceLinkToken(incoming.surface, incoming.metadata.startPayload);
|
|
650
|
+
if (!payload) {
|
|
651
|
+
await sink.emit({
|
|
652
|
+
type: "error",
|
|
653
|
+
message: "This chat surface link is expired or invalid. Please start linking again from AdminForth.",
|
|
654
|
+
});
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
658
|
+
const authResourceId = this.adminforth.config.auth!.usersResourceId!;
|
|
659
|
+
const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
|
|
660
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
|
|
661
|
+
const adminUserRecord = await this.adminforth.resource(authResourceId).get([
|
|
662
|
+
Filters.EQ(primaryKeyField, payload.adminUserId),
|
|
663
|
+
]);
|
|
664
|
+
|
|
665
|
+
await this.adminforth.resource(authResourceId).update(payload.adminUserId, {
|
|
666
|
+
[externalUserIdField]: {
|
|
667
|
+
...(adminUserRecord[externalUserIdField] ?? {}),
|
|
668
|
+
[incoming.surface]: incoming.externalUserId,
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
await sink.emit({
|
|
672
|
+
type: "done",
|
|
673
|
+
text: `${incoming.surface} account connected to AdminForth.`,
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
|
|
579
679
|
private async handleChatSurfaceMessage(
|
|
580
680
|
adapter: ChatSurfaceAdapter,
|
|
581
681
|
incoming: ChatSurfaceIncomingMessage,
|
|
582
682
|
sink: ChatSurfaceEventSink,
|
|
583
683
|
) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
});
|
|
684
|
+
if (await this.handleChatSurfaceLink(incoming, sink)) {
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
588
687
|
|
|
589
|
-
|
|
688
|
+
const authResourceId = this.adminforth.config.auth!.usersResourceId!;
|
|
689
|
+
const authResource = this.adminforth.config.resources.find((resource) => resource.resourceId === authResourceId)!;
|
|
690
|
+
const primaryKeyField = authResource.columns.find((column) => column.primaryKey)!.name!;
|
|
691
|
+
const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
692
|
+
const adminUserRecord = (
|
|
693
|
+
await this.adminforth.resource(authResourceId).list(Filters.IS_NOT_EMPTY(externalUserIdField))
|
|
694
|
+
).find((user) => user[externalUserIdField]?.[adapter.name] === incoming.externalUserId);
|
|
695
|
+
|
|
696
|
+
if (!adminUserRecord) {
|
|
590
697
|
await sink.emit({
|
|
591
698
|
type: "error",
|
|
592
699
|
message: "This chat account is not authorized to use AdminForth Agent.",
|
|
@@ -594,6 +701,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
594
701
|
return;
|
|
595
702
|
}
|
|
596
703
|
|
|
704
|
+
const adminUser = {
|
|
705
|
+
pk: adminUserRecord[primaryKeyField],
|
|
706
|
+
username: adminUserRecord[this.adminforth.config.auth!.usernameField],
|
|
707
|
+
dbUser: adminUserRecord,
|
|
708
|
+
};
|
|
709
|
+
|
|
597
710
|
await this.handleTurn({
|
|
598
711
|
prompt: incoming.prompt,
|
|
599
712
|
sessionId: await this.getOrCreateChatSurfaceSession(incoming, adminUser),
|
|
@@ -607,12 +720,66 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
607
720
|
}
|
|
608
721
|
|
|
609
722
|
setupEndpoints(server: IHttpServer) {
|
|
723
|
+
if (this.getChatSurfaceConnectActionAdapters().length) {
|
|
724
|
+
server.endpoint({
|
|
725
|
+
method: "POST",
|
|
726
|
+
path: "/agent/surfaces/connectable",
|
|
727
|
+
handler: async ({ adminUser }) => {
|
|
728
|
+
const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
729
|
+
const externalIds = adminUser.dbUser[externalUserIdField] ?? {};
|
|
730
|
+
|
|
731
|
+
return {
|
|
732
|
+
surfaces: this.getChatSurfaceConnectActionAdapters().map((adapter) => ({
|
|
733
|
+
name: adapter.name,
|
|
734
|
+
externalUserId: externalIds[adapter.name] ?? null,
|
|
735
|
+
})),
|
|
736
|
+
};
|
|
737
|
+
},
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
610
741
|
for (const adapter of this.options.chatSurfaceAdapters ?? []) {
|
|
742
|
+
const connectActionAdapter = adapter as ChatSurfaceAdapterWithConnectAction;
|
|
743
|
+
if (connectActionAdapter.createConnectAction) {
|
|
744
|
+
server.endpoint({
|
|
745
|
+
method: "POST",
|
|
746
|
+
path: `/agent/surface/${adapter.name}/connect-action`,
|
|
747
|
+
handler: async ({ adminUser }) => {
|
|
748
|
+
const token = this.createChatSurfaceLinkToken(adapter.name, adminUser);
|
|
749
|
+
const action = await connectActionAdapter.createConnectAction!({ token });
|
|
750
|
+
|
|
751
|
+
return {
|
|
752
|
+
action,
|
|
753
|
+
};
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
server.endpoint({
|
|
757
|
+
method: "POST",
|
|
758
|
+
path: `/agent/surface/${adapter.name}/disconnect`,
|
|
759
|
+
handler: async ({ adminUser }) => {
|
|
760
|
+
const externalUserIdField = this.options.chatExternalIdsField ?? DEFAULT_ADMIN_USER_EXTERNAL_USER_ID_FIELD;
|
|
761
|
+
const externalIds = {
|
|
762
|
+
...(adminUser.dbUser[externalUserIdField] ?? {}),
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
delete externalIds[adapter.name];
|
|
766
|
+
|
|
767
|
+
await this.adminforth.resource(this.adminforth.config.auth!.usersResourceId!).update(adminUser.pk, {
|
|
768
|
+
[externalUserIdField]: externalIds,
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
return {
|
|
772
|
+
ok: true,
|
|
773
|
+
};
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
611
778
|
server.endpoint({
|
|
612
779
|
method: "POST",
|
|
613
780
|
noAuth: true,
|
|
614
781
|
path: `/agent/surface/${adapter.name}/webhook`,
|
|
615
|
-
handler: async (ctx) => {
|
|
782
|
+
handler: async (ctx: IAdminForthEndpointHandlerInput) => {
|
|
616
783
|
const surfaceContext = {
|
|
617
784
|
body: ctx.body,
|
|
618
785
|
headers: ctx.headers,
|
|
@@ -641,8 +808,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
641
808
|
method: 'POST',
|
|
642
809
|
path: `/agent/get-placeholder-messages`,
|
|
643
810
|
handler: async ({ headers, adminUser }) => {
|
|
644
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
645
|
-
|
|
646
811
|
if (!this.options.placeholderMessages) {
|
|
647
812
|
return {
|
|
648
813
|
messages: [],
|
|
@@ -650,7 +815,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
650
815
|
}
|
|
651
816
|
|
|
652
817
|
const messages = await this.options.placeholderMessages({
|
|
653
|
-
adminUser:
|
|
818
|
+
adminUser: adminUser,
|
|
654
819
|
headers,
|
|
655
820
|
});
|
|
656
821
|
|
|
@@ -663,7 +828,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
663
828
|
method: 'POST',
|
|
664
829
|
path: `/agent/response`,
|
|
665
830
|
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
666
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
667
831
|
const data = this.parseBody(agentResponseBodySchema, body, response);
|
|
668
832
|
if (!data) return;
|
|
669
833
|
const emit = createSseEventEmitter(_raw_express_res, {
|
|
@@ -678,7 +842,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
678
842
|
userTimeZone: data.timeZone ?? 'UTC',
|
|
679
843
|
currentPage: data.currentPage,
|
|
680
844
|
abortSignal,
|
|
681
|
-
adminUser:
|
|
845
|
+
adminUser: adminUser,
|
|
682
846
|
emit,
|
|
683
847
|
failureLogMessage: "Agent response streaming failed",
|
|
684
848
|
abortLogMessage: "Agent response streaming aborted by the client",
|
|
@@ -691,7 +855,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
691
855
|
path: `/agent/speech-response`,
|
|
692
856
|
target: 'upload',
|
|
693
857
|
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
694
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
695
858
|
const req = _raw_express_req as ExpressMulterRequest;
|
|
696
859
|
const audioAdapter = this.options.audioAdapter;
|
|
697
860
|
if (!audioAdapter) {
|
|
@@ -761,7 +924,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
761
924
|
userTimeZone: data.timeZone ?? 'UTC',
|
|
762
925
|
currentPage,
|
|
763
926
|
abortSignal,
|
|
764
|
-
adminUser:
|
|
927
|
+
adminUser: adminUser,
|
|
765
928
|
emit: async (event) => {
|
|
766
929
|
if (event.type === "tool-call") {
|
|
767
930
|
await emit(event);
|
|
@@ -871,10 +1034,9 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
871
1034
|
method: 'POST',
|
|
872
1035
|
path: `/agent/get-sessions`,
|
|
873
1036
|
handler: async ({body, adminUser, response }) => {
|
|
874
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
875
1037
|
const data = this.parseBody(getSessionsBodySchema, body, response);
|
|
876
1038
|
if (!data) return;
|
|
877
|
-
const userId =
|
|
1039
|
+
const userId = adminUser.pk;
|
|
878
1040
|
const limit = data.limit ?? 20;
|
|
879
1041
|
const sessions = await this.adminforth.resource(this.options.sessionResource.resourceId).list(
|
|
880
1042
|
[Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]
|
|
@@ -892,13 +1054,12 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
892
1054
|
method: 'POST',
|
|
893
1055
|
path: `/agent/get-session-info`,
|
|
894
1056
|
handler: async ({body, adminUser, response }) => {
|
|
895
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
896
1057
|
const parsedBody = sessionIdBodySchema.safeParse(body);
|
|
897
1058
|
if (!parsedBody.success) {
|
|
898
1059
|
response.setStatus(422, parsedBody.error.message);
|
|
899
1060
|
return;
|
|
900
1061
|
}
|
|
901
|
-
const userId =
|
|
1062
|
+
const userId = adminUser.pk;
|
|
902
1063
|
const sessionId = parsedBody.data.sessionId;
|
|
903
1064
|
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
904
1065
|
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
@@ -943,11 +1104,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
943
1104
|
method: 'POST',
|
|
944
1105
|
path: `/agent/create-session`,
|
|
945
1106
|
handler: async ({body, adminUser, response }) => {
|
|
946
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
947
1107
|
const data = this.parseBody(createSessionBodySchema, body, response);
|
|
948
1108
|
if (!data) return;
|
|
949
1109
|
const triggerMessage = data.triggerMessage;
|
|
950
|
-
const userId =
|
|
1110
|
+
const userId = adminUser.pk;
|
|
951
1111
|
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
952
1112
|
const newSession = {
|
|
953
1113
|
[this.options.sessionResource.idField]: randomUUID(),
|
|
@@ -967,11 +1127,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
967
1127
|
method: 'POST',
|
|
968
1128
|
path: `/agent/delete-session`,
|
|
969
1129
|
handler: async ({body, adminUser, response }) => {
|
|
970
|
-
const currentAdminUser = requireAdminUser(adminUser);
|
|
971
1130
|
const data = this.parseBody(sessionIdBodySchema, body, response);
|
|
972
1131
|
if (!data) return;
|
|
973
1132
|
const sessionId = data.sessionId;
|
|
974
|
-
const userId =
|
|
1133
|
+
const userId = adminUser.pk;
|
|
975
1134
|
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
|
|
976
1135
|
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
|
|
977
1136
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@langchain/core": "^1.1.40",
|
|
34
34
|
"@langchain/langgraph": "^1.2.8",
|
|
35
35
|
"@langchain/langgraph-checkpoint": "^1.0.1",
|
|
36
|
-
"adminforth": "
|
|
36
|
+
"adminforth": "^2.60.0",
|
|
37
37
|
"dayjs": "^1.11.20",
|
|
38
38
|
"langchain": "^1.3.3",
|
|
39
39
|
"multer": "^2.1.1",
|
package/types.ts
CHANGED
|
@@ -110,4 +110,9 @@ export interface PluginOptions extends PluginsCommonOptions {
|
|
|
110
110
|
* Falls back to an in-memory MemorySaver when omitted.
|
|
111
111
|
*/
|
|
112
112
|
checkpointResource?: ICheckpointResource;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Optional field for storing external chat IDs.
|
|
116
|
+
*/
|
|
117
|
+
chatExternalIdsField?: string;
|
|
113
118
|
}
|