Skip to content

Commit e305ea3

Browse files
committed
docs: update custom tool definition to use admin.express.endpoint for API registration
1 parent fd34e38 commit e305ea3

1 file changed

Lines changed: 65 additions & 57 deletions

File tree

  • adminforth/documentation/docs/tutorial/08-Plugins

adminforth/documentation/docs/tutorial/08-Plugins/01-agent.md

Lines changed: 65 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -580,16 +580,16 @@ Please send record counts to all admin users
580580

581581
### Writing custom tools
582582

583-
To define a custom tool you should simply define an express API route in your app using `admin.express.withSchema` wrapper which makes the route available for the agent (clear and predictable schema is a crucial part of making the tool work well).
583+
To define a custom tool, register an API endpoint with `admin.express.endpoint`. The endpoint schema makes the API visible in OpenAPI, and the endpoint `handler` gives the agent a direct function it can call as a tool.
584584

585-
If you are using `admin.express.authorize` wrapper for authorization, adminuser will be injected atomatically from user which sits on the surface and controls the agent. In other words all permissions and access rights of the agent are defined by the admin user which is controlling this agent. At the same time all actions done by agent are automatically attributed in the audit log to the admin user which is controlling the agent.
585+
`admin.express.withSchema` is useful when you already have a regular Express route and want it to appear in the generated OpenAPI document. However, a route registered only with `withSchema` is not a directly callable agent tool, because it does not register a direct OpenAPI handler. For agent tools, use `admin.express.endpoint`.
586+
587+
By default, `admin.express.endpoint` applies AdminForth authorization. The endpoint handler receives `adminUser` from the user who is controlling the agent. In other words, all permissions and access rights of the agent are defined by that admin user. At the same time, actions done by the agent are automatically attributed in the audit log to the admin user who is controlling the agent.
586588

587589
This example uses the same email adapter pattern shown in the Email Invite and Email Password Reset plugins. The transport below uses Mailgun only to keep the snippet short; you can replace it with SES or any other adapter from [List of adapters](/docs/tutorial/ListOfAdapters/).
588590

589591
```ts title="./api.ts"
590-
import type { Express, Response } from 'express';
591-
import { Filters, type IAdminForth, type IAdminUserExpressRequest } from 'adminforth';
592-
import * as z from 'zod';
592+
import { Filters, type IAdminForth } from 'adminforth';
593593
import EmailAdapterMailgun from '@adminforth/email-adapter-mailgun';
594594

595595
const agentEmailAdapter = new EmailAdapterMailgun({
@@ -610,59 +610,67 @@ function renderEmailHtml(body: string) {
610610
return `<html><body><pre style="font-family: sans-serif; white-space: pre-wrap;">${escapeHtml(body)}</pre></body></html>`;
611611
}
612612

613-
export function initApi(app: Express, admin: IAdminForth) {
614-
app.post('/send_email_to_user',
615-
admin.express.withSchema(
616-
{
617-
description: 'Send an email to one AdminForth user by id. Use this after the user row is resolved.',
618-
request: z.object({
619-
userId: z.string(),
620-
subject: z.string().min(1),
621-
body: z.string().min(1),
622-
}),
623-
response: z.object({
624-
ok: z.boolean(),
625-
email: z.string().email().optional(),
626-
error: z.string().optional(),
627-
}),
613+
type SendEmailBody = {
614+
userId: string;
615+
subject: string;
616+
body: string;
617+
};
618+
619+
export function initApi(admin: IAdminForth) {
620+
admin.express.endpoint({
621+
method: 'POST',
622+
path: '/send_email_to_user',
623+
description: 'Send an email to one AdminForth user by id. Use this after the user row is resolved.',
624+
request_schema: {
625+
type: 'object',
626+
additionalProperties: false,
627+
required: ['userId', 'subject', 'body'],
628+
properties: {
629+
userId: { type: 'string' },
630+
subject: { type: 'string', minLength: 1 },
631+
body: { type: 'string', minLength: 1 },
632+
},
633+
},
634+
response_schema: {
635+
type: 'object',
636+
additionalProperties: false,
637+
required: ['ok'],
638+
properties: {
639+
ok: { type: 'boolean' },
640+
email: { type: 'string' },
641+
error: { type: 'string' },
628642
},
629-
admin.express.authorize(
630-
async (req: IAdminUserExpressRequest, res: Response) => {
631-
const { userId, subject, body } = req.body as {
632-
userId: string;
633-
subject: string;
634-
body: string;
635-
};
636-
637-
await agentEmailAdapter.validate();
638-
639-
const user = await admin.resource('adminuser').get(
640-
Filters.EQ('id', userId),
641-
);
642-
643-
if (!user || typeof user.email !== 'string' || !user.email) {
644-
res.status(404).json({ ok: false, error: 'User not found' });
645-
return;
646-
}
647-
648-
const result = await agentEmailAdapter.sendEmail(
649-
agentSendFrom,
650-
user.email,
651-
body,
652-
renderEmailHtml(body),
653-
subject,
654-
);
655-
656-
if (!result.ok) {
657-
res.status(500).json({ ok: false, error: result.error ?? 'Failed to send email' });
658-
return;
659-
}
660-
661-
res.json({ ok: true, email: user.email });
662-
}
663-
)
664-
)
665-
);
643+
},
644+
handler: async ({ body, response }) => {
645+
const { userId, subject, body: emailBody } = body as SendEmailBody;
646+
647+
await agentEmailAdapter.validate();
648+
649+
const user = await admin.resource('adminuser').get(
650+
Filters.EQ('id', userId),
651+
);
652+
653+
if (!user) {
654+
response.setStatus(404, 'User not found');
655+
return { ok: false, error: 'User not found' };
656+
}
657+
658+
const result = await agentEmailAdapter.sendEmail(
659+
agentSendFrom,
660+
user.email as string,
661+
emailBody,
662+
renderEmailHtml(emailBody),
663+
subject,
664+
);
665+
666+
if (!result.ok) {
667+
response.setStatus(500, 'Failed to send email');
668+
return { ok: false, error: result.error ?? 'Failed to send email' };
669+
}
670+
671+
return { ok: true, email: user.email };
672+
},
673+
});
666674
}
667675
```
668676

0 commit comments

Comments
 (0)