1+ import type { Actor , Build } from 'apify-client' ;
12import { z } from 'zod' ;
23import zodToJsonSchema from 'zod-to-json-schema' ;
34
45import { ApifyClient } from '../apify-client.js' ;
5- import { HelperTools } from '../const.js' ;
6+ import { APIFY_STORE_URL , HelperTools } from '../const.js' ;
67import type { ExtendedPricingInfo , IActorInputSchema , InternalTool , ToolEntry } from '../types.js' ;
78import { ajv } from '../utils/ajv.js' ;
89import { getCurrentPricingInfo , pricingInfoToString } from '../utils/pricing-info.js' ;
@@ -14,47 +15,34 @@ const getActorDetailsToolArgsSchema = z.object({
1415 . describe ( `Actor ID or full name in the format "username/name", e.g., "apify/rag-web-browser".` ) ,
1516} ) ;
1617
17- interface IGetActorDetailsToolResult {
18- id : string ;
19- actorFullName : string ;
20-
21- isPublic : boolean ;
22- isDeprecated : boolean ;
23- createdAt : string ;
24- modifiedAt : string ;
25-
26- categories ?: string [ ] ;
27- description : string ;
28- readme : string ;
29-
30- inputSchema : IActorInputSchema ;
31-
32- pricingInfo : string ; // We convert the pricing info into a string representation
33-
34- usageStatistics : {
35- totalUsers : {
36- allTime : number ;
37- last7Days : number ;
38- last30Days : number ;
39- last90Days : number ;
40- } ;
41- failedRunsInLast30Days : number | string ; // string for 'unknown' case
42- }
18+ // Helper function to format categories from uppercase with underscores to proper case
19+ function formatCategories ( categories ?: string [ ] ) : string [ ] {
20+ if ( ! categories ) return [ ] ;
21+
22+ return categories . map ( ( category ) => {
23+ const formatted = category
24+ . toLowerCase ( )
25+ . split ( '_' )
26+ . map ( ( word ) => word . charAt ( 0 ) . toUpperCase ( ) + word . slice ( 1 ) )
27+ . join ( ' ' ) ;
28+ // Special case for MCP server, AI, and SEO tools
29+ return formatted . replace ( 'Mcp Server' , 'MCP Server' ) . replace ( 'Ai' , 'AI' ) . replace ( 'Seo' , 'SEO' ) ;
30+ } ) ;
4331}
4432
4533export const getActorDetailsTool : ToolEntry = {
4634 type : 'internal' ,
4735 tool : {
4836 name : HelperTools . ACTOR_GET_DETAILS ,
49- description : `Retrieve information about an Actor by its ID or full name.\n`
50- + 'The Actor name is always composed of "username/name", for example, "apify/rag-web-browser" .\n'
51- + 'This tool returns information about the Actor, including whether it is public or deprecated, '
52- + 'when it was created or modified, the categories in which the Actor is listed, a description, '
53- + 'a README (the Actor\'s documentation), the input schema, and usage statistics - such as how many users are using it '
54- + 'and the number of failed runs of the Actor.\n'
55- + 'For example, use this tool when a user wants to know more about a specific Actor or wants to use optional '
56- + 'or advanced parameters of the Actor that are not listed in the default Actor tool input schema - '
57- + 'so you know the details and how to pass them.' ,
37+ description : `Retrieve comprehensive details about an Actor using its ID or full name.\n`
38+ + `This tool provides the Actor's title, description, URL, documentation (README), input schema, categories, pricing, and usage statistics .\n`
39+ + `Specify the Actor name in the format "username/name" (e.g., "apify/rag-web-browser").\n`
40+ + `The response is formatted in markdown and should be rendered as-is.\n`
41+ + `USAGE:\n`
42+ + `- Use when a user requests information about an Actor, such as its details, description, input schema, or documentation .\n`
43+ + `EXAMPLES:\n`
44+ + `- user_input: How to use apify/rag-web-browser\n`
45+ + `- user_input: What is the input schema for apify/rag-web-browser` ,
5846 inputSchema : zodToJsonSchema ( getActorDetailsToolArgsSchema ) ,
5947 ajvValidate : ajv . compile ( zodToJsonSchema ( getActorDetailsToolArgsSchema ) ) ,
6048 call : async ( toolArgs ) => {
@@ -63,7 +51,7 @@ export const getActorDetailsTool: ToolEntry = {
6351 const parsed = getActorDetailsToolArgsSchema . parse ( args ) ;
6452 const client = new ApifyClient ( { token : apifyToken } ) ;
6553
66- const [ actorInfo , buildInfo ] = await Promise . all ( [
54+ const [ actorInfo , buildInfo ] : [ Actor | undefined , Build | undefined ] = await Promise . all ( [
6755 client . actor ( parsed . actor ) . get ( ) ,
6856 client . actor ( parsed . actor ) . defaultBuild ( ) . then ( async ( build ) => build . get ( ) ) ,
6957 ] ) ;
@@ -83,40 +71,34 @@ export const getActorDetailsTool: ToolEntry = {
8371
8472 const currentPricingInfo = getCurrentPricingInfo ( actorInfo . pricingInfos || [ ] , new Date ( ) ) ;
8573
86- const result : IGetActorDetailsToolResult = {
87- id : actorInfo . id ,
88- actorFullName : `${ actorInfo . username } /${ actorInfo . name } ` ,
89-
90- isPublic : actorInfo . isPublic ,
91- isDeprecated : actorInfo . isDeprecated || false ,
92- createdAt : actorInfo . createdAt . toISOString ( ) ,
93- modifiedAt : actorInfo . modifiedAt . toISOString ( ) ,
94-
95- categories : actorInfo . categories ,
96- description : actorInfo . description || 'No description provided.' ,
97- readme : buildInfo . actorDefinition . readme || 'No README provided.' ,
98-
99- inputSchema,
100-
101- pricingInfo : pricingInfoToString ( currentPricingInfo as ( ExtendedPricingInfo | null ) ) ,
74+ // Format categories for display
75+ const formattedCategories = formatCategories ( actorInfo . categories ) ;
76+
77+ // Note: In the public API, we are missing maintainedByApify property, so we cannot use it here.
78+ // Note: Actor rating is not in public API, we need to add it (actorUtils.getActorReviewRatingNumber(actorId))
79+ const actorFullName = `${ actorInfo . username } /${ actorInfo . name } ` ;
80+ const markdownLines = [
81+ `Actor details (always present Actor information in this format, always include URL):\n` ,
82+ `# [${ actorInfo . title } ](${ APIFY_STORE_URL } /${ actorFullName } ) (${ actorFullName } )` ,
83+ `**Developed by:** ${ actorInfo . username } Maintained by ${ actorInfo . username === 'apify' ? '(Apify)' : '(community)' } ` ,
84+ `**Description:** ${ actorInfo . description || 'No description provided.' } ` ,
85+ `**Categories:** ${ formattedCategories . length ? formattedCategories . join ( ', ' ) : 'Uncategorized' } ` ,
86+ `**Pricing:** ${ pricingInfoToString ( currentPricingInfo as ( ExtendedPricingInfo | null ) ) } ` ,
87+ `**Stats:** ${ actorInfo . stats . totalUsers . toLocaleString ( ) } total users, ${ actorInfo . stats . totalUsers30Days . toLocaleString ( ) } monthly users` ,
88+ `Last modified: ${ actorInfo . modifiedAt . toISOString ( ) } ` ,
89+ ] ;
90+ if ( actorInfo . isDeprecated ) {
91+ markdownLines . push ( '\n>This Actor is deprecated and may not be maintained anymore.' ) ;
92+ }
93+ const actorCard = markdownLines . join ( '\n' ) ;
10294
103- usageStatistics : {
104- totalUsers : {
105- allTime : actorInfo . stats . totalUsers ,
106- last7Days : actorInfo . stats . totalUsers7Days ,
107- last30Days : actorInfo . stats . totalUsers30Days ,
108- last90Days : actorInfo . stats . totalUsers90Days ,
109- } ,
110- failedRunsInLast30Days : (
111- 'publicActorRunStats30Days' in actorInfo . stats && 'FAILED' in ( actorInfo . stats . publicActorRunStats30Days as object )
112- ) ? ( actorInfo . stats . publicActorRunStats30Days as { FAILED : number } ) . FAILED : 'unknown' ,
113- } ,
114- } ;
11595 return {
116- content : [ {
117- type : 'text' ,
118- text : JSON . stringify ( result ) ,
119- } ] ,
96+ content : [
97+ { type : 'text' , text : actorCard } ,
98+ // LLM properly format Actor card, if README and input schema are separate text blocks
99+ { type : 'text' , text : `**README**:\n\n${ buildInfo . actorDefinition . readme || 'No README provided.' } ` } ,
100+ { type : 'text' , text : `**Input Schema**:\n\n${ JSON . stringify ( inputSchema , null , 0 ) } ` } ,
101+ ] ,
120102 } ;
121103 } ,
122104 } as InternalTool ,
0 commit comments