On this page

Streaming exec runs a command on a computer and delivers output line-by-line via Server-Sent Events. Use it for long-running builds, installs, or any command where you want incremental output rather than waiting for completion.

Base path: /api/v1/computers/{id}/exec/stream


Quick Start

import { Miosa } from '@miosa/sdk';

const client = new Miosa();

// Stream a long-running build
for await (const event of client.exec.stream(computerId, {
  command: 'pip install torch',
  timeout: 300,
})) {
  if (event.type === 'output') {
    process.stdout.write(event.data.line);
  } else if (event.type === 'exit') {
    console.log(`Exit code: ${event.data.exitCode}`);
    break;
  }
}
# Obtain a streaming ticket first, then connect
TICKET=$(curl -s -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/stream 
  -H "Authorization: Bearer $MIOSA_API_KEY" 
  -H "Content-Type: application/json" 
  -d '{"command": "pip install torch", "timeout": 300}' | jq -r .ticket)

curl -N "https://api.miosa.ai/api/v1/computers/{id}/exec/stream/events?ticket=$TICKET" 
  -H "Accept: text/event-stream"

Endpoints

MethodPathDescription
POST/computers/{id}/exec/streamStart a streaming exec and obtain a ticket
GET/computers/{id}/exec/stream/eventsSSE stream (requires ticket)
POST/computers/{id}/execNon-streaming exec (waits for completion)
POST/computers/{id}/exec/pythonNon-streaming Python exec

Start Streaming Exec

POST /api/v1/computers/{id}/exec/stream

Starts the command and returns a short-lived ticket for the SSE stream.

Request Body

FieldTypeRequiredDescription
commandstringYesShell command to execute
timeoutintegerNoTimeout in seconds (default: 30, max: 300)
shellstringNoShell to use (default: /bin/bash)

Response — 202 Accepted

{
  "ticket": "exec_short_lived_token",
  "expires_at": 1712700060,
  "stream_url": "/api/v1/computers/{id}/exec/stream/events?ticket=exec_short_lived_token"
}

The ticket expires in 60 seconds — open the EventSource connection immediately.

Errors

StatusErrorCause
409COMPUTER_NOT_RUNNINGComputer is not running
502AGENT_UNAVAILABLEIn-VM agent unreachable
curl -X POST https://api.miosa.ai/api/v1/computers/{id}/exec/stream 
  -H "Authorization: Bearer $MIOSA_API_KEY" 
  -H "Content-Type: application/json" 
  -d '{"command": "npm install", "timeout": 120}'

Connect to SSE Stream

GET /api/v1/computers/{id}/exec/stream/events?ticket={ticket}

Event Types

EventPayload FieldsDescription
outputline, streamA line of stdout or stderr
exitexit_code, duration_msCommand finished; stream closes after this
timeouttimeout_secondsCommand killed after timeout
errormessageInternal error before command started

Event Payload Examples


event: output
data: {"line": "Collecting torch\n", "stream": "stdout"}

event: output
data: {"line": "  Downloading torch-2.3.0-...\n", "stream": "stdout"}

event: exit
data: {"exit_code": 0, "duration_ms": 45312}

stream Values

ValueDescription
stdoutStandard output
stderrStandard error
// Browser
const es = new EventSource(`/api/v1/computers/${id}/exec/stream/events?ticket=${ticket}`);

es.addEventListener('output', e => {
  const { line, stream } = JSON.parse(e.data);
  console.log(`[${stream}] ${line}`);
});

es.addEventListener('exit', e => {
  const { exit_code } = JSON.parse(e.data);
  console.log(`Exited with code ${exit_code}`);
  es.close();
});

es.addEventListener('error', () => {
  // Connection dropped or ticket expired
  es.close();
});

Comparison: Streaming vs Non-Streaming

AspectPOST /execPOST /exec/stream
Response timingAfter command completesImmediate (ticket)
Output deliveryAll at onceLine by line via SSE
Max timeout300 s300 s
Best forQuick commandsLong builds, installs
SDK methodexec.run()exec.stream()

Common Recipes

Monitor a build

for event in client.exec.stream(computer_id, command="make build", timeout=300):
    if event.type == "output":
        print(event.data["line"], end="")
    elif event.type == "exit":
        if event.data["exit_code"] != 0:
            raise RuntimeError(f"Build failed with exit code {event.data['exit_code']}")
        print("Build succeeded")
        break

Capture both streams separately

const stdout: string[] = [];
const stderr: string[] = [];

for await (const event of client.exec.stream(computerId, { command: 'myapp 2>&1 | tee /tmp/output.log' })) {
  if (event.type === 'output') {
    if (event.data.stream === 'stdout') stdout.push(event.data.line);
    else stderr.push(event.data.line);
  } else if (event.type === 'exit') {
    break;
  }
}

Timeout handling

try:
    for event in client.exec.stream(computer_id, command="long-task", timeout=60):
        if event.type == "timeout":
            print(f"Command killed after {event.data['timeout_seconds']}s")
            break
        elif event.type == "exit":
            break
except Exception as e:
    print(f"Stream error: {e}")

Was this helpful?