Skip to content

Commit 180112f

Browse files
committed
refactor: split get-actor-run into default/openai variants
Extract the mode-specific branches from run.ts (getActorRun) into: - default/get-actor-run.ts — full JSON run dump - openai/get-actor-run.ts — abbreviated text with widget metadata Shared schema, metadata, and data-fetching logic extracted to core/get-actor-run-common.ts. Mode-independent tools in run.ts (getActorRunLog, abortActorRun) are untouched. The getActorRun export becomes a thin adapter dispatching based on uiMode.
1 parent bdf56a1 commit 180112f

File tree

4 files changed

+303
-160
lines changed

4 files changed

+303
-160
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { z } from 'zod';
2+
3+
import log from '@apify/log';
4+
5+
import type { ApifyClient } from '../../apify-client.js';
6+
import { HelperTools, TOOL_STATUS } from '../../const.js';
7+
import { getWidgetConfig, WIDGET_URIS } from '../../resources/widgets.js';
8+
import type { HelperTool, ToolInputSchema } from '../../types.js';
9+
import { compileSchema } from '../../utils/ajv.js';
10+
import { buildMCPResponse } from '../../utils/mcp.js';
11+
import { generateSchemaFromItems } from '../../utils/schema-generation.js';
12+
import { getActorRunOutputSchema } from '../structured-output-schemas.js';
13+
14+
/**
15+
* Zod schema for get-actor-run arguments — shared between default and openai variants.
16+
*/
17+
export const getActorRunArgs = z.object({
18+
runId: z.string()
19+
.min(1)
20+
.describe('The ID of the Actor run.'),
21+
});
22+
23+
const GET_ACTOR_RUN_DESCRIPTION = `Get detailed information about a specific Actor run by runId.
24+
The results will include run metadata (status, timestamps), performance stats, and resource IDs (datasetId, keyValueStoreId, requestQueueId).
25+
26+
CRITICAL WARNING: NEVER call this tool immediately after call-actor in UI mode. The call-actor response includes a widget that automatically polls for updates. Calling this tool after call-actor is FORBIDDEN and unnecessary.
27+
28+
USAGE:
29+
- Use ONLY when user explicitly asks about a specific run's status or details.
30+
- Use ONLY for runs that were started outside the current conversation.
31+
- DO NOT use this tool as part of the call-actor workflow in UI mode.
32+
33+
USAGE EXAMPLES:
34+
- user_input: Show details of run y2h7sK3Wc (where y2h7sK3Wc is an existing run)
35+
- user_input: What is the datasetId for run y2h7sK3Wc?`;
36+
37+
/**
38+
* Shared tool metadata for get-actor-run — everything except the `call` handler.
39+
* Used by both default and openai variants.
40+
*/
41+
export const getActorRunMetadata: Omit<HelperTool, 'call'> = {
42+
type: 'internal',
43+
name: HelperTools.ACTOR_RUNS_GET,
44+
description: GET_ACTOR_RUN_DESCRIPTION,
45+
inputSchema: z.toJSONSchema(getActorRunArgs) as ToolInputSchema,
46+
outputSchema: getActorRunOutputSchema,
47+
ajvValidate: compileSchema({ ...z.toJSONSchema(getActorRunArgs), additionalProperties: true }),
48+
requiresSkyfirePayId: true,
49+
_meta: {
50+
...getWidgetConfig(WIDGET_URIS.ACTOR_RUN)?.meta,
51+
},
52+
annotations: {
53+
title: 'Get Actor run',
54+
readOnlyHint: true,
55+
idempotentHint: true,
56+
openWorldHint: false,
57+
},
58+
};
59+
60+
/**
61+
* Structured content returned from fetching actor run data.
62+
*/
63+
export type ActorRunStructuredContent = {
64+
runId: string;
65+
actorName?: string;
66+
status: string;
67+
startedAt: string;
68+
finishedAt?: string;
69+
stats?: unknown;
70+
dataset?: {
71+
datasetId: string;
72+
itemCount: number;
73+
schema: unknown;
74+
previewItems: unknown[];
75+
};
76+
};
77+
78+
/**
79+
* Result of fetching actor run data — shared between both variants.
80+
*/
81+
export type FetchActorRunResult = {
82+
run: Record<string, unknown>;
83+
structuredContent: ActorRunStructuredContent;
84+
};
85+
86+
/**
87+
* Fetches actor run data, resolves actor name, and fetches dataset results if completed.
88+
* Shared data-fetching logic used by both default and openai variants.
89+
*
90+
* Returns the run data and structured content, or an early error response.
91+
*/
92+
export async function fetchActorRunData(params: {
93+
runId: string;
94+
client: ApifyClient;
95+
mcpSessionId?: string;
96+
}): Promise<{ error: object } | { result: FetchActorRunResult }> {
97+
const { runId, client, mcpSessionId } = params;
98+
99+
const run = await client.run(runId).get();
100+
101+
if (!run) {
102+
return {
103+
error: buildMCPResponse({
104+
texts: [`Run with ID '${runId}' not found.`],
105+
isError: true,
106+
toolStatus: TOOL_STATUS.SOFT_FAIL,
107+
}),
108+
};
109+
}
110+
111+
log.debug('Get actor run', { runId, status: run.status, mcpSessionId });
112+
113+
let actorName: string | undefined;
114+
if (run.actId) {
115+
try {
116+
const actor = await client.actor(run.actId).get();
117+
if (actor) {
118+
actorName = `${actor.username}/${actor.name}`;
119+
}
120+
} catch (error) {
121+
log.warning(`Failed to fetch actor name for run ${runId}`, { mcpSessionId, error });
122+
}
123+
}
124+
125+
const structuredContent: ActorRunStructuredContent = {
126+
runId: run.id,
127+
actorName,
128+
status: run.status,
129+
startedAt: run.startedAt?.toISOString() || '',
130+
finishedAt: run.finishedAt?.toISOString(),
131+
stats: run.stats,
132+
};
133+
134+
// If completed, fetch dataset results
135+
if (run.status === 'SUCCEEDED' && run.defaultDatasetId) {
136+
const dataset = client.dataset(run.defaultDatasetId);
137+
const datasetItems = await dataset.listItems({ limit: 5 });
138+
139+
const generatedSchema = generateSchemaFromItems(datasetItems.items, {
140+
clean: true,
141+
arrayMode: 'all',
142+
});
143+
144+
structuredContent.dataset = {
145+
datasetId: run.defaultDatasetId,
146+
itemCount: datasetItems.count,
147+
schema: generatedSchema || { type: 'object', properties: {} },
148+
previewItems: datasetItems.items,
149+
};
150+
}
151+
152+
return { result: { run: run as unknown as Record<string, unknown>, structuredContent } };
153+
}

src/tools/default/get-actor-run.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { createApifyClientWithSkyfireSupport } from '../../apify-client.js';
2+
import { TOOL_STATUS } from '../../const.js';
3+
import type { InternalToolArgs, ToolEntry } from '../../types.js';
4+
import { logHttpError } from '../../utils/logging.js';
5+
import { buildMCPResponse, buildUsageMeta } from '../../utils/mcp.js';
6+
import {
7+
fetchActorRunData,
8+
getActorRunArgs,
9+
getActorRunMetadata,
10+
} from '../core/get-actor-run-common.js';
11+
12+
/**
13+
* Default mode get-actor-run tool.
14+
* Returns full JSON dump of the run without widget metadata.
15+
*/
16+
export const defaultGetActorRun: ToolEntry = {
17+
...getActorRunMetadata,
18+
call: async (toolArgs: InternalToolArgs) => {
19+
const { args, apifyToken, apifyMcpServer, mcpSessionId } = toolArgs;
20+
const parsed = getActorRunArgs.parse(args);
21+
22+
const client = createApifyClientWithSkyfireSupport(apifyMcpServer, args, apifyToken);
23+
24+
try {
25+
const fetchResult = await fetchActorRunData({
26+
runId: parsed.runId,
27+
client,
28+
mcpSessionId,
29+
});
30+
31+
if ('error' in fetchResult) {
32+
return fetchResult.error;
33+
}
34+
35+
const { run, structuredContent } = fetchResult.result;
36+
37+
const texts = [
38+
`# Actor Run Information\n\`\`\`json\n${JSON.stringify(run, null, 2)}\n\`\`\``,
39+
];
40+
41+
return buildMCPResponse({
42+
texts,
43+
structuredContent,
44+
_meta: buildUsageMeta(run),
45+
});
46+
} catch (error) {
47+
logHttpError(error, 'Failed to get Actor run', { runId: parsed.runId });
48+
return buildMCPResponse({
49+
texts: [`Failed to get Actor run '${parsed.runId}': ${error instanceof Error ? error.message : String(error)}.
50+
Please verify the run ID and ensure that the run exists.`],
51+
isError: true,
52+
toolStatus: TOOL_STATUS.SOFT_FAIL,
53+
});
54+
}
55+
},
56+
} as const;

src/tools/openai/get-actor-run.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { createApifyClientWithSkyfireSupport } from '../../apify-client.js';
2+
import { TOOL_STATUS } from '../../const.js';
3+
import { getWidgetConfig, WIDGET_URIS } from '../../resources/widgets.js';
4+
import type { InternalToolArgs, ToolEntry } from '../../types.js';
5+
import { logHttpError } from '../../utils/logging.js';
6+
import { buildMCPResponse, buildUsageMeta } from '../../utils/mcp.js';
7+
import {
8+
fetchActorRunData,
9+
getActorRunArgs,
10+
getActorRunMetadata,
11+
} from '../core/get-actor-run-common.js';
12+
13+
/**
14+
* OpenAI mode get-actor-run tool.
15+
* Returns abbreviated text with widget metadata for interactive progress display.
16+
*/
17+
export const openaiGetActorRun: ToolEntry = {
18+
...getActorRunMetadata,
19+
call: async (toolArgs: InternalToolArgs) => {
20+
const { args, apifyToken, apifyMcpServer, mcpSessionId } = toolArgs;
21+
const parsed = getActorRunArgs.parse(args);
22+
23+
const client = createApifyClientWithSkyfireSupport(apifyMcpServer, args, apifyToken);
24+
25+
try {
26+
const fetchResult = await fetchActorRunData({
27+
runId: parsed.runId,
28+
client,
29+
mcpSessionId,
30+
});
31+
32+
if ('error' in fetchResult) {
33+
return fetchResult.error;
34+
}
35+
36+
const { run, structuredContent } = fetchResult.result;
37+
38+
const statusText = run.status === 'SUCCEEDED' && structuredContent.dataset
39+
? `Actor run ${parsed.runId} completed successfully with ${structuredContent.dataset.itemCount} items. A widget has been rendered with the details.`
40+
: `Actor run ${parsed.runId} status: ${run.status as string}. A progress widget has been rendered.`;
41+
42+
const widgetConfig = getWidgetConfig(WIDGET_URIS.ACTOR_RUN);
43+
const usageMeta = buildUsageMeta(run);
44+
return buildMCPResponse({
45+
texts: [statusText],
46+
structuredContent,
47+
_meta: {
48+
...widgetConfig?.meta,
49+
...usageMeta,
50+
},
51+
});
52+
} catch (error) {
53+
logHttpError(error, 'Failed to get Actor run', { runId: parsed.runId });
54+
return buildMCPResponse({
55+
texts: [`Failed to get Actor run '${parsed.runId}': ${error instanceof Error ? error.message : String(error)}.
56+
Please verify the run ID and ensure that the run exists.`],
57+
isError: true,
58+
toolStatus: TOOL_STATUS.SOFT_FAIL,
59+
});
60+
}
61+
},
62+
} as const;

0 commit comments

Comments
 (0)