aimask@web:~$
[quickstart][github]
> aimask --info
window.aimask · MV3 · v0.1

Your code, the tokens of whoever uses it

Make what you want.

[ no keys ]

You never touch an API key. Theirs stays in their browser.

[ no bill ]

They pay for what they use. You get no bill.

[ no backend ]

No server. One window.aimask call on the page.

> aimask --quickstart

Quickstart

$ aimask install# add the sdk

Types, a detector, named errors. Zero deps.

> shellbash
npm install @aimask/sdk
$ aimask detect# find the provider

Injected at document_start. null means not installed.

> detect.tstypescript
import { getAimask } from "@aimask/sdk";

const aimask = getAimask(); // Aimask | null
if (aimask === null) {
  // extension not installed, link the user to install it
}
$ aimask session# request a budget

First call opens consent and a budget. Silent after that, until spent or revoked.

> session.tstypescript
const status = await aimask.availability();
// "available" | "requires-consent" | "unavailable"

const opened = await aimask.requestSession({
  needs: { context: null, vision: null, tools: null, json: null },
  prefer: { tier: "fast", models: null },
  intent: "Summarize the current article",
});

if (!opened.ok) return; // opened.error.code explains why
const session = opened.data;
$ aimask chat# run a completion

Every field is explicit. What you send is what runs.

> chat.tstypescript
const result = await session.chat({
  messages: [
    {
      role: "user",
      content: "Hello",
      name: null,
      tool_call_id: null,
      tool_calls: null,
    },
  ],
  tools: null,
  tool_choice: null,
  response_format: null,
  temperature: null,
  max_tokens: 300,
  stop: null,
  signal: null,
});

if (!result.ok) return; // result.error.code
console.log(result.data.message.content);
! NOTE  No default spends their money. If it isn't in the call, it doesn't happen.
$ aimask stream# token by token

Same shape as chat. Pass an AbortSignal to stop.

> stream.tstypescript
const stream = session.chatStream({
  /* same shape as chat */
});

for await (const delta of stream) {
  if (!delta.ok) break; // delta.error.code
  if (delta.data.content) {
    process.stdout.write(delta.data.content);
  }
}
$ aimask errors# typed errors & events

Nothing throws. Errors come back with a named code. Subscribe to budget and connection changes.

> errors.tstypescript
import { ERROR_CODES } from "@aimask/sdk";

const result = await session.chat(/* ... */);
if (!result.ok && result.error.code === ERROR_CODES.BUDGET_EXCEEDED) {
  // the per-origin allowance is exhausted
}

aimask.on("budgetlow", () => {}); // 80% of the origin budget reached
aimask.on("disconnect", () => {}); // session revoked or extension locked
aimask.on("modelchanged", (data) => {}); // resolved model changed
> aimask --errors --list

Error codes

CodeNameMeaning
4001USER_REJECTEDUser said no.
4100UNAUTHORIZEDNo active session for this site.
4200BUDGET_EXCEEDEDThis site's allowance is used up.
4290RATE_LIMITEDThis site is asking too fast.
4400CAPABILITY_UNAVAILABLENo model can do what was asked.
4900DISCONNECTEDExtension locked or model unreachable.
5000PROVIDER_ERRORThe model's provider failed.