1import * as aiplatform from "@distilled.cloud/gcp/aiplatform_v1";
2import * as Effect from "effect/Effect";
3import { waitForOperation as waitForGcpOperation } from "../Operation.ts";
4
5export const resourceNameFromOperation = (
6 operation: aiplatform.GoogleLongrunningOperation,
7): string | undefined => {
8 const response = operation.response;
9 const responseName = response?.name;
10 if (typeof responseName === "string" && responseName.length > 0) {
11 return responseName;
12 }
13 const metadata = operation.metadata;
14 const target = metadata?.target;
15 if (typeof target === "string" && target.length > 0) {
16 return target;
17 }
18 return undefined;
19};
20
21/**
22 * Wait for a Vertex AI long-running operation and return the finished
23 * operation (so callers can read its `response`). Index deploys and
24 * feature-store provisioning take up to ~30 minutes.
25 *
26 * An operation that failed with ALREADY_EXISTS (6) counts as done (a lost
27 * create race). With `notFoundOk`, NOT_FOUND (5) — or an operation record
28 * that is gone — counts as done too (a lost delete race).
29 */
30export const waitForOperation = (
31 operation: aiplatform.GoogleLongrunningOperation,
32 options?: { notFoundOk?: boolean; alreadyExistsOk?: boolean },
33) =>
34 Effect.gen(function* () {
35 const get = (name: string) =>
36 aiplatform.getProjectsLocationsOperations({ name });
37 const finished = yield* waitForGcpOperation(operation, get, {
38 budget: "30 minutes",
39 }).pipe(
40 Effect.as(true),
41 Effect.catchTag("GCP.OperationFailed", (error) =>
42 error.code === 6 || (options?.notFoundOk === true && error.code === 5)
43 ? Effect.succeed(false)
44 : Effect.fail(error),
45 ),
46 Effect.catchTag("NotFound", (error) =>
47 options?.notFoundOk === true
48 ? Effect.succeed(false)
49 : Effect.fail(error),
50 ),
51 );
52 if (!finished || operation.done === true || !operation.name) {
53 return operation;
54 }
55 return yield* get(operation.name);
56 });