Skip to content

Extension Bridge

Copy page

@divinci-ai/extension-bridge lets any website talk to the Divinci Local Inference browser extension, so chat runs on the user’s device — private, free, and with no server round-trip — and falls back to the Divinci cloud API when the extension isn’t installed.

It has zero Divinci-internal dependencies and works in any browser. Unlike the other packages here, it is designed to be dropped into a site that has nothing else of ours in it.

Terminal window
npm install @divinci-ai/extension-bridge
import { createDivinciBridge } from "@divinci-ai/extension-bridge";
// cloudChat is your existing cloud path — wire it to @divinci-ai/client.
// Omit it to require the extension.
const divinci = createDivinciBridge({ cloudChat });
const { fullText, source } = await divinci.chat(
{ messages: [{ role: "user", content: "Summarize this page." }] },
{ onToken: (delta) => append(delta) },
);
console.log(source); // "extension" (on-device) or "cloud"

source tells you which path served the turn, so you can badge on-device responses differently, or track how often the extension is doing the work.

The first time a site calls chat() through the extension, the user sees a one-time in-page prompt — “Allow example.com to run AI chat on your device?”.

const { present, extensionVersion, supportedModels } = await divinci.detect();
if (present) showOnDeviceBadge();

hasExtension() is a synchronous presence check when you only need a boolean and can’t await.

These reach the extension directly and have no cloud equivalent:

await divinci.requestAccess(["chat"]); // pre-warm the consent prompt
const card = await divinci.agentCard(); // the local agent's A2A Agent Card
const task = await divinci.task("..."); // submit an A2A task to the local agent

Behaviour without an extension is deliberately asymmetric, so a missing extension never looks like a silent success:

CallNo extension present
chat()falls back to cloudChat, or rejects if you didn’t supply one
detect()resolves with present: false
hasExtension()returns false
requestAccess()returns []
agentCard() / task()reject

ConsentScope is "chat" | "webmcp" | "a2a". Request only what you use — a site asking for all three gets a broader prompt and is likelier to be denied.

The extension injects window.divinci from a MAIN-world content script. This package is a thin, dependency-injected dispatcher over it: extension-first, cloud-fallback.

All dispatch logic is pure — both window and cloudChat are options — so it is fully testable without a browser, and you can unit-test your integration by passing a fake.

const divinci = createDivinciBridge({
win: fakeWindow, // inject a stub instead of the real window
cloudChat: fakeCloudChat,
});