Agent context for @inbrowser/model
Purpose
The model layer. Three public seams:
- Root (
@inbrowser/model).src/contract.tsdefines the oneModelClientcontract the whole stack shares (relay + agent both consume it). Usage helpers,withRetry, and shared provider config types live here. No provider factories. No engine. - Providers (
@inbrowser/model/providers/<name>). Eachsrc/providers/<name>.tsis a cloud provider (Gemini, Firebase AI Logic, OpenRouter, Requesty, Anthropic, Ollama, Claude-CLI, Claude-Code, …), returning aModelClient. Firebase AI Logic is a constructed-model adapter; the others are provider factories. - Local (
@inbrowser/model/local). On-device engine wrapping@huggingface/transformersbehind a narrowEnginesurface (src/engine.ts) that streamsEngineEvent. AlsocreateEngineModelClient, presets, worker helpers.
The engine is also a ModelClient, via createEngineModelClient
(src/engine-client.ts; exported from @inbrowser/model/local). It wraps an Engine,
widening the engine’s EngineEvent stream to the contract’s
ModelEvent. The old engine→relay/agent adapter subpaths were removed;
this single wrapper replaces them.
Layering invariants
src/index.tsis the lightweight contract/utilities interface. It must not export engine modules, engine types, presets, worker helpers, or provider factories.src/local.tsis the opt-in on-device interface. All Transformers-backed runtime exports and engine-specific helpers/types belong there.- Public providers are imported as
@inbrowser/model/providers/<name>. Do not export internals (gemini-protocol,providers/typesas a deep path — types are re-exported from the root). src/contract.tsis type-only (zero runtime imports) so importing the contract never pulls in the engine or@huggingface/transformers.src/types.tsis the canonical engine type surface. Engine-side files import engine types from here.src/engine.tsis the only module that holds runtime model state.- Each
src/providers/<name>.tsimports the contract types and emitsModelEvents. Pure Gemini protocol helpers shared by the raw Gemini and Firebase AI Logic transports live insrc/providers/gemini-protocol.ts; transport decoders remain provider-local. Providers do not import the relay or the agent — the dependency points inward (relay/agent depend on this package’s contract, never the reverse). src/worker.tsreturns the sameEngineshapecreateEnginereturns. Consumers must not need to know which side ofpostMessagethe engine lives on.
Vocabulary
Use the precise terms — they show up in types, comments, and PRs:
- ModelRef (locator) vs ModelPreset (locator + static config) vs Engine (loaded runtime).
- Backend: WebGPU / WASM, the ORT execution provider. Not “GPU mode.”
dtype: precision selection. Not “model size.”- Cold start = fetch + init + warmup. Three distinct phases,
each with its own
LoadProgressvariant.
Don’t
- Don’t add
createGemmaEngine/createPhi3Engine/ sugar factories. New models are newModelPresetentries. - Don’t put the agent’s tool-calling polyfill logic here. The native
envelope recognition (
parseToolCalls) is mechanical and stays; the prompt-engineered polyfill is a strategy and belongs in@inbrowser/agent. - Don’t widen
EngineEventwith cloud-only concepts (cost, thoughtSignature). Translate at thecreateEngineModelClientboundary (src/engine-client.ts), not in the engine. - Don’t re-introduce provider exports into
@inbrowser/relay— the providers live here now and the relay consumes them asModelClientFactorys. - Don’t make
@huggingface/transformersa regular dependency. It’s an optional peer used through@inbrowser/model/local; local-inference consumers install it explicitly. (The Claude Code Agent SDK, used only byclaudeCodeModelClient, is an optional peer dep.) - Don’t make
firebasea dependency or initialize Firebase/App Check in this package.createFirebaseAiLogicModelClientaccepts a structural, caller-constructedGenerativeModel; the host owns its Firebase app, backend, location, authentication, and App Check lifecycle.
Status
Contract + cloud providers are the live path: relay and agent both
consume a ModelClient from here. The engine loads and generate()
streams real tokens, and the engine is now a ModelClient via
createEngineModelClient (the engine→ModelClient adapter). The next
slice is the site wiring that drives a local engine through the agent
end to end (the in-browser docs-chat toggle). Firebase AI Logic’s core
text/thinking/custom-tool path is implemented through
createFirebaseAiLogicModelClient; its Live, Imagen, template, multimodal,
and hybrid lifecycle surfaces remain intentionally outside ModelClient.