1import * as GCP from "@/GCP";
2import { GcpEnvironment } from "@/GCP/Environment";
3import * as Test from "@/Test/Alchemy";
4import * as aiplatform from "@distilled.cloud/gcp/aiplatform_v1";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: GCP.providers() });
11
12const logLevel = Effect.provideService(
13 MinimumLogLevel,
14 process.env.DEBUG ? "Debug" : "Info",
15);
16
17const waitUntilGone = (name: string) =>
18 aiplatform.getProjectsLocationsReasoningEnginesMemories({ name }).pipe(
19 Effect.as("found" as const),
20 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
21 Effect.repeat({
22 schedule: Schedule.spaced("2 seconds"),
23 until: (status) => status === "gone",
24 times: 10,
25 }),
26 );
27
28test.provider(
29 "getProjectsLocationsReasoningEnginesMemories on a missing memory fails with a typed tag",
30 (stack) =>
31 Effect.gen(function* () {
32 const { project } = yield* GcpEnvironment.current;
33 yield* stack.destroy();
34
35 const error = yield* Effect.flip(
36 aiplatform.getProjectsLocationsReasoningEnginesMemories({
37 name: `projects/${project}/locations/us-central1/reasoningEngines/1234567890123456789/memories/1234567890123456789`,
38 }),
39 );
40 expect(error._tag).toEqual("NotFound");
41
42 yield* stack.destroy();
43 }).pipe(logLevel),
44 {
45 tags: ["provider:gcp", "provider:gcp:aiplatform", "live"],
46 timeout: 90_000,
47 },
48);
49
50test.provider(
51 "create, update, and delete a reasoning engine memory",
52 (stack) =>
53 Effect.gen(function* () {
54 yield* stack.destroy();
55
56 const created = yield* stack.deploy(
57 Effect.gen(function* () {
58 const engine = yield* GCP.AIPlatform.ReasoningEngine("Agent", {
59 location: "us-central1",
60 displayName: "alchemy-memory-engine",
61 description: "test",
62 labels: { env: "test" },
63 spec: { agentFramework: "custom" },
64 });
65 const memory = yield* GCP.AIPlatform.ReasoningEnginesMemory("Pref", {
66 reasoningEngine: engine.name,
67 location: "us-central1",
68 scope: { user_id: "alchemy-user" },
69 fact: "the user prefers concise answers",
70 displayName: "user-pref",
71 description: "first",
72 });
73 return { engine, memory };
74 }),
75 );
76
77 expect(created.memory.name).toContain("/memories/");
78 expect(created.memory.reasoningEngine).toEqual(created.engine.name);
79 expect(created.memory.fact).toEqual("the user prefers concise answers");
80
81 const fetched =
82 yield* aiplatform.getProjectsLocationsReasoningEnginesMemories({
83 name: created.memory.name,
84 });
85 expect(fetched.name).toEqual(created.memory.name);
86
87 const updated = yield* stack.deploy(
88 Effect.gen(function* () {
89 const engine = yield* GCP.AIPlatform.ReasoningEngine("Agent", {
90 reasoningEngineId: created.engine.reasoningEngineId,
91 location: "us-central1",
92 displayName: "alchemy-memory-engine",
93 description: "test",
94 labels: { env: "test" },
95 spec: { agentFramework: "custom" },
96 });
97 const memory = yield* GCP.AIPlatform.ReasoningEnginesMemory("Pref", {
98 reasoningEngine: engine.name,
99 memoryId: created.memory.memoryId,
100 location: "us-central1",
101 scope: { user_id: "alchemy-user" },
102 fact: "the user prefers detailed answers",
103 displayName: "user-pref",
104 description: "second",
105 });
106 return { engine, memory };
107 }),
108 );
109
110 expect(updated.memory.name).toEqual(created.memory.name);
111 expect(updated.memory.fact).toEqual("the user prefers detailed answers");
112 expect(updated.memory.description).toEqual("second");
113
114 yield* stack.destroy();
115 const gone = yield* waitUntilGone(created.memory.name);
116 expect(gone).toEqual("gone");
117 }).pipe(logLevel),
118 {
119 tags: ["provider:gcp", "provider:gcp:aiplatform", "live"],
120 timeout: 180_000,
121 },
122);