diff --git a/src/docs/src/AI/convo.md b/src/docs/src/AI/convo.md index 0cd8e7bd2..618fc199a 100644 --- a/src/docs/src/AI/convo.md +++ b/src/docs/src/AI/convo.md @@ -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. | diff --git a/src/puter-js/src/modules/ai/convo.js b/src/puter-js/src/modules/ai/convo.js index d1cf69347..91d6312a5 100644 --- a/src/puter-js/src/modules/ai/convo.js +++ b/src/puter-js/src/modules/ai/convo.js @@ -1101,7 +1101,11 @@ export class Conversation extends RealtimeEvents { * @returns {Promise} */ interrupt(position) { - this.assertConnected(); + try { + this.assertConnected(); + } catch (error) { + return Promise.reject(error); + } return this.#interrupt(position, 'manual'); } diff --git a/src/puter-js/src/modules/ai/convo.test.js b/src/puter-js/src/modules/ai/convo.test.js index 923b1b816..2b75a27e6 100644 --- a/src/puter-js/src/modules/ai/convo.test.js +++ b/src/puter-js/src/modules/ai/convo.test.js @@ -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,