1import { ProviderDriverKind } from "@t3tools/contracts";
2import { GrokSettings } from "../settings.ts";
3import * as Crypto from "effect/Crypto";
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 makeCachedProviderMaintenanceResolution,
35 makeManualOnlyProviderMaintenanceCapabilities,
36 makeProviderMaintenanceCapabilities,
37 type ProviderMaintenanceCapabilitiesResolver,
38 resolveProviderMaintenanceCapabilitiesEffect,
39} from "@t3tools/provider-core/server/maintenanceResolver";
40import {
41 haveProviderSnapshotSettingsChanged,
42 makeProviderSnapshotSettingsSource,
43 type ProviderSnapshotSettings,
44} from "@t3tools/provider-core/server/snapshotSettings";
45const decodeGrokSettings = Schema.decodeSync(GrokSettings);
46
47const 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".
50const 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".
55const 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
75export 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
86export 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};