1import * as cci from "@distilled.cloud/gcp/contactcenterinsights_v1";
2import type { GcpOpContext } from "@distilled.cloud/gcp/Protocol";
3import * as Effect from "effect/Effect";
4import * as Schedule from "effect/Schedule";
5import { waitForOperation as waitForLongRunningOperation } from "../Operation.ts";
6
7/** Name of the resource a finished create operation produced. */
8export const resourceNameFromOperation = (
9 operation: cci.GoogleLongrunningOperation,
10): string | undefined => {
11 const response = operation.response;
12 const responseName = response?.name;
13 if (typeof responseName === "string" && responseName.length > 0) {
14 return responseName;
15 }
16 const metadata = operation.metadata;
17 const target = metadata?.target;
18 if (typeof target === "string" && target.length > 0) {
19 return target;
20 }
21 return undefined;
22};
23
24/**
25 * Wait on a Contact Center AI Insights long-running operation. Issue-model deploy/undeploy can take tens of minutes.
26 * `ALREADY_EXISTS` (a create race) counts as success; so does `NOT_FOUND`
27 * when `notFoundOk` (deletes). Returns the final operation.
28 */
29export const waitForOperation = (
30 operation: cci.GoogleLongrunningOperation,
31 options?: { notFoundOk?: boolean },
32) =>
33 Effect.suspend(() => {
34 let latest = operation;
35 return waitForLongRunningOperation(
36 operation,
37 (name) => {
38 const get = cci.getProjectsLocationsOperations({ name }).pipe(
39 Effect.tap((current) =>
40 Effect.sync(() => {
41 latest = current;
42 }),
43 ),
44 );
45 const observe: Effect.Effect<
46 cci.GoogleLongrunningOperation,
47 cci.GetProjectsLocationsOperationsError,
48 GcpOpContext
49 > =
50 options?.notFoundOk === true
51 ? get.pipe(
52 Effect.catchTag("NotFound", () =>
53 Effect.succeed<cci.GoogleLongrunningOperation>({
54 name,
55 done: true,
56 }),
57 ),
58 )
59 : get.pipe(
60 // A just-returned operation can briefly 404 on read.
61 Effect.retry({
62 while: (error) => error._tag === "NotFound",
63 times: 5,
64 schedule: Schedule.exponential("250 millis"),
65 }),
66 );
67 return observe;
68 },
69 { budget: "30 minutes", interval: "10 seconds" },
70 ).pipe(
71 Effect.map(() => latest),
72 // google.rpc.Code ALREADY_EXISTS = 6, NOT_FOUND = 5.
73 Effect.catchTag("GCP.OperationFailed", (error) =>
74 error.code === 6 || (options?.notFoundOk === true && error.code === 5)
75 ? Effect.succeed(latest)
76 : Effect.fail(error),
77 ),
78 );
79 });