|
1 | 1 | import { Runloop } from '../index'; |
2 | 2 | import type * as Core from '../core'; |
3 | | -import type { DevboxAsyncExecutionDetailView } from '../resources/devboxes/devboxes'; |
| 3 | +import { RunloopError } from '../error'; |
| 4 | +import type { DevboxAsyncExecutionDetailView, DevboxSendStdInResult } from '../resources/devboxes/devboxes'; |
| 5 | +import type { ExecutionSendStdInParams } from '../resources/devboxes/executions'; |
4 | 6 | import { longPollUntil, resolveLongPollTimeoutMs, type LongPollRequestOptions } from '../lib/polling'; |
5 | 7 | import { ExecutionResult } from './execution-result'; |
6 | 8 |
|
@@ -64,21 +66,51 @@ export class Execution { |
64 | 66 | } |
65 | 67 | } |
66 | 68 |
|
67 | | - // Doesn't work as expected, the execution is killed when the stdin is sent. |
68 | | - // /** |
69 | | - // * Send input to the execution's stdin. |
70 | | - // * |
71 | | - // * @param input - The input to send |
72 | | - // * @param options - Request options |
73 | | - // */ |
74 | | - // async sendStdIn(input: string, options?: Core.RequestOptions): Promise<void> { |
75 | | - // await this.client.devboxes.executions.sendStdIn( |
76 | | - // this._devboxId, |
77 | | - // this._executionId, |
78 | | - // { text: input }, |
79 | | - // options, |
80 | | - // ); |
81 | | - // } |
| 69 | + /** |
| 70 | + * Send input to the execution's stdin. The execution must have been started with `attach_stdin: true`. |
| 71 | + * |
| 72 | + * @example |
| 73 | + * ```typescript |
| 74 | + * const execution = await devbox.cmd.execAsync('cat', { attach_stdin: true }); |
| 75 | + * await execution.sendStdIn('Hello from stdin!\n'); |
| 76 | + * await execution.closeStdIn(); |
| 77 | + * const result = await execution.result(); |
| 78 | + * ``` |
| 79 | + * |
| 80 | + * @param {string} input - The text to write to stdin |
| 81 | + * @param {Core.RequestOptions} [options] - Request options |
| 82 | + * @returns {Promise<void>} Promise that resolves once the input has been delivered |
| 83 | + * @throws {RunloopError} If the API reports that the input was not delivered |
| 84 | + */ |
| 85 | + async sendStdIn(input: string, options?: Core.RequestOptions): Promise<void> { |
| 86 | + await this.sendStdInRequest({ text: input }, options); |
| 87 | + } |
| 88 | + |
| 89 | + /** |
| 90 | + * Close the execution's stdin by sending EOF, so commands that read until end of input can finish. |
| 91 | + * |
| 92 | + * @param {Core.RequestOptions} [options] - Request options |
| 93 | + * @returns {Promise<void>} Promise that resolves once EOF has been delivered |
| 94 | + * @throws {RunloopError} If the API reports that EOF was not delivered |
| 95 | + */ |
| 96 | + async closeStdIn(options?: Core.RequestOptions): Promise<void> { |
| 97 | + await this.sendStdInRequest({ signal: 'EOF' }, options); |
| 98 | + } |
| 99 | + |
| 100 | + private async sendStdInRequest( |
| 101 | + body: ExecutionSendStdInParams, |
| 102 | + options?: Core.RequestOptions, |
| 103 | + ): Promise<void> { |
| 104 | + // executions.sendStdIn() would treat `{ signal: 'EOF' }` as RequestOptions (its `signal` key collides |
| 105 | + // with the AbortSignal option) and drop the body, so post to the endpoint directly. |
| 106 | + const response = await this.client.post<unknown, DevboxSendStdInResult>( |
| 107 | + `/v1/devboxes/${this._devboxId}/executions/${this._executionId}/send_std_in`, |
| 108 | + { ...options, body }, |
| 109 | + ); |
| 110 | + if (!response.success) { |
| 111 | + throw new RunloopError(`Failed to send stdin to execution ${this._executionId}`); |
| 112 | + } |
| 113 | + } |
82 | 114 |
|
83 | 115 | /** |
84 | 116 | * Wait for the execution to complete and return the result. |
|
0 commit comments