Pedelec SDK
Pedelec 是一套 browser SDK 與本機橋接架構,讓 Web App 可以和 Codex、Antigravity、OpenCode、Cursor、Claude Code,或使用 Ollama 的 agent 協作。
Web App 可以透過 Pedelec:
- 在使用者電腦上建立 agent session;
- 傳送使用者指令並接收串流輸出的文字;
- 將範圍明確的前端能力以 tool 的形式提供給 agent;
- 恢復或結束 session;
- 在 UI 顯示連線、網站核准、provider 與 session lifecycle 狀態。
Pedelec 解決什麼問題
Section titled “Pedelec 解決什麼問題”一般聊天 API 通常只接收文字再回傳文字,但 agent 整合經常需要更多能力:
- agent 可能需要讀取目前頁面、編輯器、canvas、選取內容或應用程式 state;
- agent turn 暫停時,Web App 可能需要開啟對話框等待使用者確認;
- 使用者可能想沿用已經安裝並登入的本機 provider CLI;
- 瀏覽器頁面不應直接取得任意啟動本機 process 的權限。
Pedelec 將責任拆開。Web App 負責 UI 與 tool handler;Pedelec Extension 與 Desktop Runtime 負責本機傳輸、session lifecycle 以及 provider process。
Web App ↓ @kaoruisaac/pedelecPedelec Chrome Extension ↓ Chrome Native MessagingPedelec native host ↓ 本機 Core IPCPedelec Desktop Runtime ↓ provider processCodex / Antigravity / OpenCode / Cursor / Claude Code / Ollama上圖是 control/event path。Asset file body 使用另一條內部路徑:Web App fetch(PUT/GET) → Desktop-managed 127.0.0.1 loopback asset transfer server → session .pedelec-runtime/assets/。只使用 asset API,不要依賴內部 URL、port、token 或 ticket lifecycle。
SDK 不會要求 Web App 自行啟動 localhost server,也不會直接啟動 provider executable。它只和已安裝的 Extension 溝通,再由 Extension 將通過使用者核准的要求送往 Desktop Runtime。
SDK 使用前提
Section titled “SDK 使用前提”Pedelec SDK 必須在瀏覽器頁面環境中執行,並且需要:
- 使用者已安裝 Pedelec Chrome Extension。
- 使用者安裝後已至少正常開啟一次 Pedelec Desktop App,完成 binary setup、Native Messaging host registration 與 launch config 建立。
- 完成初始化後,native host 可在 Core request 時嘗試 background 啟動 Desktop;此操作可能失敗,應視為 Desktop/installation unavailable 處理。
- 目標 provider 在使用者本機可用。CLI 型 provider 使用
codex、agy、opencode、cursor-agent或claude;Ollama provider 使用 Pedelec 隨附的pedelec-agent。
SDK 不適合直接在 Node.js、SSR server 或 background worker 裡使用;它需要 Chrome 頁面環境中的 extension runtime messaging。
安裝與引用 SDK
Section titled “安裝與引用 SDK”安裝已發布的套件:
npm install @kaoruisaac/pedelec在 Web App 端引用:
import { Pedelec, defineTool } from "@kaoruisaac/pedelec";最小使用範例
Section titled “最小使用範例”import { Pedelec, defineTool } from "@kaoruisaac/pedelec";
const pedelec = new Pedelec();
const session = await pedelec.createSession({ provider: "codex", effortLevel: "high", skills: { guidance: "需要瀏覽器頁面資訊時使用 get_current_page。", tools: [ defineTool({ name: "get_current_page", description: "讀取目前瀏覽器頁面的 title 與 URL。", argsSchema: { type: "object", properties: {}, required: [], }, handler: () => ({ url: location.href, title: document.title, }), }), ], },});
session.onChat((text) => { // 一個完成的 logical assistant message console.log(text);});
session.onChatDelta((delta) => { // live UI 可選用的 best-effort 文字增量 console.log(delta);});
session.onStatus((status) => { // idle | running | waiting_tool_result | ended | error console.log("status", status);});
session.onError((error) => { console.error(error.code, error.message, error.details);});
await session.sendText("請幫我分析目前頁面的狀態");sendText() 會在本輪 agent 回應完成後 resolve;如果 session 已經在處理上一個 prompt,新的 sendText() 會被拒絕,避免同一個 session 同時跑多個請求。
建立 Session
Section titled “建立 Session”首次在某個 origin 呼叫 createSession() 或 resumeSession() 時,extension 會要求使用者在 popup 中核准該 origin。核准後同一個 origin 之後可直接建立 session。
可以先查詢目前 origin 的 approval 狀態,用來決定是否顯示「Connect Pedelec」類 UI:
const status = await pedelec.getApprovalStatus();
console.log(status.installed, status.approved, status.origin);指定 provider 與 effort
Section titled “指定 provider 與 effort”const session = await pedelec.createSession({ provider: "opencode", effortLevel: "high",});目前 SDK 支援的 provider code:
| Provider | Code |
|---|---|
| Codex | codex |
| Antigravity | antigravity |
| OpenCode | opencode |
| Cursor | cursor |
| Claude Code | claude |
| Ollama | ollama |
Ollama session 使用 Desktop 設定的 endpoint 與內附 pedelec-agent。選中的 effort profile 必須有 model;空 profile 會回傳 MODEL_REQUIRED,不會 fallback 到 default。
Desktop Settings 也可替 Ollama session 設定 optional Tavily API key。設定後,內附 agent 可自行判斷是否使用 Tavily web search;目前固定使用 basic search depth,每次最多回傳五筆結果。未設定 key 時,不會向 Ollama model 提供 web-search tool。
使用 Desktop App 預設 provider
Section titled “使用 Desktop App 預設 provider”如果 Desktop App 已設定 default provider,可以不傳 provider:
const session = await pedelec.createSession({ skills: { guidance: "使用者要求更新 counter 時使用 update_counter。", tools: [ defineTool({ name: "update_counter", description: "依照 delta 更新畫面上的 counter。", argsSchema: { type: "object", required: ["delta"], properties: { delta: { type: "number", description: "Counter delta.", }, }, }, }), ], },});這會只讀取 Desktop App 的 defaultProvider。如果沒有設定 default provider,SDK 會拋出 DEFAULT_PROVIDER_NOT_SET;具體 effort argv 由 Core 解析 Desktop 設定。
只指定 provider
Section titled “只指定 provider”const session = await pedelec.createSession({ provider: "codex",});只傳 provider 時,SDK 使用 effortLevel: "default"。非 Ollama provider 可使用空 profile;Ollama 的選中 profile 沒有 model 時會回傳 MODEL_REQUIRED。
Session 生命週期
Section titled “Session 生命週期”SDK 建立的 session 預設是 page-scoped。autoEndOnDisconnect 預設為 true,因此頁面重新整理、關閉 tab,或該 session 的最後一個 SDK connection 中斷時,Pedelec 會自動結束對應的 Desktop thread。
Demo 與 page-scoped app 建議使用預設值。若需要在頁面切換後 resumeSession,或跨頁共用同一個 session,請明確設定 autoEndOnDisconnect: false:
const session = await pedelec.createSession({ provider: "codex", autoEndOnDisconnect: false,});查詢 Providers
Section titled “查詢 Providers”const providers = await pedelec.listProviders();
for (const provider of providers) { console.log(provider.code, provider.available, provider.isDefault, provider.error);}回傳格式:
type ProviderInfo = { name: string; code: "codex" | "antigravity" | "opencode" | "cursor" | "claude" | "ollama"; available: boolean; isDefault: boolean; error: string | null;};isDefault 表示 Desktop settings 選擇的 provider,與 available 無關;即使 default provider unavailable 也仍是 true。available: false 通常代表該 provider CLI 沒有安裝,或不在 PATH 裡。
對 Ollama 而言,available: true 只代表 Pedelec 找得到 pedelec-agent;不代表 endpoint 可連線、credential 有效,或指定 model 已下載。
讀取 Desktop App 設定
Section titled “讀取 Desktop App 設定”const settings = await pedelec.getSettings();
console.log(settings.defaultProvider);回傳格式:
type PedelecSettings = { defaultProvider: "codex" | "antigravity" | "opencode" | "cursor" | "claude" | "ollama" | null;};接收 Agent 回應
Section titled “接收 Agent 回應”使用 onChat() 接收完成的 logical assistant message:
session.onChat((text) => { saveCompletedAssistantMessage(text);});如果要做即時串流 UI,另外使用 onChatDelta() 累積文字增量:
const chunks: string[] = [];
session.onChatDelta((delta) => { chunks.push(delta); render(chunks.join(""));});Delta delivery 是 best-effort,而且依 Provider 能力而定。Delta boundary 沒有語意,Pedelec 也不會根據後續 completed message 自行合成缺少的 suffix delta。
監聽 Session 狀態
Section titled “監聽 Session 狀態”session.onStatus((status) => { switch (status) { case "idle": break; case "running": break; case "waiting_tool_result": break; case "ended": break; case "error": break; }});常見狀態:
| 狀態 | 意義 |
|---|---|
idle |
session 可接收下一個 prompt |
running |
agent 正在處理使用者輸入 |
waiting_tool_result |
agent 發出 tool call,正在等待前端回傳結果 |
ended |
session 已結束 |
error |
session 發生錯誤 |
Error Event 來源
Section titled “Error Event 來源”Public SDK 透過 session.onError((error, ctx) => ...) 回報錯誤。error 是 PedelecError;ctx.source 表示 callback 是由 Core event 還是 SDK-side handling 產生:
"core":錯誤事件來自 Core/provider execution path;"sdk":SDK 在處理 local request、disconnect 或 protocol condition 時產生錯誤。
type ErrorEventContext = PedelecEventContext & { type: "error" | "sdk_error"; source: "core" | "sdk";};
session.onError((error, ctx) => { console.error(ctx.source, error.code, error.message, error.details);});目前公開 callback context 不會另外提供 provider responsibility 欄位。不要判斷 ctx.source === "provider",也不要預期存在 ctx.provider。
getSettings() 與 listProviders() 需要 origin approval 且可能開啟 popup。Settings 絕不公開 provider credentials,而 provider entry 僅含上述欄位;SDK 會忽略 Extension response 中新增的未知欄位,以保持 forward compatibility。請以 getApprovalStatus().appConnected 做非敏感 Desktop connectivity probe。
Tool Calling:讓 Agent 操作 Web App
Section titled “Tool Calling:讓 Agent 操作 Web App”Pedelec 的 tool calling 流程是:
- Web App 在
createSession提供skills: { guidance, tools }。 - Desktop Runtime 驗證 manifest、在記憶體保存 ToolRegistry,並在首次 provider prompt 直接注入 guidance 與 tool index。
- Agent 需要前端資料或操作時,用
pedelec-cli --thread-id <pedelec_thread_id> tool-spec <tool>取得完整 schema,再執行pedelec-cli --thread-id <pedelec_thread_id> tool-call ...。 - Desktop Runtime 收到 tool call 後,透過 native host 與 extension 送回 SDK。
- SDK 觸發
session.onTool()。 - Web App 執行對應工具並 return result。
- SDK 自動把 result submit 回 Desktop Runtime,再交給 agent 繼續推理。
每個 defineTool 使用 argsSchema 描述 tool arguments,這份描述會送給 provider / agent。root schema 必須是 object。argsSchema 是 Pedelec Tool Args Schema subset,不是完整 JSON Schema;它支援常見的 string、number、integer、boolean、array、object、oneOf node,以及 description、default、examples、enum、數值範圍、array 長度限制與 required 等欄位。default 只是給 agent 的提示,SDK 不會自動補值。舊的 shorthand input schema 不再支援。第一版也不支援 $defs、$ref、additionalProperties、exclusiveMinimum、exclusiveMaximum、multipleOf、format;需要重用 schema 時請用 TypeScript const。
範例:
session.onTool(async (tool, args) => { if (tool === "get_current_page") { return { url: location.href, title: document.title, selectedText: window.getSelection()?.toString() ?? "", }; }
if (tool === "update_counter") { const { delta } = args as { delta: number }; counter.value += delta; return { counter: counter.value, delta, }; }
return { error: { code: "TOOL_NOT_FOUND", message: `Unknown tool: ${tool}`, }, };});onTool() 的回傳值必須可以 JSON serialize。SDK 會自動把回傳值送回 runtime,不需要手動呼叫 submit_tool_result。
Resume 既有 Session
Section titled “Resume 既有 Session”如果你有保存 sessionId,可以重新接回既有 session:
const session = await pedelec.resumeSession("thread_abc123");
session.onChat((text) => { console.log(text);});
await session.sendText("繼續剛剛的工作");resumeSession() 是以新的 SDK handle 重新 attach 到既有 Core thread;如果該 thread 已是 ended,它仍會維持 ended,不會自動恢復。若要在明確 end() 後繼續使用原本的 handle,請使用 session.resume():
await session.end();await session.resume();await session.sendText("從原本的 handle 繼續工作");同一個 handle 的 resume() 會保留 handlers、usage、provider/effort metadata、prepared 狀態與 provider session identity;成功後才由 Core snapshot 確認為 idle,provider work 仍會在下一個 operation 時 lazy 啟動。workspace 缺失或不可開啟會回傳 WORKSPACE_OPEN_FAILED;transport-detached 的舊 handle 不能 resume,請改用 resumeSession(sessionId)。
結束 Session
Section titled “結束 Session”await session.end();結束後,該 session 不能再呼叫 sendText(),直到同一個 handle 成功 resume();若不再使用原本的 handle,或它已 transport-detached,請重新 resumeSession(sessionId) 或 createSession()。
結束 session 不會立即刪除其 workspace。未指定 workspace.path 的 session 使用 Desktop 管理的暫存資料:Desktop App 正常退出時會清除 managed workspace,異常結束後的殘留會在下次啟動時清理。指定 absolute workspace.path 則是 application 管理的 workspace,Pedelec 在 session 結束、App 退出或 stale cleanup 時都不會刪除它,多個 active session 可以共用。關閉主視窗只會隱藏 App,不會觸發 managed cleanup。Ended session 不能透過 asset API 存取保留的 workspace,explicit shared workspace 的 filesystem conflict 也不由 Pedelec 協調。
若啟用 autoEndOnDisconnect,disconnect cleanup 的目標與呼叫 session.end() 相同:thread 會被結束,且不再被視為 active。
建議所有 SDK 操作都包在 try/catch,並同時註冊 onError():
session.onError((error) => { console.error("session error", error);});
try { await session.sendText("請幫我修改這段內容");} catch (error) { console.error("send failed", error);}常見錯誤:
| code | 可能原因 |
|---|---|
EXTENSION_UNAVAILABLE |
SDK 不在瀏覽器頁面中執行,或 extension 無法連線 |
EXTENSION_DISCONNECTED |
extension 連線中斷 |
SDK_BRIDGE_TIMEOUT |
extension 沒有在 timeout 內回應 |
APPROVAL_REJECTED |
使用者拒絕目前 origin 使用 Pedelec |
APPROVAL_TIMEOUT |
使用者未在期限內完成 origin approval |
OPEN_POPUP_FAILED |
extension 無法自動開啟 approval popup |
NATIVE_HOST_UNAVAILABLE |
Chrome Native Messaging host 無法連線 |
DEFAULT_PROVIDER_NOT_SET |
Desktop App 尚未設定 default provider |
DEFAULT_PROVIDER_UNAVAILABLE |
default provider 不可用 |
SESSION_BUSY |
同一個 session 已有 prompt 正在執行 |
SESSION_ENDED |
session 已結束 |
TOOL_HANDLER_NOT_FOUND |
agent 呼叫 tool,但 Web App 沒有註冊 handler |
建議閱讀順序
Section titled “建議閱讀順序”- 整合前先確認環境需求與瀏覽器支援。
- 依照快速開始建立 session 並接收第一段回應。
- 閱讀 Pedelec 如何運作理解外部可觀察的架構與信任邊界。
- 使用 Tool Calling讓 agent 讀取或操作前端狀態。
- 在
PedelecAPI Reference查詢精確方法與型別。