Skip to content

Commit 30e7372

Browse files
committed
feat(grok): add Grok CLI provider via ACP
Wires the Grok CLI (https://x.ai/cli) into the existing ACP runtime so X Premium / SuperGrok users get a first-class provider. - GrokDriver registered through builtInDrivers - GrokProvider / GrokAdapter handle session lifecycle, event streams, and model switching via session/set_model - GrokAcpSupport probes auth methods and drives the xai.api_key flow - GrokTextGeneration adds Grok as an option (not a default) for git commit/branch generation - grok-build registered as the only Grok model id (only one currently exposed by the CLI) - UI: Grok icon, provider selector entry, settings driver metadata, provider icon mapping Defaults are unchanged: Codex remains the default provider. Closes #2808
1 parent 4f0f24f commit 30e7372

28 files changed

Lines changed: 2605 additions & 17 deletions

apps/server/scripts/acp-mock-agent.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,21 @@ function modeState(): AcpSchema.SessionModeState {
208208
};
209209
}
210210

211+
const grokAcpModels: ReadonlyArray<AcpSchema.ModelInfo> = [
212+
{ modelId: "grok-build", name: "Grok Build" },
213+
{ modelId: "grok-mock-alt", name: "Grok Mock Alt" },
214+
];
215+
216+
function modelState(): AcpSchema.SessionModelState {
217+
const modelId = grokAcpModels.some((model) => model.modelId === currentModelId)
218+
? currentModelId
219+
: "grok-build";
220+
return {
221+
currentModelId: modelId,
222+
availableModels: grokAcpModels,
223+
};
224+
}
225+
211226
const program = Effect.gen(function* () {
212227
const agent = yield* EffectAcpAgent.AcpAgent;
213228

@@ -228,6 +243,7 @@ const program = Effect.gen(function* () {
228243
Effect.succeed({
229244
sessionId,
230245
modes: modeState(),
246+
models: modelState(),
231247
configOptions: configOptions(),
232248
}),
233249
);
@@ -244,11 +260,28 @@ const program = Effect.gen(function* () {
244260
.pipe(
245261
Effect.as({
246262
modes: modeState(),
263+
models: modelState(),
247264
configOptions: configOptions(),
248265
}),
249266
),
250267
);
251268

269+
yield* agent.handleSetSessionModel((request) =>
270+
Effect.gen(function* () {
271+
if (!grokAcpModels.some((model) => model.modelId === request.modelId)) {
272+
return yield* AcpError.AcpRequestError.invalidParams(
273+
`Unknown mock model id: ${request.modelId}`,
274+
{
275+
method: "session/set_model",
276+
params: request,
277+
},
278+
);
279+
}
280+
currentModelId = request.modelId;
281+
return {};
282+
}),
283+
);
284+
252285
yield* agent.handleSetSessionConfigOption((request) =>
253286
Effect.gen(function* () {
254287
if (exitOnSetConfigOption) {
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { GrokSettings, ProviderDriverKind, type ServerProvider } from "@t3tools/contracts";
2+
import * as Duration from "effect/Duration";
3+
import * as Effect from "effect/Effect";
4+
import * as FileSystem from "effect/FileSystem";
5+
import * as Path from "effect/Path";
6+
import * as Schema from "effect/Schema";
7+
import * as Stream from "effect/Stream";
8+
import { HttpClient } from "effect/unstable/http";
9+
import { ChildProcessSpawner } from "effect/unstable/process";
10+
11+
import { ServerConfig } from "../../config.ts";
12+
import { makeGrokTextGeneration } from "../../textGeneration/GrokTextGeneration.ts";
13+
import { ProviderDriverError } from "../Errors.ts";
14+
import { makeGrokAdapter } from "../Layers/GrokAdapter.ts";
15+
import {
16+
buildInitialGrokProviderSnapshot,
17+
checkGrokProviderStatus,
18+
enrichGrokSnapshot,
19+
} from "../Layers/GrokProvider.ts";
20+
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
21+
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
22+
import {
23+
defaultProviderContinuationIdentity,
24+
type ProviderDriver,
25+
type ProviderInstance,
26+
} from "../ProviderDriver.ts";
27+
import type { ServerProviderDraft } from "../providerSnapshot.ts";
28+
import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";
29+
import {
30+
makeManualOnlyProviderMaintenanceCapabilities,
31+
makeStaticProviderMaintenanceResolver,
32+
resolveProviderMaintenanceCapabilitiesEffect,
33+
} from "../providerMaintenance.ts";
34+
const decodeGrokSettings = Schema.decodeSync(GrokSettings);
35+
36+
const DRIVER_KIND = ProviderDriverKind.make("grok");
37+
const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5);
38+
const UPDATE = makeStaticProviderMaintenanceResolver(
39+
makeManualOnlyProviderMaintenanceCapabilities({
40+
provider: DRIVER_KIND,
41+
packageName: null,
42+
}),
43+
);
44+
45+
export type GrokDriverEnv =
46+
| ChildProcessSpawner.ChildProcessSpawner
47+
| FileSystem.FileSystem
48+
| HttpClient.HttpClient
49+
| Path.Path
50+
| ProviderEventLoggers
51+
| ServerConfig;
52+
53+
const withInstanceIdentity =
54+
(input: {
55+
readonly instanceId: ProviderInstance["instanceId"];
56+
readonly displayName: string | undefined;
57+
readonly accentColor: string | undefined;
58+
readonly continuationGroupKey: string;
59+
}) =>
60+
(snapshot: ServerProviderDraft): ServerProvider => ({
61+
...snapshot,
62+
instanceId: input.instanceId,
63+
driver: DRIVER_KIND,
64+
...(input.displayName ? { displayName: input.displayName } : {}),
65+
...(input.accentColor ? { accentColor: input.accentColor } : {}),
66+
continuation: { groupKey: input.continuationGroupKey },
67+
});
68+
69+
export const GrokDriver: ProviderDriver<GrokSettings, GrokDriverEnv> = {
70+
driverKind: DRIVER_KIND,
71+
metadata: {
72+
displayName: "Grok",
73+
supportsMultipleInstances: true,
74+
},
75+
configSchema: GrokSettings,
76+
defaultConfig: (): GrokSettings => decodeGrokSettings({}),
77+
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
78+
Effect.gen(function* () {
79+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
80+
const httpClient = yield* HttpClient.HttpClient;
81+
const eventLoggers = yield* ProviderEventLoggers;
82+
const processEnv = mergeProviderInstanceEnvironment(environment);
83+
const continuationIdentity = defaultProviderContinuationIdentity({
84+
driverKind: DRIVER_KIND,
85+
instanceId,
86+
});
87+
const stampIdentity = withInstanceIdentity({
88+
instanceId,
89+
displayName,
90+
accentColor,
91+
continuationGroupKey: continuationIdentity.continuationKey,
92+
});
93+
const effectiveConfig = { ...config, enabled } satisfies GrokSettings;
94+
const maintenanceCapabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(UPDATE, {
95+
binaryPath: effectiveConfig.binaryPath,
96+
env: processEnv,
97+
});
98+
99+
const adapter = yield* makeGrokAdapter(effectiveConfig, {
100+
environment: processEnv,
101+
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
102+
instanceId,
103+
});
104+
const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv);
105+
106+
const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv).pipe(
107+
Effect.map(stampIdentity),
108+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
109+
);
110+
111+
const snapshot = yield* makeManagedServerProvider<GrokSettings>({
112+
maintenanceCapabilities,
113+
getSettings: Effect.succeed(effectiveConfig),
114+
streamSettings: Stream.never,
115+
haveSettingsChanged: () => false,
116+
initialSnapshot: (settings) =>
117+
buildInitialGrokProviderSnapshot(settings).pipe(Effect.map(stampIdentity)),
118+
checkProvider,
119+
enrichSnapshot: ({ snapshot: currentSnapshot, publishSnapshot }) =>
120+
enrichGrokSnapshot({
121+
snapshot: currentSnapshot,
122+
maintenanceCapabilities,
123+
publishSnapshot,
124+
httpClient,
125+
}),
126+
refreshInterval: SNAPSHOT_REFRESH_INTERVAL,
127+
}).pipe(
128+
Effect.mapError(
129+
(cause) =>
130+
new ProviderDriverError({
131+
driver: DRIVER_KIND,
132+
instanceId,
133+
detail: `Failed to build Grok snapshot: ${cause.message ?? String(cause)}`,
134+
cause,
135+
}),
136+
),
137+
);
138+
139+
return {
140+
instanceId,
141+
driverKind: DRIVER_KIND,
142+
continuationIdentity,
143+
displayName,
144+
accentColor,
145+
enabled,
146+
snapshot,
147+
adapter,
148+
textGeneration,
149+
} satisfies ProviderInstance;
150+
}),
151+
};

0 commit comments

Comments
 (0)