| author | |
| committer | |
| log | bd8e3ee008e4c78085ebfbf47bf216389a92ac75 |
| tree | 63c9fa1697a3fa8d931167505fb7aab682dc710e |
| parent | c1d8a22f073e36768a8af5b0ef6472d802cbfa4d |
| signature | Signed by PGP key B5690EEEBB952194 |
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 | ); |
| 235 | 235 | ||
| 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 | ); |
| 247 | 248 | ||
| 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 | }); |
| 261 | 263 |
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 { |
| 28 | 28 | ||
| 29 | const CURSOR_TIMEOUT_MS = 180_000; | 29 | const CURSOR_TIMEOUT_MS = 180_000; |
| 30 | 30 | ||
| 31 | function mapCursorAcpError( | 31 | const 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 | |||
| 47 | function 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 | } | ||
| 55 | 32 | ||
| 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 | }); |
| 122 | 100 | ||
| 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 | ? cause | 120 | ? 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 | ); |
| 146 | 128 | ||
| ... | @@ -157,21 +139,26 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu | ... | @@ -157,21 +139,26 @@ export const makeCursorTextGeneration = Effect.fn("makeCursorTextGeneration")(fu |
| 157 | 139 | ||
| 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 | ? cause | 156 | ? 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 { |
| 31 | 31 | ||
| 32 | const GROK_TIMEOUT_MS = 180_000; | 32 | const GROK_TIMEOUT_MS = 180_000; |
| 33 | 33 | ||
| 34 | function mapGrokAcpError( | 34 | const 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 | |||
| 50 | function 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 | } | ||
| 58 | 35 | ||
| 59 | export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(function* ( | 36 | export 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 | }); |
| 118 | 95 | ||
| 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 | ? cause | 112 | ? 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 | ); |
| 139 | 120 | ||
| ... | @@ -150,21 +131,26 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi | ... | @@ -150,21 +131,26 @@ export const makeGrokTextGeneration = Effect.fn("makeGrokTextGeneration")(functi |
| 150 | 131 | ||
| 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 | ? cause | 148 | ? 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" |
| 348 | 348 | ||
| 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 | }); |
| 362 | 363 |