Skip to content
Source
Sign inSign up

driver.ts

276 lines10.4 KB

1import { ProviderDriverKind } from "@t3tools/contracts";
2import { GrokSettings } from "../settings.ts";
3import * as SecureRandom from "effect/crypto/SecureRandom";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7import * as Schema from "effect/Schema";
8import * as HttpClient from "effect/http/HttpClient";
9import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
10​
11import * as ProviderLatestVersions from "@t3tools/provider-core/server/ProviderLatestVersions";
12import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost";
13import * as ProviderEventLoggers from "@t3tools/provider-core/server/ProviderEventLoggers";
14import { makeGrokTextGeneration } from "./textGeneration.ts";
15import { GrokAdapterV2Driver, type GrokAdapterV2DriverEnv } from "./adapter.ts";
16import { ProviderDriverError } from "@t3tools/provider-core/server/errors";
17import {
18 buildInitialGrokProviderSnapshot,
19 checkGrokProviderStatus,
20 enrichGrokSnapshot,
21} from "./status.ts";
22import { grokUsageReader } from "./usage.ts";
23import { readGrokAccount } from "./usageLimits.ts";
24import { makeManagedServerProvider } from "@t3tools/provider-core/server/managedProvider";
25import {
26 defaultProviderContinuationIdentity,
27 type ProviderDriver,
28 type ProviderInstance,
29} from "@t3tools/provider-core/server/driver";
30import { withInstanceIdentity } from "@t3tools/provider-core/server/instanceIdentity";
31import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment";
32import { discoverGrokSkills } from "./skills.ts";
33import {
34 homebrewOwnershipFromCommandPath,
35 isNixStoreCommandPath,
36 makeCachedProviderMaintenanceResolution,
37 makeManualOnlyProviderMaintenanceCapabilities,
38 makeProviderMaintenanceCapabilities,
39 type ProviderMaintenanceCapabilitiesResolver,
40 resolveHomebrewProviderMaintenance,
41 resolveMiseProviderMaintenance,
42 resolveProviderMaintenanceCapabilitiesEffect,
43} from "@t3tools/provider-core/server/maintenanceResolver";
44import {
45 haveProviderSnapshotSettingsChanged,
46 makeProviderSnapshotSettingsSource,
47 type ProviderSnapshotSettings,
48} from "@t3tools/provider-core/server/snapshotSettings";
49const decodeGrokSettings = Schema.decodeSync(GrokSettings);
50​
51const DRIVER_KIND = ProviderDriverKind.make("grok");
52// npm latest matches the stable channel installed by grok update.
53const GROK_NPM_PACKAGE = "@xai-official/grok";
54// `grok update` installs under `GROK_HOME` from the resolved executable and instance environment.
55// npm 12 blocks the postinstall that moves the new binary in, yet exits 0, so allow it by env.
56// Mise and Homebrew casks upgrade through their manager; `grok update` would install a second
57// copy, so a cask its brew cannot vouch for stays manual, as does a Nix store install.
58const UPDATE: ProviderMaintenanceCapabilitiesResolver = {
59 resolve: Effect.fn("resolveGrokMaintenance")(function* (context) {
60 if (!context) {
61 return makeManualOnlyProviderMaintenanceCapabilities({
62 provider: DRIVER_KIND,
63 packageName: GROK_NPM_PACKAGE,
64 });
65 }
66 const mise = yield* resolveMiseProviderMaintenance(
67 { provider: DRIVER_KIND, packageName: GROK_NPM_PACKAGE },
68 { tool: "grok" },
69 context,
70 );
71 if (mise) return mise;
72 if ([context.resolvedCommandPath, context.realCommandPath].some(isNixStoreCommandPath)) {
73 return makeManualOnlyProviderMaintenanceCapabilities({
74 provider: DRIVER_KIND,
75 packageName: GROK_NPM_PACKAGE,
76 installPath: context.realCommandPath,
77 });
78 }
79 const homebrew = homebrewOwnershipFromCommandPath(context.realCommandPath);
80 // Only a cask is Grok's: a formula keg such as `node` holding an npm global is not.
81 if (homebrew?.kind === "cask") {
82 return (
83 (yield* resolveHomebrewProviderMaintenance(
84 { provider: DRIVER_KIND, packageName: GROK_NPM_PACKAGE },
85 homebrew,
86 context,
87 )) ??
88 makeManualOnlyProviderMaintenanceCapabilities({
89 provider: DRIVER_KIND,
90 packageName: GROK_NPM_PACKAGE,
91 installPath: context.realCommandPath,
92 })
93 );
94 }
95 return makeProviderMaintenanceCapabilities({
96 provider: DRIVER_KIND,
97 packageName: GROK_NPM_PACKAGE,
98 updateExecutable: context.resolvedCommandPath,
99 updateArgs: ["update"],
100 updateLockKey: "grok",
101 platform: context.platform,
102 env: { ...context.env, npm_config_allow_scripts: GROK_NPM_PACKAGE },
103 });
104 }),
105};
106​
107export type GrokDriverEnv =
108 | GrokAdapterV2DriverEnv
109 | ProviderHost.ProviderHost
110 | ChildProcessSpawner.ChildProcessSpawner
111 | SecureRandom.SecureRandom
112 | FileSystem.FileSystem
113 | HttpClient.HttpClient
114 | ProviderLatestVersions.ProviderLatestVersions
115 | Path.Path
116 | ProviderEventLoggers.ProviderEventLoggers;
117​
118export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv, Path.Path> = {
119 driverKind: DRIVER_KIND,
120 metadata: {
121 displayName: "Grok",
122 supportsMultipleInstances: true,
123 },
124 configSchema: GrokSettings,
125 defaultConfig: (): GrokSettings => decodeGrokSettings({}),
126 usage: grokUsageReader,
127 create: Effect.fn("GrokDriver.create")(function* ({
128 instanceId,
129 displayName,
130 accentColor,
131 environment,
132 enabled,
133 config,
134 }) {
135 const random = yield* SecureRandom.SecureRandom;
136 const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
137 const httpClient = yield* HttpClient.HttpClient;
138 const latestVersions = yield* ProviderLatestVersions.ProviderLatestVersions;
139 const fileSystem = yield* FileSystem.FileSystem;
140 const path = yield* Path.Path;
141 const host = yield* ProviderHost.ProviderHost;
142 const { cwd } = host.paths;
143 const processEnv = yield* mergeProviderInstanceEnvironment(environment);
144 const continuationIdentity = defaultProviderContinuationIdentity({
145 driverKind: DRIVER_KIND,
146 instanceId,
147 });
148 const stampIdentity = withInstanceIdentity({
149 instanceId,
150 driverKind: DRIVER_KIND,
151 displayName,
152 accentColor,
153 continuationGroupKey: continuationIdentity.continuationKey,
154 });
155 const effectiveConfig = { ...config, enabled } satisfies GrokSettings;
156 const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution(
157 resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
158 binaryPath: effectiveConfig.binaryPath,
159 env: processEnv,
160 }).pipe(
161 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
162 Effect.provideService(FileSystem.FileSystem, fileSystem),
163 Effect.provideService(Path.Path, path),
164 ),
165 );
166 const orchestrationAdapter = yield* GrokAdapterV2Driver.create({
167 instanceId,
168 displayName,
169 accentColor,
170 environment,
171 enabled,
172 config,
173 }).pipe(
174 Effect.mapError(
175 (cause) =>
176 new ProviderDriverError({
177 driver: DRIVER_KIND,
178 instanceId,
179 detail: "Failed to build Grok orchestration adapter.",
180 cause,
181 }),
182 ),
183 );
184 const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv);
185​
186 const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv, cwd).pipe(
187 Effect.filterOrElse(
188 (snapshot) =>
189 !(
190 effectiveConfig.enabled &&
191 snapshot.installed &&
192 snapshot.auth.status === "authenticated"
193 ),
194 (snapshot) =>
195 readGrokAccount(processEnv).pipe(
196 Effect.map(({ email, usageLimits }) => ({
197 ...snapshot,
198 auth: email ? { ...snapshot.auth, email } : snapshot.auth,
199 usageLimits,
200 })),
201 ),
202 ),
203 Effect.map(stampIdentity),
204 Effect.provideService(HttpClient.HttpClient, httpClient),
205 Effect.provideService(FileSystem.FileSystem, fileSystem),
206 Effect.provideService(Path.Path, path),
207 Effect.provideService(SecureRandom.SecureRandom, random),
208 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
209 );
210​
211 const snapshotSettings = yield* makeProviderSnapshotSettingsSource(effectiveConfig);
212 const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<GrokSettings>>({
213 resolveMaintenance,
214 getSettings: snapshotSettings.getSettings,
215 streamSettings: snapshotSettings.streamSettings,
216 haveSettingsChanged: haveProviderSnapshotSettingsChanged,
217 initialSnapshot: (settings) =>
218 buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)),
219 checkProvider,
220 enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) =>
221 resolveMaintenance().pipe(
222 Effect.flatMap((maintenanceCapabilities) =>
223 enrichGrokSnapshot({
224 snapshot: currentSnapshot,
225 maintenanceCapabilities,
226 enableProviderUpdateChecks: settings.enableProviderUpdateChecks,
227 publishSnapshot,
228 }),
229 ),
230 Effect.provideService(HttpClient.HttpClient, httpClient),
231 Effect.provideService(ProviderLatestVersions.ProviderLatestVersions, latestVersions),
232 ),
233 }).pipe(
234 Effect.mapError(
235 (cause) =>
236 new ProviderDriverError({
237 driver: DRIVER_KIND,
238 instanceId,
239 detail: "Failed to build Grok snapshot.",
240 cause,
241 }),
242 ),
243 );
244 const snapshotForCwd = (workspaceCwd: string) =>
245 !effectiveConfig.enabled
246 ? snapshot.getSnapshot
247 : Effect.all([
248 snapshot.getSnapshot,
249 discoverGrokSkills(effectiveConfig, processEnv, workspaceCwd).pipe(
250 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
251 Effect.mapError(
252 (cause) =>
253 new ProviderDriverError({
254 driver: DRIVER_KIND,
255 instanceId,
256 detail: `Failed to discover Grok skills for '${workspaceCwd}'`,
257 cause,
258 }),
259 ),
260 ),
261 ]).pipe(Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills })));
262​
263 return {
264 instanceId,
265 driverKind: DRIVER_KIND,
266 continuationIdentity,
267 displayName,
268 accentColor,
269 enabled,
270 snapshot,
271 snapshotForCwd,
272 orchestrationAdapter,
273 textGeneration,
274 } satisfies ProviderInstance;
275 }),
276};