@@ -11,6 +11,50 @@ import { formatActorToActorCard, formatActorToStructuredCard } from './actor-car
1111import { logHttpError } from './logging.js' ;
1212import { buildMCPResponse } from './mcp.js' ;
1313
14+ /**
15+ * Convert a type object to TypeScript-like string representation.
16+ * Used for human-readable text output.
17+ *
18+ * Example:
19+ * Input: { first_number: "number", tags: ["string"], user: { name: "string" } }
20+ * Output: "{ first_number: number, tags: string[], user: { name: string } }"
21+ */
22+ function typeObjectToString ( obj : Record < string , unknown > ) : string {
23+ const pairs : string [ ] = [ ] ;
24+
25+ for ( const [ key , value ] of Object . entries ( obj ) ) {
26+ if ( Array . isArray ( value ) ) {
27+ // Array type
28+ const itemType = typeValueToString ( value [ 0 ] ) ;
29+ pairs . push ( `${ key } : ${ itemType } []` ) ;
30+ } else if ( typeof value === 'object' && value !== null ) {
31+ // Nested object type
32+ const nestedStr = typeObjectToString ( value as Record < string , unknown > ) ;
33+ pairs . push ( `${ key } : ${ nestedStr } ` ) ;
34+ } else if ( typeof value === 'string' ) {
35+ // Primitive type
36+ pairs . push ( `${ key } : ${ value } ` ) ;
37+ }
38+ }
39+
40+ return `{ ${ pairs . join ( ', ' ) } }` ;
41+ }
42+
43+ /**
44+ * Convert a single type value to string.
45+ */
46+ function typeValueToString ( value : unknown ) : string {
47+ if ( Array . isArray ( value ) ) {
48+ const itemType = typeValueToString ( value [ 0 ] ) ;
49+ return `${ itemType } []` ;
50+ } if ( typeof value === 'object' && value !== null ) {
51+ return typeObjectToString ( value as Record < string , unknown > ) ;
52+ } if ( typeof value === 'string' ) {
53+ return value ;
54+ }
55+ return 'unknown' ;
56+ }
57+
1458// Keep the type here since it is a self-contained module
1559export type ActorDetailsResult = {
1660 actorInfo : Actor ;
@@ -98,7 +142,7 @@ export function processActorDetailsForResponse(details: ActorDetailsResult) {
98142 * Used by both public and internal fetch-actor-details tools.
99143 *
100144 * Behavior:
101- * - If output is undefined or empty object: use defaults (all true except mcpTools)
145+ * - If output is undefined or empty object: use defaults (all true except mcpTools and outputSchema )
102146 * - If any property is explicitly set: only include sections with explicit true values
103147 */
104148export const actorDetailsOutputOptionsSchema = z . object ( {
@@ -109,6 +153,7 @@ export const actorDetailsOutputOptionsSchema = z.object({
109153 metadata : z . boolean ( ) . optional ( ) . describe ( 'Include developer, categories, last modified date, and deprecation status.' ) ,
110154 inputSchema : z . boolean ( ) . optional ( ) . describe ( 'Include required input parameters schema.' ) ,
111155 readme : z . boolean ( ) . optional ( ) . describe ( 'Include full README documentation.' ) ,
156+ outputSchema : z . boolean ( ) . optional ( ) . describe ( 'Include inferred output schema from recent successful runs (TypeScript type).' ) ,
112157 mcpTools : z . boolean ( ) . optional ( ) . describe ( 'List available tools (only for MCP server Actors).' ) ,
113158} ) ;
114159
@@ -120,6 +165,7 @@ export const actorDetailsOutputDefaults = {
120165 metadata : true ,
121166 inputSchema : true ,
122167 readme : true ,
168+ outputSchema : false ,
123169 mcpTools : false ,
124170} ;
125171
@@ -145,6 +191,7 @@ export function resolveOutputOptions(output?: z.infer<typeof actorDetailsOutputO
145191 metadata : output ?. metadata === true ,
146192 inputSchema : output ?. inputSchema === true ,
147193 readme : output ?. readme === true ,
194+ outputSchema : output ?. outputSchema === true ,
148195 mcpTools : output ?. mcpTools === true ,
149196 } ;
150197}
@@ -224,7 +271,7 @@ You can search for available Actors using the tool: ${HelperTools.STORE_SEARCH}.
224271
225272/**
226273 * Build text and structured response for actor details.
227- * Handles all resolved output options: description, stats, readme, inputSchema, mcpTools.
274+ * Handles all resolved output options: description, stats, readme, inputSchema, outputSchema, mcpTools.
228275 * All output properties should be boolean (resolved via resolveOutputOptions).
229276 */
230277export async function buildActorDetailsTextResponse ( options : {
@@ -238,17 +285,19 @@ export async function buildActorDetailsTextResponse(options: {
238285 metadata : boolean ;
239286 readme : boolean ;
240287 inputSchema : boolean ;
288+ outputSchema : boolean ;
241289 mcpTools : boolean ;
242290 } ;
243291 cardOptions : ActorCardOptions ;
244292 apifyClient : ApifyClient ;
245293 apifyToken : string ;
294+ actorOutputSchema ?: Record < string , unknown > | null ;
246295 skyfireMode ?: boolean ;
247296} ) : Promise < {
248297 texts : string [ ] ;
249298 structuredContent : Record < string , unknown > ;
250299} > {
251- const { actorName, details, output, cardOptions, apifyClient, apifyToken, skyfireMode } = options ;
300+ const { actorName, details, output, cardOptions, apifyClient, apifyToken, actorOutputSchema , skyfireMode } = options ;
252301
253302 const actorUrl = `https://apify.com/${ details . actorInfo . username } /${ details . actorInfo . name } ` ;
254303 const formattedReadme = details . readme . replace ( / ^ # / , `# [README](${ actorUrl } /readme): ` ) ;
@@ -276,6 +325,16 @@ export async function buildActorDetailsTextResponse(options: {
276325 texts . push ( `# [Input schema](${ actorUrl } /input)\n\`\`\`json\n${ JSON . stringify ( details . inputSchema ) } \n\`\`\`` ) ;
277326 }
278327
328+ // Add output schema if requested
329+ if ( output . outputSchema ) {
330+ if ( actorOutputSchema && Object . keys ( actorOutputSchema ) . length > 0 ) {
331+ const typeString = typeObjectToString ( actorOutputSchema ) ;
332+ texts . push ( `# Output Schema (TypeScript)\nInferred from recent successful runs:\n\`\`\`typescript\ntype ActorOutput = ${ typeString } \n\`\`\`` ) ;
333+ } else {
334+ texts . push ( `# Output Schema\nNo output schema available. The Actor may not have recent successful runs, or the output structure could not be determined.` ) ;
335+ }
336+ }
337+
279338 // Handle MCP tools
280339 if ( output . mcpTools ) {
281340 const message = await getMcpToolsMessage ( actorName , apifyClient , apifyToken , skyfireMode ) ;
@@ -287,6 +346,7 @@ export async function buildActorDetailsTextResponse(options: {
287346 actorInfo : needsCard ? details . actorCardStructured : undefined ,
288347 readme : output . readme ? formattedReadme : undefined ,
289348 inputSchema : output . inputSchema ? details . inputSchema : undefined ,
349+ outputSchema : output . outputSchema ? actorOutputSchema : undefined ,
290350 } ;
291351
292352 return { texts, structuredContent } ;
0 commit comments