Download src/adapters/webext.js from nyaaorick/everything-webgpu: direct link, hf CLI and curl.
- Browser
- Download file 6.5 kB
-
https://huggingface.co/nyaaorick/everything-webgpu/resolve/main/src/adapters/webext.js
- Command line
-
hf download hf://nyaaorick/everything-webgpu/src/adapters/webext.js
-
curl -L -o webext.js https://huggingface.co/nyaaorick/everything-webgpu/resolve/main/src/adapters/webext.js
6.5 kB
| /** | |
| * WebExtension adapter: the storage passthrough, and the message/port router. | |
| * | |
| * This is the whole extension-specific half of what used to be | |
| * `src/background/background.js`. The engine no longer knows it exists — it is | |
| * one way to reach a `ScheduledEngine`, and a page reaches the same object by | |
| * calling its methods. | |
| * | |
| * The wire format is unchanged byte-for-byte from `everything-webgpu/v1`, so | |
| * the popup, the manager page, the e2e devtest page and any external extension | |
| * keep working with no edits. | |
| */ | |
| import { asEngineError } from "../engine/errors.js"; | |
| import { OP, PORT_NAME, PORT_OP, PROTOCOL } from "./protocol.js"; | |
| /** | |
| * `browser.storage.local` already *is* the StorageAdapter shape — `get(key)` | |
| * and `set(obj)`. The two-method interface was chosen for that reason, so this | |
| * is a passthrough rather than a translation layer. | |
| * | |
| * @returns {import("../engine/model-store.js").StorageAdapter} | |
| */ | |
| export function webExtensionStorage() { | |
| return browser.storage.local; | |
| } | |
| /** | |
| * Wires a ScheduledEngine onto this extension's runtime messaging. | |
| * | |
| * @param {import("../engine/engine.js").ScheduledEngine} engine | |
| * @returns {() => void} detach | |
| */ | |
| export function attachWebExtensionTransport(engine) { | |
| const subscribers = new Set(); | |
| const unsubscribe = engine.subscribe((state) => { | |
| broadcast({ protocol: PROTOCOL, op: PORT_OP.ENGINE_STATE, state }); | |
| }); | |
| function broadcast(msg) { | |
| for (const port of subscribers) { | |
| try { | |
| port.postMessage(msg); | |
| } catch { | |
| subscribers.delete(port); | |
| } | |
| } | |
| } | |
| // ------------------------------------------------------------- routing ---- | |
| async function handle(msg) { | |
| if (!msg || msg.protocol !== PROTOCOL) { | |
| throw new Error(`Expected protocol "${PROTOCOL}", got "${msg && msg.protocol}".`); | |
| } | |
| switch (msg.op) { | |
| case OP.STATUS: | |
| return { ok: true, state: engine.state, webgpu: engine.hasWebGPU }; | |
| case OP.LIST_MODELS: | |
| return { ok: true, models: await engine.listModels(), state: engine.state }; | |
| case OP.LOAD: | |
| return { ok: true, state: await engine.load(msg.modelId) }; | |
| case OP.UNLOAD: | |
| return { ok: true, state: await engine.unload() }; | |
| case OP.CHAT: | |
| return { ok: true, ...(await engine.complete(msg)) }; | |
| case OP.BATCH: | |
| return { ok: true, results: await engine.batch(msg) }; | |
| case OP.CANCEL: | |
| return { ok: true, cancelled: engine.cancel(msg.id ?? msg.session) }; | |
| case OP.CONFIGURE: | |
| return { ok: true, ...(await engine.configure(msg)) }; | |
| default: | |
| throw new Error(`Unknown op "${msg.op}".`); | |
| } | |
| } | |
| /** | |
| * `error` stays a plain string so every existing caller keeps working; `code` | |
| * and `detail` are added beside it. Drop the string once consumers have | |
| * moved — the wire protocol is versioned (`everything-webgpu/v1`) and this is | |
| * an addition, not a break. | |
| */ | |
| const respond = (msg) => | |
| handle(msg).catch((err) => { | |
| const e = asEngineError(err); | |
| return { ok: false, error: e.message, code: e.code, ...(e.detail ? { detail: e.detail } : {}) }; | |
| }); | |
| /** | |
| * The allowlist is a property of *this transport*, not of the engine: it | |
| * answers "which other extension may send me messages", a question that has | |
| * no meaning for a page that already holds the object. | |
| */ | |
| async function denyExternal(sender) { | |
| const { allowedExternalIds } = await engine.store.getSettings(); | |
| const id = sender?.id; | |
| if (allowedExternalIds.length === 0 || allowedExternalIds.includes(id)) return null; | |
| return { ok: false, error: `Extension "${id}" is not on this engine's allowlist.` }; | |
| } | |
| function attachPort(port) { | |
| if (port.name !== PORT_NAME) return; | |
| subscribers.add(port); | |
| port.onDisconnect.addListener(() => subscribers.delete(port)); | |
| port.postMessage({ protocol: PROTOCOL, op: PORT_OP.ENGINE_STATE, state: engine.state }); | |
| const send = (msg) => { | |
| try { | |
| port.postMessage(msg); | |
| } catch { | |
| /* port closed mid-stream */ | |
| } | |
| }; | |
| port.onMessage.addListener(async (msg) => { | |
| const id = msg?.id; | |
| try { | |
| if (!msg || msg.protocol !== PROTOCOL) throw new Error(`Expected protocol "${PROTOCOL}".`); | |
| switch (msg.op) { | |
| case PORT_OP.SUBSCRIBE: | |
| return send({ protocol: PROTOCOL, op: PORT_OP.ENGINE_STATE, state: engine.state }); | |
| case PORT_OP.CHAT_STREAM: { | |
| const result = await engine.complete(msg, (delta) => | |
| send({ protocol: PROTOCOL, op: PORT_OP.CHUNK, id, delta }), | |
| ); | |
| return send({ protocol: PROTOCOL, op: PORT_OP.DONE, id, ...result }); | |
| } | |
| case PORT_OP.BATCH_STREAM: { | |
| const results = await engine.batch(msg, (item) => | |
| send({ protocol: PROTOCOL, op: PORT_OP.ITEM, id, ...item }), | |
| ); | |
| return send({ protocol: PROTOCOL, op: PORT_OP.DONE, id, results }); | |
| } | |
| case PORT_OP.ABORT: | |
| return void engine.cancel(msg.session ?? id); | |
| default: | |
| return send({ protocol: PROTOCOL, op: msg.op, id, ...(await respond(msg)) }); | |
| } | |
| } catch (err) { | |
| const e = asEngineError(err); | |
| send({ | |
| protocol: PROTOCOL, | |
| op: PORT_OP.ERROR, | |
| id, | |
| error: e.message, | |
| code: e.code, | |
| ...(e.detail ? { detail: e.detail } : {}), | |
| }); | |
| } | |
| }); | |
| } | |
| const onMessage = (msg) => respond(msg); | |
| const onMessageExternal = async (msg, sender) => (await denyExternal(sender)) ?? respond(msg); | |
| const onConnectExternal = async (port) => { | |
| const denial = await denyExternal(port.sender); | |
| if (denial) { | |
| port.postMessage({ protocol: PROTOCOL, op: PORT_OP.ERROR, error: denial.error }); | |
| port.disconnect(); | |
| return; | |
| } | |
| attachPort(port); | |
| }; | |
| browser.runtime.onMessage.addListener(onMessage); | |
| browser.runtime.onMessageExternal.addListener(onMessageExternal); | |
| browser.runtime.onConnect.addListener(attachPort); | |
| browser.runtime.onConnectExternal.addListener(onConnectExternal); | |
| return () => { | |
| browser.runtime.onMessage.removeListener(onMessage); | |
| browser.runtime.onMessageExternal.removeListener(onMessageExternal); | |
| browser.runtime.onConnect.removeListener(attachPort); | |
| browser.runtime.onConnectExternal.removeListener(onConnectExternal); | |
| unsubscribe(); | |
| subscribers.clear(); | |
| }; | |
| } | |