Creating the Client
Pedelec is the entry point to the browser SDK. One instance owns one external connection to the Pedelec Chrome Extension and manages request correlation, bridge timeouts, event routing, and the session objects created through that client.
Constructor
Section titled “Constructor”import { Pedelec } from "@kaoruisaac/pedelec";
const pedelec = new Pedelec();The constructor immediately attempts to connect to the configured Pedelec Extension ID. It does not wait for the Desktop App or provider to become available.
If the constructor runs without a browser window, the instance cannot use the browser bridge. If chrome.runtime.connect is unavailable or an initial connection attempt fails, later bridge operations retry the connection; a transient startup failure does not permanently poison the client.
Options
Section titled “Options”const pedelec = new Pedelec({ bridgeTimeoutMs: 30_000,});type PedelecOptions = { bridgeTimeoutMs?: number;};bridgeTimeoutMs
Section titled “bridgeTimeoutMs”Maximum time a request waits for a response from the extension bridge.
- Default:
30_000milliseconds. - Values below
1are clamped to1. - The timeout applies to SDK bridge requests such as
listProviders(),getSettings(),createSession(), and low-levelrequest<T>()calls. - It is not the same as a tool’s
timeoutMs. - It does not limit the total duration of
sendText().sendText()first waits for its bridge request and then waits for the active agent turn to finish.
workspaceFolderPicker() is the exception: while the native picker is open, it intentionally has no SDK bridge wall-clock timeout. Extension/native disconnects and explicit bridge errors still reject it immediately.
A timeout rejects with SDK_BRIDGE_TIMEOUT and includes request metadata in details.
try { await pedelec.listProviders();} catch (error) { if ((error as { code?: string }).code === "SDK_BRIDGE_TIMEOUT") { showConnectionHelp(); }}Increasing this value can help on unusually slow local startup paths, but a very large value can also leave the UI looking frozen when the extension or native bridge is broken. Prefer visible loading state and layer-specific troubleshooting.
Recommended lifetime
Section titled “Recommended lifetime”Use one client per browser page lifecycle.
let pedelec: Pedelec | null = null;
export function getPedelec() { pedelec ??= new Pedelec(); return pedelec;}A single client can create and track multiple PedelecSession objects. Sharing the client avoids unnecessary extension ports and gives the SDK one place to route session events.
Do not treat a client as a cross-page singleton. A page refresh creates a new JavaScript environment and therefore a new client and extension port.
Browser-only initialization
Section titled “Browser-only initialization”Plain browser application
Section titled “Plain browser application”if (typeof window !== "undefined") { const pedelec = new Pedelec(); startPedelecUI(pedelec);}Framework lifecycle
Section titled “Framework lifecycle”Create the client in the framework’s browser lifecycle and keep it out of server-serialized state.
// Conceptual exampleonBrowserMount(() => { const pedelec = new Pedelec(); setPedelecClient(pedelec);});Avoid this in an SSR module:
// Do not do this in a module imported by the server renderer.export const pedelec = new Pedelec();That instance is constructed while window is missing and remains unavailable after hydration.
Extension connection behavior
Section titled “Extension connection behavior”The client creates a unique channelId and opens an external runtime port. Every request contains that channel ID and a unique request ID. Session events are ignored when they belong to another channel.
When the port disconnects:
- requests already sent through that port reject with
EXTENSION_DISCONNECTEDand are not replayed; - each registered session receives an SDK-originated error callback and its handle becomes unusable; and
- the parent client remains reusable and lazily opens one replacement port for the next new operation.
getApprovalStatus() and checkAvailability() also use this retry path. If a replacement connection attempt fails, the current operation reports the unavailable error and a later operation can try again. To continue a session after a disconnect, call resumeSession(sessionId) explicitly; the old PedelecSession handle is never silently moved to the replacement route.
Client versus session responsibility
Section titled “Client versus session responsibility”Client (Pedelec) |
Session (PedelecSession) |
|---|---|
| Extension transport | One agent conversation/thread |
| Provider and settings queries | Turn lifecycle |
| Create/resume requests | Chat, status, tool, error callbacks |
| Request timeout | prepare(), sendText(), end() |
| Event routing by session ID | Session status and handler registration |
Health checks
Section titled “Health checks”Use checkAvailability() when the UI needs readiness through Desktop before session creation:
const availability = await pedelec.checkAvailability();It does not create/resume sessions or open approval. getApprovalStatus() performs a non-sensitive ping, so even an unapproved origin can probe Desktop without opening a popup; approved origins additionally use getSettings(). launchAttempted means a ping or settings probe was sent, not that Desktop definitely launched.
No single method proves that every layer is healthy:
const approval = await pedelec.getApprovalStatus();const providers = approval.installed ? await pedelec.listProviders() : [];getApprovalStatus().appConnectedis the non-sensitive Core connectivity result; it does not imply approval or provider readiness.getSettings()andlistProviders()require origin approval and can trigger its popup. They expose only public defaults andname/code/available/isDefault/error; unknown response fields are ignored for forward compatibility.- Creating a session verifies the selected provider configuration more deeply.
- Sending a turn verifies provider execution.
Use progressive checks rather than one permanent connected boolean.
Multiple clients
Section titled “Multiple clients”Multiple clients are technically possible. Each opens a separate extension port and receives its own channel-specific events. Prefer multiple clients only when separate application roots truly need independent lifecycles.
For multiple views of the same session, it is usually simpler to share one client and application store. When a session is routed through several SDK connections, autoEndOnDisconnect ends it only after its final route disappears.
Continue with Providers and effort profiles.