@@ -2,14 +2,15 @@ import type { Client } from '@modelcontextprotocol/sdk/client/index.js';
22import { Ajv } from 'ajv' ;
33import type { ActorCallOptions } from 'apify-client' ;
44
5+ import { LruCache } from '@apify/datastructures' ;
56import log from '@apify/log' ;
67
78import { ApifyClient } from '../apify-client.js' ;
8- import { ACTOR_ADDITIONAL_INSTRUCTIONS , ACTOR_MAX_MEMORY_MBYTES } from '../const.js' ;
9+ import { ACTOR_ADDITIONAL_INSTRUCTIONS , ACTOR_MAX_MEMORY_MBYTES , TOOL_CACHE_MAX_SIZE , TOOL_CACHE_TTL_SECS } from '../const.js' ;
910import { getActorsMCPServerURL , isActorMCPServer } from '../mcp/actors.js' ;
1011import { createMCPClient } from '../mcp/client.js' ;
1112import { getMCPServerTools } from '../mcp/proxy.js' ;
12- import type { ToolWrap } from '../types.js' ;
13+ import type { ToolCacheEntry , ToolWrap } from '../types.js' ;
1314import { getActorDefinition } from './build.js' ;
1415import {
1516 actorNameToToolName ,
@@ -58,6 +59,10 @@ export async function callActorGetDataset(
5859 }
5960}
6061
62+ // Cache for normal Actor tools
63+ const normalActorToolsCache = new LruCache < ToolCacheEntry > ( {
64+ maxLength : TOOL_CACHE_MAX_SIZE ,
65+ } ) ;
6166/**
6267 * This function is used to fetch normal non-MCP server Actors as a tool.
6368 *
@@ -83,13 +88,31 @@ export async function getNormalActorsAsTools(
8388 actors : string [ ] ,
8489 apifyToken : string ,
8590) : Promise < ToolWrap [ ] > {
91+ const tools : ToolWrap [ ] = [ ] ;
92+ const actorsLoadedFromCache : string [ ] = [ ] ;
93+ for ( const actorID of actors ) {
94+ const cacheEntry = normalActorToolsCache . get ( actorID ) ;
95+ if ( cacheEntry && cacheEntry . expiresAt > Date . now ( ) ) {
96+ tools . push ( cacheEntry . tool ) ;
97+ actorsLoadedFromCache . push ( actorID ) ;
98+ }
99+ }
100+ const actorsToLoad = actors . filter ( ( actorID ) => ! actorsLoadedFromCache . includes ( actorID ) ) ;
101+ if ( actorsToLoad . length === 0 ) {
102+ return tools ;
103+ }
104+
86105 const ajv = new Ajv ( { coerceTypes : 'array' , strict : false } ) ;
87106 const getActorDefinitionWithToken = async ( actorId : string ) => {
88107 return await getActorDefinition ( actorId , apifyToken ) ;
89108 } ;
90- const results = await Promise . all ( actors . map ( getActorDefinitionWithToken ) ) ;
91- const tools : ToolWrap [ ] = [ ] ;
92- for ( const result of results ) {
109+ const results = await Promise . all ( actorsToLoad . map ( getActorDefinitionWithToken ) ) ;
110+
111+ // Zip the results with their corresponding actorIDs
112+ for ( let i = 0 ; i < results . length ; i ++ ) {
113+ const result = results [ i ] ;
114+ const actorID = actorsToLoad [ i ] ;
115+
93116 if ( result ) {
94117 if ( result . input && 'properties' in result . input && result . input ) {
95118 result . input . properties = markInputPropertiesAsRequired ( result . input ) ;
@@ -100,7 +123,7 @@ export async function getNormalActorsAsTools(
100123 }
101124 try {
102125 const memoryMbytes = result . defaultRunOptions ?. memoryMbytes || ACTOR_MAX_MEMORY_MBYTES ;
103- tools . push ( {
126+ const tool : ToolWrap = {
104127 type : 'actor' ,
105128 tool : {
106129 name : actorNameToToolName ( result . actorFullName ) ,
@@ -110,6 +133,11 @@ export async function getNormalActorsAsTools(
110133 ajvValidate : ajv . compile ( result . input || { } ) ,
111134 memoryMbytes : memoryMbytes > ACTOR_MAX_MEMORY_MBYTES ? ACTOR_MAX_MEMORY_MBYTES : memoryMbytes ,
112135 } ,
136+ } ;
137+ tools . push ( tool ) ;
138+ normalActorToolsCache . add ( actorID , {
139+ tool,
140+ expiresAt : Date . now ( ) + TOOL_CACHE_TTL_SECS * 1000 ,
113141 } ) ;
114142 } catch ( validationError ) {
115143 log . error ( `Failed to compile AJV schema for Actor: ${ result . actorFullName } . Error: ${ validationError } ` ) ;
0 commit comments