Skip to content

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.

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.

const pedelec = new Pedelec({
bridgeTimeoutMs: 30_000,
});
type PedelecOptions = {
bridgeTimeoutMs?: number;
};

Maximum time a request waits for a response from the extension bridge.

  • Default: 30_000 milliseconds.
  • Values below 1 are clamped to 1.
  • The timeout applies to SDK bridge requests such as listProviders(), getSettings(), createSession(), and low-level request<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.

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.

if (typeof window !== "undefined") {
const pedelec = new Pedelec();
startPedelecUI(pedelec);
}

Create the client in the framework’s browser lifecycle and keep it out of server-serialized state.

// Conceptual example
onBrowserMount(() => {
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.

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_DISCONNECTED and 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 (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

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().appConnected is the non-sensitive Core connectivity result; it does not imply approval or provider readiness.
  • getSettings() and listProviders() require origin approval and can trigger its popup. They expose only public defaults and name/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 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.