@@ -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 ,
@@ -20,6 +21,11 @@ import {
2021 shortenProperties ,
2122} from './utils.js' ;
2223
24+ // Cache for normal Actor tools
25+ const normalActorToolsCache = new LruCache < ToolCacheEntry > ( {
26+ maxLength : TOOL_CACHE_MAX_SIZE ,
27+ } ) ;
28+
2329/**
2430 * Calls an Apify actor and retrieves the dataset items.
2531 *
@@ -83,13 +89,35 @@ export async function getNormalActorsAsTools(
8389 actors : string [ ] ,
8490 apifyToken : string ,
8591) : Promise < ToolWrap [ ] > {
92+ const tools : ToolWrap [ ] = [ ] ;
93+ const actorsToLoad : string [ ] = [ ] ;
94+ for ( const actorID of actors ) {
95+ const cacheEntry = normalActorToolsCache . get ( actorID ) ;
96+ if ( cacheEntry && cacheEntry . expiresAt > Date . now ( ) ) {
97+ tools . push ( cacheEntry . tool ) ;
98+ } else {
99+ actorsToLoad . push ( actorID ) ;
100+ }
101+ }
102+ if ( actorsToLoad . length === 0 ) {
103+ return tools ;
104+ }
105+
86106 const ajv = new Ajv ( { coerceTypes : 'array' , strict : false } ) ;
87107 const getActorDefinitionWithToken = async ( actorId : string ) => {
88108 return await getActorDefinition ( actorId , apifyToken ) ;
89109 } ;
90- const results = await Promise . all ( actors . map ( getActorDefinitionWithToken ) ) ;
91- const tools : ToolWrap [ ] = [ ] ;
92- for ( const result of results ) {
110+ const results = await Promise . all ( actorsToLoad . map ( getActorDefinitionWithToken ) ) ;
111+
112+ // Zip the results with their corresponding actorIDs
113+ for ( let i = 0 ; i < results . length ; i ++ ) {
114+ const result = results [ i ] ;
115+ // We need to get the orignal input from the user
116+ // sonce the user can input real Actor ID like '3ox4R101TgZz67sLr' instead of
117+ // 'username/actorName' even though we encourage that.
118+ // And the getActorDefinition does not return the original input it received, just the actorFullName or actorID
119+ const actorIDOrName = actorsToLoad [ i ] ;
120+
93121 if ( result ) {
94122 if ( result . input && 'properties' in result . input && result . input ) {
95123 result . input . properties = markInputPropertiesAsRequired ( result . input ) ;
@@ -100,7 +128,7 @@ export async function getNormalActorsAsTools(
100128 }
101129 try {
102130 const memoryMbytes = result . defaultRunOptions ?. memoryMbytes || ACTOR_MAX_MEMORY_MBYTES ;
103- tools . push ( {
131+ const tool : ToolWrap = {
104132 type : 'actor' ,
105133 tool : {
106134 name : actorNameToToolName ( result . actorFullName ) ,
@@ -110,6 +138,11 @@ export async function getNormalActorsAsTools(
110138 ajvValidate : ajv . compile ( result . input || { } ) ,
111139 memoryMbytes : memoryMbytes > ACTOR_MAX_MEMORY_MBYTES ? ACTOR_MAX_MEMORY_MBYTES : memoryMbytes ,
112140 } ,
141+ } ;
142+ tools . push ( tool ) ;
143+ normalActorToolsCache . add ( actorIDOrName , {
144+ tool,
145+ expiresAt : Date . now ( ) + TOOL_CACHE_TTL_SECS * 1000 ,
113146 } ) ;
114147 } catch ( validationError ) {
115148 log . error ( `Failed to compile AJV schema for Actor: ${ result . actorFullName } . Error: ${ validationError } ` ) ;
0 commit comments