Skip to content

Commit 0df901d

Browse files
committed
fix: enforce strict type-checking in tsconfig.json
fix: update CheckpointPendingWrite type in checkpointer.ts fix: use customComponentsDir from adminforth config in systemPrompt.ts fix: add acceptLanguage parameter to ApiBasedToolCallParams and related functions
1 parent 8fbc310 commit 0df901d

5 files changed

Lines changed: 42 additions & 20 deletions

File tree

agent/checkpointer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type { PluginOptions } from "../types.js";
1212
import { Filters } from "adminforth";
1313

1414
const ROOT_CHECKPOINT_NAMESPACE = "__root__";
15+
type CheckpointRow = Record<string, unknown>;
1516

1617
export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
1718
constructor(
@@ -227,7 +228,7 @@ export class AdminForthCheckpointSaver extends BaseCheckpointSaver {
227228
[{ field: r.sequenceField, direction: "asc" }],
228229
);
229230

230-
const pendingWrites: CheckpointPendingWrite[] = writesRows.flatMap((row) => {
231+
const pendingWrites: CheckpointPendingWrite[] = writesRows.flatMap((row: CheckpointRow) => {
231232
const taskId = String(row[r.taskIdField] ?? "");
232233
const write = this.deserialize<PendingWrite>(row[r.writesPayloadField]);
233234
if (!write) {

agent/systemPrompt.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,9 @@ export async function buildAgentSystemPrompt(
9595
adminforth: IAdminForth,
9696
hiddenResourceIds: Iterable<string> = [],
9797
) {
98+
const customComponentsDir = adminforth.config.customization.customComponentsDir ?? "custom";
9899
const [primarySkills, defaultSkills] = await Promise.all([
99-
listProjectSkillManifests(adminforth.config.customization.customComponentsDir),
100+
listProjectSkillManifests(customComponentsDir),
100101
listBundledSkillManifests(),
101102
]);
102103
const adminBasePath = adminforth.config.baseUrlSlashed;

apiBasedTools.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export type ApiBasedToolCallParams = {
169169
abortSignal?: AbortSignal;
170170
inputs?: Record<string, unknown>;
171171
userTimeZone?: string;
172+
acceptLanguage?: string;
172173
};
173174

174175
export type ApiBasedTool = {
@@ -516,8 +517,9 @@ async function callOpenApiSchema(params: {
516517
schema: RegisteredApiToolSchema;
517518
toolName: string;
518519
userTimeZone?: string;
520+
acceptLanguage?: string;
519521
}) {
520-
const { adminforth, adminUser, abortSignal, inputs, schema, toolName, userTimeZone } = params;
522+
const { adminforth, adminUser, abortSignal, inputs, schema, toolName, userTimeZone, acceptLanguage } = params;
521523
const method = schema.method.toUpperCase();
522524
const normalizedInputs = normalizeDateTimeInputsToUtc(
523525
(inputs ?? {}) as Record<string, unknown>,
@@ -535,12 +537,13 @@ async function callOpenApiSchema(params: {
535537

536538
const response = createDirectToolResponse();
537539
logger.info(`Calling OpenAPI tool "${toolName}" with direct handler`);
540+
const lang = acceptLanguage ?? "en";
538541
const tr = (
539542
msg: string,
540543
category: string,
541544
trParams: unknown,
542545
pluralizationNumber?: number,
543-
) => adminforth.tr(msg, category, undefined, trParams, pluralizationNumber);
546+
) => adminforth.tr(msg, category, lang, trParams, pluralizationNumber);
544547
const output = await schema.handler({
545548
body,
546549
query,
@@ -614,7 +617,7 @@ export function prepareApiBasedTools(
614617
description: schema.description,
615618
input_schema: schema.request_schema,
616619
output_schema: schema.response_schema,
617-
call: async ({ adminUser, adminuser, abortSignal, inputs, userTimeZone } = {}) => {
620+
call: async ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) => {
618621
if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
619622
return YAML.stringify({
620623
error: 'RESOURCE_NOT_AVAILABLE',
@@ -630,6 +633,7 @@ export function prepareApiBasedTools(
630633
toolName,
631634
inputs,
632635
userTimeZone,
636+
acceptLanguage,
633637
});
634638

635639
const processedOutput = await applyToolOverride({

index.ts

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,22 @@ function getErrorMessage(error: unknown): string {
9191
return error instanceof Error ? error.message : String(error);
9292
}
9393

94+
function requireAdminUser(adminUser: AdminUser | undefined): AdminUser {
95+
if (!adminUser) {
96+
throw new Error("AdminForth Agent endpoint requires an authenticated admin user");
97+
}
98+
99+
return adminUser;
100+
}
101+
94102
export default class AdminForthAgentPlugin extends AdminForthPlugin {
95103
options: PluginOptions;
96104
agentSystemPromptPromise: Promise<string>;
97105
private checkpointer: BaseCheckpointSaver | null = null;
98106
private parseBody<T>(
99107
schema: z.ZodType<T>,
100108
body: unknown,
101-
response: { setStatus: (code: number, message?: string) => void },
109+
response: { setStatus: (code: number, message: string) => void },
102110
): T | null {
103111
const parsed = schema.safeParse(body ?? {});
104112
if (!parsed.success) {
@@ -258,7 +266,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
258266
const systemPrompt = buildAgentTurnSystemPrompt({
259267
agentSystemPrompt: await this.agentSystemPromptPromise,
260268
adminUser: input.adminUser,
261-
usernameField: this.adminforth.config.auth.usernameField,
269+
usernameField: this.adminforth.config.auth!.usernameField,
262270
userLanguage,
263271
});
264272
const apiBasedTools = buildApiBasedTools(
@@ -279,7 +287,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
279287
adminUser: input.adminUser,
280288
adminforth: this.adminforth,
281289
apiBasedTools,
282-
customComponentsDir: this.adminforth.config.customization.customComponentsDir,
290+
customComponentsDir: this.adminforth.config.customization.customComponentsDir ?? "custom",
283291
sessionId: input.sessionId,
284292
turnId: input.turnId,
285293
currentPage: input.currentPage,
@@ -402,14 +410,16 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
402410
method: 'POST',
403411
path: `/agent/get-placeholder-messages`,
404412
handler: async ({ headers, adminUser }) => {
413+
const currentAdminUser = requireAdminUser(adminUser);
414+
405415
if (!this.options.placeholderMessages) {
406416
return {
407417
messages: [],
408418
};
409419
}
410420

411421
const messages = await this.options.placeholderMessages({
412-
adminUser,
422+
adminUser: currentAdminUser,
413423
headers,
414424
});
415425

@@ -422,6 +432,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
422432
method: 'POST',
423433
path: `/agent/response`,
424434
handler: async ({ body, adminUser, response, _raw_express_res, abortSignal }) => {
435+
const currentAdminUser = requireAdminUser(adminUser);
425436
const data = this.parseBody(agentResponseBodySchema, body, response);
426437
if (!data) return;
427438
const stream = createAgentEventStream(_raw_express_res, {vercelAiUiMessageStream: true, closeActiveBlockOnToolStart: true});
@@ -435,7 +446,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
435446
userTimeZone: data.timeZone ?? 'UTC',
436447
currentPage: data.currentPage,
437448
abortSignal,
438-
adminUser,
449+
adminUser: currentAdminUser,
439450
emitToolCallEvent: stream.toolCall,
440451
emitReasoningDelta: stream.reasoningDelta,
441452
emitTextDelta: stream.textDelta,
@@ -452,16 +463,17 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
452463
path: `/agent/speech-response`,
453464
target: 'upload',
454465
handler: async ({ body, adminUser, response, _raw_express_req, _raw_express_res, abortSignal }) => {
466+
const currentAdminUser = requireAdminUser(adminUser);
455467
const req = _raw_express_req as ExpressMulterRequest;
456468
const audioAdapter = this.options.audioAdapter;
457469
if (!audioAdapter) {
458-
response.setStatus(400, undefined);
470+
response.setStatus(400, "Audio adapter is not configured for AdminForth Agent");
459471
return { error: "Audio adapter is not configured for AdminForth Agent" };
460472
}
461473
const data = this.parseBody(agentSpeechResponseBodySchema, body, response);
462474
if (!data) return;
463475
if (!req.file) {
464-
response.setStatus(400, undefined);
476+
response.setStatus(400, "Audio file is required");
465477
return { error: "Audio file is required" };
466478
}
467479
const stream = createAgentEventStream(_raw_express_res);
@@ -511,7 +523,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
511523
userTimeZone: data.timeZone ?? 'UTC',
512524
currentPage,
513525
abortSignal,
514-
adminUser,
526+
adminUser: currentAdminUser,
515527
emitToolCallEvent: stream.toolCall,
516528
failureLogMessage: "Agent speech response failed",
517529
abortLogMessage: "Agent speech response aborted by the client",
@@ -589,7 +601,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
589601
logger.info("Agent speech audio streaming aborted by the client");
590602
} else {
591603
logger.error(`Agent speech audio streaming failed:\n${error}`);
592-
stream.error(error);
604+
stream.error(getErrorMessage(error));
593605
}
594606
stream.end();
595607
return null;
@@ -600,9 +612,10 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
600612
method: 'POST',
601613
path: `/agent/get-sessions`,
602614
handler: async ({body, adminUser, response }) => {
615+
const currentAdminUser = requireAdminUser(adminUser);
603616
const data = this.parseBody(getSessionsBodySchema, body, response);
604617
if (!data) return;
605-
const userId = adminUser.pk;
618+
const userId = currentAdminUser.pk;
606619
const limit = data.limit ?? 20;
607620
const sessions = await this.adminforth.resource(this.options.sessionResource.resourceId).list(
608621
[Filters.EQ(this.options.sessionResource.askerIdField, userId)], limit, undefined, [Sorts.DESC(this.options.sessionResource.createdAtField)]
@@ -620,12 +633,13 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
620633
method: 'POST',
621634
path: `/agent/get-session-info`,
622635
handler: async ({body, adminUser, response }) => {
636+
const currentAdminUser = requireAdminUser(adminUser);
623637
const parsedBody = sessionIdBodySchema.safeParse(body);
624638
if (!parsedBody.success) {
625639
response.setStatus(422, parsedBody.error.message);
626640
return;
627641
}
628-
const userId = adminUser.pk;
642+
const userId = currentAdminUser.pk;
629643
const sessionId = parsedBody.data.sessionId;
630644
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
631645
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
@@ -647,7 +661,7 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
647661
title: session[this.options.sessionResource.titleField],
648662
timestamp: session[this.options.sessionResource.createdAtField],
649663
messages: turns.flatMap(turn => {
650-
const messages = [];
664+
const messages: Array<{ text: string; role: 'user' | 'assistant' }> = [];
651665
if (turn.prompt) {
652666
messages.push({
653667
text: turn.prompt,
@@ -670,10 +684,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
670684
method: 'POST',
671685
path: `/agent/create-session`,
672686
handler: async ({body, adminUser, response }) => {
687+
const currentAdminUser = requireAdminUser(adminUser);
673688
const data = this.parseBody(createSessionBodySchema, body, response);
674689
if (!data) return;
675690
const triggerMessage = data.triggerMessage;
676-
const userId = adminUser.pk;
691+
const userId = currentAdminUser.pk;
677692
const title = triggerMessage?.slice(0, 40) || "New Session";
678693
const newSession = {
679694
[this.options.sessionResource.idField]: randomUUID(),
@@ -693,10 +708,11 @@ export default class AdminForthAgentPlugin extends AdminForthPlugin {
693708
method: 'POST',
694709
path: `/agent/delete-session`,
695710
handler: async ({body, adminUser, response }) => {
711+
const currentAdminUser = requireAdminUser(adminUser);
696712
const data = this.parseBody(sessionIdBodySchema, body, response);
697713
if (!data) return;
698714
const sessionId = data.sessionId;
699-
const userId = adminUser.pk;
715+
const userId = currentAdminUser.pk;
700716
const session = await this.adminforth.resource(this.options.sessionResource.resourceId).get(
701717
[Filters.EQ(this.options.sessionResource.idField, sessionId)]
702718
);

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"outDir": "./dist", /* Specify an output folder for all emitted files. */
66
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. */
77
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
8-
"strict": false, /* Enable all strict type-checking options. */
8+
"strict": true, /* Enable all strict type-checking options. */
99
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
1010
},
1111
"exclude": ["node_modules", "dist", "custom"], /* Exclude files from compilation. */

0 commit comments

Comments
 (0)