MCPcopy Index your code
hub / github.com/TanStack/ai / spawnProcess

Method spawnProcess

packages/ai-sandbox-docker/src/handle.ts:226–298  ·  view source on GitHub ↗
(
    command: string,
    opts?: ProcessOptions,
  )

Source from the content-addressed store, hash-verified

224 }
225
226 private async spawnProcess(
227 command: string,
228 opts?: ProcessOptions,
229 ): Promise<SpawnHandle> {
230 const exec = await this.container.exec({
231 Cmd: ['sh', '-c', command],
232 AttachStdin: true,
233 AttachStdout: true,
234 AttachStderr: true,
235 WorkingDir: opts?.cwd ? this.abs(opts.cwd) : this.workdir,
236 Env: this.envArray(opts?.env),
237 })
238 const stream = await exec.start({ hijack: true, stdin: true })
239 const outPT = new PassThrough()
240 const errPT = new PassThrough()
241 this.docker.modem.demuxStream(stream, outPT, errPT)
242 /*
243 * Close the demuxed output streams when the hijacked exec stream finishes,
244 * so consumers iterating `stdout`/`stderr` (for await ... of) terminate.
245 * A normal EOF emits `end`, but a destroyed stream (e.g. from kill()) emits
246 * only `close` and never `end` — so we must also end the PassThroughs on
247 * `close`/`error`, or the consumer hangs forever waiting for the iterator to
248 * complete. `end()` is idempotent, so handling multiple events is safe.
249 */
250 const endOutputs = (): void => {
251 outPT.end()
252 errPT.end()
253 }
254 stream.on('end', endOutputs)
255 stream.on('close', endOutputs)
256 stream.on('error', endOutputs)
257 if (opts?.signal) {
258 opts.signal.addEventListener('abort', () => stream.destroy(), {
259 once: true,
260 })
261 }
262
263 return {
264 pid: -1, // docker exec does not surface a host-visible pid
265 stdout: decodeStream(outPT),
266 stderr: decodeStream(errPT),
267 stdin: {
268 write: (data) =>
269 new Promise<void>((resolve, reject) => {
270 stream.write(data, (err) => (err ? reject(err) : resolve()))
271 }),
272 end: () => {
273 stream.end()
274 return Promise.resolve()
275 },
276 },
277 wait: async () => {
278 await new Promise<void>((resolve) => {
279 if (outPT.readableEnded) {
280 resolve()
281 return
282 }
283 // Resolve on whichever of these fires first. A clean exit emits

Callers 1

constructorMethod · 0.95

Calls 11

absMethod · 0.95
envArrayMethod · 0.95
rejectFunction · 0.85
writeMethod · 0.80
resolveMethod · 0.80
decodeStreamFunction · 0.70
execMethod · 0.65
startMethod · 0.65
destroyMethod · 0.65
endMethod · 0.65
addEventListenerMethod · 0.45

Tested by

no test coverage detected