1import {
2 AntigravitySettings,
3 CodexSettings,
4 ProviderDriverKind,
5 type ProviderInstallCancelInput,
6 type ProviderInstanceId,
7 ProviderSetupError,
8 type ProviderSetupInput,
9} from "@t3tools/contracts";
10import { resolveCommandPath } from "@t3tools/shared/shell";
11import * as Effect from "effect/Effect";
12import * as Schema from "effect/Schema";
13import * as Stream from "effect/Stream";
14
15import * as CodexInstallation from "./CodexInstallation.ts";
16import * as ServerSettings from "../serverSettings.ts";
17import * as AntigravityInstallation from "./AntigravityInstallation.ts";
18import { deriveProviderInstanceConfigMap } from "./ProviderInstanceRegistryHydration.ts";
19import * as ProviderInstanceRegistry from "./ProviderInstanceRegistry.ts";
20import * as ProviderRegistry from "./ProviderRegistry.ts";
21import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
22
23const ANTIGRAVITY = ProviderDriverKind.make("antigravity");
24const hasBinaryPath = Schema.is(Schema.Struct({ binaryPath: Schema.String }));
25const decodeCodexSettings = Schema.decodeUnknownEffect(CodexSettings);
26const decodeAntigravitySettings = Schema.decodeUnknownEffect(AntigravitySettings);
27
28/** Route instance setup to the environment-owned installer without owning the download. */
29export const makeProviderInstallation = Effect.fn("makeProviderInstallation")(function* () {
30 const antigravityInstallation = yield* AntigravityInstallation.AntigravityInstallation;
31 const codexInstallation = yield* CodexInstallation.CodexInstallation;
32 const instances = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
33 const providers = yield* ProviderRegistry.ProviderRegistry;
34 const settings = yield* ServerSettings.ServerSettingsService;
35
36 const readEntries = Effect.fn("ProviderInstallation.readEntries")(function* (
37 instanceId: ProviderInstanceId,
38 operation: string,
39 ) {
40 const current = yield* settings.getSettings.pipe(
41 Effect.mapError(
42 () =>
43 new ProviderSetupError({
44 instanceId,
45 operation,
46 detail: "Could not read provider installation settings.",
47 }),
48 ),
49 );
50 return deriveProviderInstanceConfigMap(current);
51 });
52
53 const requireInstance = Effect.fn("ProviderInstallation.requireInstance")(function* (
54 instanceId: ProviderInstanceId,
55 operation: string,
56 managedOnly = false,
57 ) {
58 const instance = yield* instances.getInstance(instanceId);
59 const isCodex = instance?.driverKind === ProviderDriverKind.make("codex");
60 if (instance?.driverKind !== ANTIGRAVITY && !isCodex) {
61 return yield* new ProviderSetupError({
62 instanceId,
63 operation,
64 detail: "Managed installation is not available for this provider instance.",
65 });
66 }
67 const installation = isCodex ? codexInstallation : antigravityInstallation;
68 const entries = yield* readEntries(instanceId, operation);
69 const invalidConfig = () =>
70 new ProviderSetupError({
71 instanceId,
72 operation,
73 detail: "The provider instance configuration is invalid.",
74 });
75 const config = isCodex
76 ? yield* decodeCodexSettings(entries[instanceId]?.config ?? {}).pipe(
77 Effect.mapError(invalidConfig),
78 )
79 : yield* decodeAntigravitySettings(entries[instanceId]?.config ?? {}).pipe(
80 Effect.mapError(invalidConfig),
81 );
82 if (isCodex && (!("setupMode" in config) || config.setupMode !== "managed")) {
83 return yield* new ProviderSetupError({
84 instanceId,
85 operation,
86 detail: "Choose managed setup to install Codex in T3 Code.",
87 });
88 }
89 if (managedOnly && config.binaryPath && (!isCodex || config.binaryPath !== "codex")) {
90 return yield* new ProviderSetupError({
91 instanceId,
92 operation,
93 detail:
94 "This instance uses a custom executable. Clear its binary path to manage installation in T3 Code.",
95 });
96 }
97 return { installation, driver: instance.driverKind };
98 });
99
100 const failure =
101 (instanceId: ProviderInstanceId) =>
102 (
103 error:
104 | AntigravityInstallation.AntigravityInstallationError
105 | CodexInstallation.CodexInstallationError,
106 ) =>
107 new ProviderSetupError({ instanceId, operation: error.operation, detail: error.detail });
108
109 const start = Effect.fn("ProviderInstallation.start")(function* (input: ProviderSetupInput) {
110 const { installation } = yield* requireInstance(input.instanceId, "install", true);
111 return yield* installation.start.pipe(Effect.mapError(failure(input.instanceId)));
112 });
113
114 const cancel = Effect.fn("ProviderInstallation.cancel")(function* (
115 input: ProviderInstallCancelInput,
116 ) {
117 const { installation } = yield* requireInstance(input.instanceId, "cancel-install");
118 return yield* installation
119 .cancel(input.operationId)
120 .pipe(Effect.mapError(failure(input.instanceId)));
121 });
122
123 const subscribe = (input: ProviderSetupInput) =>
124 Stream.unwrap(
125 requireInstance(input.instanceId, "observe-install").pipe(
126 Effect.map(({ installation }) => installation.changes),
127 ),
128 );
129
130 const remove = Effect.fn("ProviderInstallation.remove")(function* (input: ProviderSetupInput) {
131 const { installation, driver } = yield* requireInstance(
132 input.instanceId,
133 "remove-install",
134 true,
135 );
136 const entries = yield* readEntries(input.instanceId, "remove-install");
137 const protectedPaths = yield* Effect.forEach(
138 Object.values(entries),
139 Effect.fnUntraced(function* (entry) {
140 if (!hasBinaryPath(entry.config) || !entry.config.binaryPath.trim()) {
141 return [];
142 }
143 const binaryPath = entry.config.binaryPath.trim();
144 const resolved = yield* resolveCommandPath(binaryPath, {
145 env: yield* mergeProviderInstanceEnvironment(entry.environment),
146 });
147 return resolved === null ? [binaryPath] : [binaryPath, resolved];
148 }),
149 );
150 yield* installation
151 .remove(protectedPaths.flat())
152 .pipe(Effect.mapError(failure(input.instanceId)));
153 const allInstances = yield* instances.listInstances;
154 yield* Effect.forEach(
155 allInstances.filter((instance) => instance.driverKind === driver),
156 (instance) => providers.refreshInstance(instance.instanceId),
157 { discard: true },
158 );
159 return yield* installation.state;
160 });
161
162 return { start, cancel, subscribe, remove };
163});