Skip to content
11 changes: 11 additions & 0 deletions apps/server/src/provider/testFixtures/grok-text-mock-agent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ for await (const line of NodeReadline.createInterface({ input: process.stdin }))
NodeFS.appendFileSync(process.env.T3_ACP_REQUEST_LOG_PATH, `${line}\n`);
}
if (request.id === undefined) continue;
if (request.method === "session/prompt" && process.env.T3_ACP_PROMPT_ERROR_MESSAGE) {
write({
jsonrpc: "2.0",
id: request.id,
error: {
code: Number(process.env.T3_ACP_PROMPT_ERROR_CODE ?? -32003),
message: process.env.T3_ACP_PROMPT_ERROR_MESSAGE,
},
});
continue;
}
let result;
switch (request.method) {
case "initialize":
Expand Down
54 changes: 54 additions & 0 deletions apps/server/src/textGeneration/GrokTextGeneration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,58 @@ it.layer(layerGrokTextGenerationTest)("GrokTextGeneration", (it) => {
}),
),
);

it.effect("maps ACP failures to safe details and preserves their cause", () =>
Effect.gen(function* () {
for (const [code, detail] of [
[-32003, "Grok usage limit reached. Try again later."],
[-32000, "Grok authentication is required."],
[-32602, "Grok ACP request failed."],
] as const) {
yield* withFakeAcpGrok(
{
T3_ACP_PROMPT_ERROR_MESSAGE: "private provider payload",
T3_ACP_PROMPT_ERROR_CODE: String(code),
},
(textGeneration) =>
Effect.gen(function* () {
const error = yield* textGeneration
.generateThreadTitle({
cwd: process.cwd(),
message: "the lint job is red",
modelSelection: createModelSelection(
ProviderInstanceId.make("grok"),
"grok-mock-alt",
),
})
.pipe(Effect.flip);
expect(error.detail).toBe(detail);
expect(error.cause).toMatchObject({
code,
errorMessage: "private provider payload",
});
}),
);
}
}),
);

it.effect("explains rejected model settings without copying provider text", () =>
withFakeAcpGrok({}, (textGeneration) =>
Effect.gen(function* () {
const error = yield* textGeneration
.generateThreadTitle({
cwd: process.cwd(),
message: "the lint job is red",
modelSelection: createModelSelection(
ProviderInstanceId.make("grok"),
"grok-mock-missing",
),
})
.pipe(Effect.flip);
expect(error.cause).toMatchObject({ code: -32602, errorMessage: "Unknown Grok model" });
expect(error.detail).toBe("Grok rejected the text generation model settings.");
}),
),
);
});
26 changes: 23 additions & 3 deletions packages/provider-grok/src/server/textGeneration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as Option from "effect/Option";
import * as Ref from "effect/Ref";
import * as Schema from "effect/Schema";
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
import type * as EffectAcpErrors from "effect-acp/errors";
import * as EffectAcpErrors from "effect-acp/errors";

import { TextGenerationError } from "@t3tools/contracts";
import type { GrokSettings } from "../settings.ts";
Expand All @@ -18,10 +18,25 @@ import {
makeGrokAcpRuntime,
resolveGrokAcpBaseModelId,
} from "./acpSupport.ts";
import { xAiRateLimitedErrorCode } from "./xaiAcpExtension.ts";

const GROK_TIMEOUT_MS = 180_000;

const isTextGenerationError = Schema.is(TextGenerationError);
const isAcpRequestError = Schema.is(EffectAcpErrors.AcpRequestError);

function grokAcpFailureDetail(cause: EffectAcpErrors.AcpError, fallback: string) {
if (!isAcpRequestError(cause)) return fallback;
switch (cause.code) {
case xAiRateLimitedErrorCode:
return "Grok usage limit reached. Try again later.";
// ACP auth_required
case -32000:
return "Grok authentication is required.";
default:
return fallback;
}
}

export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(function* (
grokSettings: GrokSettings,
Expand Down Expand Up @@ -75,7 +90,12 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi
mapError: (cause) =>
new TextGenerationError({
operation,
detail: "Failed to set Grok ACP base model for text generation.",
detail: grokAcpFailureDetail(
cause,
isAcpRequestError(cause) && cause.code === -32602
? "Grok rejected the text generation model settings."
: "Failed to set Grok ACP base model for text generation.",
),
cause,
}),
});
Expand All @@ -98,7 +118,7 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi
? cause
: new TextGenerationError({
operation,
detail: "Grok ACP request failed.",
detail: grokAcpFailureDetail(cause, "Grok ACP request failed."),
cause,
}),
),
Expand Down
Loading