authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-04-01 08:44:38-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-01 15:44:38+00:00
logc542ce336965261dd01c6e670b603434596e3527
tree6b1786a5569c9323b12eba3e8fc698b658b6b5b3
parent15f0be848df55ce27a9c24e622cd58e987886240
signature Signed by PGP key B5690EEEBB952194

[codex] migrate Effect.fn in apps/server/src/git/Layers/ClaudeTextGeneration.ts (#1625)

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

1 files changed, 108 insertions(+), 114 deletions(-)

apps/server/src/git/Layers/ClaudeTextGeneration.ts+108-114
...@@ -66,7 +66,7 @@ const makeClaudeTextGeneration = Effect.gen(function* () {...@@ -66,7 +66,7 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
66 * Spawn the Claude CLI with structured JSON output and return the parsed,66 * Spawn the Claude CLI with structured JSON output and return the parsed,
67 * schema-validated result.67 * schema-validated result.
68 */68 */
69 const runClaudeJson = <S extends Schema.Top>({69 const runClaudeJson = Effect.fn("runClaudeJson")(function* <S extends Schema.Top>({
70 operation,70 operation,
71 cwd,71 cwd,
72 prompt,72 prompt,
...@@ -82,131 +82,125 @@ const makeClaudeTextGeneration = Effect.gen(function* () {...@@ -82,131 +82,125 @@ const makeClaudeTextGeneration = Effect.gen(function* () {
82 prompt: string;82 prompt: string;
83 outputSchemaJson: S;83 outputSchemaJson: S;
84 modelSelection: ClaudeModelSelection;84 modelSelection: ClaudeModelSelection;
85 }): Effect.Effect<S["Type"], TextGenerationError, S["DecodingServices"]> =>85 }): Effect.fn.Return<S["Type"], TextGenerationError, S["DecodingServices"]> {
86 Effect.gen(function* () {86 const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson));
87 const jsonSchemaStr = JSON.stringify(toJsonSchemaObject(outputSchemaJson));87 const normalizedOptions = normalizeClaudeModelOptionsWithCapabilities(
88 const normalizedOptions = normalizeClaudeModelOptionsWithCapabilities(88 getClaudeModelCapabilities(modelSelection.model),
89 getClaudeModelCapabilities(modelSelection.model),89 modelSelection.options,
90 modelSelection.options,90 );
91 );91 const settings = {
92 const settings = {92 ...(typeof normalizedOptions?.thinking === "boolean"
93 ...(typeof normalizedOptions?.thinking === "boolean"93 ? { alwaysThinkingEnabled: normalizedOptions.thinking }
94 ? { alwaysThinkingEnabled: normalizedOptions.thinking }94 : {}),
95 : {}),95 ...(normalizedOptions?.fastMode ? { fastMode: true } : {}),
96 ...(normalizedOptions?.fastMode ? { fastMode: true } : {}),96 };
97 };97
9898 const claudeSettings = yield* Effect.map(
99 const claudeSettings = yield* Effect.map(99 serverSettingsService.getSettings,
100 serverSettingsService.getSettings,100 (settings) => settings.providers.claudeAgent,
101 (settings) => settings.providers.claudeAgent,101 ).pipe(Effect.catch(() => Effect.undefined));
102 ).pipe(Effect.catch(() => Effect.undefined));102
103103 const runClaudeCommand = Effect.fn("runClaudeJson.runClaudeCommand")(function* () {
104 const runClaudeCommand = Effect.gen(function* () {104 const command = ChildProcess.make(
105 const command = ChildProcess.make(105 claudeSettings?.binaryPath || "claude",
106 claudeSettings?.binaryPath || "claude",106 [
107 [107 "-p",
108 "-p",108 "--output-format",
109 "--output-format",109 "json",
110 "json",110 "--json-schema",
111 "--json-schema",111 jsonSchemaStr,
112 jsonSchemaStr,112 "--model",
113 "--model",113 resolveApiModelId(modelSelection),
114 resolveApiModelId(modelSelection),114 ...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []),
115 ...(normalizedOptions?.effort ? ["--effort", normalizedOptions.effort] : []),115 ...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []),
116 ...(Object.keys(settings).length > 0 ? ["--settings", JSON.stringify(settings)] : []),116 "--dangerously-skip-permissions",
117 "--dangerously-skip-permissions",117 ],
118 ],118 {
119 {119 cwd,
120 cwd,120 shell: process.platform === "win32",
121 shell: process.platform === "win32",121 stdin: {
122 stdin: {122 stream: Stream.encodeText(Stream.make(prompt)),
123 stream: Stream.encodeText(Stream.make(prompt)),
124 },
125 },123 },
124 },
125 );
126
127 const child = yield* commandSpawner
128 .spawn(command)
129 .pipe(
130 Effect.mapError((cause) =>
131 normalizeCliError("claude", operation, cause, "Failed to spawn Claude CLI process"),
132 ),
126 );133 );
127134
128 const child = yield* commandSpawner135 const [stdout, stderr, exitCode] = yield* Effect.all(
129 .spawn(command)136 [
130 .pipe(137 readStreamAsString(operation, child.stdout),
138 readStreamAsString(operation, child.stderr),
139 child.exitCode.pipe(
131 Effect.mapError((cause) =>140 Effect.mapError((cause) =>
132 normalizeCliError("claude", operation, cause, "Failed to spawn Claude CLI process"),141 normalizeCliError("claude", operation, cause, "Failed to read Claude CLI exit code"),
133 ),142 ),
134 );143 ),
135144 ],
136 const [stdout, stderr, exitCode] = yield* Effect.all(145 { concurrency: "unbounded" },
137 [146 );
138 readStreamAsString(operation, child.stdout),147
139 readStreamAsString(operation, child.stderr),148 if (exitCode !== 0) {
140 child.exitCode.pipe(149 const stderrDetail = stderr.trim();
141 Effect.mapError((cause) =>150 const stdoutDetail = stdout.trim();
142 normalizeCliError(151 const detail = stderrDetail.length > 0 ? stderrDetail : stdoutDetail;
143 "claude",152 return yield* new TextGenerationError({
144 operation,153 operation,
145 cause,154 detail:
146 "Failed to read Claude CLI exit code",155 detail.length > 0
147 ),156 ? `Claude CLI command failed: ${detail}`
148 ),157 : `Claude CLI command failed with code ${exitCode}.`,
158 });
159 }
160
161 return stdout;
162 });
163
164 const rawStdout = yield* runClaudeCommand().pipe(
165 Effect.scoped,
166 Effect.timeoutOption(CLAUDE_TIMEOUT_MS),
167 Effect.flatMap(
168 Option.match({
169 onNone: () =>
170 Effect.fail(
171 new TextGenerationError({ operation, detail: "Claude CLI request timed out." }),
149 ),172 ),
150 ],173 onSome: (value) => Effect.succeed(value),
151 { concurrency: "unbounded" },174 }),
152 );175 ),
176 );
153177
154 if (exitCode !== 0) {178 const envelope = yield* Schema.decodeEffect(Schema.fromJsonString(ClaudeOutputEnvelope))(
155 const stderrDetail = stderr.trim();179 rawStdout,
156 const stdoutDetail = stdout.trim();180 ).pipe(
157 const detail = stderrDetail.length > 0 ? stderrDetail : stdoutDetail;181 Effect.catchTag("SchemaError", (cause) =>
158 return yield* new TextGenerationError({182 Effect.fail(
183 new TextGenerationError({
159 operation,184 operation,
160 detail:185 detail: "Claude CLI returned unexpected output format.",
161 detail.length > 0186 cause,
162 ? `Claude CLI command failed: ${detail}`
163 : `Claude CLI command failed with code ${exitCode}.`,
164 });
165 }
166
167 return stdout;
168 });
169
170 const rawStdout = yield* runClaudeCommand.pipe(
171 Effect.scoped,
172 Effect.timeoutOption(CLAUDE_TIMEOUT_MS),
173 Effect.flatMap(
174 Option.match({
175 onNone: () =>
176 Effect.fail(
177 new TextGenerationError({ operation, detail: "Claude CLI request timed out." }),
178 ),
179 onSome: (value) => Effect.succeed(value),
180 }),187 }),
181 ),188 ),
182 );189 ),
183190 );
184 const envelope = yield* Schema.decodeEffect(Schema.fromJsonString(ClaudeOutputEnvelope))(
185 rawStdout,
186 ).pipe(
187 Effect.catchTag("SchemaError", (cause) =>
188 Effect.fail(
189 new TextGenerationError({
190 operation,
191 detail: "Claude CLI returned unexpected output format.",
192 cause,
193 }),
194 ),
195 ),
196 );
197191
198 return yield* Schema.decodeEffect(outputSchemaJson)(envelope.structured_output).pipe(192 return yield* Schema.decodeEffect(outputSchemaJson)(envelope.structured_output).pipe(
199 Effect.catchTag("SchemaError", (cause) =>193 Effect.catchTag("SchemaError", (cause) =>
200 Effect.fail(194 Effect.fail(
201 new TextGenerationError({195 new TextGenerationError({
202 operation,196 operation,
203 detail: "Claude returned invalid structured output.",197 detail: "Claude returned invalid structured output.",
204 cause,198 cause,
205 }),199 }),
206 ),
207 ),200 ),
208 );201 ),
209 });202 );
203 });
210204
211 // ---------------------------------------------------------------------------205 // ---------------------------------------------------------------------------
212 // TextGenerationShape methods206 // TextGenerationShape methods