opencodeの終了判定が誤っていたので修正
This commit is contained in:
parent
ccca0949ae
commit
2a678f3a75
@ -31,45 +31,6 @@ class MockEventStream implements AsyncGenerator<unknown, void, unknown> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HangingAfterEventsStream implements AsyncGenerator<unknown, void, unknown> {
|
|
||||||
private index = 0;
|
|
||||||
private closed = false;
|
|
||||||
private pendingResolve: ((value: IteratorResult<unknown, void>) => void) | undefined;
|
|
||||||
readonly returnSpy = vi.fn(async () => {
|
|
||||||
this.closed = true;
|
|
||||||
this.pendingResolve?.({ done: true, value: undefined });
|
|
||||||
return { done: true as const, value: undefined };
|
|
||||||
});
|
|
||||||
|
|
||||||
constructor(private readonly events: unknown[]) {}
|
|
||||||
|
|
||||||
[Symbol.asyncIterator](): AsyncGenerator<unknown, void, unknown> {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
async next(): Promise<IteratorResult<unknown, void>> {
|
|
||||||
if (this.closed) {
|
|
||||||
return { done: true, value: undefined };
|
|
||||||
}
|
|
||||||
if (this.index < this.events.length) {
|
|
||||||
const value = this.events[this.index];
|
|
||||||
this.index += 1;
|
|
||||||
return { done: false, value };
|
|
||||||
}
|
|
||||||
return new Promise<IteratorResult<unknown, void>>((resolve) => {
|
|
||||||
this.pendingResolve = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async return(): Promise<IteratorResult<unknown, void>> {
|
|
||||||
return this.returnSpy();
|
|
||||||
}
|
|
||||||
|
|
||||||
async throw(e?: unknown): Promise<IteratorResult<unknown, void>> {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const { createOpencodeMock } = vi.hoisted(() => ({
|
const { createOpencodeMock } = vi.hoisted(() => ({
|
||||||
createOpencodeMock: vi.fn(),
|
createOpencodeMock: vi.fn(),
|
||||||
}));
|
}));
|
||||||
@ -188,9 +149,9 @@ describe('OpenCodeClient stream cleanup', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should complete without hanging when assistant message is completed', async () => {
|
it('should continue after assistant message completed and finish on session.idle', async () => {
|
||||||
const { OpenCodeClient } = await import('../infra/opencode/client.js');
|
const { OpenCodeClient } = await import('../infra/opencode/client.js');
|
||||||
const stream = new HangingAfterEventsStream([
|
const stream = new MockEventStream([
|
||||||
{
|
{
|
||||||
type: 'message.part.updated',
|
type: 'message.part.updated',
|
||||||
properties: {
|
properties: {
|
||||||
@ -208,6 +169,17 @@ describe('OpenCodeClient stream cleanup', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'message.part.updated',
|
||||||
|
properties: {
|
||||||
|
part: { id: 'p-1', type: 'text', text: 'done more' },
|
||||||
|
delta: ' more',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'session.idle',
|
||||||
|
properties: { sessionID: 'session-3' },
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const promptAsync = vi.fn().mockResolvedValue(undefined);
|
const promptAsync = vi.fn().mockResolvedValue(undefined);
|
||||||
@ -235,7 +207,7 @@ describe('OpenCodeClient stream cleanup', () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
expect(result.status).toBe('done');
|
expect(result.status).toBe('done');
|
||||||
expect(result.content).toBe('done');
|
expect(result.content).toBe('done more');
|
||||||
expect(disposeInstance).toHaveBeenCalledWith(
|
expect(disposeInstance).toHaveBeenCalledWith(
|
||||||
{ directory: '/tmp' },
|
{ directory: '/tmp' },
|
||||||
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
expect.objectContaining({ signal: expect.any(AbortSignal) }),
|
||||||
|
|||||||
@ -455,13 +455,51 @@ export class OpenCodeClient {
|
|||||||
};
|
};
|
||||||
const info = messageProps.info;
|
const info = messageProps.info;
|
||||||
const isCurrentAssistantMessage = info?.sessionID === sessionId && info.role === 'assistant';
|
const isCurrentAssistantMessage = info?.sessionID === sessionId && info.role === 'assistant';
|
||||||
const isCompleted = typeof info?.time?.completed === 'number';
|
if (isCurrentAssistantMessage) {
|
||||||
if (isCurrentAssistantMessage && isCompleted) {
|
const streamError = extractOpenCodeErrorMessage(info?.error);
|
||||||
const streamError = extractOpenCodeErrorMessage(info.error);
|
|
||||||
if (streamError) {
|
if (streamError) {
|
||||||
success = false;
|
success = false;
|
||||||
failureMessage = streamError;
|
failureMessage = streamError;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sseEvent.type === 'message.completed') {
|
||||||
|
const completedProps = sseEvent.properties as {
|
||||||
|
info?: {
|
||||||
|
sessionID?: string;
|
||||||
|
role?: 'assistant' | 'user';
|
||||||
|
error?: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const info = completedProps.info;
|
||||||
|
const isCurrentAssistantMessage = info?.sessionID === sessionId && info.role === 'assistant';
|
||||||
|
if (isCurrentAssistantMessage) {
|
||||||
|
const streamError = extractOpenCodeErrorMessage(info?.error);
|
||||||
|
if (streamError) {
|
||||||
|
success = false;
|
||||||
|
failureMessage = streamError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sseEvent.type === 'message.failed') {
|
||||||
|
const failedProps = sseEvent.properties as {
|
||||||
|
info?: {
|
||||||
|
sessionID?: string;
|
||||||
|
role?: 'assistant' | 'user';
|
||||||
|
error?: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const info = failedProps.info;
|
||||||
|
const isCurrentAssistantMessage = info?.sessionID === sessionId && info.role === 'assistant';
|
||||||
|
if (isCurrentAssistantMessage) {
|
||||||
|
success = false;
|
||||||
|
failureMessage = extractOpenCodeErrorMessage(info?.error) ?? 'OpenCode message failed';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user