@adminforth/agent 1.52.6 → 1.53.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/apiBasedTools.ts CHANGED
@@ -480,6 +480,15 @@ function normalizeDateTimeInputsToUtc(
480
480
  }
481
481
 
482
482
  const METHODS_WITHOUT_REQUEST_BODY = new Set<string>(['GET', 'HEAD']);
483
+ const TOOL_HANDLER_TIMEOUT_MS = 15_000;
484
+ const EMPTY_HANDLER_RESPONSE_ERROR = {
485
+ error: 'EMPTY_HANDLER_RESPONSE',
486
+ message: 'Tool handler completed without returning a response.',
487
+ };
488
+ const TOOL_HANDLER_TIMEOUT_ERROR = {
489
+ error: 'TOOL_HANDLER_TIMEOUT',
490
+ message: `Tool handler timed out after ${TOOL_HANDLER_TIMEOUT_MS / 1000} seconds.`,
491
+ };
483
492
 
484
493
  function createDirectToolResponse(): IAdminForthHttpResponse & {
485
494
  headers: Array<[string, string]>;
@@ -514,6 +523,30 @@ function validationErrorResponse(
514
523
  };
515
524
  }
516
525
 
526
+ async function withToolHandlerTimeout<T>(callback: (abortSignal: AbortSignal) => T | Promise<T>, abortSignal?: AbortSignal) {
527
+ const controller = new AbortController();
528
+ const abortHandler = () => controller.abort();
529
+ let timeout: ReturnType<typeof setTimeout>;
530
+ const timeoutPromise = new Promise<typeof TOOL_HANDLER_TIMEOUT_ERROR>((resolve) => {
531
+ timeout = setTimeout(() => {
532
+ controller.abort();
533
+ resolve(TOOL_HANDLER_TIMEOUT_ERROR);
534
+ }, TOOL_HANDLER_TIMEOUT_MS);
535
+ });
536
+
537
+ abortSignal?.addEventListener('abort', abortHandler, { once: true });
538
+
539
+ try {
540
+ return await Promise.race([
541
+ callback(controller.signal),
542
+ timeoutPromise,
543
+ ]);
544
+ } finally {
545
+ clearTimeout(timeout!);
546
+ abortSignal?.removeEventListener('abort', abortHandler);
547
+ }
548
+ }
549
+
517
550
  async function callOpenApiSchema(params: {
518
551
  adminforth: IAdminForth;
519
552
  adminUser?: AdminUser;
@@ -549,19 +582,21 @@ async function callOpenApiSchema(params: {
549
582
  trParams: unknown,
550
583
  pluralizationNumber?: number,
551
584
  ) => adminforth.tr(msg, category, lang, trParams, pluralizationNumber);
552
- const output = await schema.handler({
553
- body,
554
- query,
555
- headers: {},
556
- cookies: [],
557
- adminUser,
558
- response,
559
- requestUrl: schema.path,
560
- abortSignal: abortSignal ?? new AbortController().signal,
561
- _raw_express_req: undefined as never,
562
- _raw_express_res: undefined as never,
563
- tr,
564
- });
585
+ const output = await withToolHandlerTimeout((handlerAbortSignal) => schema.handler({
586
+ body,
587
+ query,
588
+ headers: {},
589
+ cookies: [],
590
+ adminUser,
591
+ response,
592
+ requestUrl: schema.path,
593
+ abortSignal: handlerAbortSignal,
594
+ _raw_express_req: undefined as never,
595
+ _raw_express_res: undefined as never,
596
+ tr,
597
+ }),
598
+ abortSignal,
599
+ );
565
600
 
566
601
  if (response.message) {
567
602
  return response.status >= 400
@@ -573,6 +608,10 @@ async function callOpenApiSchema(params: {
573
608
  : response.message;
574
609
  }
575
610
 
611
+ if (output === undefined) {
612
+ return EMPTY_HANDLER_RESPONSE_ERROR;
613
+ }
614
+
576
615
  if (output === null) {
577
616
  return { status: response.status };
578
617
  }
@@ -592,6 +631,10 @@ async function callOpenApiSchema(params: {
592
631
  : output;
593
632
  }
594
633
 
634
+ function stringifyToolOutput(output: unknown): string {
635
+ return YAML.stringify(output === undefined ? EMPTY_HANDLER_RESPONSE_ERROR : output);
636
+ }
637
+
595
638
  export function prepareApiBasedTools(
596
639
  adminforth: IAdminForth,
597
640
  hiddenResourceIds: Iterable<string> = [],
@@ -624,7 +667,7 @@ export function prepareApiBasedTools(
624
667
  agent: schema.agent,
625
668
  call: async ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) => {
626
669
  if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
627
- return YAML.stringify({
670
+ return stringifyToolOutput({
628
671
  error: 'RESOURCE_NOT_AVAILABLE',
629
672
  message: 'This resource is not available to the agent.',
630
673
  });
@@ -650,7 +693,7 @@ export function prepareApiBasedTools(
650
693
  userTimeZone,
651
694
  });
652
695
 
653
- return YAML.stringify(processedOutput);
696
+ return stringifyToolOutput(processedOutput);
654
697
  },
655
698
  };
656
699
  }
package/build.log CHANGED
@@ -63,5 +63,5 @@ custom/speech_recognition_frontend/voiceActivityDetection.ts
63
63
  custom/speech_recognition_frontend/types/
64
64
  custom/speech_recognition_frontend/types/voice-activity-detection.d.ts
65
65
 
66
- sent 1,683,211 bytes received 940 bytes 3,368,302.00 bytes/sec
66
+ sent 1,683,270 bytes received 940 bytes 3,368,420.00 bytes/sec
67
67
  total size is 1,679,042 speedup is 1.00
@@ -316,6 +316,15 @@ function normalizeDateTimeInputsToUtc(body, adminforth, userTimeZone) {
316
316
  return normalizeValue(body);
317
317
  }
318
318
  const METHODS_WITHOUT_REQUEST_BODY = new Set(['GET', 'HEAD']);
319
+ const TOOL_HANDLER_TIMEOUT_MS = 15000;
320
+ const EMPTY_HANDLER_RESPONSE_ERROR = {
321
+ error: 'EMPTY_HANDLER_RESPONSE',
322
+ message: 'Tool handler completed without returning a response.',
323
+ };
324
+ const TOOL_HANDLER_TIMEOUT_ERROR = {
325
+ error: 'TOOL_HANDLER_TIMEOUT',
326
+ message: `Tool handler timed out after ${TOOL_HANDLER_TIMEOUT_MS / 1000} seconds.`,
327
+ };
319
328
  function createDirectToolResponse() {
320
329
  const headers = [];
321
330
  return {
@@ -339,6 +348,30 @@ function validationErrorResponse(error, details) {
339
348
  details,
340
349
  };
341
350
  }
351
+ function withToolHandlerTimeout(callback, abortSignal) {
352
+ return __awaiter(this, void 0, void 0, function* () {
353
+ const controller = new AbortController();
354
+ const abortHandler = () => controller.abort();
355
+ let timeout;
356
+ const timeoutPromise = new Promise((resolve) => {
357
+ timeout = setTimeout(() => {
358
+ controller.abort();
359
+ resolve(TOOL_HANDLER_TIMEOUT_ERROR);
360
+ }, TOOL_HANDLER_TIMEOUT_MS);
361
+ });
362
+ abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.addEventListener('abort', abortHandler, { once: true });
363
+ try {
364
+ return yield Promise.race([
365
+ callback(controller.signal),
366
+ timeoutPromise,
367
+ ]);
368
+ }
369
+ finally {
370
+ clearTimeout(timeout);
371
+ abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.removeEventListener('abort', abortHandler);
372
+ }
373
+ });
374
+ }
342
375
  function callOpenApiSchema(params) {
343
376
  return __awaiter(this, void 0, void 0, function* () {
344
377
  const { adminforth, adminUser, abortSignal, inputs, schema, toolName, userTimeZone, acceptLanguage } = params;
@@ -355,7 +388,7 @@ function callOpenApiSchema(params) {
355
388
  logger.info(`Calling OpenAPI tool "${toolName}" with direct handler`);
356
389
  const lang = acceptLanguage !== null && acceptLanguage !== void 0 ? acceptLanguage : "en";
357
390
  const tr = (msg, category, trParams, pluralizationNumber) => adminforth.tr(msg, category, lang, trParams, pluralizationNumber);
358
- const output = yield schema.handler({
391
+ const output = yield withToolHandlerTimeout((handlerAbortSignal) => schema.handler({
359
392
  body,
360
393
  query,
361
394
  headers: {},
@@ -363,11 +396,11 @@ function callOpenApiSchema(params) {
363
396
  adminUser,
364
397
  response,
365
398
  requestUrl: schema.path,
366
- abortSignal: abortSignal !== null && abortSignal !== void 0 ? abortSignal : new AbortController().signal,
399
+ abortSignal: handlerAbortSignal,
367
400
  _raw_express_req: undefined,
368
401
  _raw_express_res: undefined,
369
402
  tr,
370
- });
403
+ }), abortSignal);
371
404
  if (response.message) {
372
405
  return response.status >= 400
373
406
  ? {
@@ -377,6 +410,9 @@ function callOpenApiSchema(params) {
377
410
  }
378
411
  : response.message;
379
412
  }
413
+ if (output === undefined) {
414
+ return EMPTY_HANDLER_RESPONSE_ERROR;
415
+ }
380
416
  if (output === null) {
381
417
  return { status: response.status };
382
418
  }
@@ -393,6 +429,9 @@ function callOpenApiSchema(params) {
393
429
  : output;
394
430
  });
395
431
  }
432
+ function stringifyToolOutput(output) {
433
+ return YAML.stringify(output === undefined ? EMPTY_HANDLER_RESPONSE_ERROR : output);
434
+ }
396
435
  export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
397
436
  const apiBasedTools = {};
398
437
  const openApiSchemas = adminforth.openApi.registeredSchemas;
@@ -413,7 +452,7 @@ export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
413
452
  agent: schema.agent,
414
453
  call: (...args_1) => __awaiter(this, [...args_1], void 0, function* ({ adminUser, adminuser, abortSignal, inputs, userTimeZone, acceptLanguage } = {}) {
415
454
  if (isHiddenResourceCall(hiddenResourceIdSet, inputs)) {
416
- return YAML.stringify({
455
+ return stringifyToolOutput({
417
456
  error: 'RESOURCE_NOT_AVAILABLE',
418
457
  message: 'This resource is not available to the agent.',
419
458
  });
@@ -436,7 +475,7 @@ export function prepareApiBasedTools(adminforth, hiddenResourceIds = []) {
436
475
  toolName,
437
476
  userTimeZone,
438
477
  });
439
- return YAML.stringify(processedOutput);
478
+ return stringifyToolOutput(processedOutput);
440
479
  }),
441
480
  };
442
481
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/agent",
3
- "version": "1.52.6",
3
+ "version": "1.53.1",
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.0.1",
28
+ "adminforth": "3.1.1",
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.0.1"
70
+ "adminforth": "3.1.1"
71
71
  }
72
72
  }