跳到內容

快速開始

本頁會使用第一個 available provider 建立一個 browser-side session,並加入可實際使用 UI 所需的最小 lifecycle handling。

請在應用程式已進入 browser 後建立:

import {
Pedelec,
type PedelecError,
type PedelecSession,
} from "@kaoruisaac/pedelec";
const pedelec = new Pedelec();

同一個 page 通常共用這個 client,不要每次點按鈕就建立新的 instance。

const approval = await pedelec.getApprovalStatus();
if (!approval.installed) {
throw new Error("請安裝或啟用 Pedelec Chrome Extension。 ");
}
const providers = await pedelec.listProviders();
const provider = providers.find((item) => item.available);
if (!provider) {
throw new Error("無法啟動或連線 Pedelec Desktop;請手動開啟或修復安裝後再設定 provider。 ");
}

讀取 approval status 不會主動要求核准。真正建立或 resume session 時,尚未核准的網站才會進入 approval flow。

const session = await pedelec.createSession({
provider: provider.code,
});

只提供 provider 時,SDK 使用 default effort;Core 從 Desktop Settings 解析 profile。可以在有無 provider 時傳入 effortLevel: "low""high"。Ollama 選中的 profile 必須有 model。

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() 只在狀態變化時觸發。初始狀態請使用 session.getStatus()

try {
assistantText = "";
setComposerDisabled(true);
await session.sendText("說明這個應用程式可以做什麼。 ");
console.log("本次 agent turn 已完成。 ");
} catch (error) {
const pedelecError = error as PedelecError;
renderError(`${pedelecError.code}: ${pedelecError.message}`);
} finally {
if (session.getStatus() === "idle") {
setComposerDisabled(false);
}
}

sendText() 會等待 Core 發出相符的 semantic operation completion 才 resolve;單獨恢復成 idle 或 transport 接到文字都不代表完成。

一個 session 同時只接受一個 active turn。Turn 進行中請 disable send button,同時仍需捕捉 SESSION_BUSY,避免兩條 application path race。

await session.end();
unsubscribeChatDelta();
unsubscribeChat();
unsubscribeStatus();
unsubscribeError();
unsubscribeEnded();

對已 ended 的 session 再呼叫 end() 不會重複執行。Session 處於 ended 期間,相同 handle 必須先成功呼叫 session.resume() 回到 idle,才能接收新訊息。

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 目前無法使用。 ");
}
const providers = await pedelec.listProviders();
const provider = providers.find((item) => item.available);
if (!provider) {
throw new Error("目前沒有 available provider。 ");
}
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;
}
}
  • 不要只 throw generic error,應提供 approval 操作說明;
  • 當產品需要本地 override 時,讓使用者選擇 provider 與 provider-independent effort level;
  • 依頁面需求定義 tools;
  • 依 lifecycle 需求保存或刻意丟棄 sessionId
  • 分開處理 Extension、native host、Core 與 provider failure。

接著閱讀網站核准與連線狀態,或開始定義 browser-side tools