<!-- OpenSmartRoute: Browser extension. https://opensmartroute.ai/docs/EXTENSION -->
# Browser extension

The OpenSmartRoute browser extension is a **pre-send advisor** for the chat sites people already use -
ChatGPT, Claude, Gemini, Microsoft Copilot and Perplexity. While you type, a small card next to the
composer shows what the request is, how many tokens it carries, whether it contains personal details or
pasted secrets, which kind of model it deserves and what it would cost across the OpenSmartRoute
catalogue. Everything runs on-device by default; the platform is only involved when you ask for it.

Source: platform/extension (`platform/extension/README.md`). Chrome and Edge builds share one package
(Manifest V3); a Firefox build is produced by the same script.

## What the card shows

| Row | Source | What you can do |
|---|---|---|
| Task, domains, tokens, complexity | On-device signals (a TypeScript port of the router's heuristics) | See what the router would infer before sending |
| Personal details found (email, phone, card, IBAN, SSN, Aadhaar, IP, API keys and tokens) | On-device patterns, the same list the platform's guard uses | **Redact** replaces them in the composer with `<EMAIL_1>`-style placeholders; the mapping stays in the page so you can restore the answer yourself |
| Model hint | The tier the request needs versus the model label the site shows in its picker | Switch to a fast model for a simple task or to a thinking model for a reasoning task |
| Tighten prompt | On-device compaction (filler, duplicated lines, whitespace) | **Tighten** replaces the draft and shows the tokens saved |
| Quote | On-device estimate over the cached public catalogue (`GET /api/v1/models`), or the platform's full quote (`POST /api/v1/estimate`) when the analysis policy allows | Recommended, cheapest and best-quality candidates with estimated cost; prices carry the catalogue date |
| Cross-site hint | Best-quality vendor for this task, when it is another site you have enabled | Open that site in a new tab; the extension never navigates for you |

The extension **reads only the draft in the composer** and the model label. It never reads or changes
what the site sends, never blocks a request and never automates the site's UI beyond replacing your own
text when you click Redact or Tighten.

## Anywhere you write, anything you select

The extension behaves like a writing assistant and an AI sidebar at the same time:

- **Field badge** - a small mark sits in the bottom-right corner of the field you are typing in and shows how
  many things the advisor found (personal details, writing slips, a model hint, a tighter version). Its colour
  is the risk: grey nothing, green suggestions only, amber personal details, red secrets or financial identifiers.
  Click it to open or close the card. On the chat sites the card opens as you type; everywhere else the badge
  comes first and the card opens on demand - except when a draft carries a secret or a card number, which opens
  it straight away.
- **Selection actions** - select text on any enabled page and a small toolbar appears above it: *Explain*,
  *Summarise*, *Rewrite*, *Translate*, *Fix grammar* and *Ask…*. Each runs through the routed side panel with
  the selection attached, so the router picks the model and you see the decision under the answer. The same
  actions are in the right-click menu, together with *Summarise this page* and *Open routed chat*.
- **Edge tab and shortcut** - a slim tab on the right edge opens the side panel (right-click it to hide it on
  that site); `Alt+Shift+O` opens it anywhere. The empty side panel offers *Summarise this page* and the
  quick actions for whatever is attached; an *Attached* chip shows what will travel with the next message and can be detached.
- **Assist in every text field** (options, off by default) - registers the advisor for all sites (or for the
  sites you have granted) so mail, docs, tickets and forms get the badge too. The browser asks for access once.

## Analysis policy

Chosen at onboarding and changeable in the options page.

| Level | What leaves the browser |
|---|---|
| **Local only** (default) | Only the public catalogue download (`GET /api/v1/models`, no user data). Quotes are computed on-device |
| **Quote with the platform** | The draft is sent to `POST /api/v1/estimate` - anonymously (rate limited) or with your key - **after** on-device redaction unless you turn that off. The platform stores no request text (activity rows carry request id and metadata only) |

Keys are kept in `chrome.storage.local` (not synced). Sign out from the popup or revoke the key from
the dashboard's API keys page.

## Sign in

Popup -> **Sign in to OpenSmartRoute**. The extension starts a device sign-in
(`POST /api/v1/auth/device/code`), opens the approval page in a tab and polls
`POST /api/v1/auth/device/token` until you approve; the minted key is labelled *Browser extension* in
the dashboard. For a self-hosted `osr serve` or an existing key, paste the token on the options page
and set the API prefix to *Self-hosted osr serve*.

Once signed in, quotes are metered as `estimate` on your workspace, tenant rules apply
(set the tenant in options) and the traffic is attributed to the app `browser-extension`: every call carries
`X-OSR-App: browser-extension`, and the dashboard's *Usage* page breaks metered calls down **by app**
(`GET /api/v1/usage` -> `by_app`), so a workspace can see how much of its routed traffic came from the
extension, the CLI or its own integrations.

## Routed chat in the side panel

Popup -> **Open routed chat** (or **Continue in the side panel** on the card when you are signed in). The
side panel sends your messages to `POST /v1/chat/completions` with `model: "auto"` and streams the answer;
under each answer it shows the decision - the target that answered, the confidence, the cost and latency the
platform measured, the plan and whether a fallback ran - and thumbs that post `POST /api/v1/feedback`, so the
workspace's learners hear how the answer went. The three sliders set the objective (`osr.objective`:
quality / cost / latency) for every request. From a chat site, *Continue in the side panel* carries the
visible turns and your draft across as history.

## Metering provider API calls

Two channels, both off until you switch them on in options.

- **Provider consoles and your own apps** - an observe-only `webRequest` listener on the LLM API hosts
  (OpenAI, Azure OpenAI, Anthropic, Gemini, OpenRouter, Groq, Mistral, Together, DeepSeek, xAI, Cohere), on
  `localhost` / `127.0.0.1` and on any origin you add. It reads the request body (model, messages) and the
  status, estimates the completion from `max_tokens`, prices the call from the catalogue and never blocks or
  changes anything. The browser asks for access to those hosts once. The popup shows today's calls and
  estimated cost per vendor.
- **DevTools panel** (F12 -> *OpenSmartRoute*) - every LLM call the inspected tab makes with the exact
  `usage` from the response body (OpenAI chat and Responses, Anthropic Messages, Gemini, Cohere; JSON and
  SSE), latency, status, the detected task and the catalogue price; a detail view with the messages and the
  answer; JSONL / CSV export; and **Replay through OpenSmartRoute**, which sends the same messages through
  the router and shows both answers with both costs side by side.

**Share observed usage with my workspace** (options, signed in) uploads call *shapes* - vendor, model,
token counts, site, task type, complexity - to `POST /api/v1/usage/observed` every few minutes. No text
field is ever sent; the platform rejects unknown fields (`422`). The workspace's opportunity report
(`GET /api/v1/usage/opportunity`) compares what those calls cost at list price with what the router's
recommended target would have cost for the same token shape.

### The Opportunity page

Signed-in users read the report on `/platform/dashboard/opportunity`: observed calls and spend, the
router's quote for the same requests, the gap, the models with the widest gap and the target the router
would have picked, breakdowns by model, site and task, the per-call history (`GET /api/v1/usage/observed`,
newest first, never any text) and a CSV export. Only calls the catalogue can price count towards the gap,
so both sides describe the same requests; the page says so and never calls the number a saving. Rows are
kept for 90 days and deleted with the workspace. While nothing has been observed yet the page explains
what to install and how to switch sharing on.

## Install

The product page `/extension` on the website is the front door: it offers the store listing for the
visitor's browser (Chrome Web Store, Microsoft Edge Add-ons) once the deployment sets
`NEXT_PUBLIC_EXTENSION_CHROME_URL` / `NEXT_PUBLIC_EXTENSION_EDGE_URL` at build time, a download when
`NEXT_PUBLIC_EXTENSION_DOWNLOAD_URL` points at a release zip, and otherwise the developer-mode steps below.
The page recognises an installed extension - the platform-origin content script sets
`data-osr-extension="<version>"` on `<html>` - and turns the call to action into a link to the Opportunity
page. Signed-in users on a Chromium browser without the extension see a one-time, dismissible suggestion
on the dashboard overview and the Usage page.

**Managed browsers.** A web page cannot install an extension by itself (browsers removed inline install in
2018); what installs it automatically for everyone in a company is the `ExtensionSettings` policy - Group
Policy or Intune for Edge and Chrome on Windows, Google Admin for managed Chrome profiles. The product page
renders the snippet with the store id (`NEXT_PUBLIC_EXTENSION_CHROME_ID` / `_EDGE_ID`):

```json
{ "<extension-id>": { "installation_mode": "force_installed", "update_url": "https://clients2.google.com/service/update2/crx" } }
```

Edge uses the same shape with `https://edge.microsoft.com/extensionwebstorebase/v1/crx`.

Until the store listings are published, load the unpacked build:

```bash
cd platform/extension
npm install
npm run build:chrome            # dist/chrome (Chrome, Edge)
npm run build:firefox           # dist/firefox
npm run pack                    # zips for the stores
```

- Chrome: `chrome://extensions` -> Developer mode -> **Load unpacked** -> `platform/extension/dist/chrome`.
- Edge: `edge://extensions` -> Developer mode -> **Load unpacked** -> the same folder.
- Firefox: `about:debugging#/runtime/this-firefox` -> **Load Temporary Add-on** -> `dist/firefox/manifest.json`.

Permissions: `storage`, `alarms`, `sidePanel`, `webRequest` (observe-only; no install warning), `contextMenus`,
`scripting` and `activeTab` (page summaries and the optional assist-everywhere script), the five chat sites for
the content script, and the platform origin (`https://opensmartroute.ai`, `localhost`, `127.0.0.1`) for the
worker. The LLM API hosts are optional host permissions requested when you switch on *Provider consoles*; all
sites are requested when you switch on *Assist in every text field*; a self-hosted platform URL on another
origin is requested when you save it.

## Test

```bash
npm run check    # eslint, tsc, vitest (analysis library, card rendering, adapters)
npm run e2e      # Playwright: loads the unpacked build in real Chrome and Edge
```

The end-to-end suite starts a mocked platform API and serves a fixture chat page at `https://chatgpt.com/`
through request interception, so the real content-script match, the shadow-DOM card, redaction, the
model hint, the quote policy, the popup counters, the device sign-in and the options page are all
exercised in both browsers without touching the live sites. The same run covers the side panel (streamed
routed answer, metadata, feedback), the DevTools panel (exact usage, pricing, replay) and the observer
(a page's API call metered, uploaded as a shape), audits the card (light and dark
scheme) and every extension page with axe against WCAG 2.1 A/AA and checks that the card is fully keyboard
operable (Enter on Redact and the collapse control, Escape hides it).

## Design

The card and the pages follow the brand guide: the real mark (positive artwork on light surfaces, reverse
on dark), the Quicksand wordmark with *Smart* in the cyan text tone, the website palette in both colour
schemes (paper, snow, mist, line, ink), hairline rules instead of shadows for emphasis, mono numerals in
the quote ledger and the three brand hues reserved for the tier badge and the pick labels. The card sits
just above the composer, right-aligned with it, collapses to a single line, folds long candidate lists
behind *Show all*, shows a loading row while a draft is first analysed and respects `prefers-reduced-motion`.

## Privacy summary

- Default policy is local only; nothing about your prompt leaves the browser.
- With the quote policy, personal details are redacted on-device before the draft is sent.
- No telemetry. No remote code: the heuristics and adapters are bundled; the catalogue is data.
- Keys are stored locally, never synced, and can be revoked from the dashboard at any time.
