Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/tools/actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ EXAMPLES:
}
const content = [
// TODO: update result to say: this is result of info step, you must now call again with step=call and proper input
{ type: 'text', text: `**Input Schema:**\n${JSON.stringify(details.inputSchema, null, 0)}` },
{ type: 'text', text: `# Input Schema: \n${JSON.stringify(details.inputSchema, null, 0)}` },
];
/**
* Add Skyfire instructions also in the info step since clients are most likely truncating the long tool description of the call-actor.
Expand Down
24 changes: 17 additions & 7 deletions src/tools/fetch-actor-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,23 @@ USAGE EXAMPLES:
content: [{ type: 'text', text: `Actor information for '${parsed.actor}' was not found. Please check the Actor ID or name and ensure the Actor exists.` }],
};
}
return {
content: [
{ type: 'text', text: `**Actor card**:\n${details.actorCard}` },
{ type: 'text', text: `**README:**\n${details.readme}` },
{ type: 'text', text: `**Input Schema:**\n${JSON.stringify(details.inputSchema, null, 0)}` },
],
};

const actorUrl = `https://apify.com/${details.actorInfo.username}/${details.actorInfo.name}`;
// Add link to README title
details.readme = details.readme.replace(/^# /, `# [README](${actorUrl}/readme): `);

const content = [
{ type: 'text', text: `# Actor information\n${details.actorCard}` },
{ type: 'text', text: `${details.readme}` },
];

// Include input schema if it has properties
if (details.inputSchema.properties || Object.keys(details.inputSchema.properties).length !== 0) {
content.push({ type: 'text', text: `# [Input schema](${actorUrl}/input)\n\`\`\`json\n${JSON.stringify(details.inputSchema, null, 0)}\n\`\`\`` });
}
// Return the actor card, README, and input schema (if it has non-empty properties) as separate text blocks
// This allows better formatting in the final output
return { content };
},
} as InternalTool,
};
11 changes: 8 additions & 3 deletions src/tools/store_collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,14 @@ USAGE EXAMPLES:
content: [
{
type: 'text',
text: `**Search query:** ${parsed.search}\n\n`
+ `**Number of Actors found:** ${actorCards.length}\n\n`
+ `**Actor cards:**\n${actorCards.join('\n\n')}`,
text: `
# Search results:
- **Search query:** ${parsed.search}
- **Number of Actors found:** ${actorCards.length}

# Actors:

${actorCards.join('\n\n')}`,
},
],
};
Expand Down
21 changes: 11 additions & 10 deletions src/utils/actor-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,16 @@ export function formatActorToActorCard(
}

const actorFullName = `${actor.username}/${actor.name}`;
const actorUrl = `${APIFY_STORE_URL}/${actorFullName}`;

// Build the markdown lines
const markdownLines = [
`# [${actor.title}](${APIFY_STORE_URL}/${actorFullName}) (${actorFullName})`,
`**Developed by:** ${actor.username} ${actor.username === 'apify' ? '(Apify)' : '(community)'}`,
`**Description:** ${actor.description || 'No description provided.'}`,
`**Categories:** ${formattedCategories.length ? formattedCategories.join(', ') : 'Uncategorized'}`,
`**Pricing:** ${pricingInfo}`,
`## [${actor.title}](${actorUrl}) (\`${actorFullName}\`)`,
`- **URL:** ${actorUrl}`,
`- **Developed by:** [${actor.username}](${APIFY_STORE_URL}/${actor.username}) ${actor.username === 'apify' ? '(Apify)' : '(community)'}`,
`- **Description:** ${actor.description || 'No description provided.'}`,
`- **Categories:** ${formattedCategories.length ? formattedCategories.join(', ') : 'Uncategorized'}`,
`- **[Pricing](${actorUrl}/pricing):** ${pricingInfo}`,
];

// Add stats - handle different stat structures
Expand Down Expand Up @@ -80,18 +82,18 @@ export function formatActorToActorCard(
}

if (statsParts.length > 0) {
markdownLines.push(`**Stats:** ${statsParts.join(', ')}`);
markdownLines.push(`- **Stats:** ${statsParts.join(', ')}`);
}
}

// Add rating if available (ActorStoreList only)
if ('actorReviewRating' in actor && actor.actorReviewRating) {
markdownLines.push(`**Rating:** ${actor.actorReviewRating.toFixed(2)} out of 5`);
markdownLines.push(`- **Rating:** ${actor.actorReviewRating.toFixed(2)} out of 5`);
}

// Add modification date if available
if ('modifiedAt' in actor) {
markdownLines.push(`**Last modified:** ${actor.modifiedAt.toISOString()}`);
markdownLines.push(`- **Last modified:** ${actor.modifiedAt.toISOString()}`);
}

// Add deprecation warning if applicable
Expand All @@ -111,7 +113,6 @@ export function formatActorsListToActorCard(actors: (Actor | ExtendedActorStoreL
return [];
}
return actors.map((actor) => {
const card = formatActorToActorCard(actor);
return `- ${card}`;
return formatActorToActorCard(actor);
});
}
21 changes: 17 additions & 4 deletions src/utils/pricing-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,24 @@ function convertMinutesToGreatestUnit(minutes: number): { value: number; unit: s
return { value: Math.floor(minutes / (60 * 24)), unit: 'days' };
}

/**
* Formats the pay-per-event pricing information into a human-readable string.
*
* Example:
* This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for the following events:
* - Event title: Event description (Flat price: $X per event)
* - MCP server startup: Initial fee for starting the Kiwi MCP Server Actor (Flat price: $0.1 per event)
* - Flight search: Fee for searching flights using the Kiwi.com flight search engine (Flat price: $0.001 per event)
*
* For tiered pricing, the output is more complicated and the question is whether we want to simplify it in the future.
* @param pricingPerEvent
*/

function payPerEventPricingToString(pricingPerEvent: ExtendedPricingInfo['pricingPerEvent']): string {
if (!pricingPerEvent || !pricingPerEvent.actorChargeEvents) return 'No event pricing information available.';
const eventStrings: string[] = [];
for (const event of Object.values(pricingPerEvent.actorChargeEvents)) {
let eventStr = `- ${event.eventTitle}: ${event.eventDescription} `;
let eventStr = `\t- **${event.eventTitle}**: ${event.eventDescription} `;
if (typeof event.eventPriceUsd === 'number') {
eventStr += `(Flat price: $${event.eventPriceUsd} per event)`;
} else if (event.eventTieredPricingUsd) {
Expand All @@ -58,14 +71,14 @@ function payPerEventPricingToString(pricingPerEvent: ExtendedPricingInfo['pricin
}
eventStrings.push(eventStr);
}
return `This Actor charges per event as follows:\n${eventStrings.join('\n')}`;
return `This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for the following events:\n${eventStrings.join('\n')}`;
}

export function pricingInfoToString(pricingInfo: ExtendedPricingInfo | null): string {
// If there is no pricing infos entries the Actor is free to use
// based on https://github.com/apify/apify-core/blob/058044945f242387dde2422b8f1bef395110a1bf/src/packages/actor/src/paid_actors/paid_actors_common.ts#L691
if (pricingInfo === null || pricingInfo.pricingModel === ACTOR_PRICING_MODEL.FREE) {
return 'This Actor is free to use; the user only pays for the computing resources consumed by the Actor.';
return 'User pays for the computing resources consumed by the Actor';
}
if (pricingInfo.pricingModel === ACTOR_PRICING_MODEL.PRICE_PER_DATASET_ITEM) {
const customUnitName = pricingInfo.unitName !== 'result' ? pricingInfo.unitName : '';
Expand All @@ -92,5 +105,5 @@ export function pricingInfoToString(pricingInfo: ExtendedPricingInfo | null): st
if (pricingInfo.pricingModel === ACTOR_PRICING_MODEL.PAY_PER_EVENT) {
return payPerEventPricingToString(pricingInfo.pricingPerEvent);
}
return 'unknown';
return 'Not available';
}