Quick Start
This guide creates a single browser-side session with the first available provider. It also shows the minimum lifecycle handling needed for a usable UI.
1. Create a client
Section titled “1. Create a client”Create the client after your application is running in the browser:
import { Pedelec, type PedelecError, type PedelecSession,} from "@kaoruisaac/pedelec";
const pedelec = new Pedelec();A page should normally share this client instead of creating a new instance for every button click.
2. Check the extension and providers
Section titled “2. Check the extension and providers”const approval = await pedelec.getApprovalStatus();
if (!approval.installed) { throw new Error("Install or enable the Pedelec Chrome Extension.");}
const providers = await pedelec.listProviders();const provider = providers.find((item) => item.available);
if (!provider) { throw new Error("Pedelec Desktop could not start or connect; open it manually or repair installation, then configure a provider.");}The site does not need to be approved just to read approval status. Session creation or resume starts the approval flow when approval is still required.
3. Create a session
Section titled “3. Create a session”const session = await pedelec.createSession({ provider: provider.code,});When only provider is supplied, the SDK uses effort level default; Core resolves that profile from Desktop Settings. You can pass effortLevel: "low" or "high" with or without an explicit provider. Ollama requires a model in the selected profile.
4. Register callbacks before sending
Section titled “4. Register callbacks before sending”let assistantText = "";
const unsubscribeChatDelta = session.onChatDelta((delta, ctx) => { assistantText += delta; renderAssistantMessage(assistantText); console.debug("turn", ctx.turnId, "received", delta.length, "characters");});
const unsubscribeChat = session.onChat((text, ctx) => { console.debug("completed message", ctx.turnId, text);});
const unsubscribeStatus = session.onStatus((status, ctx) => { renderSessionStatus(status); setComposerDisabled(status !== "idle"); console.debug(ctx.previousStatus, "→", status, ctx.source);});
const unsubscribeError = session.onError((error, ctx) => { renderError(`${error.code}: ${error.message}`); console.error(ctx.type, error.details);});
const unsubscribeEnded = session.onEnded(() => { setComposerDisabled(true);});onStatus() only fires when the status changes. Use session.getStatus() for the initial state.
5. Send a message
Section titled “5. Send a message”try { assistantText = ""; setComposerDisabled(true);
await session.sendText("Explain what this application can do.");
console.log("The agent turn is complete.");} catch (error) { const pedelecError = error as PedelecError; renderError(`${pedelecError.code}: ${pedelecError.message}`);} finally { if (session.getStatus() === "idle") { setComposerDisabled(false); }}sendText() resolves after Core emits the matching semantic operation completion. It does not resolve merely because the request reached the transport or the session reported idle.
A session accepts only one active turn. Disable the send button while a turn is active and still handle SESSION_BUSY in case two application paths race.
6. End and clean up
Section titled “6. End and clean up”await session.end();
unsubscribeChatDelta();unsubscribeChat();unsubscribeStatus();unsubscribeError();unsubscribeEnded();end() is idempotent on an already-ended session. While ended, the same handle cannot accept another message until session.resume() successfully returns it to idle.
Complete example
Section titled “Complete example”import { Pedelec, type PedelecError, type PedelecSession,} from "@kaoruisaac/pedelec";
let activeSession: PedelecSession | null = null;let disposeSessionHandlers: (() => void) | null = null;
export async function connectAndRun(prompt: string) { const pedelec = new Pedelec();
const approval = await pedelec.getApprovalStatus(); if (!approval.installed) { throw new Error("Pedelec extension is unavailable."); }
const providers = await pedelec.listProviders(); const provider = providers.find((item) => item.available); if (!provider) { throw new Error("No provider is available."); }
const session = await pedelec.createSession({ provider: provider.code, }); activeSession = session;
let text = ""; const offChatDelta = session.onChatDelta((delta) => { text += delta; document.querySelector("#answer")!.textContent = text; }); const offStatus = session.onStatus((status) => { document.querySelector("#status")!.textContent = status; }); const offError = session.onError((error) => { document.querySelector("#error")!.textContent = `${error.code}: ${error.message}`; }); const offEnded = session.onEnded(() => { document.querySelector("#status")!.textContent = "ended"; });
disposeSessionHandlers = () => { offChatDelta(); offStatus(); offError(); offEnded(); };
try { await session.sendText(prompt); } catch (error) { const value = error as PedelecError; console.error(value.code, value.message, value.details); throw error; }}
export async function disconnect() { const session = activeSession; activeSession = null;
try { await session?.end(); } finally { disposeSessionHandlers?.(); disposeSessionHandlers = null; }}What to add next
Section titled “What to add next”A production UI should also:
- present approval instructions rather than throwing generic errors;
- let users choose a provider and provider-independent effort level when the product needs a local override;
- define tools for page-specific actions;
- preserve or deliberately discard
sessionIdbased on lifecycle requirements; and - handle extension, native host, Core, and provider failures separately.
Continue with Origin approval and connection state or start defining browser-side tools.