1// @effect-diagnostics nodeBuiltinImport:off - resolves the mock ACP agent script path relative to this test file.
2import * as NodePath from "node:path";
3import * as NodeURL from "node:url";
4
5import * as NodeServices from "@effect/platform-node/NodeServices";
6import { describe, expect, it } from "@effect/vitest";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Schema from "effect/Schema";
10import { HttpClient, HttpClientResponse } from "effect/http";
11import { GrokSettings } from "@t3tools/provider-grok/settings";
12
13import {
14 buildGrokModelCapabilities,
15 buildGrokModelsFromSessionModelState,
16 buildInitialGrokProviderSnapshot,
17 checkGrokProviderStatus,
18 grokSlashCommandsFromInitialize,
19 parseGrokModelsCliOutput,
20} from "@t3tools/provider-grok/testing";
21import { execScriptSource, writeFakeCli } from "@t3tools/provider-testing/fakeCli";
22import { grokUsageResponseToLimits, readGrokAccount } from "@t3tools/provider-grok/testing";
23
24const decodeGrokSettings = Schema.decodeSync(GrokSettings);
25const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
26
27const LOGGED_IN_MODELS_OUTPUT = [
28 "You are logged in with grok.com.",
29 "",
30 "Default model: grok-4.6",
31 "",
32 "Available models:",
33 " * grok-4.6 (default)",
34 " - grok-4.5",
35 "",
36].join("\n");
37
38const LOGGED_OUT_MODELS_OUTPUT = LOGGED_IN_MODELS_OUTPUT.replace(
39 "You are logged in with grok.com.",
40 "You are not authenticated.",
41);
42
43describe("parseGrokModelsCliOutput", () => {
44 it("reads login state and model slugs, marking the default", () => {
45 const parsed = parseGrokModelsCliOutput(LOGGED_IN_MODELS_OUTPUT);
46 expect(parsed.authenticated).toBe(true);
47 expect(parsed.models.map((model) => [model.slug, model.isDefault ?? false])).toEqual([
48 ["grok-4.6", true],
49 ["grok-4.5", false],
50 ]);
51 });
52
53 it("detects a logged-out CLI even though it exits 0", () => {
54 expect(parseGrokModelsCliOutput(LOGGED_OUT_MODELS_OUTPUT).authenticated).toBe(false);
55 });
56
57 it("returns unknown auth for unrecognized output", () => {
58 expect(parseGrokModelsCliOutput("grok 9.9.9\n").authenticated).toBeNull();
59 });
60});
61
62describe("grokSlashCommandsFromInitialize", () => {
63 it("publishes native ACP commands and input hints without permission overrides", () => {
64 const commands = grokSlashCommandsFromInitialize({
65 protocolVersion: 1,
66 _meta: {
67 availableCommands: [
68 { name: "compact", description: "Compress history", input: { hint: "what to preserve" } },
69 {
70 name: "always-approve",
71 description: "Skip permission prompts",
72 input: { hint: "on|off" },
73 },
74 { name: "context", description: "Show context usage", input: null },
75 { name: "session-info", description: "Show session details" },
76 { name: "deep-research", description: "Research a topic", input: { hint: "<query>" } },
77 { name: "workflow", description: "Manage workflows", input: { hint: "runs" } },
78 { name: "goal", description: "Manage an autonomous goal", input: { hint: "status" } },
79 ],
80 },
81 });
82 expect(commands.map((command) => command.name)).toEqual([
83 "compact",
84 "session-info",
85 "deep-research",
86 "workflow",
87 "goal",
88 ]);
89 expect(commands[0]?.input).toEqual({ hint: "what to preserve" });
90 expect(commands[2]).toEqual({
91 name: "deep-research",
92 description: "Research a topic",
93 input: { hint: "<query>" },
94 });
95 });
96
97 it("keeps valid commands when other metadata entries are malformed", () => {
98 const commands = grokSlashCommandsFromInitialize({
99 protocolVersion: 1,
100 _meta: {
101 availableCommands: [
102 null,
103 { name: "broken", description: 42 },
104 { name: " ", description: "Empty name" },
105 { name: " session-info ", description: " Session details " },
106 { name: "session-info", description: "Updated session details" },
107 ],
108 },
109 });
110 expect(commands.map((command) => command.name)).toEqual(["compact", "session-info"]);
111 expect(commands[1]?.description).toBe("Updated session details");
112 });
113
114 it("keeps compact available for older agents without command metadata", () => {
115 for (const _meta of [undefined, {}, { availableCommands: "invalid" }]) {
116 expect(
117 grokSlashCommandsFromInitialize({ protocolVersion: 1, ...(_meta ? { _meta } : {}) }).map(
118 (command) => command.name,
119 ),
120 ).toEqual(["compact"]);
121 }
122 });
123});
124
125describe("buildGrokModelsFromSessionModelState", () => {
126 it("marks the agent's current model as default and keeps reasoning options", () => {
127 const models = buildGrokModelsFromSessionModelState({
128 currentModelId: "grok-4.6",
129 availableModels: [
130 {
131 modelId: "grok-4.6",
132 name: "Grok 4.6",
133 _meta: {
134 supportsReasoningEffort: true,
135 reasoningEffort: "high",
136 reasoningEfforts: [{ value: "high", label: "High", default: true }],
137 },
138 },
139 { modelId: "grok-4.5", name: "Grok 4.5" },
140 ],
141 });
142 expect(models.map((model) => [model.slug, model.isDefault ?? false])).toEqual([
143 ["grok-4.6", true],
144 ["grok-4.5", false],
145 ]);
146 expect(models[0]?.capabilities?.optionDescriptors).toHaveLength(1);
147 });
148});
149
150describe("buildGrokModelCapabilities", () => {
151 it("preserves ACP-provided reasoning labels and the active default", () => {
152 const capabilities = buildGrokModelCapabilities({
153 modelId: "grok-4.6",
154 name: "Grok 4.6",
155 _meta: {
156 supportsReasoningEffort: true,
157 reasoningEffort: "xhigh",
158 reasoningEfforts: [
159 { value: "xhigh", label: "Extra High Effort", default: true },
160 { value: "high", label: "High Effort", default: true },
161 { value: "medium", label: "Medium Effort" },
162 { value: "low", label: "Low Effort" },
163 ],
164 },
165 });
166
167 expect(capabilities.optionDescriptors).toEqual([
168 {
169 id: "reasoningEffort",
170 label: "Reasoning",
171 type: "select",
172 currentValue: "xhigh",
173 options: [
174 { id: "xhigh", label: "Extra High Effort", isDefault: true },
175 { id: "high", label: "High Effort" },
176 { id: "medium", label: "Medium Effort" },
177 { id: "low", label: "Low Effort" },
178 ],
179 },
180 ]);
181 });
182
183 it("uses raw ACP values when option labels are omitted", () => {
184 const capabilities = buildGrokModelCapabilities({
185 modelId: "grok-4.6",
186 name: "Grok 4.6",
187 _meta: {
188 supportsReasoningEffort: true,
189 reasoningEffort: "xhigh",
190 reasoningEfforts: [{ value: "xhigh" }, { value: "medium" }],
191 },
192 });
193
194 expect(capabilities.optionDescriptors).toEqual([
195 {
196 id: "reasoningEffort",
197 label: "Reasoning",
198 type: "select",
199 currentValue: "xhigh",
200 options: [
201 { id: "xhigh", label: "xhigh" },
202 { id: "medium", label: "medium" },
203 ],
204 },
205 ]);
206 });
207
208 it("keeps ACP current effort separate from its collapsed advertised default", () => {
209 const capabilities = buildGrokModelCapabilities({
210 modelId: "grok-4.6",
211 name: "Grok 4.6",
212 _meta: {
213 supportsReasoningEffort: true,
214 reasoningEffort: "medium",
215 reasoningEfforts: [
216 { value: "xhigh", label: "Extra High Effort", default: true },
217 { value: "high", label: "High Effort", default: true },
218 { value: "medium", label: "Medium Effort" },
219 ],
220 },
221 });
222
223 expect(capabilities.optionDescriptors).toEqual([
224 {
225 id: "reasoningEffort",
226 label: "Reasoning",
227 type: "select",
228 currentValue: "medium",
229 options: [
230 { id: "xhigh", label: "Extra High Effort", isDefault: true },
231 { id: "high", label: "High Effort" },
232 { id: "medium", label: "Medium Effort" },
233 ],
234 },
235 ]);
236 });
237
238 it("preserves ACP descriptions and falls back from invalid values to valid ids", () => {
239 const capabilities = buildGrokModelCapabilities({
240 modelId: "grok-4.6",
241 name: "Grok 4.6",
242 _meta: {
243 supportsReasoningEffort: true,
244 reasoningEffort: "high",
245 reasoningEfforts: [
246 {
247 id: "high",
248 value: "not a token",
249 label: "High Effort",
250 description: "Higher implementation quality",
251 default: true,
252 },
253 { id: "bad id", value: "also invalid", label: "Invalid" },
254 ],
255 },
256 });
257
258 expect(capabilities.optionDescriptors).toEqual([
259 {
260 id: "reasoningEffort",
261 label: "Reasoning",
262 type: "select",
263 currentValue: "high",
264 options: [
265 {
266 id: "high",
267 label: "High Effort",
268 description: "Higher implementation quality",
269 isDefault: true,
270 },
271 ],
272 },
273 ]);
274 });
275
276 it("accepts an advertised ACP menu when the support flag is omitted", () => {
277 const capabilities = buildGrokModelCapabilities({
278 modelId: "grok-4.6",
279 name: "Grok 4.6",
280 _meta: {
281 reasoningEffort: "high",
282 reasoningEfforts: [{ value: "high", label: "High Effort", default: true }],
283 },
284 });
285
286 expect(capabilities.optionDescriptors).toHaveLength(1);
287 });
288
289 it("honors an explicit ACP opt-out even when a menu is present", () => {
290 const capabilities = buildGrokModelCapabilities({
291 modelId: "grok-4.6",
292 name: "Grok 4.6",
293 _meta: {
294 supportsReasoningEffort: false,
295 reasoningEfforts: [{ value: "high", label: "High Effort", default: true }],
296 },
297 });
298
299 expect(capabilities.optionDescriptors).toEqual([]);
300 });
301
302 it("does not synthesize a reasoning menu when ACP omits it", () => {
303 expect(
304 buildGrokModelCapabilities({
305 modelId: "grok-4.6",
306 name: "Grok 4.6",
307 _meta: { supportsReasoningEffort: true, reasoningEffort: "xhigh" },
308 }).optionDescriptors,
309 ).toEqual([]);
310 });
311
312 it("keeps non-reasoning Grok models free of reasoning controls", () => {
313 expect(
314 buildGrokModelCapabilities({ modelId: "grok-build", name: "Grok Build" }).optionDescriptors,
315 ).toEqual([]);
316 });
317});
318
319describe("buildInitialGrokProviderSnapshot", () => {
320 it.effect("returns a disabled snapshot when settings.enabled is false", () =>
321 Effect.gen(function* () {
322 const snapshot = yield* buildInitialGrokProviderSnapshot(
323 decodeGrokSettings({ enabled: false }),
324 );
325 expect(snapshot.enabled).toBe(false);
326 expect(snapshot.status).toBe("disabled");
327 expect(snapshot.installed).toBe(false);
328 expect(snapshot.message).toContain("disabled");
329 }),
330 );
331
332 it.effect("returns a disabled snapshot by default — Grok is opt-in", () =>
333 Effect.gen(function* () {
334 const snapshot = yield* buildInitialGrokProviderSnapshot(decodeGrokSettings({}));
335 expect(snapshot.enabled).toBe(false);
336 expect(snapshot.status).toBe("disabled");
337 }),
338 );
339
340 it.effect("returns a pending snapshot when enabled", () =>
341 Effect.gen(function* () {
342 const snapshot = yield* buildInitialGrokProviderSnapshot(
343 decodeGrokSettings({ enabled: true }),
344 );
345 expect(snapshot.enabled).toBe(true);
346 expect(snapshot.installed).toBe(true);
347 expect(snapshot.status).toBe("warning");
348 expect(snapshot.version).toBeNull();
349 expect(snapshot.message).toContain("Checking Grok");
350 expect(snapshot.requiresNewThreadForModelChange).toBeUndefined();
351 expect(snapshot.supportsConversationRollback).toBe(false);
352 }),
353 );
354});
355
356it.layer(NodeServices.layer)("checkGrokProviderStatus", (it) => {
357 it.effect("reports the binary as missing when the binary path does not resolve", () =>
358 Effect.gen(function* () {
359 const snapshot = yield* checkGrokProviderStatus(
360 decodeGrokSettings({
361 enabled: true,
362 binaryPath: "/definitely/not/installed/grok-binary",
363 }),
364 );
365 expect(snapshot.enabled).toBe(true);
366 expect(snapshot.installed).toBe(false);
367 expect(snapshot.status).toBe("error");
368 expect(snapshot.message).toMatch(/not installed|not on PATH|Failed to execute/);
369 }),
370 );
371
372 it.effect("reports an installed CLI as unhealthy when --version exits non-zero", () =>
373 Effect.gen(function* () {
374 const secretStderr = "broken grok install: secret-token-value";
375 const snapshot = yield* Effect.scoped(
376 Effect.gen(function* () {
377 const fs = yield* FileSystem.FileSystem;
378 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-grok-version-" });
379 const grokPath = writeFakeCli({
380 directory: dir,
381 name: "grok",
382 source: [
383 `process.stderr.write(${JSON.stringify(`${secretStderr}\n`)});`,
384 "process.exit(2);",
385 "",
386 ].join("\n"),
387 });
388
389 return yield* checkGrokProviderStatus(
390 decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
391 );
392 }),
393 );
394
395 expect(snapshot.enabled).toBe(true);
396 expect(snapshot.installed).toBe(true);
397 expect(snapshot.status).toBe("error");
398 expect(snapshot.message).toBe("Grok CLI is installed but failed to run.");
399 expect(snapshot.message).not.toContain(secretStderr);
400 }),
401 );
402
403 // A stand-in for the Grok CLI: `--version` and `models` print canned text,
404 // and `agent stdio` execs the mock ACP agent so `initialize` returns model metadata.
405 const writeFakeGrokCli = (input: { readonly modelsOutput: string; readonly acp: boolean }) =>
406 Effect.gen(function* () {
407 const fs = yield* FileSystem.FileSystem;
408 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-grok-probe-" });
409 const mockAgentPath = NodePath.resolve(__dirname, "../../scripts/acp-mock-agent.ts");
410 return writeFakeCli({
411 directory: dir,
412 name: "grok",
413 source: [
414 'if (process.argv[2] === "--version") {',
415 ' process.stdout.write("grok 1.0.13\\n");',
416 " process.exit(0);",
417 "}",
418 'if (process.argv[2] === "models") {',
419 ` process.stdout.write(${JSON.stringify(input.modelsOutput)});`,
420 " process.exit(0);",
421 "}",
422 'if (process.argv[2] !== "agent") process.exit(1);',
423 ...(input.acp ? [execScriptSource({ scriptPath: mockAgentPath })] : ["process.exit(3);"]),
424 "",
425 ].join("\n"),
426 });
427 });
428
429 it.effect("reports ready with ACP-discovered models when logged in", () =>
430 Effect.gen(function* () {
431 const snapshot = yield* Effect.scoped(
432 Effect.gen(function* () {
433 const grokPath = yield* writeFakeGrokCli({
434 modelsOutput: LOGGED_IN_MODELS_OUTPUT,
435 acp: true,
436 });
437 return yield* checkGrokProviderStatus(
438 decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
439 { ...process.env, XAI_API_KEY: "" },
440 );
441 }),
442 );
443
444 expect(snapshot.status).toBe("ready");
445 expect(snapshot.version).toBe("1.0.13");
446 expect(snapshot.auth).toEqual({
447 status: "authenticated",
448 type: "cached_token",
449 label: "Grok account",
450 });
451 // The mock agent advertises grok-4.6 with reasoning options in initialize._meta.
452 expect(snapshot.models.map((model) => model.slug)).toEqual(["grok-4.6", "grok-mock-alt"]);
453 expect(snapshot.models[0]?.isDefault).toBe(true);
454 expect(
455 snapshot.models[0]?.capabilities?.optionDescriptors?.map((option) => option.id) ?? [],
456 ).toEqual(["reasoningEffort"]);
457 }),
458 );
459
460 it.effect("reports unauthenticated from `grok models` without starting a session", () =>
461 Effect.gen(function* () {
462 const snapshot = yield* Effect.scoped(
463 Effect.gen(function* () {
464 const grokPath = yield* writeFakeGrokCli({
465 modelsOutput: LOGGED_OUT_MODELS_OUTPUT,
466 acp: true,
467 });
468 return yield* checkGrokProviderStatus(
469 decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
470 { ...process.env, XAI_API_KEY: "" },
471 );
472 }),
473 );
474
475 expect(snapshot.status).toBe("error");
476 expect(snapshot.auth.status).toBe("unauthenticated");
477 expect(snapshot.message).toContain("grok login");
478 expect(snapshot.models.map((model) => model.slug)).toEqual(["grok-4.6", "grok-mock-alt"]);
479 }),
480 );
481
482 it.effect("falls back to CLI-listed models with a warning when ACP initialize fails", () =>
483 Effect.gen(function* () {
484 const snapshot = yield* Effect.scoped(
485 Effect.gen(function* () {
486 const grokPath = yield* writeFakeGrokCli({
487 modelsOutput: LOGGED_IN_MODELS_OUTPUT,
488 acp: false,
489 });
490 return yield* checkGrokProviderStatus(
491 decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
492 { ...process.env, XAI_API_KEY: "" },
493 );
494 }),
495 );
496
497 expect(snapshot.status).toBe("warning");
498 expect(snapshot.installed).toBe(true);
499 expect(snapshot.auth.status).toBe("authenticated");
500 expect(snapshot.models.map((model) => [model.slug, model.isDefault ?? false])).toEqual([
501 ["grok-4.6", true],
502 ["grok-4.5", false],
503 ]);
504 expect(snapshot.message).toContain("ACP initialize failed");
505 expect(snapshot.slashCommands.map((command) => command.name)).toEqual(["compact"]);
506 }),
507 );
508
509 it.effect("treats XAI_API_KEY as authenticated regardless of CLI login state", () =>
510 Effect.gen(function* () {
511 const snapshot = yield* Effect.scoped(
512 Effect.gen(function* () {
513 const grokPath = yield* writeFakeGrokCli({
514 modelsOutput: LOGGED_OUT_MODELS_OUTPUT,
515 acp: false,
516 });
517 return yield* checkGrokProviderStatus(
518 decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
519 { ...process.env, XAI_API_KEY: "xai-test-key" },
520 );
521 }),
522 );
523
524 expect(snapshot.auth).toEqual({
525 status: "authenticated",
526 type: "api_key",
527 label: "xAI API key",
528 });
529 expect(snapshot.status).toBe("warning");
530 }),
531 );
532});
533
534describe("Grok usage limits", () => {
535 const checkedAt = "2026-09-16T00:00:00.000Z";
536
537 it("uses the reported subscription percentage and weekly reset", () => {
538 const limits = grokUsageResponseToLimits(
539 {
540 config: {
541 creditUsagePercent: 100,
542 currentPeriod: {
543 type: "USAGE_PERIOD_TYPE_WEEKLY",
544 end: "2026-09-18T03:10:30.159171+00:00",
545 },
546 },
547 },
548 checkedAt,
549 );
550 expect(limits.windows).toEqual([
551 {
552 id: "subscription",
553 kind: "weekly",
554 label: "Weekly",
555 usedPercent: 100,
556 resetsAt: "2026-09-18T03:10:30.159Z",
557 },
558 ]);
559 });
560
561 it("does not invent allowance or reset dates when billing omits them", () => {
562 for (const response of [{}, { config: {} }, { config: { creditUsagePercent: NaN } }]) {
563 const limits = grokUsageResponseToLimits(response, checkedAt);
564 expect(limits.windows).toEqual([]);
565 // Nothing metered yet, which xAI reports by omitting the field until
566 // usage registers. Marking it `unsupported` would drop the account from
567 // the Limits view for good; leaving the marker off keeps it listed.
568 expect(limits.unavailable).toBeUndefined();
569 }
570 expect(
571 grokUsageResponseToLimits({ config: { creditUsagePercent: 0 } }, checkedAt).windows,
572 ).toEqual([{ id: "subscription", kind: "other", label: "Subscription", usedPercent: 0 }]);
573 expect(
574 grokUsageResponseToLimits(
575 {
576 config: {
577 creditUsagePercent: 120,
578 currentPeriod: { type: "USAGE_PERIOD_TYPE_MONTHLY", end: "invalid" },
579 },
580 },
581 checkedAt,
582 ).windows,
583 ).toEqual([{ id: "subscription", kind: "monthly", label: "Monthly", usedPercent: 100 }]);
584 });
585});
586
587it.layer(NodeServices.layer)("readGrokAccount", (it) => {
588 it.effect("reads the configured Grok home and prefers the current login scope to legacy", () =>
589 Effect.gen(function* () {
590 const fs = yield* FileSystem.FileSystem;
591 const directory = yield* fs.makeTempDirectoryScoped();
592 yield* fs.writeFileString(
593 NodePath.join(directory, "auth.json"),
594 '{"https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828":{"key":"session-token","auth_mode":"oauth"},"https://accounts.x.ai/sign-in":{"key":"legacy-token"}}',
595 );
596 const { usageLimits: limits } = yield* readGrokAccount({
597 GROK_HOME: directory,
598 HOME: "/unrelated-home",
599 }).pipe(
600 Effect.provideService(
601 HttpClient.HttpClient,
602 HttpClient.make((request) => {
603 expect(request.method).toBe("GET");
604 expect(request.url).toBe("https://cli-chat-proxy.grok.com/v1/billing?format=credits");
605 expect(request.headers.authorization).toBe("Bearer session-token");
606 return Effect.succeed(
607 HttpClientResponse.fromWeb(
608 request,
609 Response.json({ config: { creditUsagePercent: 37 } }),
610 ),
611 );
612 }),
613 ),
614 );
615 expect(limits.windows[0]?.usedPercent).toBe(37);
616 }).pipe(Effect.scoped),
617 );
618
619 it.effect(
620 "uses GROK_AUTH without reading stored credentials and accepts the legacy login scope",
621 () =>
622 readGrokAccount({
623 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"legacy-token"}}',
624 }).pipe(
625 Effect.provideService(
626 FileSystem.FileSystem,
627 FileSystem.makeNoop({
628 readFileString: (path) =>
629 path.endsWith("auth.json")
630 ? Effect.die("must not read stored credentials")
631 : Effect.succeed(""),
632 }),
633 ),
634 Effect.provideService(
635 HttpClient.HttpClient,
636 HttpClient.make((request) => {
637 expect(request.headers.authorization).toBe("Bearer legacy-token");
638 return Effect.succeed(
639 HttpClientResponse.fromWeb(
640 request,
641 Response.json({ config: { creditUsagePercent: 12 } }),
642 ),
643 );
644 }),
645 ),
646 Effect.tap(({ usageLimits }) =>
647 Effect.sync(() => expect(usageLimits.windows[0]?.usedPercent).toBe(12)),
648 ),
649 ),
650 );
651
652 it.effect("does not use unrelated scopes, API keys, or custom auth deployments", () =>
653 Effect.gen(function* () {
654 for (const environment of [
655 {
656 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-account"}}',
657 XAI_API_KEY: "different-api-account",
658 },
659 { GROK_AUTH: '{"https://other.example":{"key":"unrelated-token"}}' },
660 { GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"api-key","auth_mode":"api_key"}}' },
661 { GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":" "}}' },
662 {
663 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"session-token"}}',
664 GROK_OIDC_ISSUER: "https://custom.example",
665 },
666 {
667 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-account"}}',
668 GROK_MODELS_BASE_URL: "https://custom.example/v1",
669 },
670 {
671 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-account"}}',
672 GROK_OAUTH2_PRINCIPAL_TYPE: "Team",
673 },
674 {
675 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-account"}}',
676 GROK_OAUTH2_PRINCIPAL_ID: "team-id",
677 },
678 {
679 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"session-token"}}',
680 GROK_CONFIG: '{"auth_provider_command":"custom-auth"}',
681 },
682 {
683 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"session-token"}}',
684 GROK_CONFIG_PATH: "/custom-config.toml",
685 },
686 ]) {
687 const { usageLimits: limits } = yield* readGrokAccount({
688 HOME: "/definitely/not/a/grok-home",
689 ...environment,
690 }).pipe(
691 Effect.provideService(
692 HttpClient.HttpClient,
693 HttpClient.make(() => Effect.die("must not request another account's quota")),
694 ),
695 );
696 expect(limits.windows).toEqual([]);
697 expect(limits.unavailable?.reason).toBe("unsupported");
698 }
699 }),
700 );
701
702 it.effect(
703 "reports a missing login as unsupported and malformed credentials as a sanitized failure",
704 () =>
705 Effect.gen(function* () {
706 const fs = yield* FileSystem.FileSystem;
707 const directory = yield* fs.makeTempDirectoryScoped();
708 const client = HttpClient.make(() => Effect.die("must not request without credentials"));
709 const { usageLimits: missing } = yield* readGrokAccount({ HOME: directory }).pipe(
710 Effect.provideService(HttpClient.HttpClient, client),
711 );
712 expect(missing.unavailable?.reason).toBe("unsupported");
713 for (const contents of [
714 "private-token-invalid-json",
715 '{"https://accounts.x.ai/sign-in":{"key":42}}',
716 ]) {
717 const { usageLimits: malformed } = yield* readGrokAccount({
718 GROK_HOME: directory,
719 GROK_AUTH: contents,
720 }).pipe(Effect.provideService(HttpClient.HttpClient, client));
721 expect(malformed.unavailable).toEqual({
722 reason: "probeFailed",
723 message: "Grok could not read usage limits.",
724 });
725 }
726 }).pipe(Effect.scoped),
727 );
728
729 it.effect(
730 "does not request subscription limits for custom account or endpoint configuration",
731 () =>
732 Effect.gen(function* () {
733 for (const config of [
734 '[auth]\nprovider_command = "custom-auth"',
735 '[grok_com_config]\nissuer = "https://custom.example"',
736 'endpoints.proxy = "https://custom.example"',
737 ]) {
738 const { usageLimits: limits } = yield* readGrokAccount({
739 GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-token"}}',
740 }).pipe(
741 Effect.provideService(
742 FileSystem.FileSystem,
743 FileSystem.makeNoop({
744 readFileString: (path) => {
745 expect(path.endsWith("config.toml")).toBe(true);
746 return Effect.succeed(config);
747 },
748 }),
749 ),
750 Effect.provideService(
751 HttpClient.HttpClient,
752 HttpClient.make(() => Effect.die("must not request another account's quota")),
753 ),
754 );
755 expect(limits.windows).toEqual([]);
756 expect(limits.unavailable?.reason).toBe("unsupported");
757 }
758 }),
759 );
760
761 it.effect("sanitizes HTTP failures and malformed billing responses, keeping the account", () =>
762 Effect.gen(function* () {
763 for (const response of [
764 new Response("private response", { status: 401 }),
765 Response.json({ config: { creditUsagePercent: "private-value" } }),
766 ]) {
767 const { email, usageLimits: limits } = yield* readGrokAccount({
768 HOME: "/definitely/not/a/grok-home",
769 GROK_AUTH:
770 '{"https://accounts.x.ai/sign-in":{"key":"private-token","email":"someone@example.com"}}',
771 }).pipe(
772 Effect.provideService(
773 HttpClient.HttpClient,
774 HttpClient.make((request) =>
775 Effect.succeed(HttpClientResponse.fromWeb(request, response)),
776 ),
777 ),
778 );
779 expect(email).toBe("someone@example.com");
780 expect(limits.windows).toEqual([]);
781 expect(limits.unavailable).toEqual({
782 reason: "probeFailed",
783 message: "Grok could not read usage limits.",
784 });
785 }
786 }),
787 );
788});
789
790it.layer(NodeServices.layer)("readGrokAccount email", (it) => {
791 it.effect("names the account whose limits were read, and nothing for other auth", () =>
792 Effect.gen(function* () {
793 const current = '"https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828"';
794 const cases = [
795 [
796 { GROK_AUTH: `{${current}:{"key":"t","email":" Someone@Example.com "}}` },
797 "Someone@Example.com",
798 ],
799 [
800 {
801 GROK_AUTH: `{${current}:{"key":"t","email":"current@example.com"},"https://accounts.x.ai/sign-in":{"key":"t","email":"legacy@example.com"}}`,
802 },
803 "current@example.com",
804 ],
805 [{ GROK_AUTH: `{${current}:{"key":"t"}}` }, undefined],
806 [
807 {
808 GROK_AUTH: `{${current}:{"key":"t","email":"someone@example.com"}}`,
809 XAI_API_KEY: "api-key",
810 },
811 undefined,
812 ],
813 [
814 {
815 GROK_AUTH: `{${current}:{"key":"t","email":"someone@example.com","auth_mode":"api_key"}}`,
816 },
817 undefined,
818 ],
819 [{ GROK_AUTH: "not-json" }, undefined],
820 ] as const;
821 for (const [environment, expected] of cases) {
822 const { email, usageLimits } = yield* readGrokAccount({
823 HOME: "/definitely/not/a/grok-home",
824 ...environment,
825 }).pipe(
826 Effect.provideService(
827 HttpClient.HttpClient,
828 HttpClient.make((request) =>
829 Effect.succeed(
830 HttpClientResponse.fromWeb(
831 request,
832 Response.json({ config: { creditUsagePercent: 25 } }),
833 ),
834 ),
835 ),
836 ),
837 );
838 expect(email).toBe(expected);
839 if (expected) expect(usageLimits.windows[0]?.usedPercent).toBe(25);
840 }
841 }),
842 );
843});