@adminforth/agent 1.44.2 → 1.45.1
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/agentTurnService.ts +526 -0
- package/build.log +3 -2
- package/chatSurfaceService.ts +189 -0
- package/custom/ChatSurfaceSettings.vue +125 -0
- package/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/agentTurnService.d.ts +70 -0
- package/dist/agentTurnService.js +453 -0
- package/dist/chatSurfaceService.d.ts +29 -0
- package/dist/chatSurfaceService.js +142 -0
- package/dist/custom/ChatSurfaceSettings.vue +125 -0
- package/dist/custom/incremark_code_renderers/incremarkRenderer.ts +5 -5
- package/dist/endpoints/chatSurfaces.d.ts +3 -0
- package/dist/endpoints/chatSurfaces.js +91 -0
- package/dist/endpoints/context.d.ts +30 -0
- package/dist/endpoints/context.js +1 -0
- package/dist/endpoints/core.d.ts +3 -0
- package/dist/endpoints/core.js +106 -0
- package/dist/endpoints/sessions.d.ts +3 -0
- package/dist/endpoints/sessions.js +177 -0
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +9 -0
- package/dist/index.d.ts +5 -42
- package/dist/index.js +50 -808
- package/dist/sessionStore.d.ts +19 -0
- package/dist/sessionStore.js +83 -0
- package/dist/types.d.ts +4 -0
- package/endpoints/chatSurfaces.ts +93 -0
- package/endpoints/context.ts +66 -0
- package/endpoints/core.ts +113 -0
- package/endpoints/sessions.ts +183 -0
- package/errors.ts +10 -0
- package/index.ts +60 -907
- package/package.json +2 -2
- package/sessionStore.ts +94 -0
- package/types.ts +5 -0
- package/agentResponseEvents.ts +0 -1
- package/dist/agentResponseEvents.d.ts +0 -1
- package/dist/agentResponseEvents.js +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AdminUser, IAdminForth } from "adminforth";
|
|
2
|
+
import type { ChatSurfaceIncomingMessage } from "adminforth";
|
|
3
|
+
import type { PreviousUserMessage } from "./agent/languageDetect.js";
|
|
4
|
+
import type { PluginOptions } from "./types.js";
|
|
5
|
+
export declare const AGENT_SYSTEM_TURN_PROMPT = "__adminforth_system_message__";
|
|
6
|
+
export declare class AgentSessionStore {
|
|
7
|
+
private getAdminforth;
|
|
8
|
+
private options;
|
|
9
|
+
constructor(getAdminforth: () => IAdminForth, options: PluginOptions);
|
|
10
|
+
createNewTurn(sessionId: string, prompt: string, response?: string): Promise<any>;
|
|
11
|
+
createSystemTurn(sessionId: string, systemMessage: string): Promise<any>;
|
|
12
|
+
getSessionTurns(sessionId: string): Promise<{
|
|
13
|
+
prompt: any;
|
|
14
|
+
response: any;
|
|
15
|
+
}[]>;
|
|
16
|
+
getPreviousUserMessages(sessionId: string): Promise<PreviousUserMessage[]>;
|
|
17
|
+
getChatSurfaceSessionId(incoming: ChatSurfaceIncomingMessage): string;
|
|
18
|
+
getOrCreateChatSurfaceSession(incoming: ChatSurfaceIncomingMessage, adminUser: AdminUser): Promise<string>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
import { Filters, Sorts } from "adminforth";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
export const AGENT_SYSTEM_TURN_PROMPT = "__adminforth_system_message__";
|
|
13
|
+
export class AgentSessionStore {
|
|
14
|
+
constructor(getAdminforth, options) {
|
|
15
|
+
this.getAdminforth = getAdminforth;
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
createNewTurn(sessionId, prompt, response) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const turnId = randomUUID();
|
|
21
|
+
const turnRecord = {
|
|
22
|
+
[this.options.turnResource.idField]: turnId,
|
|
23
|
+
[this.options.turnResource.sessionIdField]: sessionId,
|
|
24
|
+
[this.options.turnResource.promptField]: prompt,
|
|
25
|
+
[this.options.turnResource.responseField]: response !== null && response !== void 0 ? response : "not_finished",
|
|
26
|
+
};
|
|
27
|
+
const newTurn = yield this.getAdminforth().resource(this.options.turnResource.resourceId).create(turnRecord);
|
|
28
|
+
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
createSystemTurn(sessionId, systemMessage) {
|
|
32
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
const turnId = randomUUID();
|
|
34
|
+
const turnRecord = {
|
|
35
|
+
[this.options.turnResource.idField]: turnId,
|
|
36
|
+
[this.options.turnResource.sessionIdField]: sessionId,
|
|
37
|
+
[this.options.turnResource.promptField]: AGENT_SYSTEM_TURN_PROMPT,
|
|
38
|
+
[this.options.turnResource.responseField]: systemMessage,
|
|
39
|
+
};
|
|
40
|
+
const newTurn = yield this.getAdminforth().resource(this.options.turnResource.resourceId).create(turnRecord);
|
|
41
|
+
return newTurn.createdRecord[this.options.turnResource.idField];
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
getSessionTurns(sessionId) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], undefined, undefined, [Sorts.ASC(this.options.turnResource.createdAtField)]);
|
|
47
|
+
return turns.map(turn => ({
|
|
48
|
+
prompt: turn[this.options.turnResource.promptField],
|
|
49
|
+
response: turn[this.options.turnResource.responseField],
|
|
50
|
+
}));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
getPreviousUserMessages(sessionId) {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
const turns = yield this.getAdminforth().resource(this.options.turnResource.resourceId).list([Filters.EQ(this.options.turnResource.sessionIdField, sessionId)], 2, undefined, [Sorts.DESC(this.options.turnResource.createdAtField)]);
|
|
56
|
+
return turns
|
|
57
|
+
.reverse()
|
|
58
|
+
.filter((turn) => turn[this.options.turnResource.promptField] !== AGENT_SYSTEM_TURN_PROMPT)
|
|
59
|
+
.map((turn) => ({
|
|
60
|
+
text: turn[this.options.turnResource.promptField],
|
|
61
|
+
}));
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
getChatSurfaceSessionId(incoming) {
|
|
65
|
+
return `${incoming.surface}:${incoming.externalConversationId}`;
|
|
66
|
+
}
|
|
67
|
+
getOrCreateChatSurfaceSession(incoming, adminUser) {
|
|
68
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
69
|
+
const sessionId = this.getChatSurfaceSessionId(incoming);
|
|
70
|
+
const sessionResource = this.getAdminforth().resource(this.options.sessionResource.resourceId);
|
|
71
|
+
const session = yield sessionResource.get([Filters.EQ(this.options.sessionResource.idField, sessionId)]);
|
|
72
|
+
if (session) {
|
|
73
|
+
return sessionId;
|
|
74
|
+
}
|
|
75
|
+
yield sessionResource.create({
|
|
76
|
+
[this.options.sessionResource.idField]: sessionId,
|
|
77
|
+
[this.options.sessionResource.titleField]: incoming.prompt.slice(0, 40) || "New Session",
|
|
78
|
+
[this.options.sessionResource.askerIdField]: adminUser.pk,
|
|
79
|
+
});
|
|
80
|
+
return sessionId;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
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 {};
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdminUser,
|
|
3
|
+
ChatSurfaceAdapter,
|
|
4
|
+
ChatSurfaceEventSink,
|
|
5
|
+
ChatSurfaceIncomingMessage,
|
|
6
|
+
IAdminForth,
|
|
7
|
+
} from "adminforth";
|
|
8
|
+
import type { ZodType } from "zod";
|
|
9
|
+
import type {
|
|
10
|
+
HandleSpeechTurnInput,
|
|
11
|
+
HandleTurnInput,
|
|
12
|
+
RunAndPersistAgentResponseInput,
|
|
13
|
+
RunAndPersistAgentResponseResult,
|
|
14
|
+
} from "../agentTurnService.js";
|
|
15
|
+
import type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
|
|
16
|
+
import type { PluginOptions } from "../types.js";
|
|
17
|
+
|
|
18
|
+
export type { ChatSurfaceAdapterWithConnectAction } from "../chatSurfaceService.js";
|
|
19
|
+
|
|
20
|
+
export type EndpointResponse = {
|
|
21
|
+
setStatus: (code: number, message: string) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type SessionTurn = {
|
|
25
|
+
prompt: string;
|
|
26
|
+
response: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type AgentEndpointsContext = {
|
|
30
|
+
adminforth: IAdminForth;
|
|
31
|
+
options: PluginOptions;
|
|
32
|
+
parseBody<T>(schema: ZodType<T>, body: unknown, response: EndpointResponse): T | null;
|
|
33
|
+
handleTurn(input: HandleTurnInput): Promise<RunAndPersistAgentResponseResult>;
|
|
34
|
+
handleSpeechTurn(input: HandleSpeechTurnInput): Promise<RunAndPersistAgentResponseResult | null>;
|
|
35
|
+
runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput): Promise<RunAndPersistAgentResponseResult>;
|
|
36
|
+
getSessionTurns(sessionId: string): Promise<SessionTurn[]>;
|
|
37
|
+
createNewTurn(sessionId: string, prompt: string, response?: string): Promise<string>;
|
|
38
|
+
createSystemTurn(sessionId: string, systemMessage: string): Promise<string>;
|
|
39
|
+
getChatSurfaceConnectActionAdapters(): ChatSurfaceAdapterWithConnectAction[];
|
|
40
|
+
createChatSurfaceLinkToken(surface: string, adminUser: AdminUser): string;
|
|
41
|
+
handleChatSurfaceMessage(
|
|
42
|
+
adapter: ChatSurfaceAdapter,
|
|
43
|
+
incoming: ChatSurfaceIncomingMessage,
|
|
44
|
+
sink: ChatSurfaceEventSink,
|
|
45
|
+
): Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type CoreEndpointsContext = Pick<
|
|
49
|
+
AgentEndpointsContext,
|
|
50
|
+
"options" | "parseBody" | "handleTurn" | "handleSpeechTurn"
|
|
51
|
+
>;
|
|
52
|
+
|
|
53
|
+
export type SessionEndpointsContext = Pick<
|
|
54
|
+
AgentEndpointsContext,
|
|
55
|
+
"adminforth" | "options" | "parseBody" | "getSessionTurns" | "createNewTurn"
|
|
56
|
+
| "createSystemTurn"
|
|
57
|
+
>;
|
|
58
|
+
|
|
59
|
+
export type ChatSurfaceEndpointsContext = Pick<
|
|
60
|
+
AgentEndpointsContext,
|
|
61
|
+
| "adminforth"
|
|
62
|
+
| "options"
|
|
63
|
+
| "getChatSurfaceConnectActionAdapters"
|
|
64
|
+
| "createChatSurfaceLinkToken"
|
|
65
|
+
| "handleChatSurfaceMessage"
|
|
66
|
+
>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { IHttpServer } from "adminforth";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { createSseEventEmitter } from "../surfaces/web-sse/createSseEventEmitter.js";
|
|
4
|
+
import type { CurrentPageContext } from "../agent/tools/getUserLocation.js";
|
|
5
|
+
import type { CoreEndpointsContext } from "./context.js";
|
|
6
|
+
|
|
7
|
+
type MulterFile = {
|
|
8
|
+
buffer: Buffer;
|
|
9
|
+
originalname: string;
|
|
10
|
+
mimetype: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type ExpressMulterRequest = { file?: MulterFile };
|
|
14
|
+
|
|
15
|
+
const agentResponseBodySchema = z.object({
|
|
16
|
+
message: z.string(),
|
|
17
|
+
sessionId: z.string(),
|
|
18
|
+
mode: z.string().nullish(),
|
|
19
|
+
timeZone: z.string().optional(),
|
|
20
|
+
currentPage: z.custom<CurrentPageContext>().optional(),
|
|
21
|
+
}).strict();
|
|
22
|
+
|
|
23
|
+
const agentSpeechResponseBodySchema = agentResponseBodySchema.omit({ message: true });
|
|
24
|
+
|
|
25
|
+
export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServer) {
|
|
26
|
+
server.endpoint({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: `/agent/get-placeholder-messages`,
|
|
29
|
+
handler: async ({ headers, adminUser }) => {
|
|
30
|
+
if (!ctx.options.placeholderMessages) {
|
|
31
|
+
return {
|
|
32
|
+
messages: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const messages = await ctx.options.placeholderMessages({
|
|
37
|
+
adminUser: adminUser!,
|
|
38
|
+
headers,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
messages,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
server.endpoint({
|
|
48
|
+
method: 'POST',
|
|
49
|
+
path: `/agent/response`,
|
|
50
|
+
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
51
|
+
const data = ctx.parseBody(agentResponseBodySchema, body, response);
|
|
52
|
+
if (!data) return;
|
|
53
|
+
const emit = createSseEventEmitter(_raw_express_res, {
|
|
54
|
+
vercelAiUiMessageStream: true,
|
|
55
|
+
closeActiveBlockOnToolStart: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await ctx.handleTurn({
|
|
59
|
+
prompt: data.message,
|
|
60
|
+
sessionId: data.sessionId,
|
|
61
|
+
modeName: data.mode,
|
|
62
|
+
userTimeZone: data.timeZone ?? 'UTC',
|
|
63
|
+
currentPage: data.currentPage,
|
|
64
|
+
abortSignal,
|
|
65
|
+
adminUser: adminUser!,
|
|
66
|
+
emit,
|
|
67
|
+
failureLogMessage: "Agent response streaming failed",
|
|
68
|
+
abortLogMessage: "Agent response streaming aborted by the client",
|
|
69
|
+
});
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
server.endpoint({
|
|
75
|
+
method: 'POST',
|
|
76
|
+
path: `/agent/speech-response`,
|
|
77
|
+
target: 'upload',
|
|
78
|
+
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
79
|
+
const req = _raw_express_req as ExpressMulterRequest;
|
|
80
|
+
const audioAdapter = ctx.options.audioAdapter;
|
|
81
|
+
if (!audioAdapter) {
|
|
82
|
+
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
83
|
+
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
84
|
+
}
|
|
85
|
+
const data = ctx.parseBody(agentSpeechResponseBodySchema, body, response);
|
|
86
|
+
if (!data) return;
|
|
87
|
+
if (!req.file) {
|
|
88
|
+
response.setStatus(400, "Audio file is required");
|
|
89
|
+
return { error: "Audio file is required" };
|
|
90
|
+
}
|
|
91
|
+
const emit = createSseEventEmitter(_raw_express_res);
|
|
92
|
+
|
|
93
|
+
await ctx.handleSpeechTurn({
|
|
94
|
+
audioAdapter,
|
|
95
|
+
audio: {
|
|
96
|
+
buffer: req.file.buffer,
|
|
97
|
+
filename: req.file.originalname,
|
|
98
|
+
mimeType: req.file.mimetype,
|
|
99
|
+
},
|
|
100
|
+
sessionId: data.sessionId,
|
|
101
|
+
modeName: data.mode,
|
|
102
|
+
userTimeZone: data.timeZone ?? 'UTC',
|
|
103
|
+
currentPage: data.currentPage,
|
|
104
|
+
abortSignal,
|
|
105
|
+
adminUser: adminUser!,
|
|
106
|
+
emit,
|
|
107
|
+
failureLogMessage: "Agent speech response failed",
|
|
108
|
+
abortLogMessage: "Agent speech response aborted by the client",
|
|
109
|
+
});
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { IHttpServer } from "adminforth";
|
|
2
|
+
import { Filters, Sorts } from "adminforth";
|
|
3
|
+
import { randomUUID } from "crypto";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { AGENT_SYSTEM_TURN_PROMPT } from "../sessionStore.js";
|
|
6
|
+
import type { SessionEndpointsContext } from "./context.js";
|
|
7
|
+
|
|
8
|
+
const addSystemMessageBodySchema = z.object({
|
|
9
|
+
sessionId: z.string(),
|
|
10
|
+
systemMessage: z.string(),
|
|
11
|
+
}).strict();
|
|
12
|
+
|
|
13
|
+
const getSessionsBodySchema = z.object({
|
|
14
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
15
|
+
}).strict();
|
|
16
|
+
|
|
17
|
+
const sessionIdBodySchema = z.object({
|
|
18
|
+
sessionId: z.string(),
|
|
19
|
+
}).strict();
|
|
20
|
+
|
|
21
|
+
const createSessionBodySchema = z.object({
|
|
22
|
+
triggerMessage: z.string().optional(),
|
|
23
|
+
}).strict();
|
|
24
|
+
|
|
25
|
+
export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHttpServer) {
|
|
26
|
+
server.endpoint({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: `/agent/get-sessions`,
|
|
29
|
+
handler: async ({body, adminUser, response }) => {
|
|
30
|
+
const data = ctx.parseBody(getSessionsBodySchema, body, response);
|
|
31
|
+
if (!data) return;
|
|
32
|
+
const userId = adminUser!.pk;
|
|
33
|
+
const limit = data.limit ?? 20;
|
|
34
|
+
const sessions = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).list(
|
|
35
|
+
[Filters.EQ(ctx.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(ctx.options.sessionResource.createdAtField)]
|
|
36
|
+
);
|
|
37
|
+
return {
|
|
38
|
+
sessions: sessions.map((session) => ({
|
|
39
|
+
sessionId: session[ctx.options.sessionResource.idField],
|
|
40
|
+
title: session[ctx.options.sessionResource.titleField],
|
|
41
|
+
timestamp: session[ctx.options.sessionResource.createdAtField],
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
server.endpoint({
|
|
48
|
+
method: 'POST',
|
|
49
|
+
path: `/agent/get-session-info`,
|
|
50
|
+
handler: async ({body, adminUser, response }) => {
|
|
51
|
+
const data = ctx.parseBody(sessionIdBodySchema, body, response);
|
|
52
|
+
if (!data) return;
|
|
53
|
+
const userId = adminUser!.pk;
|
|
54
|
+
const sessionId = data.sessionId;
|
|
55
|
+
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
56
|
+
[Filters.EQ(ctx.options.sessionResource.idField, sessionId)]
|
|
57
|
+
);
|
|
58
|
+
if (!session) {
|
|
59
|
+
return {
|
|
60
|
+
error: 'Session not found'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (session[ctx.options.sessionResource.askerIdField] !== userId) {
|
|
64
|
+
return {
|
|
65
|
+
error: 'Unauthorized'
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const turns = await ctx.getSessionTurns(sessionId);
|
|
69
|
+
return {
|
|
70
|
+
session: {
|
|
71
|
+
sessionId,
|
|
72
|
+
title: session[ctx.options.sessionResource.titleField],
|
|
73
|
+
timestamp: session[ctx.options.sessionResource.createdAtField],
|
|
74
|
+
messages: turns.flatMap(turn => {
|
|
75
|
+
const messages: Array<{ text: string; role: 'user' | 'assistant' | 'system' }> = [];
|
|
76
|
+
if (turn.prompt === AGENT_SYSTEM_TURN_PROMPT) {
|
|
77
|
+
messages.push({
|
|
78
|
+
text: turn.response,
|
|
79
|
+
role: 'system',
|
|
80
|
+
});
|
|
81
|
+
return messages;
|
|
82
|
+
}
|
|
83
|
+
if (turn.prompt) {
|
|
84
|
+
messages.push({
|
|
85
|
+
text: turn.prompt,
|
|
86
|
+
role: 'user',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (turn.response && turn.response !== "not_finished") {
|
|
90
|
+
messages.push({
|
|
91
|
+
text: turn.response,
|
|
92
|
+
role: 'assistant',
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return messages;
|
|
96
|
+
}),
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
server.endpoint({
|
|
103
|
+
method: 'POST',
|
|
104
|
+
path: `/agent/create-session`,
|
|
105
|
+
handler: async ({body, adminUser, response }) => {
|
|
106
|
+
const data = ctx.parseBody(createSessionBodySchema, body, response);
|
|
107
|
+
if (!data) return;
|
|
108
|
+
const triggerMessage = data.triggerMessage;
|
|
109
|
+
const userId = adminUser!.pk;
|
|
110
|
+
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
111
|
+
const newSession = {
|
|
112
|
+
[ctx.options.sessionResource.idField]: randomUUID(),
|
|
113
|
+
[ctx.options.sessionResource.titleField]: title,
|
|
114
|
+
[ctx.options.sessionResource.askerIdField]: userId,
|
|
115
|
+
};
|
|
116
|
+
const { createdRecord } = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).create(newSession);
|
|
117
|
+
return {
|
|
118
|
+
sessionId: createdRecord[ctx.options.sessionResource.idField],
|
|
119
|
+
title: createdRecord[ctx.options.sessionResource.titleField],
|
|
120
|
+
timestamp: createdRecord[ctx.options.sessionResource.createdAtField],
|
|
121
|
+
messages: []
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
server.endpoint({
|
|
127
|
+
method: 'POST',
|
|
128
|
+
path: `/agent/delete-session`,
|
|
129
|
+
handler: async ({body, adminUser, response }) => {
|
|
130
|
+
const data = ctx.parseBody(sessionIdBodySchema, body, response);
|
|
131
|
+
if (!data) return;
|
|
132
|
+
const sessionId = data.sessionId;
|
|
133
|
+
const userId = adminUser!.pk;
|
|
134
|
+
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
135
|
+
[Filters.EQ(ctx.options.sessionResource.idField, sessionId)]
|
|
136
|
+
);
|
|
137
|
+
if (!session) {
|
|
138
|
+
return {
|
|
139
|
+
error: 'Session not found'
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (session[ctx.options.sessionResource.askerIdField] !== userId) {
|
|
143
|
+
return {
|
|
144
|
+
error: 'Unauthorized'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).delete(sessionId);
|
|
148
|
+
const turns = await ctx.adminforth.resource(ctx.options.turnResource.resourceId).list(
|
|
149
|
+
[Filters.EQ(ctx.options.turnResource.sessionIdField, sessionId)]
|
|
150
|
+
);
|
|
151
|
+
await Promise.all(turns.map(turn => ctx.adminforth.resource(ctx.options.turnResource.resourceId).delete(turn[ctx.options.turnResource.idField])));
|
|
152
|
+
return {
|
|
153
|
+
ok: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
server.endpoint({
|
|
159
|
+
method: 'POST',
|
|
160
|
+
path: `/agent/add-system-message-to-turns`,
|
|
161
|
+
handler: async ({body, adminUser, response }) => {
|
|
162
|
+
const data = ctx.parseBody(addSystemMessageBodySchema, body, response);
|
|
163
|
+
if (!data) return;
|
|
164
|
+
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
165
|
+
[Filters.EQ(ctx.options.sessionResource.idField, data.sessionId)]
|
|
166
|
+
);
|
|
167
|
+
if (!session) {
|
|
168
|
+
return {
|
|
169
|
+
error: 'Session not found'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (session[ctx.options.sessionResource.askerIdField] !== adminUser!.pk) {
|
|
173
|
+
return {
|
|
174
|
+
error: 'Unauthorized'
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
await ctx.createSystemTurn(data.sessionId, data.systemMessage);
|
|
178
|
+
return {
|
|
179
|
+
ok: true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
package/errors.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function isAbortError(error: unknown): boolean {
|
|
2
|
+
return typeof error === "object" &&
|
|
3
|
+
error !== null &&
|
|
4
|
+
"name" in error &&
|
|
5
|
+
(error.name === "AbortError" || error.name === "APIUserAbortError");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getErrorMessage(error: unknown): string {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|