authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-06-20 12:29:24-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-06-20 12:29:24-07:00
logbd8e3ee008e4c78085ebfbf47bf216389a92ac75
tree63c9fa1697a3fa8d931167505fb7aab682dc710e
parentc1d8a22f073e36768a8af5b0ef6472d802cbfa4d
signature Signed by PGP key B5690EEEBB952194

Align text generation error catches (#3292)

Co-authored-by: codex <codex@users.noreply.github.com>

5 files changed, 91 insertions(+), 114 deletions(-)

apps/server/src/textGeneration/ClaudeTextGeneration.ts+20-18
...@@ -234,28 +234,30 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu...@@ -234,28 +234,30 @@ export const makeClaudeTextGeneration = Effect.fn("makeClaudeTextGeneration")(fu
234 );234 );
235235
236 const envelope = yield* decodeClaudeOutputEnvelope(rawStdout).pipe(236 const envelope = yield* decodeClaudeOutputEnvelope(rawStdout).pipe(
237 Effect.catchTag("SchemaError", (cause) =>237 Effect.catchTags({
238 Effect.fail(238 SchemaError: (cause) =>
239 new TextGenerationError({239 Effect.fail(
240 operation,240 new TextGenerationError({
241 detail: "Claude CLI returned unexpected output format.",241 operation,
242 cause,242 detail: "Claude CLI returned unexpected output format.",
243 }),243 cause,
244 ),244 }),
245 ),245 ),
246 }),
246 );247 );
247248
248 const decodeOutput = Schema.decodeEffect(outputSchemaJson);249 const decodeOutput = Schema.decodeEffect(outputSchemaJson);
249 return yield* decodeOutput(envelope.structured_output).pipe(250 return yield* decodeOutput(envelope.structured_output).pipe(
250 Effect.catchTag("SchemaError", (cause) =>251 Effect.catchTags({
251 Effect.fail(252 SchemaError: (cause) =>
252 new TextGenerationError({253 Effect.fail(
253 operation,254 new TextGenerationError({
254 detail: "Claude returned invalid structured output.",255 operation,
255 cause,256 detail: "Claude returned invalid structured output.",
256 }),257 cause,
257 ),258 }),
258 ),259 ),
260 }),
259 );261 );
260 });262 });
261263
apps/server/src/textGeneration/CodexTextGeneration.ts+10-9
...@@ -281,15 +281,16 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func...@@ -281,15 +281,16 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func
281 }),281 }),
282 ),282 ),
283 Effect.flatMap(decodeOutput),283 Effect.flatMap(decodeOutput),
284 Effect.catchTag("SchemaError", (cause) =>284 Effect.catchTags({
285 Effect.fail(285 SchemaError: (cause) =>
286 new TextGenerationError({286 Effect.fail(
287 operation,287 new TextGenerationError({
288 detail: "Codex returned invalid structured output.",288 operation,
289 cause,289 detail: "Codex returned invalid structured output.",
290 }),290 cause,
291 ),291 }),
292 ),292 ),
293 }),
293 );294 );
294 }).pipe(Effect.ensuring(cleanup));295 }).pipe(Effect.ensuring(cleanup));
295 });296 });
apps/server/src/textGeneration/CursorTextGeneration.ts+27-40
...@@ -28,30 +28,7 @@ import {...@@ -28,30 +28,7 @@ import {
2828
29const CURSOR_TIMEOUT_MS = 180_000;29const CURSOR_TIMEOUT_MS = 180_000;
3030
31function mapCursorAcpError(31const isTextGenerationError = Schema.is(TextGenerationError);
32 operation:
33 | "generateCommitMessage"
34 | "generatePrContent"
35 | "generateBranchName"
36 | "generateThreadTitle",
37 detail: string,
38 cause: unknown,
39): TextGenerationError {
40 return new TextGenerationError({
41 operation,
42 detail,
43 ...(cause !== undefined ? { cause } : {}),
44 });
45}
46
47function isTextGenerationError(error: unknown): error is TextGenerationError {
48 return (
49 typeof error === "object" &&
50 error !== null &&
51 "_tag" in error &&
52 error._tag === "TextGenerationError"
53 );
54}
5532
56/**33/**
57 * Build a Cursor text-generation closure bound to a specific `CursorSettings`34 * Build a Cursor text-generation closure bound to a specific `CursorSettings`
...@@ -111,13 +88,14 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu...@@ -111,13 +88,14 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu
111 model: modelSelection.model,88 model: modelSelection.model,
112 selections: modelSelection.options,89 selections: modelSelection.options,
113 mapError: ({ cause, configId, step }) =>90 mapError: ({ cause, configId, step }) =>
114 mapCursorAcpError(91 new TextGenerationError({
115 operation,92 operation,
116 step === "set-config-option"93 detail:
117 ? `Failed to set Cursor ACP config option "${configId}" for text generation.`94 step === "set-config-option"
118 : "Failed to set Cursor ACP base model for text generation.",95 ? `Failed to set Cursor ACP config option "${configId}" for text generation.`
96 : "Failed to set Cursor ACP base model for text generation.",
119 cause,97 cause,
120 ),98 }),
121 });99 });
122100
123 return yield* runtime.prompt({101 return yield* runtime.prompt({
...@@ -140,7 +118,11 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu...@@ -140,7 +118,11 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu
140 Effect.mapError((cause) =>118 Effect.mapError((cause) =>
141 isTextGenerationError(cause)119 isTextGenerationError(cause)
142 ? cause120 ? cause
143 : mapCursorAcpError(operation, "Cursor ACP request failed.", cause),121 : new TextGenerationError({
122 operation,
123 detail: "Cursor ACP request failed.",
124 cause,
125 }),
144 ),126 ),
145 );127 );
146128
...@@ -157,21 +139,26 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu...@@ -157,21 +139,26 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu
157139
158 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(outputSchemaJson));140 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(outputSchemaJson));
159 return yield* decodeOutput(extractJsonObject(rawResult)).pipe(141 return yield* decodeOutput(extractJsonObject(rawResult)).pipe(
160 Effect.catchTag("SchemaError", (cause) =>142 Effect.catchTags({
161 Effect.fail(143 SchemaError: (cause) =>
162 new TextGenerationError({144 Effect.fail(
163 operation,145 new TextGenerationError({
164 detail: "Cursor Agent returned invalid structured output.",146 operation,
165 cause,147 detail: "Cursor Agent returned invalid structured output.",
166 }),148 cause,
167 ),149 }),
168 ),150 ),
151 }),
169 );152 );
170 }).pipe(153 }).pipe(
171 Effect.mapError((cause) =>154 Effect.mapError((cause) =>
172 isTextGenerationError(cause)155 isTextGenerationError(cause)
173 ? cause156 ? cause
174 : mapCursorAcpError(operation, "Cursor ACP text generation failed.", cause),157 : new TextGenerationError({
158 operation,
159 detail: "Cursor ACP text generation failed.",
160 cause,
161 }),
175 ),162 ),
176 Effect.scoped,163 Effect.scoped,
177 );164 );
apps/server/src/textGeneration/GrokTextGeneration.ts+24-38
...@@ -31,30 +31,7 @@ import {...@@ -31,30 +31,7 @@ import {
3131
32const GROK_TIMEOUT_MS = 180_000;32const GROK_TIMEOUT_MS = 180_000;
3333
34function mapGrokAcpError(34const isTextGenerationError = Schema.is(TextGenerationError);
35 operation:
36 | "generateCommitMessage"
37 | "generatePrContent"
38 | "generateBranchName"
39 | "generateThreadTitle",
40 detail: string,
41 cause: unknown,
42): TextGenerationError {
43 return new TextGenerationError({
44 operation,
45 detail,
46 ...(cause !== undefined ? { cause } : {}),
47 });
48}
49
50function isTextGenerationError(error: unknown): error is TextGenerationError {
51 return (
52 typeof error === "object" &&
53 error !== null &&
54 "_tag" in error &&
55 error._tag === "TextGenerationError"
56 );
57}
5835
59export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(function* (36export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(function* (
60 grokSettings: GrokSettings,37 grokSettings: GrokSettings,
...@@ -109,11 +86,11 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi...@@ -109,11 +86,11 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi
109 currentModelId: currentGrokModelIdFromSessionSetup(started.sessionSetupResult),86 currentModelId: currentGrokModelIdFromSessionSetup(started.sessionSetupResult),
110 requestedModelId: resolvedModel,87 requestedModelId: resolvedModel,
111 mapError: (cause) =>88 mapError: (cause) =>
112 mapGrokAcpError(89 new TextGenerationError({
113 operation,90 operation,
114 "Failed to set Grok ACP base model for text generation.",91 detail: "Failed to set Grok ACP base model for text generation.",
115 cause,92 cause,
116 ),93 }),
117 });94 });
11895
119 return yield* runtime.prompt({96 return yield* runtime.prompt({
...@@ -133,7 +110,11 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi...@@ -133,7 +110,11 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi
133 Effect.mapError((cause: EffectAcpErrors.AcpError | TextGenerationError) =>110 Effect.mapError((cause: EffectAcpErrors.AcpError | TextGenerationError) =>
134 isTextGenerationError(cause)111 isTextGenerationError(cause)
135 ? cause112 ? cause
136 : mapGrokAcpError(operation, "Grok ACP request failed.", cause),113 : new TextGenerationError({
114 operation,
115 detail: "Grok ACP request failed.",
116 cause,
117 }),
137 ),118 ),
138 );119 );
139120
...@@ -150,21 +131,26 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi...@@ -150,21 +131,26 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi
150131
151 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(outputSchemaJson));132 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(outputSchemaJson));
152 return yield* decodeOutput(extractJsonObject(trimmed)).pipe(133 return yield* decodeOutput(extractJsonObject(trimmed)).pipe(
153 Effect.catchTag("SchemaError", (cause) =>134 Effect.catchTags({
154 Effect.fail(135 SchemaError: (cause) =>
155 new TextGenerationError({136 Effect.fail(
156 operation,137 new TextGenerationError({
157 detail: "Grok Agent returned invalid structured output.",138 operation,
158 cause,139 detail: "Grok Agent returned invalid structured output.",
159 }),140 cause,
160 ),141 }),
161 ),142 ),
143 }),
162 );144 );
163 }).pipe(145 }).pipe(
164 Effect.mapError((cause) =>146 Effect.mapError((cause) =>
165 isTextGenerationError(cause)147 isTextGenerationError(cause)
166 ? cause148 ? cause
167 : mapGrokAcpError(operation, "Grok ACP text generation failed.", cause),149 : new TextGenerationError({
150 operation,
151 detail: "Grok ACP text generation failed.",
152 cause,
153 }),
168 ),154 ),
169 Effect.scoped,155 Effect.scoped,
170 );156 );
apps/server/src/textGeneration/OpenCodeTextGeneration.ts+10-9
...@@ -348,15 +348,16 @@ export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration"...@@ -348,15 +348,16 @@ export const makeOpenCodeTextGeneration = Effect.fn("makeOpenCodeTextGeneration"
348348
349 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(input.outputSchemaJson));349 const decodeOutput = Schema.decodeEffect(Schema.fromJsonString(input.outputSchemaJson));
350 return yield* decodeOutput(extractJsonObject(rawOutput)).pipe(350 return yield* decodeOutput(extractJsonObject(rawOutput)).pipe(
351 Effect.catchTag("SchemaError", (cause) =>351 Effect.catchTags({
352 Effect.fail(352 SchemaError: (cause) =>
353 new TextGenerationError({353 Effect.fail(
354 operation: input.operation,354 new TextGenerationError({
355 detail: "OpenCode returned invalid structured output.",355 operation: input.operation,
356 cause,356 detail: "OpenCode returned invalid structured output.",
357 }),357 cause,
358 ),358 }),
359 ),359 ),
360 }),
360 );361 );
361 });362 });
362363