SDK reference

vos.ai — AI

Hosted chat completions with no API keys and no billing integration of your own. VibeOS runs the request on behalf of the signed-in user, enforces their plan and quota, and hands you back a string.

window.vos.ai2 methodsrequires ai: true

How AI works in VibeOS

Your app never talks to a model provider. It calls window.vos.ai.chat(); the host attaches the user's identity, picks the model, applies their plan limits, and returns the finished reply. You ship no keys and pay for nothing.

  • Model choice is host-controlled. There is no model parameter — VibeOS selects it, so your app automatically benefits from upgrades.
  • No streaming surface. The host may stream internally, but your Promise resolves once with the complete text. Show a typing indicator rather than a per-token animation.
  • Chat is the only model call. There is no image generation, embedding or speech method today.

Declare the capability in your manifest, or every AI call is rejected:

json
{
  "permissions": {
    "ai": true
  }
}

ai.chat

ai.chat

requires ai: true
chat(messages: { role: "system" | "user" | "assistant"; content: string }[]): Promise<string>

returns the assistant's reply as a string

Sends a message list and resolves with the assistant's reply. Put your instructions in a system message, the user's input in user, and previous replies back in as assistant to give the model memory.

javascript
const reply = await window.vos.ai.chat([
  { role: "system", content: "You are a concise writing assistant. Reply in one sentence." },
  { role: "user", content: "Summarise this note: " + noteText },
]);

// reply is a plain string — the assistant's full message.
render(reply);

ai.chat is the authoritative access check. Permission, sign-in state, plan and quota are all enforced here at call time, so handle its rejection properly even if you pre-checked with canUse.

javascript
try {
  const reply = await window.vos.ai.chat(messages);
  render(reply);
} catch (err) {
  // err is a plain Error — match on the message text.
  const m = (err.message || "").toLowerCase();

  if (m.includes("sign in") || m.includes("auth")) {
    setStatus("Sign in to VibeOS to use AI features.");
  } else if (m.includes("upgrade") || m.includes("plan")) {
    setStatus("AI is not included in your current plan.");
  } else if (m.includes("rate limit")) {
    setStatus("Too many requests — try again in a moment.");
  } else if (m.includes("permission")) {
    setStatus("This app was installed without AI permission.");
  } else {
    setStatus("AI request failed. Please try again.");
  }
  // Do NOT silently fall back to a local engine when the user chose AI —
  // the host shows its own sign-in / upgrade prompt for these failures.
}

ai.canUse

ai.canUse

none — safe to call always
canUse(category?: string): Promise<{ ok: boolean; code?: string; requiredPlan?: string; message?: string }>

returns an entitlement hint, cached for about 30 seconds

A cheap, non-authoritative pre-check for deciding whether to show AI controls at all. It sends no model request and the result is cached for roughly 30 seconds, so it is fine to call on load and on window focus. category defaults to "chat".

javascript
// Cheap pre-check: no model request, result cached ~30s.
const access = await window.vos.ai.canUse("chat");

if (!access.ok) {
  // access.code says WHY, so you can show the right call to action.
  showBanner(access.code, access.message);
  disableAiControls();
}

canUse deliberately fails open

If your app has the ai permission, the current host answers { ok: true } unless it is certain access is unavailable — it would rather let you try than block on a stale answer. Use it to hide UI, never as your only guard: the real decision happens inside ai.chat.

When the app was installed without the AI permission, it returns { ok: false, code: "ai_disabled" } immediately without contacting the host.

Access codes

canUse reports these in its code field. The same conditions cause ai.chat to reject, and the host shows the user its own sign-in, upgrade or top-up prompt.

CodeMeaningWhat your app should do
ai_disabledThe installed app has no ai permission.Hide AI features entirely and ship an update with the permission declared.
auth_requiredNobody is signed in to VibeOS.Invite the user to sign in; the host opens the sign-in flow itself.
upgrade_requiredThe user's plan does not include AI.Explain the feature needs an upgrade and keep the rest of the app usable.
quota_exceededThe user is out of AI credits.Say so plainly and offer the non-AI path if you have one.
rate_limitedToo many requests in a short window.Back off and retry with increasing delay.

A single switch on access.code covers every state — pre-check with canUse, then route each outcome to the right UI:

javascript
const access = await window.vos.ai.canUse("chat");

if (!access.ok) {
  switch (access.code) {
    case "ai_disabled":
      // App was installed without the ai permission — no point showing AI UI at all.
      hideAiFeatures();
      break;

    case "auth_required":
      // Nobody is signed in. The host opens the sign-in flow itself;
      // just explain why and retry once they are in.
      showNotice("Sign in to VibeOS to use the AI assistant.");
      onSignIn(() => recheckAccess());
      break;

    case "upgrade_required":
      showNotice("AI features need a plan upgrade — everything else still works.");
      break;

    case "quota_exceeded":
      showNotice("You're out of AI credits. Try the manual mode meanwhile.");
      offerFallback();
      break;

    case "rate_limited":
      // Brief, self-healing state — disable the button and retry.
      disableSendButton("Trying again…");
      setTimeout(recheckAccess, 5000);
      break;

    default:
      showNotice("AI is unavailable right now. Please try again later.");
  }
}

async function recheckAccess() {
  const access = await window.vos.ai.canUse("chat");
  if (access.ok) enableAiFeatures();
}

Match on the message, not on err.code

Rejections cross the sandbox boundary as a plain Error, so today err.code is undefined inside an app even though the host classifies the failure internally. Branch on err.message as shown above, and keep a generic fallback. See errors & limits.

Patterns

Multi-turn conversation

The SDK is stateless — the model remembers nothing between calls. Keep the transcript yourself and trim it so requests stay fast.

javascript
// Keep a rolling transcript and send it each turn.
const SYSTEM = { role: "system", content: "You are Vibo, a friendly VibeOS helper." };
const history = [];

async function send(userText) {
  history.push({ role: "user", content: userText });

  // Trim old turns so requests stay small and fast.
  const recent = history.slice(-12);

  const reply = await window.vos.ai.chat([SYSTEM, ...recent]);
  history.push({ role: "assistant", content: reply });
  return reply;
}

Backoff on rate limits

javascript
async function chatWithRetry(messages, attempts = 3) {
  let delay = 800;
  for (let i = 0; i < attempts; i++) {
    try {
      return await window.vos.ai.chat(messages);
    } catch (err) {
      const rateLimited = (err.message || "").toLowerCase().includes("rate limit");
      if (!rateLimited || i === attempts - 1) throw err;
      await new Promise((r) => setTimeout(r, delay));
      delay *= 2; // exponential backoff
    }
  }
}

Structured output

Models sometimes wrap JSON in prose or code fences. Extract and validate before you trust the shape.

javascript
// Ask for JSON, but never trust it blindly.
const raw = await window.vos.ai.chat([
  { role: "system", content: 'Reply with JSON only: {"title":string,"tags":string[]}' },
  { role: "user", content: noteText },
]);

let data = null;
try {
  const match = raw.match(/\{[\s\S]*\}/); // strip stray prose or code fences
  data = match ? JSON.parse(match[0]) : null;
} catch {
  data = null;
}

if (!data || typeof data.title !== "string") {
  setStatus("Could not read the AI response — try again.");
}

Limits & review

  • Each SDK call — including ai.chat — has a hard 30-second timeout. Keep prompts and expected replies modest, and split long jobs into steps.
  • Token and request quotas belong to the user's plan, not to your app. There is no way to query the remaining balance; handle the failure instead.
  • Always show progress. A frozen window during a multi-second model call is the most common reason an AI app fails review.
  • Never send file contents or personal data to the model without the user asking for it, and say in your listing description what leaves the device.

A complete worked example

The AI chatbot example is a full, submittable app built on vos.ai — transcript handling, error states and all.