| 1 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 2 | import { GrokSettings } from "../settings.ts"; |
| 3 | import * as Crypto from "effect/Crypto"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as FileSystem from "effect/FileSystem"; |
| 6 | import * as Path from "effect/Path"; |
| 7 | import * as Schema from "effect/Schema"; |
| 8 | import * as HttpClient from "effect/http/HttpClient"; |
| 9 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 10 | |
| 11 | import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions"; |
| 12 | import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; |
| 13 | import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers"; |
| 14 | import { makeGrokTextGeneration } from "./textGeneration.ts"; |
| 15 | import { GrokAdapterV2Driver, type GrokAdapterV2DriverEnv } from "./adapter.ts"; |
| 16 | import { ProviderDriverError } from "@t3tools/provider-core/server/errors"; |
| 17 | import { |
| 18 | buildInitialGrokProviderSnapshot, |
| 19 | checkGrokProviderStatus, |
| 20 | enrichGrokSnapshot, |
| 21 | } from "./status.ts"; |
| 22 | import { grokUsageReader } from "./usage.ts"; |
| 23 | import { readGrokAccount } from "./usageLimits.ts"; |
| 24 | import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider"; |
| 25 | import { |
| 26 | defaultProviderContinuationIdentity, |
| 27 | type ProviderDriver, |
| 28 | type ProviderInstance, |
| 29 | } from "@t3tools/provider-core/server/driver"; |
| 30 | import { withInstanceIdentity } from "@t3tools/provider-core/server/instanceIdentity"; |
| 31 | import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; |
| 32 | import { discoverGrokSkills } from "./skills.ts"; |
| 33 | import { |
| 34 | makeCachedProviderMaintenanceResolution, |
| 35 | makeManualOnlyProviderMaintenanceCapabilities, |
| 36 | makeProviderMaintenanceCapabilities, |
| 37 | type ProviderMaintenanceCapabilitiesResolver, |
| 38 | resolveProviderMaintenanceCapabilitiesEffect, |
| 39 | } from "@t3tools/provider-core/server/maintenanceResolver"; |
| 40 | import { |
| 41 | haveProviderSnapshotSettingsChanged, |
| 42 | makeProviderSnapshotSettingsSource, |
| 43 | type ProviderSnapshotSettings, |
| 44 | } from "@t3tools/provider-core/server/snapshotSettings"; |
| 45 | const decodeGrokSettings = Schema.decodeSync(GrokSettings); |
| 46 | |
| 47 | const DRIVER_KIND = ProviderDriverKind.make("grok"); |
| 48 | // npm's `latest` tracks Grok's stable channel, the one `grok update` installs |
| 49 | // by default, so the registry stays the source for "latest". |
| 50 | const GROK_NPM_PACKAGE = "@xai-official/grok"; |
| 51 | // `grok update` finds the installer that owns the binary itself, so the |
| 52 | // resolved executable is its own updater. It installs under `GROK_HOME`, so it |
| 53 | // runs with the instance's environment. No executable means nothing to update, |
| 54 | // not "whatever is on PATH". |
| 55 | const UPDATE: ProviderMaintenanceCapabilitiesResolver = { |
| 56 | resolve: (context) => |
| 57 | Effect.succeed( |
| 58 | context |
| 59 | ? makeProviderMaintenanceCapabilities({ |
| 60 | provider: DRIVER_KIND, |
| 61 | packageName: GROK_NPM_PACKAGE, |
| 62 | updateExecutable: context.resolvedCommandPath, |
| 63 | updateArgs: ["update"], |
| 64 | updateLockKey: "grok", |
| 65 | platform: context.platform, |
| 66 | env: context.env, |
| 67 | }) |
| 68 | : makeManualOnlyProviderMaintenanceCapabilities({ |
| 69 | provider: DRIVER_KIND, |
| 70 | packageName: GROK_NPM_PACKAGE, |
| 71 | }), |
| 72 | ), |
| 73 | }; |
| 74 | |
| 75 | export type GrokDriverEnv = |
| 76 | | GrokAdapterV2DriverEnv |
| 77 | | ProviderHost.ProviderHost |
| 78 | | ChildProcessSpawner.ChildProcessSpawner |
| 79 | | Crypto.Crypto |
| 80 | | FileSystem.FileSystem |
| 81 | | HttpClient.HttpClient |
| 82 | | ProviderLatestVersions.ProviderLatestVersions |
| 83 | | Path.Path |
| 84 | | ProviderEventLoggers.ProviderEventLoggers; |
| 85 | |
| 86 | export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv, Path.Path> = { |
| 87 | driverKind: DRIVER_KIND, |
| 88 | metadata: { |
| 89 | displayName: "Grok", |
| 90 | supportsMultipleInstances: true, |
| 91 | }, |
| 92 | configSchema: GrokSettings, |
| 93 | defaultConfig: (): GrokSettings => decodeGrokSettings({}), |
| 94 | usage: grokUsageReader, |
| 95 | create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => |
| 96 | Effect.gen(function* () { |
| 97 | const crypto = yield* Crypto.Crypto; |
| 98 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 99 | const httpClient = yield* HttpClient.HttpClient; |
| 100 | const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions; |
| 101 | const fileSystem = yield* FileSystem.FileSystem; |
| 102 | const path = yield* Path.Path; |
| 103 | const host = yield* ProviderHost.ProviderHost; |
| 104 | const { cwd } = host.paths; |
| 105 | const processEnv = yield* mergeProviderInstanceEnvironment(environment); |
| 106 | const continuationIdentity = defaultProviderContinuationIdentity({ |
| 107 | driverKind: DRIVER_KIND, |
| 108 | instanceId, |
| 109 | }); |
| 110 | const stampIdentity = withInstanceIdentity({ |
| 111 | instanceId, |
| 112 | driverKind: DRIVER_KIND, |
| 113 | displayName, |
| 114 | accentColor, |
| 115 | continuationGroupKey: continuationIdentity.continuationKey, |
| 116 | }); |
| 117 | const effectiveConfig = { ...config, enabled } satisfies GrokSettings; |
| 118 | const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution( |
| 119 | resolveProviderMaintenanceCapabilitiesEffect(UPDATE, { |
| 120 | binaryPath: effectiveConfig.binaryPath, |
| 121 | env: processEnv, |
| 122 | }).pipe( |
| 123 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 124 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 125 | Effect.provideService(Path.Path, path), |
| 126 | ), |
| 127 | ); |
| 128 | const orchestrationAdapter = yield* GrokAdapterV2Driver.create({ |
| 129 | instanceId, |
| 130 | displayName, |
| 131 | accentColor, |
| 132 | environment, |
| 133 | enabled, |
| 134 | config, |
| 135 | }).pipe( |
| 136 | Effect.mapError( |
| 137 | (cause) => |
| 138 | new ProviderDriverError({ |
| 139 | driver: DRIVER_KIND, |
| 140 | instanceId, |
| 141 | detail: "Failed to build Grok orchestration adapter.", |
| 142 | cause, |
| 143 | }), |
| 144 | ), |
| 145 | ); |
| 146 | const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv); |
| 147 | |
| 148 | const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv, cwd).pipe( |
| 149 | Effect.flatMap((snapshot) => |
| 150 | effectiveConfig.enabled && snapshot.installed && snapshot.auth.status === "authenticated" |
| 151 | ? readGrokAccount(processEnv).pipe( |
| 152 | Effect.map(({ email, usageLimits }) => ({ |
| 153 | ...snapshot, |
| 154 | auth: email ? { ...snapshot.auth, email } : snapshot.auth, |
| 155 | usageLimits, |
| 156 | })), |
| 157 | ) |
| 158 | : Effect.succeed(snapshot), |
| 159 | ), |
| 160 | Effect.map(stampIdentity), |
| 161 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 162 | Effect.provideService(FileSystem.FileSystem, fileSystem), |
| 163 | Effect.provideService(Path.Path, path), |
| 164 | Effect.provideService(Crypto.Crypto, crypto), |
| 165 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 166 | ); |
| 167 | |
| 168 | const snapshotSettings = yield* makeProviderSnapshotSettingsSource(effectiveConfig); |
| 169 | const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<GrokSettings>>({ |
| 170 | resolveMaintenance, |
| 171 | getSettings: snapshotSettings.getSettings, |
| 172 | streamSettings: snapshotSettings.streamSettings, |
| 173 | haveSettingsChanged: haveProviderSnapshotSettingsChanged, |
| 174 | initialSnapshot: (settings) => |
| 175 | buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), |
| 176 | checkProvider, |
| 177 | enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => |
| 178 | resolveMaintenance().pipe( |
| 179 | Effect.flatMap((maintenanceCapabilities) => |
| 180 | enrichGrokSnapshot({ |
| 181 | snapshot: currentSnapshot, |
| 182 | maintenanceCapabilities, |
| 183 | enableProviderUpdateChecks: settings.enableProviderUpdateChecks, |
| 184 | publishSnapshot, |
| 185 | }), |
| 186 | ), |
| 187 | Effect.provideService(HttpClient.HttpClient, httpClient), |
| 188 | Effect.provideService(ProviderLatestVersions.ProviderLatestVersions, latestVersions), |
| 189 | ), |
| 190 | }).pipe( |
| 191 | Effect.mapError( |
| 192 | (cause) => |
| 193 | new ProviderDriverError({ |
| 194 | driver: DRIVER_KIND, |
| 195 | instanceId, |
| 196 | detail: "Failed to build Grok snapshot.", |
| 197 | cause, |
| 198 | }), |
| 199 | ), |
| 200 | ); |
| 201 | const snapshotForCwd = (workspaceCwd: string) => |
| 202 | !effectiveConfig.enabled |
| 203 | ? snapshot.getSnapshot |
| 204 | : Effect.all([ |
| 205 | snapshot.getSnapshot, |
| 206 | discoverGrokSkills(effectiveConfig, processEnv, workspaceCwd).pipe( |
| 207 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 208 | Effect.mapError( |
| 209 | (cause) => |
| 210 | new ProviderDriverError({ |
| 211 | driver: DRIVER_KIND, |
| 212 | instanceId, |
| 213 | detail: `Failed to discover Grok skills for '${workspaceCwd}'`, |
| 214 | cause, |
| 215 | }), |
| 216 | ), |
| 217 | ), |
| 218 | ]).pipe(Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills }))); |
| 219 | |
| 220 | return { |
| 221 | instanceId, |
| 222 | driverKind: DRIVER_KIND, |
| 223 | continuationIdentity, |
| 224 | displayName, |
| 225 | accentColor, |
| 226 | enabled, |
| 227 | snapshot, |
| 228 | snapshotForCwd, |
| 229 | orchestrationAdapter, |
| 230 | textGeneration, |
| 231 | } satisfies ProviderInstance; |
| 232 | }), |
| 233 | }; |