Skip to content

Commit ae455a6

Browse files
committed
Fix streaming generation with Claude.
1 parent 7e71d5f commit ae455a6

2 files changed

Lines changed: 84 additions & 2 deletions

File tree

electron/__tests__/claude.test.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,88 @@ process.stdin.on('end', () => {
181181
}
182182
});
183183

184+
test('maps Claude structured output deltas to response progress', async () => {
185+
const directory = await mkdtemp(join(tmpdir(), 'codiff-claude-structured-progress-'));
186+
const fakeClaudePath = join(directory, 'claude');
187+
const previousClaudePath = process.env.CODIFF_CLAUDE_PATH;
188+
const events = [
189+
{
190+
event: {
191+
content_block: { signature: '', thinking: '', type: 'thinking' },
192+
type: 'content_block_start',
193+
},
194+
type: 'stream_event',
195+
},
196+
{
197+
event: {
198+
delta: { thinking: 'Planning', type: 'thinking_delta' },
199+
type: 'content_block_delta',
200+
},
201+
type: 'stream_event',
202+
},
203+
{
204+
event: {
205+
content_block: {
206+
id: 'toolu_1',
207+
input: {},
208+
name: 'StructuredOutput',
209+
type: 'tool_use',
210+
},
211+
type: 'content_block_start',
212+
},
213+
type: 'stream_event',
214+
},
215+
{
216+
event: {
217+
delta: { partial_json: '{"version":1}', type: 'input_json_delta' },
218+
type: 'content_block_delta',
219+
},
220+
type: 'stream_event',
221+
},
222+
{
223+
is_error: false,
224+
result: '{"version":1}',
225+
structured_output: { version: 1 },
226+
type: 'result',
227+
},
228+
];
229+
230+
try {
231+
await writeFile(
232+
fakeClaudePath,
233+
`#!/usr/bin/env node
234+
process.stdin.resume();
235+
process.stdin.on('end', () => {
236+
process.stdout.write(${JSON.stringify(events.map((event) => JSON.stringify(event)).join('\n'))});
237+
});
238+
`,
239+
);
240+
await chmod(fakeClaudePath, 0o755);
241+
process.env.CODIFF_CLAUDE_PATH = fakeClaudePath;
242+
const phases: Array<string> = [];
243+
244+
await expect(
245+
runClaude(directory, 'prompt', { type: 'object' }, 'walkthrough.json', 'Timed out.', {
246+
onProgress: (phase) => phases.push(phase),
247+
}),
248+
).resolves.toBe('{"version":1}');
249+
250+
expect(phases).toEqual([
251+
'agent-generation',
252+
'agent-generation',
253+
'response-received',
254+
'response-received',
255+
]);
256+
} finally {
257+
if (previousClaudePath == null) {
258+
delete process.env.CODIFF_CLAUDE_PATH;
259+
} else {
260+
process.env.CODIFF_CLAUDE_PATH = previousClaudePath;
261+
}
262+
await rm(directory, { force: true, recursive: true });
263+
}
264+
});
265+
184266
test('supports per-call Claude Code timeouts', async () => {
185267
const directory = await mkdtemp(join(tmpdir(), 'codiff-claude-timeout-'));
186268
const fakeClaudePath = join(directory, 'claude');

electron/claude.cjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ const createClaudeStreamParser = (onProgress) => {
157157
const blockType = streamEvent.content_block?.type;
158158
if (blockType === 'thinking') {
159159
onProgress?.('agent-generation');
160-
} else if (blockType === 'text') {
160+
} else if (blockType === 'text' || blockType === 'tool_use') {
161161
onProgress?.('response-received');
162162
}
163163
return;
@@ -168,7 +168,7 @@ const createClaudeStreamParser = (onProgress) => {
168168
const deltaType = streamEvent.delta?.type;
169169
if (deltaType === 'thinking_delta') {
170170
onProgress?.('agent-generation');
171-
} else if (deltaType === 'text_delta') {
171+
} else if (deltaType === 'text_delta' || deltaType === 'input_json_delta') {
172172
onProgress?.('response-received');
173173
}
174174
};

0 commit comments

Comments
 (0)