Polling with requests
pip install requests
import os
import time
import webbrowser
import requests
API = os.environ.get("SAI_API_URL", "https://simular-cloud-api-149671192594.us-central1.run.app")
HEADERS = {"Authorization": f"Bearer {os.environ['SAI_API_KEY']}"}
def start(task: str, machine_id: str | None = None) -> str:
body = {"message": task, "wait": False}
if machine_id:
body["machineId"] = machine_id
r = requests.post(f"{API}/v1/agents/message", json=body, headers=HEADERS, timeout=30)
if r.status_code == 429:
raise RuntimeError(r.json()["error"])
r.raise_for_status()
return r.json()["sessionId"]
def approve(approval_id: str, response: str = "yes", selections=None) -> None:
body = {"approvalId": approval_id, "response": response}
if selections is not None:
body["selections"] = selections
r = requests.post(f"{API}/v1/agents/approve", json=body, headers=HEADERS, timeout=30)
r.raise_for_status()
def run(task: str) -> str:
session_id = start(task)
cursor = None
while True:
params = {"sessionId": session_id, "wait": 30}
if cursor:
params["since"] = cursor
r = requests.get(f"{API}/v1/agents/events", params=params, headers=HEADERS, timeout=90)
if r.status_code == 429:
time.sleep(60)
continue
r.raise_for_status()
page = r.json()
cursor = page["cursor"]
for ev in page["events"]:
if ev["type"] == "data-progress":
print(" ", ev["data"]["text"])
status = page["status"]
if status == "running":
continue
if status == "needs_approval":
data = page["approval"]
if data["isLinkOnly"]:
print("Open in a browser:", data["approvalUrl"])
webbrowser.open(data["approvalUrl"])
time.sleep(15)
elif data["approvalType"] == "choice":
picks = [[q["options"][0]["value"]] for q in data["questions"]]
approve(data["approvalId"], "yes", picks)
else:
print("Approving:", data.get("command", data["title"]))
approve(data["approvalId"], "yes")
continue
if status == "idle":
return page["text"]
raise RuntimeError(
next((e["errorText"] for e in page["events"] if e["type"] == "error"), "task failed")
)
if __name__ == "__main__":
print(run("Open Notepad, type hello, save it to the desktop as hello.txt"))
POST /v1/agents/abort with {"sessionId": session_id}.
Uploading a file
from pathlib import Path
def upload(path: str) -> dict:
p = Path(path)
r = requests.post(
f"{API}/v1/agents/upload",
data=p.read_bytes(),
headers={**HEADERS, "x-filename": p.name},
timeout=120,
)
r.raise_for_status()
return r.json()
attachment = upload("report.pdf")
requests.post(
f"{API}/v1/agents/message",
json={"message": "Summarise the attached PDF", "wait": False, "attachments": [attachment]},
headers=HEADERS,
timeout=30,
)
Streaming with sseclient-py
Use this from a long-lived process where holding a connection for minutes is fine.
pip install requests sseclient-py
import json
import os
import requests
import sseclient
API = os.environ.get("SAI_API_URL", "https://simular-cloud-api-149671192594.us-central1.run.app")
HEADERS = {
"Authorization": f"Bearer {os.environ['SAI_API_KEY']}",
"Content-Type": "application/json",
"Accept": "text/event-stream",
}
resp = requests.post(
f"{API}/v1/agents/message",
json={"message": "What is on the screen right now?"},
headers=HEADERS,
stream=True,
timeout=(30, 600),
)
resp.raise_for_status()
text = []
for event in sseclient.SSEClient(resp).events():
if event.data == "[DONE]":
break
frame = json.loads(event.data)
kind = frame["type"]
if kind == "text-delta":
text.append(frame["delta"])
elif kind == "data-approval-request":
print("approval:", frame["data"]["approvalId"], frame["data"]["isLinkOnly"])
elif kind == "error":
raise RuntimeError(frame["errorText"])
print("".join(text))
POST /approve from another thread or process; the stream stays open while it waits.
