@@ -31,17 +31,16 @@ import type {
3131 * const session = await client.createSession({ model: "gpt-4" });
3232 *
3333 * // Subscribe to events
34- * const unsubscribe = session.on((event) => {
34+ * session.on((event) => {
3535 * if (event.type === "assistant.message") {
3636 * console.log(event.data.content);
3737 * }
3838 * });
3939 *
40- * // Send a message
41- * await session.send ({ prompt: "Hello, world!" });
40+ * // Send a message and wait for completion
41+ * await session.sendAndWait ({ prompt: "Hello, world!" });
4242 *
4343 * // Clean up
44- * unsubscribe();
4544 * await session.destroy();
4645 * ```
4746 */
@@ -91,6 +90,77 @@ export class CopilotSession {
9190 return ( response as { messageId : string } ) . messageId ;
9291 }
9392
93+ /**
94+ * Sends a message to this session and waits until the session becomes idle.
95+ *
96+ * This is a convenience method that combines {@link send} with waiting for
97+ * the `session.idle` event. Use this when you want to block until the
98+ * assistant has finished processing the message.
99+ *
100+ * Events are still delivered to handlers registered via {@link on} while waiting.
101+ *
102+ * @param options - The message options including the prompt and optional attachments
103+ * @param timeout - Optional timeout in milliseconds. If not provided, waits indefinitely.
104+ * @returns A promise that resolves with the final assistant message when the session becomes idle,
105+ * or undefined if no assistant message was received
106+ * @throws Error if the timeout is reached before the session becomes idle
107+ * @throws Error if the session has been destroyed or the connection fails
108+ *
109+ * @example
110+ * ```typescript
111+ * // Send and wait for completion with a 5-minute timeout
112+ * const response = await session.sendAndWait(
113+ * { prompt: "What is 2+2?" },
114+ * 300_000
115+ * );
116+ * console.log(response?.data.content); // "4"
117+ * ```
118+ */
119+ async sendAndWait ( options : MessageOptions , timeout ?: number ) : Promise < SessionEvent | undefined > {
120+ // Track whether we've started the send - only count idle events after this point
121+ let sendStarted = false ;
122+ let resolveIdle : ( ) => void ;
123+ const idlePromise = new Promise < void > ( ( resolve ) => {
124+ resolveIdle = resolve ;
125+ } ) ;
126+
127+ // Track the last assistant message received
128+ let lastAssistantMessage : SessionEvent | undefined ;
129+
130+ // Register listener BEFORE sending, but only resolve for idle events
131+ // that arrive after we've initiated the send (to ignore stale events)
132+ const unsubscribe = this . on ( ( event ) => {
133+ if ( sendStarted ) {
134+ if ( event . type === "assistant.message" ) {
135+ lastAssistantMessage = event ;
136+ } else if ( event . type === "session.idle" ) {
137+ resolveIdle ( ) ;
138+ }
139+ }
140+ } ) ;
141+
142+ try {
143+ // Mark send as started and initiate - these are synchronous so no events
144+ // can sneak in between setting the flag and starting the send
145+ sendStarted = true ;
146+ await this . send ( options ) ;
147+
148+ // Wait for idle with optional timeout
149+ if ( timeout !== undefined ) {
150+ const timeoutPromise = new Promise < never > ( ( _ , reject ) => {
151+ setTimeout ( ( ) => reject ( new Error ( `Timeout after ${ timeout } ms waiting for session.idle` ) ) , timeout ) ;
152+ } ) ;
153+ await Promise . race ( [ idlePromise , timeoutPromise ] ) ;
154+ } else {
155+ await idlePromise ;
156+ }
157+
158+ return lastAssistantMessage ;
159+ } finally {
160+ unsubscribe ( ) ;
161+ }
162+ }
163+
94164 /**
95165 * Subscribes to events from this session.
96166 *
0 commit comments