Run Actor
One script reply has been approved by the moderators Verified

Runs an Actor. You can optionally override the Actor’s input configuration by providing a custom body, this body will override the prefilled input values.

Created by jakub.drobnik222 88 days ago Picked 21 times
Submitted by jakub.drobnik222 Bun
Verified 88 days ago
1
import { ActorListSortBy, ApifyClient } from 'apify-client@^2.19.0';
2

3
type ApifyApiKey = {
4
  api_key: string;
5
};
6

7
type Apify = {
8
  token: string;
9
};
10

11
type ActorSource = 'RECENTLY_USED_ACTORS' | 'APIFY_STORE_ACTORS';
12

13
type MemoryInMb =
14
  | '128'
15
  | '256'
16
  | '512'
17
  | '1024'
18
  | '2048'
19
  | '4096'
20
  | '8192'
21
  | '16384'
22
  | '32768';
23

24
export type DynSelect_actorId = string;
25
export async function actorId(actorSource: ActorSource, api_key?: ApifyApiKey, oauth_token?: Apify) {
26
  if (!api_key?.api_key && !oauth_token?.token) {
27
    return [{ value: '', label: 'Missing Apify API key or OAuth token' }];
28
  }
29

30
  const client = createClient(api_key, oauth_token);
31

32
  const mapActorToSelectOption = (actor: any) => {
33
    const optionName = actor.title
34
      ? `${actor.title} (${actor.username}/${actor.name})`
35
      : `${actor.username}/${actor.name}`;
36

37
    return {
38
      label: optionName,
39
      value: actor.id,
40
    };
41
  };
42

43
  try {
44
    if (actorSource === 'RECENTLY_USED_ACTORS') {
45
      const recentActors = await client.actors().list({
46
        limit: 100,
47
        offset: 0,
48
        sortBy: ActorListSortBy.LAST_RUN_STARTED_AT,
49
        desc: true,
50
      });
51
      return recentActors.items.map(mapActorToSelectOption);
52
    }
53

54
    const storeActors = await client.store().list({
55
      limit: 1000,
56
      offset: 0,
57
    });
58
    return storeActors.items.map(mapActorToSelectOption);
59
  } catch (error: any) {
60
    return [
61
      { value: '', label: `Failed to load actors: ${error.message || error}` },
62
    ];
63
  }
64
}
65

66
const createClient = (api_key?: ApifyApiKey, oauth_token?: Apify): ApifyClient => {
67
  const token = oauth_token?.token ?? api_key?.api_key;
68
  if (!token) {
69
    throw new Error('Missing Apify API key or OAuth token');
70
  }
71

72
  return new ApifyClient({
73
    token: token,
74
    requestInterceptors: [
75
      (request) => {
76
        if (!request.headers) {
77
          request.headers = {};
78
        }
79
        request.headers['x-apify-integration-platform'] = 'windmill';
80
        return request;
81
      },
82
    ],
83
  });
84
};
85

86
async function pollRunStatus(
87
  client: ApifyClient,
88
  runId: string,
89
  options: { throwIfNotSucceeded: boolean; } = { throwIfNotSucceeded: false }
90
): Promise<any> {
91
  let status = '';
92
  let runData: any;
93
  while (true) {
94
    runData = await client.run(runId).get();
95
    status = runData.status;
96
    if (['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(status)) break;
97
    await new Promise((res) => setTimeout(res, 1000));
98
  }
99

100
  if (options.throwIfNotSucceeded && status !== 'SUCCEEDED') {
101
    throw new Error(`Actor run did not succeed: ${status}`);
102
  }
103

104
  return runData;
105
}
106

107
async function getBuildByTag(
108
  client: ApifyClient,
109
  actorId: string,
110
  buildTag: string,
111
  actorData: any
112
) {
113
  const buildByTag = actorData.taggedBuilds && actorData.taggedBuilds[buildTag];
114
  if (!buildByTag?.buildId) {
115
    throw new Error(
116
      `Build tag '${buildTag}' does not exist for actor ${
117
        actorData.title ?? actorData.name ?? actorId
118
      }`
119
    );
120
  }
121
  const build = await client.build(buildByTag.buildId).get();
122
  if (!build) {
123
    throw new Error(
124
      `Build with ID '${buildByTag.buildId}' not found for actor ${actorId}`
125
    );
126
  }
127
  return build;
128
}
129

130
// get default build
131
async function getDefaultBuild(client: ApifyClient, actorId: string) {
132
  const defaultBuildClient = await client.actor(actorId).defaultBuild();
133

134
  const defaultBuild = await defaultBuildClient.get();
135

136
  if (!defaultBuild) {
137
    throw new Error(`Could not fetch default build for actor ${actorId}`);
138
  }
139
  return defaultBuild;
140
}
141

142
// get default inputs from build
143
function getDefaultInputsFromBuild(build: any) {
144
  const buildInputProperties = build?.actorDefinition?.input?.properties;
145
  const defaultInput: Record<string, any> = {};
146
  if (buildInputProperties && typeof buildInputProperties === 'object') {
147
    for (const [key, property] of Object.entries(buildInputProperties)) {
148
      if (
149
        property &&
150
        typeof property === 'object' &&
151
        'prefill' in property &&
152
        (property as any).prefill !== undefined &&
153
        (property as any).prefill !== null
154
      ) {
155
        defaultInput[key] = (property as any).prefill;
156
      }
157
    }
158
  }
159
  return defaultInput;
160
}
161

162
export async function main(
163
  actorSource: ActorSource,
164
  actorId: DynSelect_actorId,
165
  customBody: object = {},
166
  timeout?: number | null,
167
  memoryInMb: MemoryInMb = '1024',
168
  buildTag?: string | null,
169
  waitForFinish: boolean = true,
170
  api_key?: ApifyApiKey,
171
  oauth_token?: Apify,
172
) {
173
  const client = createClient(api_key, oauth_token);
174

175
  try {
176
    // 1. Get the actor details
177
    const actorData = await client.actor(actorId).get();
178
    if (!actorData) {
179
      return {
180
        error: `Actor ${actorId} not found`,
181
      };
182
    }
183

184
    // 2. Build selection logic
185
    let build: any;
186
    if (buildTag) {
187
      build = await getBuildByTag(client, actorId, buildTag, actorData);
188
    } else {
189
      build = await getDefaultBuild(client, actorId);
190
    }
191

192
    // 3. Get default input for this build
193
    const defaultInput = getDefaultInputsFromBuild(build);
194

195
    // 4. Merge default input and user's input (user's input overrides)
196
    const mergedInput = { ...defaultInput, ...customBody };
197

198
    // 5. Prepare run options
199
    const runOptions: any = {
200
      waitSecs: 0, // set initial run actor to not wait for finish
201
    };
202

203
    if (timeout != null) runOptions.timeout = timeout;
204
    if (memoryInMb != null) runOptions.memory = Number(memoryInMb);
205
    if (build?.buildNumber) runOptions.build = build.buildNumber;
206

207
    // 6. Run the actor
208
    const run = await client.actor(actorId).call(mergedInput, runOptions);
209
    if (!run?.id) {
210
      return {
211
        error: `Run ID not found after running the actor`,
212
      };
213
    }
214

215
    // 7a. If waitForFinish is false, return the run data immediately
216
    if (!waitForFinish) {
217
      return { ...run };
218
    }
219

220
    // 7b. Start polling for run status until it reaches a terminal state
221
    const runId = run.id;
222
    const lastRunData = await pollRunStatus(client, runId);
223
    return { ...lastRunData };
224
  } catch (error: any) {
225
    return {
226
      error: `Failed to run actor. Reason: ${error.message}`,
227
    };
228
  }
229
}
230