Ultron Live

SDK Documentation

The official JavaScript / TypeScript SDK (ultron-live-sdk). Viewing the v2 surface.

Overview & Install

The official JavaScript / TypeScript SDK for Ultron Live — real-time AI vision, screen commentary, WebRTC streaming, realtime voice, and assessment. It works in the browser and in Node, and ships with full TypeScript types.

Install

bash
npm install ultron-live-sdk

Import

ts
// ESM (recommended)
import { UltronLive } from "ultron-live-sdk";

// CommonJS
const { UltronLive } = require("ultron-live-sdk");

Or via UMD in a plain browser page:

html
<script src="https://cdn.skypack.dev/ultron-live-sdk"></script>
You need an API key (ulk_…) or a JWT to use protected features. Get a key from your dashboard, or via the auth flow below.

Quick Start

Initialize with your key and start a screen-share commentary loop in three lines:

ts
import { UltronLive } from "ultron-live-sdk";

const ultron = new UltronLive({ apiKey: "ulk_your_api_key" });

await ultron.startScreenShare({
  model: "gemini-2.5-flash",
  onTranscript: (text) => console.log("AI:", text),
  onCreditsUpdate: (remaining) => console.log("credits:", remaining),
  onCreditsExhausted: () => alert("Top up to continue"),
});

// later…
ultron.stop();

Configuration

Create one instance with new UltronLive(config). Everything (REST, WebRTC, realtime voice) inherits these settings — including the API version.

ts
const ultron = new UltronLive({
  apiKey: "ulk_your_api_key",   // or token: "<jwt>"
  apiVersion: "v1",             // "v1" (default) or "v2"
  model: "gemini-3.1-flash-lite-preview",
  frameInterval: 1500,          // ms between captured frames
  frameQuality: 0.4,            // JPEG quality 0–1
  maxFrameWidth: 1280,
  maxFrameHeight: 720,
});
FieldTypeRequiredNotes
apiKeystringrequiredYour key (ulk_…). Either apiKey or token is required.
tokenstringoptionalJWT from auth.verifyOtp().
baseUrlstringoptionalBackend URL. Defaults to the production API.
apiVersion'v1' | 'v2'optionalBackend workflow. Default 'v1'. See Versioning.
modelModelValueoptionalDefault vision model. Default gemini-3.1-flash-lite-preview.
historyWindownumberoptionalRecent commentary strings kept for context. Default 5.
frameIntervalnumberoptionalms between frame captures. Default 1500.
frameQualitynumberoptionalJPEG quality 0–1. Default 0.4.
maxFrameWidthnumberoptionalMax capture width px. Default 1280.
maxFrameHeightnumberoptionalMax capture height px. Default 720.

Read it back any time via ultron.config, and check the active version with ultron.apiVersion. Update credentials at runtime with ultron.setApiKey(key) or ultron.setToken(jwt).

API Versions (v1 & v2)

The backend runs two parallel workflows. Choose one per instance with apiVersion — it flows through REST, createWebRTC(), and createRealtimeVoice() automatically.

v1 — Stable (default)v2 — Current
Vision pipelineOne model call per frameGolden Frame pipeline (filter + two-tier)
Extra features—Assessment engine, MCP servers
Realtime voiceStandard relayRelay with realtime tools
ts
// Stable (default) — unchanged behavior
const ultron = new UltronLive({ apiKey: "ulk_..." });

// Current — server-side frame filtering, assessment, MCP, realtime tools
const ultronV2 = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });
Backward compatible: v1 is the default, so upgrading the package never changes behavior. Calling a v2-only API (assessment.*, mcp.*) on a v1 instance throws a clear error.

Running the classic per-frame pipeline

On v2, frame filtering is managed by Ultron and tuned server-side — there is no client-side toggle, and any such keys passed to startSession() are stripped before the message is sent. To run the classic one-call-per-frame mode, use the v1 pipeline:

ts
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v1" });

Authentication

The flow is always: request an OTP → verify it → get a JWT + API key. Use signup for a new account, login for an existing one.

ts
// 1. Request an OTP (emails a 6-digit code)
await ultron.auth.signup({ email: "user@example.com", firstName: "Ada" }); // new user
// or: await ultron.auth.login({ email: "user@example.com" });            // existing user

// 2. Verify the code → JWT + user (incl. permanent apiKey)
const { data } = await ultron.auth.verifyOtp({ email: "user@example.com", otp: "123456" });
ultron.setToken(data.token);
console.log(data.user.apiKey, data.user.credits);

// 3. Live profile + credits any time
const me = await ultron.auth.getMe();
MethodDescription
auth.signup(opts)New account → emails an OTP.
auth.login(opts)Existing account → emails an OTP.
auth.sendOtp(opts)Legacy get-or-create + OTP (kept for compatibility).
auth.verifyOtp(opts)Verify OTP → { token, user }.
auth.getMe()Current profile + live credit balance.
setToken(jwt) / setApiKey(key)Update credentials at runtime.

Memory & History

Ultron remembers across sessions. Each night it distils a user's sessions into short facts that stay true — what they are building, which tools they use, how they like to be helped — and states them back in every later conversation.

Because those facts are repeated, a wrong one compounds. Surface this in your UI rather than hiding it: the user must be able to see and correct what Ultron believes about them.
ts
const { data } = await ultron.memory.get();

data.enabled;              // false = Ultron neither learns nor recalls
data.facts.forEach((f) => {
  console.log(f.text, f.established ? "" : "(seen once — tentative)");
});

// The user correcting Ultron. A user-stated fact outranks anything inferred.
await ultron.memory.addFact({ text: "Prefers short answers", category: "preference" });
await ultron.memory.updateFact(factId, { text: "Uses MongoDB Atlas" });
await ultron.memory.pinFact(factId);      // never evicted
await ultron.memory.deleteFact(factId);
await ultron.memory.clear();              // forget everything

await ultron.memory.setEnabled(false);    // stop remembering entirely

// Distillation runs nightly; run it now to see it work immediately.
await ultron.memory.refresh();

Session history

Every session gets a close-out digest when it ends — one paragraph describing the whole session — so history stays readable without replaying hundreds of frames.

ts
const { data } = await ultron.sessions.list({ limit: 20 });
data.sessions.forEach((s) => console.log(s.startedAt, s.summary));

const detail = await ultron.sessions.get(sessionId, { frames: 100 });
detail.data.summary;           // the close-out digest
detail.data.frames;            // timeline: commentary, tags, image URLs

await ultron.sessions.remove(sessionId);   // Ultron can no longer recall it

How the AI uses it

You do not wire any of this into chat(). The backend assembles context itself and picks how far back to look from the question — the live screen only for "what am I looking at?", the last couple of hours for "what did I just do?", and deep history ranked purely by relevance for "what did we decide back in July?". Profile memory is included in every request regardless.

MethodDescription
memory.get()Everything Ultron remembers about the user.
memory.setEnabled(bool)Master switch for learning and recall.
memory.addFact(input)State a fact directly { text, category?, pinned? }.
memory.updateFact(id, patch)Correct a fact; promotes it to a user statement.
memory.pinFact(id, pinned?)Protect a fact from eviction.
memory.deleteFact(id)Forget one fact.
memory.clear()Forget everything.
memory.refresh(force?)Distil recent sessions now.
sessions.list(opts?)Past sessions, newest first { page?, limit?, from?, to? }.
sessions.get(id, opts?)One session: digest, summaries, frame timeline.
sessions.remove(id)Delete a session.

Account

Profile fields, and issuing browser-safe tokens for your own end users.

ts
await ultron.auth.updateProfile({ firstName: "Ada", dateOfBirth: "1990-12-10" });
await ultron.auth.updateTimezone({ timezone: "Asia/Kolkata" });

Session tokens (server-side)

Never ship your API key to a browser. Mint a short-lived token on your server, hand it to the page, and give the page a tokenProvider that fetches a fresh one when it expires.

ts
// On your server — requires the raw API key.
const { data } = await ultron.auth.sessionTokens.create({
  endUserId: "user_42",
  ttlSeconds: 900,
});
res.json({ token: data.token });

// If a token leaks, kill it — or block that end user entirely.
await ultron.auth.sessionTokens.revoke({ jti: data.jti });
await ultron.auth.sessionTokens.revoke({ endUserId: "user_42" });
await ultron.auth.sessionTokens.unblock("user_42");
Minting is refused for anything other than the raw API key — a leaked session token cannot mint itself a longer life. Billing and subscription management are deliberately not part of this SDK; handle them on your own backend.

Screen & Video Streaming

The high-level streaming API requests screen/camera access, runs a capture loop, and emits callbacks — no manual frame handling.

ts
// Screen share
await ultron.startScreenShare({
  onTranscript: (text) => render(text),
  onCreditsUpdate: (n) => setCredits(n),
  onError: (e) => console.error(e),
});

// Any MediaStream (camera, <video>.captureStream(), etc.)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
await ultron.startVideoStream({ stream, onTranscript: console.log });

ultron.stop();

Live preview

ts
ultron.attachPreview(document.getElementById("preview"));  // attach captured stream to a <video>
const stream = ultron.getPreviewStream();                   // or wire it up yourself

Voice narration

ts
// ElevenLabs with your own key — the backend no longer synthesizes speech.
ultron.configureAudio({ voiceProvider: "elevenlabs", elevenLabsApiKey: "sk_..." });
ultron.setAudioEnabled(true);
MethodDescription
startScreenShare(opts?)Request screen share + run the commentary loop.
startVideoStream(opts)Run commentary on any MediaStream.
stop()Stop the active loop and clean up.
attachPreview(el) / detachPreview(el)Attach/detach the live stream to a <video>.
getPreviewStream()The active MediaStream, or null.
setAudioEnabled(bool) / configureAudio(opts)Toggle and configure ElevenLabs narration.
isStreaming / sessionIdStream state + active session id.

Frame Analysis & Chat

Low-level REST calls when you want to drive frames/chat yourself.

ts
const { sessionId } = await ultron.startSession();

const result = await ultron.analyseFrame({
  image: "data:image/jpeg;base64,...",
  history: previousCommentaries,
  sessionId,
});
console.log(result.response, result.creditsRemaining);

const reply = await ultron.chat({
  sessionId,
  message: "What was I working on?",
});
For high-frequency live use, prefer WebRTC (below) — it avoids per-frame HTTP overhead.

WebRTC (Low-Latency)

A persistent peer connection with data channels for frames, chat, and control — much lower latency than REST. Create one pre-wired with your credentials and version:

ts
const rtc = ultron.createWebRTC();

rtc.onFrameResult = (r) => render(r.response, r.creditsRemaining);
rtc.onConnected = () => rtc.startSession();
await rtc.connect();

// Send frames (binary preferred — no base64 bloat)
canvas.toBlob((blob) => rtc.sendFrame(blob), "image/jpeg", 0.4);

// Chat over the data channel
rtc.sendChat("What changed on screen?");

rtc.stopSession();
rtc.disconnect();

v2: Golden pipeline & live assessment

On apiVersion: "v2" the session runs the Golden Frame pipeline and can act as a live assessment session. Pass seq + captureTs so frames can be ordered, and read info.ingestFps to pace your capture.

ts
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });
const rtc = ultron.createWebRTC();

rtc.onFrameResult = (f) => console.log("frame:", f.response);
rtc.onFlowResult  = (s) => console.log("summary:", s.response);   // tier-2 narrative

// Assessment session — structured verdict instead of commentary
rtc.onAssessmentResult = (r) => console.log("verdict:", r.assessment);
rtc.onConnected = () => rtc.startSession({
  outputType: "json",
  prompt: "Verify the procedure.",
  responseSchema: { type: "object", properties: { verdict: { type: "string" } }, required: ["verdict"] },
});
await rtc.connect();

let seq = 0;
setInterval(() => rtc.sendFrameBase64(base64, { seq: seq++, captureTs: Date.now() }), 150);
// rtc.stopSession()  → emits the final assessment:result
// rtc.finalize()     → verdict mid-stream, keep streaming

Methods & callbacks

MethodDescription
connect()Connect; resolves when all 3 channels are open.
sendFrame(blob | buffer)Send a binary JPEG frame.
sendFrameBase64(b64, { seq?, captureTs? })Send a base64 frame with ordering metadata (v2).
sendChat(message, opts?)Send a chat message.
startSession(config?)Start a session; pass assessment config (outputType, prompt, template) on v2.
stopSession() / finalize()End the session / emit a verdict mid-stream (v2).
setModel(m) / ping() / disconnect()Change model, measure latency, tear down.

Callbacks: onFrameResult, onFrameError, onChatResult, onSessionStarted(id, info?), onConnected, onDisconnected, and (v2) onFlowResult, onAssessmentProgress, onAssessmentResult, onAssessmentError, onSessionStartError.

Realtime Voice

Two-way live voice with OpenAI Realtime or Gemini Live, normalized to one protocol. Audio plays back automatically by default.

ts
const voice = ultron.createRealtimeVoice({ provider: "gemini" });

voice.onReady = () => console.log("ready — start streaming mic");
voice.onTranscriptDelta = (t) => appendCaption(t);
voice.onUserTranscript = (t) => showUserSaid(t);

await voice.connect();                 // waits for relay:ready
voice.sendAudio(base64Pcm16Chunk);     // PCM16 mono, 16kHz (gemini) / 24kHz (openai)
voice.disconnect();
Wait for onReady before sending audio. The relay does not resample — send 16 kHz for Gemini, 24 kHz for OpenAI; playback is 24 kHz. Server VAD handles turn-taking and barge-in.

Assessment

Requires apiVersion: "v2". Turns media (a video, frames, or documents) into a calibrated PASS / FAIL / UNCERTAIN verdict with a confidence score.

assessAndWait uploads the media and polls until the verdict is ready:

ts
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });

const result = await ultron.assessment.assessAndWait({
  media: [fileInput.files[0]],            // video, frames, or documents (≤30, ≤18 MB each)
  outputType: "json",
  prompt: "Verify the sample-collection procedure.",
});

// Calibrated verdict (json output):
const v = JSON.parse(result.assessment);
v.verdict;        // 'PASS' | 'FAIL' | 'UNCERTAIN'
v.confidence;     // 0–100 (lowest per-check confidence)
v.needs_review;   // true → route to a human
v.checks;         // [{ name, result, confidence, observed }]

Calibrated verdict & human-in-the-loop

For outputType: "json" the fields verdict, confidence, needs_review and per-question checks[] are guaranteed. An element that can't be directly verified is capped low and marked UNCERTAIN (absence of evidence is never a confident PASS/FAIL). When confidence is below the template's confidenceThreshold (default 70), the verdict is forced to UNCERTAIN + needs_review: true — so a human is engaged automatically.

ts
const v = JSON.parse(result.assessment);
if (v.needs_review) routeToHumanReviewer(v);   // low confidence or genuinely uncertain
else applyAutomatically(v.verdict);

Add or skip questions

Adjust a template's questions for one run without editing it — append extraChecks or drop existing ones with skipChecks (by 1-based index or title). The same fields work on a live rtc.startSession() call.

ts
await ultron.assessment.assessAndWait({
  media: [file],
  templateId: tpl.id,
  extraChecks: [{ title: "Gloves worn", instruction: "Were gloves worn the whole time?" }],
  skipChecks: [2, "Identity Match"],          // drop question #2 and "Identity Match"
});

// Permanent change to the test's questions:
await ultron.assessment.templates.update(tpl.id, { checks: [...tpl.checks, newCheck] });

Off-camera collections

For privately-collected types (urine, vaginal, rectal) where only the post-collection sealing/packaging is on camera, set collectionMode: "off_camera". It auto-skips every check marked onCameraOnly: true and tells the model not to penalize the unseen collection — evaluating only what's on screen.

ts
await ultron.assessment.assessAndWait({ media: [file], templateId, collectionMode: "off_camera" });
rtc.startSession({ outputType: "json", templateId, collectionMode: "off_camera" });

// Mark collection-step questions so they drop automatically in off-camera mode:
//   checks: [{ title: "Proper Collection", instruction: "…", onCameraOnly: true }, …]

Single frame, and saved presets (templates):

ts
await ultron.assessment.assessFrame(base64DataUrl, { outputType: "json", prompt: "Gauge in green?" });

const { data: templates } = await ultron.assessment.templates.list();
await ultron.assessment.assessAndWait({ media: [file], templateId: templates[0].id, fields: { type: "swab" } });
File inputs: in the browser pass File/Blob directly; in Node pass { data: buffer, filename, contentType }. Live assessment over a screen share uses the WebRTC client (see WebRTC → v2).

MCP Servers

Requires apiVersion: "v2". Manage the user's own MCP (web-MCP) tool endpoints. HTTP transport only; secret header values are encrypted at rest and never returned (only a masked hint).
ts
const ultron = new UltronLive({ apiKey: "ulk_...", apiVersion: "v2" });

const { data: server } = await ultron.mcp.servers.create({
  name: "docs",
  url: "https://example.com/mcp",
  headers: { "X-Workspace": "acme" },
  secrets: [{ key: "Authorization", value: "Bearer abc123" }],   // encrypted at rest
});

await ultron.mcp.servers.test(server.id);     // connect + list tools to validate
const { data: servers } = await ultron.mcp.servers.list();
const { data: tools }   = await ultron.mcp.tools();   // effective tools for the user
MethodDescription
mcp.servers.list() / get(id)List / fetch the user's servers (secrets masked).
mcp.servers.create(input)Add a server { name, url, headers?, secrets? }.
mcp.servers.update(id, input) / remove(id)Update (re-encrypts secrets) / delete.
mcp.servers.test(id)Connect + list tools to validate config.
mcp.tools()Effective tools available to the user.

Models

Browse the model catalogue and switch models at runtime:

ts
ultron.models;                          // ModelMeta[] — full catalogue
ultron.getModelsByProvider("gemini");   // filter by provider
ultron.getModelMeta("gpt-4o");          // metadata for one model
ultron.setModel("gemini-2.5-flash");    // change the default (no restart)

Error Handling

All API failures throw typed errors you can branch on:

ts
import { UltronAuthError, UltronCreditsError, UltronAPIError } from "ultron-live-sdk";

try {
  await ultron.analyseFrame({ image });
} catch (err) {
  if (err instanceof UltronCreditsError) promptTopUp();        // 402
  else if (err instanceof UltronAuthError) redirectToLogin();  // 401
  else if (err instanceof UltronAPIError) console.error(err.statusCode, err.message);
}
MethodDescription
UltronAPIErrorBase error — has .statusCode and .message.
UltronAuthError401 — missing/invalid/expired credentials.
UltronCreditsError402 — insufficient credits.

Full Method Reference

The complete surface of the main UltronLive instance.

MethodDescription
auth.{ signup, login, sendOtp, verifyOtp, getMe, updateProfile, updateTimezone, getTransactions, getSubscription, cancelSubscription }Account & session.
billing.createCheckoutSession(opts)Start a Stripe checkout.
startSession() / analyseFrame(opts) / chat(opts)Low-level REST session, frame, chat.
startScreenShare(opts?) / startVideoStream(opts) / stop()High-level streaming.
attachPreview / detachPreview / getPreviewStreamLive preview.
setAudioEnabled / configureAudioElevenLabs voice narration.
models / getModelMeta / getModelsByProvider / setModelModel catalogue & selection.
createWebRTC(overrides?)Low-latency WebRTC client.
createRealtimeVoice(overrides?)Realtime voice client.
assessment.* (v2)assess, assessAndWait, get, list, assessFrame, templates.*
mcp.* (v2)servers.{ list,get,create,update,remove,test }, tools()
setApiKey / setToken / config / apiVersionCredentials, config, active version.