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.
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:
{
"permissions": {
"ai": true
}
}ai.chat
ai.chat
requires ai: truechat(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.
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.
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 alwayscanUse(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".
// 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.
| Code | Meaning | What your app should do |
|---|---|---|
ai_disabled | The installed app has no ai permission. | Hide AI features entirely and ship an update with the permission declared. |
auth_required | Nobody is signed in to VibeOS. | Invite the user to sign in; the host opens the sign-in flow itself. |
upgrade_required | The user's plan does not include AI. | Explain the feature needs an upgrade and keep the rest of the app usable. |
quota_exceeded | The user is out of AI credits. | Say so plainly and offer the non-AI path if you have one. |
rate_limited | Too 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:
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.
// 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
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.
// 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.