@adminforth/agent 2.0.8 → 2.0.10
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/apiBasedTools.ts +13 -3
- package/dist/apiBasedTools.js +10 -3
- package/dist/endpoints/context.d.ts +2 -7
- package/dist/endpoints/core.js +46 -11
- package/dist/endpoints/sessions.js +10 -15
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -9
- package/endpoints/context.ts +2 -8
- package/endpoints/core.ts +46 -8
- package/endpoints/sessions.ts +10 -10
- package/index.ts +0 -14
- package/package.json +3 -3
package/apiBasedTools.ts
CHANGED
|
@@ -635,6 +635,14 @@ function stringifyToolOutput(output: unknown): string {
|
|
|
635
635
|
return YAML.stringify(output === undefined ? EMPTY_HANDLER_RESPONSE_ERROR : output);
|
|
636
636
|
}
|
|
637
637
|
|
|
638
|
+
function attachDurationMs(output: unknown, durationMs: number): unknown {
|
|
639
|
+
if (output !== null && typeof output === 'object' && !Array.isArray(output)) {
|
|
640
|
+
return { ...output, durationMs };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return { result: output, durationMs };
|
|
644
|
+
}
|
|
645
|
+
|
|
638
646
|
export function prepareApiBasedTools(
|
|
639
647
|
adminforth: IAdminForth,
|
|
640
648
|
hiddenResourceIds: Iterable<string> = [],
|
|
@@ -666,11 +674,13 @@ export function prepareApiBasedTools(
|
|
|
666
674
|
input_schema: schema.request_schema,
|
|
667
675
|
agent: schema.agent,
|
|
668
676
|
call: async ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) => {
|
|
677
|
+
const startedAt = Date.now();
|
|
678
|
+
|
|
669
679
|
if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
|
|
670
|
-
return stringifyToolOutput({
|
|
680
|
+
return stringifyToolOutput(attachDurationMs({
|
|
671
681
|
error: 'RESOURCE_NOT_AVAILABLE',
|
|
672
682
|
message: 'This resource is not available to the agent.',
|
|
673
|
-
});
|
|
683
|
+
}, Date.now() - startedAt));
|
|
674
684
|
}
|
|
675
685
|
|
|
676
686
|
const output = await callOpenApiSchema({
|
|
@@ -693,7 +703,7 @@ export function prepareApiBasedTools(
|
|
|
693
703
|
userTimeZone,
|
|
694
704
|
});
|
|
695
705
|
|
|
696
|
-
return stringifyToolOutput(processedOutput);
|
|
706
|
+
return stringifyToolOutput(attachDurationMs(processedOutput, Date.now() - startedAt));
|
|
697
707
|
},
|
|
698
708
|
};
|
|
699
709
|
}
|
package/dist/apiBasedTools.js
CHANGED
|
@@ -432,6 +432,12 @@ function callOpenApiSchema(params) {
|
|
|
432
432
|
function stringifyToolOutput(output) {
|
|
433
433
|
return YAML.stringify(output === undefined ? EMPTY_HANDLER_RESPONSE_ERROR : output);
|
|
434
434
|
}
|
|
435
|
+
function attachDurationMs(output, durationMs) {
|
|
436
|
+
if (output !== null && typeof output === 'object' && !Array.isArray(output)) {
|
|
437
|
+
return Object.assign(Object.assign({}, output), { durationMs });
|
|
438
|
+
}
|
|
439
|
+
return { result: output, durationMs };
|
|
440
|
+
}
|
|
435
441
|
export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
|
|
436
442
|
const apiBasedTools = {};
|
|
437
443
|
const openApiSchemas = adminforth.openApi.registeredSchemas;
|
|
@@ -451,11 +457,12 @@ export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
|
|
|
451
457
|
input_schema: schema.request_schema,
|
|
452
458
|
agent: schema.agent,
|
|
453
459
|
call: (...args_1) => __awaiter(this, [...args_1], void 0, function* ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) {
|
|
460
|
+
const startedAt = Date.now();
|
|
454
461
|
if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
|
|
455
|
-
return stringifyToolOutput({
|
|
462
|
+
return stringifyToolOutput(attachDurationMs({
|
|
456
463
|
error: 'RESOURCE_NOT_AVAILABLE',
|
|
457
464
|
message: 'This resource is not available to the agent.',
|
|
458
|
-
});
|
|
465
|
+
}, Date.now() - startedAt));
|
|
459
466
|
}
|
|
460
467
|
const output = yield callOpenApiSchema({
|
|
461
468
|
adminforth,
|
|
@@ -475,7 +482,7 @@ export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
|
|
|
475
482
|
toolName,
|
|
476
483
|
userTimeZone,
|
|
477
484
|
});
|
|
478
|
-
return stringifyToolOutput(processedOutput);
|
|
485
|
+
return stringifyToolOutput(attachDurationMs(processedOutput, Date.now() - startedAt));
|
|
479
486
|
}),
|
|
480
487
|
};
|
|
481
488
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { ChatSurfaceAdapter, ChatSurfaceEventSink, ChatSurfaceIncomingMessage, IAdminForth } from "adminforth";
|
|
2
|
-
import type { ZodType } from "zod";
|
|
3
2
|
import type { HandleSpeechTurnInput, HandleTurnInput, RunAndPersistAgentResponseInput, RunAndPersistAgentResponseResult } from "../agentTurnService.js";
|
|
4
3
|
import type { PluginOptions } from "../types.js";
|
|
5
|
-
export type EndpointResponse = {
|
|
6
|
-
setStatus: (code: number, message: string) => void;
|
|
7
|
-
};
|
|
8
4
|
export type SessionTurn = {
|
|
9
5
|
prompt: string;
|
|
10
6
|
response: string;
|
|
@@ -12,7 +8,6 @@ export type SessionTurn = {
|
|
|
12
8
|
export type AgentEndpointsContext = {
|
|
13
9
|
adminforth: IAdminForth;
|
|
14
10
|
options: PluginOptions;
|
|
15
|
-
parseBody<T>(schema: ZodType<T>, body: unknown, response: EndpointResponse): T | null;
|
|
16
11
|
handleTurn(input: HandleTurnInput): Promise<RunAndPersistAgentResponseResult>;
|
|
17
12
|
handleSpeechTurn(input: HandleSpeechTurnInput): Promise<RunAndPersistAgentResponseResult | null>;
|
|
18
13
|
runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput): Promise<RunAndPersistAgentResponseResult>;
|
|
@@ -21,6 +16,6 @@ export type AgentEndpointsContext = {
|
|
|
21
16
|
createSystemTurn(sessionId: string, systemMessage: string): Promise<string>;
|
|
22
17
|
handleChatSurfaceMessage(adapter: ChatSurfaceAdapter, incoming: ChatSurfaceIncomingMessage, sink: ChatSurfaceEventSink): Promise<void>;
|
|
23
18
|
};
|
|
24
|
-
export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "
|
|
25
|
-
export type SessionEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "
|
|
19
|
+
export type CoreEndpointsContext = Pick<AgentEndpointsContext, "options" | "handleTurn" | "handleSpeechTurn">;
|
|
20
|
+
export type SessionEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "getSessionTurns" | "createNewTurn" | "createSystemTurn">;
|
|
26
21
|
export type ChatSurfaceEndpointsContext = Pick<AgentEndpointsContext, "adminforth" | "options" | "handleChatSurfaceMessage">;
|
package/dist/endpoints/core.js
CHANGED
|
@@ -9,18 +9,51 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { createSseEventEmitter } from "../surfaces/web-sse/createSseEventEmitter.js";
|
|
12
|
+
const currentPageContextSchema = z.object({
|
|
13
|
+
path: z.string(),
|
|
14
|
+
fullPath: z.string(),
|
|
15
|
+
title: z.string(),
|
|
16
|
+
url: z.string(),
|
|
17
|
+
}).strict();
|
|
12
18
|
const agentResponseBodySchema = z.object({
|
|
13
19
|
message: z.string(),
|
|
14
20
|
sessionId: z.string(),
|
|
15
21
|
mode: z.string().nullish(),
|
|
16
22
|
timeZone: z.string().optional(),
|
|
17
|
-
currentPage:
|
|
23
|
+
currentPage: currentPageContextSchema.optional(),
|
|
18
24
|
}).strict();
|
|
19
25
|
const agentApprovalBodySchema = z.object({
|
|
20
26
|
sessionId: z.string(),
|
|
21
27
|
decision: z.enum(["approve", "reject"]),
|
|
22
28
|
}).strict();
|
|
23
|
-
|
|
29
|
+
// Sent as multipart/form-data (via multer), so every field arrives as a plain string on the wire.
|
|
30
|
+
// `currentPage` is JSON.stringify'd by the client into a form field and must be parsed back into
|
|
31
|
+
// an object. Zod -> JSON-Schema conversion cannot represent `.transform()` at all (it throws), so
|
|
32
|
+
// `agentSpeechResponseShapeSchema` (plain strings, no transform) is what's passed as request_schema
|
|
33
|
+
// for the AJV pre-check, while `agentSpeechResponseBodySchema` (with the transform) is used via an
|
|
34
|
+
// inline safeParse in the handler to actually parse `currentPage` into an object.
|
|
35
|
+
const agentSpeechResponseShapeSchema = z.object({
|
|
36
|
+
sessionId: z.string(),
|
|
37
|
+
mode: z.string().nullish(),
|
|
38
|
+
timeZone: z.string().optional(),
|
|
39
|
+
currentPage: z.string().optional(),
|
|
40
|
+
}).strict();
|
|
41
|
+
const agentSpeechResponseBodySchema = agentSpeechResponseShapeSchema.extend({
|
|
42
|
+
currentPage: z.string().optional().transform((value, ctx) => {
|
|
43
|
+
if (value === undefined)
|
|
44
|
+
return undefined;
|
|
45
|
+
try {
|
|
46
|
+
return currentPageContextSchema.parse(JSON.parse(value));
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: z.ZodIssueCode.custom,
|
|
51
|
+
message: `currentPage must be a JSON-encoded object matching CurrentPageContext: ${err instanceof Error ? err.message : String(err)}`,
|
|
52
|
+
});
|
|
53
|
+
return z.NEVER;
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
24
57
|
export function setupCoreEndpoints(ctx, server) {
|
|
25
58
|
server.endpoint({
|
|
26
59
|
method: 'POST',
|
|
@@ -43,11 +76,10 @@ export function setupCoreEndpoints(ctx, server) {
|
|
|
43
76
|
server.endpoint({
|
|
44
77
|
method: 'POST',
|
|
45
78
|
path: `/agent/response`,
|
|
79
|
+
request_schema: agentResponseBodySchema,
|
|
46
80
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
|
|
47
81
|
var _b;
|
|
48
|
-
const data =
|
|
49
|
-
if (!data)
|
|
50
|
-
return;
|
|
82
|
+
const data = body;
|
|
51
83
|
const emit = createSseEventEmitter(_raw_express_res, {
|
|
52
84
|
vercelAiUiMessageStream: true,
|
|
53
85
|
closeActiveBlockOnToolStart: true,
|
|
@@ -70,10 +102,9 @@ export function setupCoreEndpoints(ctx, server) {
|
|
|
70
102
|
server.endpoint({
|
|
71
103
|
method: 'POST',
|
|
72
104
|
path: `/agent/approval`,
|
|
105
|
+
request_schema: agentApprovalBodySchema,
|
|
73
106
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_res, abortSignal }) {
|
|
74
|
-
const data =
|
|
75
|
-
if (!data)
|
|
76
|
-
return;
|
|
107
|
+
const data = body;
|
|
77
108
|
const emit = createSseEventEmitter(_raw_express_res, {
|
|
78
109
|
vercelAiUiMessageStream: true,
|
|
79
110
|
closeActiveBlockOnToolStart: true,
|
|
@@ -95,6 +126,7 @@ export function setupCoreEndpoints(ctx, server) {
|
|
|
95
126
|
method: 'POST',
|
|
96
127
|
path: `/agent/speech-response`,
|
|
97
128
|
target: 'upload',
|
|
129
|
+
request_schema: agentSpeechResponseShapeSchema,
|
|
98
130
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) {
|
|
99
131
|
var _b;
|
|
100
132
|
const req = _raw_express_req;
|
|
@@ -103,9 +135,12 @@ export function setupCoreEndpoints(ctx, server) {
|
|
|
103
135
|
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
104
136
|
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
105
137
|
}
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
108
|
-
|
|
138
|
+
const parsed = agentSpeechResponseBodySchema.safeParse(body);
|
|
139
|
+
if (!parsed.success) {
|
|
140
|
+
response.setStatus(400, 'Request body validation failed');
|
|
141
|
+
return { error: 'Request body validation failed', details: parsed.error.issues };
|
|
142
|
+
}
|
|
143
|
+
const data = parsed.data;
|
|
109
144
|
if (!req.file) {
|
|
110
145
|
response.setStatus(400, "Audio file is required");
|
|
111
146
|
return { error: "Audio file is required" };
|
|
@@ -28,11 +28,10 @@ export function setupSessionEndpoints(ctx, server) {
|
|
|
28
28
|
server.endpoint({
|
|
29
29
|
method: 'POST',
|
|
30
30
|
path: `/agent/get-sessions`,
|
|
31
|
+
request_schema: getSessionsBodySchema,
|
|
31
32
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
32
33
|
var _b;
|
|
33
|
-
const data =
|
|
34
|
-
if (!data)
|
|
35
|
-
return;
|
|
34
|
+
const data = body;
|
|
36
35
|
const userId = adminUser.pk;
|
|
37
36
|
const limit = (_b = data.limit) !== null && _b !== void 0 ? _b : 20;
|
|
38
37
|
const sessions = yield ctx.adminforth.resource(ctx.options.sessionResource.resourceId).list([Filters.EQ(ctx.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(ctx.options.sessionResource.createdAtField)]);
|
|
@@ -48,10 +47,9 @@ export function setupSessionEndpoints(ctx, server) {
|
|
|
48
47
|
server.endpoint({
|
|
49
48
|
method: 'POST',
|
|
50
49
|
path: `/agent/get-session-info`,
|
|
50
|
+
request_schema: sessionIdBodySchema,
|
|
51
51
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
52
|
-
const data =
|
|
53
|
-
if (!data)
|
|
54
|
-
return;
|
|
52
|
+
const data = body;
|
|
55
53
|
const userId = adminUser.pk;
|
|
56
54
|
const sessionId = data.sessionId;
|
|
57
55
|
const session = yield ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get([Filters.EQ(ctx.options.sessionResource.idField, sessionId)]);
|
|
@@ -101,10 +99,9 @@ export function setupSessionEndpoints(ctx, server) {
|
|
|
101
99
|
server.endpoint({
|
|
102
100
|
method: 'POST',
|
|
103
101
|
path: `/agent/create-session`,
|
|
102
|
+
request_schema: createSessionBodySchema,
|
|
104
103
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
105
|
-
const data =
|
|
106
|
-
if (!data)
|
|
107
|
-
return;
|
|
104
|
+
const data = body;
|
|
108
105
|
const triggerMessage = data.triggerMessage;
|
|
109
106
|
const userId = adminUser.pk;
|
|
110
107
|
const title = (triggerMessage === null || triggerMessage === void 0 ? void 0 : triggerMessage.slice(0, 40)) || "New Session";
|
|
@@ -125,10 +122,9 @@ export function setupSessionEndpoints(ctx, server) {
|
|
|
125
122
|
server.endpoint({
|
|
126
123
|
method: 'POST',
|
|
127
124
|
path: `/agent/delete-session`,
|
|
125
|
+
request_schema: sessionIdBodySchema,
|
|
128
126
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
129
|
-
const data =
|
|
130
|
-
if (!data)
|
|
131
|
-
return;
|
|
127
|
+
const data = body;
|
|
132
128
|
const sessionId = data.sessionId;
|
|
133
129
|
const userId = adminUser.pk;
|
|
134
130
|
const session = yield ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get([Filters.EQ(ctx.options.sessionResource.idField, sessionId)]);
|
|
@@ -153,10 +149,9 @@ export function setupSessionEndpoints(ctx, server) {
|
|
|
153
149
|
server.endpoint({
|
|
154
150
|
method: 'POST',
|
|
155
151
|
path: `/agent/add-system-message-to-turns`,
|
|
152
|
+
request_schema: addSystemMessageBodySchema,
|
|
156
153
|
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response }) {
|
|
157
|
-
const data =
|
|
158
|
-
if (!data)
|
|
159
|
-
return;
|
|
154
|
+
const data = body;
|
|
160
155
|
const session = yield ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get([Filters.EQ(ctx.options.sessionResource.idField, data.sessionId)]);
|
|
161
156
|
if (!session) {
|
|
162
157
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
12
12
|
private agentTurnService;
|
|
13
13
|
private speechTurnService;
|
|
14
14
|
private chatSurfaceService;
|
|
15
|
-
private parseBody;
|
|
16
15
|
private getCheckpointer;
|
|
17
16
|
private getInternalAgentResourceIds;
|
|
18
17
|
constructor(options: PluginOptions);
|
package/dist/index.js
CHANGED
|
@@ -28,14 +28,6 @@ import { TurnPersistenceService } from "./agent/turn/TurnPersistenceService.js";
|
|
|
28
28
|
import { TurnPromptBuilder } from "./agent/turn/TurnPromptBuilder.js";
|
|
29
29
|
import { TurnStreamConsumer } from "./agent/turn/TurnStreamConsumer.js";
|
|
30
30
|
export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
31
|
-
parseBody(schema, body, response) {
|
|
32
|
-
const parsed = schema.safeParse(body !== null && body !== void 0 ? body : {});
|
|
33
|
-
if (!parsed.success) {
|
|
34
|
-
response.setStatus(422, parsed.error.message);
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
return parsed.data;
|
|
38
|
-
}
|
|
39
31
|
getCheckpointer() {
|
|
40
32
|
if (this.checkpointer)
|
|
41
33
|
return this.checkpointer;
|
|
@@ -147,7 +139,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
147
139
|
const endpointContext = {
|
|
148
140
|
adminforth: this.adminforth,
|
|
149
141
|
options: this.options,
|
|
150
|
-
parseBody: this.parseBody.bind(this),
|
|
151
142
|
handleTurn: this.agentTurnService.handleTurn.bind(this.agentTurnService),
|
|
152
143
|
handleSpeechTurn: this.speechTurnService.handle.bind(this.speechTurnService),
|
|
153
144
|
runAndPersistAgentResponse: this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService),
|
package/endpoints/context.ts
CHANGED
|
@@ -4,7 +4,6 @@ import type {
|
|
|
4
4
|
ChatSurfaceIncomingMessage,
|
|
5
5
|
IAdminForth,
|
|
6
6
|
} from "adminforth";
|
|
7
|
-
import type { ZodType } from "zod";
|
|
8
7
|
import type {
|
|
9
8
|
HandleSpeechTurnInput,
|
|
10
9
|
HandleTurnInput,
|
|
@@ -13,10 +12,6 @@ import type {
|
|
|
13
12
|
} from "../agentTurnService.js";
|
|
14
13
|
import type { PluginOptions } from "../types.js";
|
|
15
14
|
|
|
16
|
-
export type EndpointResponse = {
|
|
17
|
-
setStatus: (code: number, message: string) => void;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
15
|
export type SessionTurn = {
|
|
21
16
|
prompt: string;
|
|
22
17
|
response: string;
|
|
@@ -25,7 +20,6 @@ export type SessionTurn = {
|
|
|
25
20
|
export type AgentEndpointsContext = {
|
|
26
21
|
adminforth: IAdminForth;
|
|
27
22
|
options: PluginOptions;
|
|
28
|
-
parseBody<T>(schema: ZodType<T>, body: unknown, response: EndpointResponse): T | null;
|
|
29
23
|
handleTurn(input: HandleTurnInput): Promise<RunAndPersistAgentResponseResult>;
|
|
30
24
|
handleSpeechTurn(input: HandleSpeechTurnInput): Promise<RunAndPersistAgentResponseResult | null>;
|
|
31
25
|
runAndPersistAgentResponse(input: RunAndPersistAgentResponseInput): Promise<RunAndPersistAgentResponseResult>;
|
|
@@ -41,12 +35,12 @@ export type AgentEndpointsContext = {
|
|
|
41
35
|
|
|
42
36
|
export type CoreEndpointsContext = Pick<
|
|
43
37
|
AgentEndpointsContext,
|
|
44
|
-
"options" | "
|
|
38
|
+
"options" | "handleTurn" | "handleSpeechTurn"
|
|
45
39
|
>;
|
|
46
40
|
|
|
47
41
|
export type SessionEndpointsContext = Pick<
|
|
48
42
|
AgentEndpointsContext,
|
|
49
|
-
"adminforth" | "options" | "
|
|
43
|
+
"adminforth" | "options" | "getSessionTurns" | "createNewTurn"
|
|
50
44
|
| "createSystemTurn"
|
|
51
45
|
>;
|
|
52
46
|
|
package/endpoints/core.ts
CHANGED
|
@@ -12,12 +12,19 @@ type MulterFile = {
|
|
|
12
12
|
|
|
13
13
|
type ExpressMulterRequest = { file?: MulterFile };
|
|
14
14
|
|
|
15
|
+
const currentPageContextSchema = z.object({
|
|
16
|
+
path: z.string(),
|
|
17
|
+
fullPath: z.string(),
|
|
18
|
+
title: z.string(),
|
|
19
|
+
url: z.string(),
|
|
20
|
+
}).strict() satisfies z.ZodType<CurrentPageContext>;
|
|
21
|
+
|
|
15
22
|
const agentResponseBodySchema = z.object({
|
|
16
23
|
message: z.string(),
|
|
17
24
|
sessionId: z.string(),
|
|
18
25
|
mode: z.string().nullish(),
|
|
19
26
|
timeZone: z.string().optional(),
|
|
20
|
-
currentPage:
|
|
27
|
+
currentPage: currentPageContextSchema.optional(),
|
|
21
28
|
}).strict();
|
|
22
29
|
|
|
23
30
|
const agentApprovalBodySchema = z.object({
|
|
@@ -25,7 +32,33 @@ const agentApprovalBodySchema = z.object({
|
|
|
25
32
|
decision: z.enum(["approve", "reject"]),
|
|
26
33
|
}).strict();
|
|
27
34
|
|
|
28
|
-
|
|
35
|
+
// Sent as multipart/form-data (via multer), so every field arrives as a plain string on the wire.
|
|
36
|
+
// `currentPage` is JSON.stringify'd by the client into a form field and must be parsed back into
|
|
37
|
+
// an object. Zod -> JSON-Schema conversion cannot represent `.transform()` at all (it throws), so
|
|
38
|
+
// `agentSpeechResponseShapeSchema` (plain strings, no transform) is what's passed as request_schema
|
|
39
|
+
// for the AJV pre-check, while `agentSpeechResponseBodySchema` (with the transform) is used via an
|
|
40
|
+
// inline safeParse in the handler to actually parse `currentPage` into an object.
|
|
41
|
+
const agentSpeechResponseShapeSchema = z.object({
|
|
42
|
+
sessionId: z.string(),
|
|
43
|
+
mode: z.string().nullish(),
|
|
44
|
+
timeZone: z.string().optional(),
|
|
45
|
+
currentPage: z.string().optional(),
|
|
46
|
+
}).strict();
|
|
47
|
+
|
|
48
|
+
const agentSpeechResponseBodySchema = agentSpeechResponseShapeSchema.extend({
|
|
49
|
+
currentPage: z.string().optional().transform((value, ctx) => {
|
|
50
|
+
if (value === undefined) return undefined;
|
|
51
|
+
try {
|
|
52
|
+
return currentPageContextSchema.parse(JSON.parse(value));
|
|
53
|
+
} catch (err) {
|
|
54
|
+
ctx.addIssue({
|
|
55
|
+
code: z.ZodIssueCode.custom,
|
|
56
|
+
message: `currentPage must be a JSON-encoded object matching CurrentPageContext: ${err instanceof Error ? err.message : String(err)}`,
|
|
57
|
+
});
|
|
58
|
+
return z.NEVER;
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
29
62
|
|
|
30
63
|
export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServer) {
|
|
31
64
|
server.endpoint({
|
|
@@ -52,9 +85,9 @@ export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServe
|
|
|
52
85
|
server.endpoint({
|
|
53
86
|
method: 'POST',
|
|
54
87
|
path: `/agent/response`,
|
|
88
|
+
request_schema: agentResponseBodySchema,
|
|
55
89
|
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
56
|
-
const data =
|
|
57
|
-
if (!data) return;
|
|
90
|
+
const data = body as z.infer<typeof agentResponseBodySchema>;
|
|
58
91
|
const emit = createSseEventEmitter(_raw_express_res, {
|
|
59
92
|
vercelAiUiMessageStream: true,
|
|
60
93
|
closeActiveBlockOnToolStart: true,
|
|
@@ -79,9 +112,9 @@ export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServe
|
|
|
79
112
|
server.endpoint({
|
|
80
113
|
method: 'POST',
|
|
81
114
|
path: `/agent/approval`,
|
|
115
|
+
request_schema: agentApprovalBodySchema,
|
|
82
116
|
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
|
|
83
|
-
const data =
|
|
84
|
-
if (!data) return;
|
|
117
|
+
const data = body as z.infer<typeof agentApprovalBodySchema>;
|
|
85
118
|
const emit = createSseEventEmitter(_raw_express_res, {
|
|
86
119
|
vercelAiUiMessageStream: true,
|
|
87
120
|
closeActiveBlockOnToolStart: true,
|
|
@@ -105,6 +138,7 @@ export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServe
|
|
|
105
138
|
method: 'POST',
|
|
106
139
|
path: `/agent/speech-response`,
|
|
107
140
|
target: 'upload',
|
|
141
|
+
request_schema: agentSpeechResponseShapeSchema,
|
|
108
142
|
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
|
|
109
143
|
const req = _raw_express_req as ExpressMulterRequest;
|
|
110
144
|
const audioAdapter = ctx.options.audioAdapter;
|
|
@@ -112,8 +146,12 @@ export function setupCoreEndpoints(ctx: CoreEndpointsContext, server: IHttpServe
|
|
|
112
146
|
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
|
|
113
147
|
return { error: "Audio adapter is not configured for AdminForth Agent" };
|
|
114
148
|
}
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
149
|
+
const parsed = agentSpeechResponseBodySchema.safeParse(body);
|
|
150
|
+
if (!parsed.success) {
|
|
151
|
+
response.setStatus(400, 'Request body validation failed');
|
|
152
|
+
return { error: 'Request body validation failed', details: parsed.error.issues };
|
|
153
|
+
}
|
|
154
|
+
const data = parsed.data;
|
|
117
155
|
if (!req.file) {
|
|
118
156
|
response.setStatus(400, "Audio file is required");
|
|
119
157
|
return { error: "Audio file is required" };
|
package/endpoints/sessions.ts
CHANGED
|
@@ -26,9 +26,9 @@ export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHtt
|
|
|
26
26
|
server.endpoint({
|
|
27
27
|
method: 'POST',
|
|
28
28
|
path: `/agent/get-sessions`,
|
|
29
|
+
request_schema: getSessionsBodySchema,
|
|
29
30
|
handler: async ({body, adminUser, response }) => {
|
|
30
|
-
const data =
|
|
31
|
-
if (!data) return;
|
|
31
|
+
const data = body as z.infer<typeof getSessionsBodySchema>;
|
|
32
32
|
const userId = adminUser!.pk;
|
|
33
33
|
const limit = data.limit ?? 20;
|
|
34
34
|
const sessions = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).list(
|
|
@@ -47,9 +47,9 @@ export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHtt
|
|
|
47
47
|
server.endpoint({
|
|
48
48
|
method: 'POST',
|
|
49
49
|
path: `/agent/get-session-info`,
|
|
50
|
+
request_schema: sessionIdBodySchema,
|
|
50
51
|
handler: async ({body, adminUser, response }) => {
|
|
51
|
-
const data =
|
|
52
|
-
if (!data) return;
|
|
52
|
+
const data = body as z.infer<typeof sessionIdBodySchema>;
|
|
53
53
|
const userId = adminUser!.pk;
|
|
54
54
|
const sessionId = data.sessionId;
|
|
55
55
|
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
@@ -102,9 +102,9 @@ export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHtt
|
|
|
102
102
|
server.endpoint({
|
|
103
103
|
method: 'POST',
|
|
104
104
|
path: `/agent/create-session`,
|
|
105
|
+
request_schema: createSessionBodySchema,
|
|
105
106
|
handler: async ({body, adminUser, response }) => {
|
|
106
|
-
const data =
|
|
107
|
-
if (!data) return;
|
|
107
|
+
const data = body as z.infer<typeof createSessionBodySchema>;
|
|
108
108
|
const triggerMessage = data.triggerMessage;
|
|
109
109
|
const userId = adminUser!.pk;
|
|
110
110
|
const title = triggerMessage?.slice(0, 40) || "New Session";
|
|
@@ -126,9 +126,9 @@ export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHtt
|
|
|
126
126
|
server.endpoint({
|
|
127
127
|
method: 'POST',
|
|
128
128
|
path: `/agent/delete-session`,
|
|
129
|
+
request_schema: sessionIdBodySchema,
|
|
129
130
|
handler: async ({body, adminUser, response }) => {
|
|
130
|
-
const data =
|
|
131
|
-
if (!data) return;
|
|
131
|
+
const data = body as z.infer<typeof sessionIdBodySchema>;
|
|
132
132
|
const sessionId = data.sessionId;
|
|
133
133
|
const userId = adminUser!.pk;
|
|
134
134
|
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
@@ -158,9 +158,9 @@ export function setupSessionEndpoints(ctx: SessionEndpointsContext, server: IHtt
|
|
|
158
158
|
server.endpoint({
|
|
159
159
|
method: 'POST',
|
|
160
160
|
path: `/agent/add-system-message-to-turns`,
|
|
161
|
+
request_schema: addSystemMessageBodySchema,
|
|
161
162
|
handler: async ({body, adminUser, response }) => {
|
|
162
|
-
const data =
|
|
163
|
-
if (!data) return;
|
|
163
|
+
const data = body as z.infer<typeof addSystemMessageBodySchema>;
|
|
164
164
|
const session = await ctx.adminforth.resource(ctx.options.sessionResource.resourceId).get(
|
|
165
165
|
[Filters.EQ(ctx.options.sessionResource.idField, data.sessionId)]
|
|
166
166
|
);
|
package/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { AdminForthPlugin } from "adminforth";
|
|
|
8
8
|
|
|
9
9
|
import type { PluginOptions } from './types.js';
|
|
10
10
|
import { MemorySaver, type BaseCheckpointSaver } from "@langchain/langgraph";
|
|
11
|
-
import { z } from "zod";
|
|
12
11
|
import { AdminForthCheckpointSaver } from "./agent/checkpointer.js";
|
|
13
12
|
import { appendCustomSystemPrompt, buildAgentSystemPrompt, DEFAULT_AGENT_SYSTEM_PROMPT} from "./agent/systemPrompt.js";
|
|
14
13
|
import { setupCoreEndpoints } from "./endpoints/core.js";
|
|
@@ -41,18 +40,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
41
40
|
private agentTurnService: AgentTurnService;
|
|
42
41
|
private speechTurnService: SpeechTurnService;
|
|
43
42
|
private chatSurfaceService: ChatSurfaceService;
|
|
44
|
-
private parseBody<T>(
|
|
45
|
-
schema: z.ZodType<T>,
|
|
46
|
-
body: unknown,
|
|
47
|
-
response: { setStatus: (code: number, message: string) => void },
|
|
48
|
-
): T | null {
|
|
49
|
-
const parsed = schema.safeParse(body ?? {});
|
|
50
|
-
if (!parsed.success) {
|
|
51
|
-
response.setStatus(422, parsed.error.message);
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return parsed.data;
|
|
55
|
-
}
|
|
56
43
|
private getCheckpointer() {
|
|
57
44
|
if (this.checkpointer) return this.checkpointer;
|
|
58
45
|
|
|
@@ -189,7 +176,6 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
|
|
|
189
176
|
const endpointContext = {
|
|
190
177
|
adminforth: this.adminforth,
|
|
191
178
|
options: this.options,
|
|
192
|
-
parseBody: this.parseBody.bind(this),
|
|
193
179
|
handleTurn: this.agentTurnService.handleTurn.bind(this.agentTurnService),
|
|
194
180
|
handleSpeechTurn: this.speechTurnService.handle.bind(this.speechTurnService),
|
|
195
181
|
runAndPersistAgentResponse: this.agentTurnService.runAndPersistAgentResponse.bind(this.agentTurnService),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/agent",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.10",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"description": "AI agent plugin for AdminForth with tool-based workflows and persistent chat sessions",
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "latest",
|
|
28
|
-
"adminforth": "^3.
|
|
28
|
+
"adminforth": "^3.8.2",
|
|
29
29
|
"semantic-release": "^24.2.1",
|
|
30
30
|
"semantic-release-slack-bot": "^4.0.2",
|
|
31
31
|
"typescript": "^5.7.3"
|
|
@@ -67,6 +67,6 @@
|
|
|
67
67
|
}
|
|
68
68
|
],
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"adminforth": "^3.
|
|
70
|
+
"adminforth": "^3.8.2"
|
|
71
71
|
}
|
|
72
72
|
}
|