| 1 | import { |
| 2 | AntigravitySettings, |
| 3 | CodexSettings, |
| 4 | ProviderDriverKind, |
| 5 | type ProviderInstallCancelInput, |
| 6 | type ProviderInstanceId, |
| 7 | ProviderSetupError, |
| 8 | type ProviderSetupInput, |
| 9 | } from "@t3tools/contracts"; |
| 10 | import { resolveCommandPath } from "@t3tools/shared/shell"; |
| 11 | import * as Effect from "effect/Effect"; |
| 12 | import * as Schema from "effect/Schema"; |
| 13 | import * as Stream from "effect/Stream"; |
| 14 | |
| 15 | import * as CodexInstallation from "./CodexInstallation.ts"; |
| 16 | import * as ServerSettings from "../serverSettings.ts"; |
| 17 | import * as AntigravityInstallation from "./AntigravityInstallation.ts"; |
| 18 | import { deriveProviderInstanceConfigMap } from "./ProviderInstanceRegistryHydration.ts"; |
| 19 | import * as ProviderInstanceRegistry from "./ProviderInstanceRegistry.ts"; |
| 20 | import * as ProviderRegistry from "./ProviderRegistry.ts"; |
| 21 | import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; |
| 22 | |
| 23 | const ANTIGRAVITY = ProviderDriverKind.make("antigravity"); |
| 24 | const hasBinaryPath = Schema.is(Schema.Struct({ binaryPath: Schema.String })); |
| 25 | const decodeCodexSettings = Schema.decodeUnknownEffect(CodexSettings); |
| 26 | const decodeAntigravitySettings = Schema.decodeUnknownEffect(AntigravitySettings); |
| 27 | |
| 28 | /** Route instance setup to the environment-owned installer without owning the download. */ |
| 29 | export 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 | }); |