fix(convo): reject interrupt readiness failures asynchronously

This commit is contained in:
404oops committed 2026-11-01 19:25:43 +01:00
1 parent 6ed7e8b452
commit db310b1bc2
3 files changed
+25 -2

No files matched your search

+1 -1
View File
@@ -46,7 +46,7 @@ The conversation exposes these properties and methods.
| `sendText(text)` | Adds a user message and requests a response. Text must be nonempty and at most 8192 characters. |
| `appendAudio(pcm)` | Appends an `Int16Array` containing PCM16, 24 kHz mono audio. A chunk contains at most 12000 samples. |
| `commitAudio()` | Commits buffered audio and requests a response. Requires turn detection to be off, through `turnDetection: false` or a native session update. The provider requires at least 100 ms of buffered audio. |
| `interrupt(position?)` | Stops built-in playback immediately and waits for cancellation and playback-history synchronization. A reply requested but not yet started is cancelled as soon as it starts. Custom players provide `{ itemId, contentIndex, playedMs }`. An idle call resolves immediately. |
| `interrupt(position?)` | Stops built-in playback immediately and waits for cancellation and playback-history synchronization. A reply requested but not yet started is cancelled as soon as it starts. Custom players provide `{ itemId, contentIndex, playedMs }`. An idle call resolves immediately. Readiness failures reject the returned Promise with `session_not_ready` or `session_closed`. |
| `send(event)` | Sends a native OpenAI JSON event. |
| `on(type, handler)` | Registers a listener and returns an unsubscribe function. Use `'*'` to receive every native and convenience event. |
| `off(type, handler)` | Removes a listener. |
+5 -1
View File
@@ -1101,7 +1101,11 @@ export class Conversation extends RealtimeEvents {
* @returns {Promise<void>}
*/
interrupt(position) {
this.assertConnected();
try {
this.assertConnected();
} catch (error) {
return Promise.reject(error);
}
return this.#interrupt(position, 'manual');
}
+19
View File
@@ -61,6 +61,25 @@ afterEach(() => {
vi.restoreAllMocks();
});
describe('interrupt readiness', () => {
it.each(['prepared', 'closed', 'reconnecting'])(
'rejects in %s state',
async (state) => {
const c = state === 'reconnecting' ? await connected() : make();
if (state === 'closed') c.close();
if (state === 'reconnecting') socket().close(1012, 'restart');
let result;
expect(() => {
result = c.interrupt();
}).not.toThrow();
await expect(result).rejects.toMatchObject({
code:
state === 'closed' ? 'session_closed' : 'session_not_ready',
});
},
);
});
describe('conversation preparation', () => {
it.each([
null,