Showing the changes to apps/server/src/git/Layers/OpenCodeTextGeneration.ts (1 of 4 files). Show all
Unified · Split
| @@ -19,10 +19,10 @@ import { · 18 unmodified lines | ||
| 19 | 19 | } from "../Prompts.ts"; |
| 20 | 20 | import { type TextGenerationShape, TextGeneration } from "../Services/TextGeneration.ts"; |
| 21 | 21 | import { |
| 22 | + extractJsonObject, | |
| 22 | 23 | sanitizeCommitSubject, |
| 23 | 24 | sanitizePrTitle, |
| 24 | 25 | sanitizeThreadTitle, |
| 25 | − toJsonSchemaObject, | |
| 26 | 26 | } from "../Utils.ts"; |
| 27 | 27 | import { |
| 28 | 28 | createOpenCodeSdkClient, |
| @@ -35,6 +35,49 @@ import { · 6 unmodified lines | ||
| 35 | 35 | |
| 36 | 36 | const OPENCODE_TEXT_GENERATION_IDLE_TTL_MS = 30_000; |
| 37 | 37 | |
| 38 | +function getOpenCodePromptErrorMessage(error: unknown): string | null { | |
| 39 | + if (!error || typeof error !== "object") { | |
| 40 | + return null; | |
| 41 | + } | |
| 42 | + | |
| 43 | + const message = | |
| 44 | + "data" in error && | |
| 45 | + error.data && | |
| 46 | + typeof error.data === "object" && | |
| 47 | + "message" in error.data && | |
| 48 | + typeof error.data.message === "string" | |
| 49 | + ? error.data.message.trim() | |
| 50 | + : ""; | |
| 51 | + if (message.length > 0) { | |
| 52 | + return message; | |
| 53 | + } | |
| 54 | + | |
| 55 | + if ("name" in error && typeof error.name === "string") { | |
| 56 | + const name = error.name.trim(); | |
| 57 | + return name.length > 0 ? name : null; | |
| 58 | + } | |
| 59 | + | |
| 60 | + return null; | |
| 61 | +} | |
| 62 | + | |
| 63 | +function getOpenCodeTextResponse(parts: ReadonlyArray<unknown> | undefined): string { | |
| 64 | + return (parts ?? []) | |
| 65 | + .flatMap((part) => { | |
| 66 | + if (!part || typeof part !== "object") { | |
| 67 | + return []; | |
| 68 | + } | |
| 69 | + if (!("type" in part) || part.type !== "text") { | |
| 70 | + return []; | |
| 71 | + } | |
| 72 | + if (!("text" in part) || typeof part.text !== "string") { | |
| 73 | + return []; | |
| 74 | + } | |
| 75 | + return [part.text]; | |
| 76 | + }) | |
| 77 | + .join("") | |
| 78 | + .trim(); | |
| 79 | +} | |
| 80 | + | |
| 38 | 81 | interface SharedOpenCodeTextGenerationServerState { |
| 39 | 82 | server: OpenCodeServerProcess | null; |
| 40 | 83 | binaryPath: string | null; |
| @@ -245,17 +288,18 @@ const makeOpenCodeTextGeneration = Effect.gen(function* () { · 204 unmodified lines | ||
| 245 | 288 | ...(input.modelSelection.options?.variant |
| 246 | 289 | ? { variant: input.modelSelection.options.variant } |
| 247 | 290 | : {}), |
| 248 | − format: { | |
| 249 | − type: "json_schema", | |
| 250 | − schema: toJsonSchemaObject(input.outputSchemaJson) as Record<string, unknown>, | |
| 251 | − }, | |
| 252 | 291 | parts: [{ type: "text", text: input.prompt }, ...fileParts], |
| 253 | 292 | }); |
| 254 | − const structured = result.data?.info?.structured; | |
| 255 | − if (structured === undefined) { | |
| 256 | − throw new Error("OpenCode returned no structured output."); | |
| 293 | + const info = result.data?.info; | |
| 294 | + const errorMessage = getOpenCodePromptErrorMessage(info?.error); | |
| 295 | + if (errorMessage) { | |
| 296 | + throw new Error(errorMessage); | |
| 297 | + } | |
| 298 | + const rawText = getOpenCodeTextResponse(result.data?.parts); | |
| 299 | + if (rawText.length === 0) { | |
| 300 | + throw new Error("OpenCode returned empty output."); | |
| 257 | 301 | } |
| 258 | − return structured; | |
| 302 | + return rawText; | |
| 259 | 303 | }, |
| 260 | 304 | catch: (cause) => |
| 261 | 305 | new TextGenerationError({ |
| @@ -266,7 +310,7 @@ const makeOpenCodeTextGeneration = Effect.gen(function* () { · 4 unmodified lines | ||
| 266 | 310 | }), |
| 267 | 311 | }); |
| 268 | 312 | |
| 269 | − const structuredOutput = | |
| 313 | + const rawOutput = | |
| 270 | 314 | settings.serverUrl.length > 0 |
| 271 | 315 | ? yield* runAgainstServer({ url: settings.serverUrl }) |
| 272 | 316 | : yield* Effect.acquireUseRelease( |
| @@ -278,7 +322,9 @@ const makeOpenCodeTextGeneration = Effect.gen(function* () { · 5 unmodified lines | ||
| 278 | 322 | releaseSharedServer, |
| 279 | 323 | ); |
| 280 | 324 | |
| 281 | − return yield* Schema.decodeUnknownEffect(input.outputSchemaJson)(structuredOutput).pipe( | |
| 325 | + return yield* Schema.decodeEffect(Schema.fromJsonString(input.outputSchemaJson))( | |
| 326 | + extractJsonObject(rawOutput), | |
| 327 | + ).pipe( | |
| 282 | 328 | Effect.catchTag("SchemaError", (cause) => |
| 283 | 329 | Effect.fail( |
| 284 | 330 | new TextGenerationError({ |