跳到內容

PedelecSession API

PedelecSession<TToolName> 代表一個 Core agent session。

import type { PedelecSession } from "@kaoruisaac/pedelec";

Application 應透過 Pedelec.createSession()Pedelec.resumeSession() 取得,不直接 constructor。符合條件的 ended handle 可再用 session.resume() 明確 reactivation。

class PedelecSession<TToolName extends string = string>

TToolName 收窄 named onTool(name, handler) overload。createSession() 可從 readonly tools array 推導。

readonly sessionId: string

Core session/thread identifier。使用 autoEndOnDisconnect: false 並準備 resume 時保存。

readonly provider: string

此 SDK handle 註冊時已知 provider。Fresh resumed handle 目前可能是 empty string。

readonly effortLevel?: "default" | "low" | "high"

此 session 使用的 normalized effort level。fresh resume handle 可能因 bridge 只回傳 ID 而為 undefined

readonly sessionCreatedAt: number

這個 session object construction 的 browser Date.now()。Resume 後不保證是原始 Core thread creation time。

readonly usage: PedelecSessionUsage

此 session 已觀察到的 normalized cumulative token usage。usage.totalTokens 初始為 undefined;只有 provider 回報受支援且有效的 total 時才會填入。SDK handle 存續期間此值只會單調增加,usage update 不會呼叫 chat、status 或 error handlers。

Core snapshot 在已知時也會包含這個值,因此 resumeSession() 可以 hydrate usage;同一個 handle 的 resume() 會保留 usage。Provider 或 payload 不支援有效 total 時則維持 undefined;目前 Cursor ACP 即屬於此情況。這個值不是 context-window occupancy、金額 cost,也不保證等同 invoice;目前 API 也不提供 input/output/cache/reasoning breakdown。

prepare(): Promise<void>

在第一個 user turn 前 optional prepare provider/session startup。

行為:

  • 成功後 idempotent;
  • concurrent prepare calls 共用 promise;
  • active user turn 時 reject SESSION_BUSY
  • ended 後 reject SESSION_ENDED
  • preparation assistant message 不送到 onChat(),delta 也不送到 onChatDelta()
  • 仍可 emit status/error;
  • 只有 Desktop 驗證 provider 回覆 trim 後完全等於 PEDELEC_PREPARED 才 resolve(前後 whitespace 允許);
  • 不是 sendText() 必要前置。
await session.prepare().catch(() => {
// Preparation 只是 optimization,保留 sendText。
});
sendText(text: string): Promise<void>

開始一個 user turn。Core 回報相符的 semantic operation completion 後 resolve;單獨的 idle 不是 completion signal。Turn failed、session ended 或已有 active turn 時 reject。

完成的 assistant message 由 onChat() 提供;Provider 有提供增量串流時,best-effort fragment 另外由 onChatDelta() 提供。

await session.sendText("Analyze the current page.");

SDK 不 trim,也不強制 non-empty;composer 應自行 validate。

常見 errors:SESSION_BUSYSESSION_ENDEDSEND_TEXT_FAILEDSESSION_ERROR 與 transport/provider error。

uploadAsset(file: File): Promise<AssetPath>
uploadAsset(file: File, targetPath: AssetPath): Promise<AssetPath>

Session 尚未結束時可 upload 一個 browser File。upload 可與 prepare、send 和 provider execution 同時進行,但每個 session 只允許一個 upload。單檔上限 100 MiB;實體儲存於 .pedelec-runtime/assets/,成功時回傳以 assets/ 為 SDK 隱含根目錄的 /... path。傳入 targetPath 可精確寫入 nested path,自動建立父目錄並覆蓋既有一般檔案。

內容經由 Desktop 內部的 127.0.0.1 loopback asset transfer server 傳輸,不經 Extension、Native Messaging 或 Core IPC message body。SDK request、loopback upload PUT 與 ticket 都可能失敗,請見 Error Codes

listAssets(): Promise<Asset[]>

以 flat array 回傳 App 與 Agent 共用的實體 .pedelec-runtime/assets/ 目錄所有層級中已完成的一般檔案。依 filesystem modification time 由新到舊排序;時間相同時依 name 排序。資料夾項目不會回傳,file 與 directory symlink 也不會回傳或被跟隨。所有層級 basename 以 .pedelec- 開頭的 entry 都會排除;其他 dotfile 與 dot-directory 中的檔案會列出。

name 是 basename,path 是相對於 assets/ 的完整 public path,例如 /results/report.jsonmodifiedAt 是 filesystem modification time,不保證等同精確的 write completion timestamp。Agent 執行期間仍可 listing;session 正在結束或已結束時會 reject SESSION_ENDED。目前不支援 pagination、delete、rename 或 move。

readAsset(path: AssetPath, type: "text"): Promise<string>
readAsset<T = JsonValue>(path: AssetPath, type: "json"): Promise<T>
readAsset(path: AssetPath, type: "file"): Promise<File>

讀取實體 shared session .pedelec-runtime/assets/ 中的已知檔案,使用 /results/report.json 之類的 nested public path。Path 必須以 / 開頭、使用 /,且不能包含空 segment、...

單檔 read 上限為 100 MiB。prepare、send 或 provider execution 進行時仍可讀取;session 正在結束或已結束時會 reject SESSION_ENDED。若 Agent 可能仍在寫入相同 path,application 或 agent workflow 應先協調完成時機再讀取。

回傳方式由 type 決定:

  • "text" 使用 strict UTF-8 decode;invalid bytes 會收到 ASSET_TEXT_DECODE_FAILED
  • "json" 先 strict UTF-8 decode 再 parse JSON;invalid JSON 會收到 ASSET_INVALID_JSON。Generic T 只提供 compile-time typing,不會驗證 parsed value;
  • "file" 回傳 browser File,並使用 Desktop 回報的 asset name、MIME type 與 modification time。
const text = await session.readAsset("/report.txt", "text");
const result = await session.readAsset<{ ok: boolean }>(
"/results/result.json",
"json",
);
const model = await session.readAsset("/model.glb", "file");

File bytes 透過 internal loopback download ticket 傳輸。Application 不應依賴 URL、token、port 或 ticket lifetime。Path、file、size、download、decode 與 parse failure 請見 Error Codes

onChat(
handler: (text: string, ctx: ChatEventContext) => void,
): () => void

註冊 completed assistant-message handler,回傳 unsubscribe function。每個 text 都是一個完整的 logical provider message。

const off = session.onChat((text, ctx) => {
transcript.addCompleted(ctx.turnId, text);
});

Preparation output 不送到 chat handlers。

onChatDelta(
handler: (text: string, ctx: ChatDeltaEventContext) => void,
): () => void

註冊 best-effort incremental assistant-text handler。Delta delivery 依 Provider 能力而定,chunk boundary 沒有語意,completed message 也不代表 Pedelec 會合成缺少的 delta。

const off = session.onChatDelta((delta, ctx) => {
transcript.appendLive(ctx.turnId, delta);
});

Preparation output 不送到 delta handlers。

onTool(
handler: (
tool: TToolName,
args: unknown,
ctx: ToolCallContext,
) => unknown | Promise<unknown>,
): () => void
onTool<TArgs = unknown, TResult = unknown>(
toolName: TToolName,
handler: ToolSpecificHandler<TArgs, TResult>,
): () => void

Named > inline > generic fallback。

const off = session.onTool(
"update_counter",
(args: { delta: number }) => ({
value: counter.add(args.delta),
}),
);

Handler 可 sync/async,回傳 JSON-compatible value。Throw 會轉成給 agent 的 TOOL_HANDLER_ERROR result。

onError(
handler: (error: PedelecError, ctx: ErrorEventContext) => void,
): () => void

觀察 Core 與 SDK session errors。

const off = session.onError((error, ctx) => {
console.error(ctx.source, error.code, error.message, error.details);
});

仍需 catch prepare()sendText()end() promise。

onStatus(
handler: (
status: PedelecSessionStatus,
ctx: StatusEventContext,
) => void,
): () => void

Local status value 改變時執行,不會立即 emit initial value。

renderStatus(session.getStatus());
const off = session.onStatus(renderStatus);
onEnded(handler: (ctx: EndedEventContext) => void): () => void

每次 transition 進入 ended 時執行一次。Handle 已經是 ended 時,重複的 ended notification 不會再次執行 callback。相同 handle 成功透過 resume()ended 回到 idle 後,之後再次 transition 回 ended 會再執行一次。

const off = session.onEnded(() => {
disableComposer();
});
getStatus(): PedelecSessionStatus

回傳目前 browser-side status snapshot。

if (session.getStatus() === "idle") {
await session.sendText(text);
}

適合 UI gating,但不能取代 catch SESSION_BUSY,因 check 與 request 之間 state 可能改變。

resume(): Promise<void>

在成功 end() 後,明確 reactivation 同一個 SDK handle。Handle object、Core thread ID、handlers、usage、provider/effort metadata、prepared 狀態與 provider session identity 都會保留。成功後 authoritative status 是 idle;下一個 provider operation 才會 lazy 啟動或 resume provider work。

Handle 不可 transport-detached;Core 必須仍包含 thread,thread 必須是 Ended(或已是 Idle 以支援 idempotent retry),且記錄的 workspace 必須仍是可載入 Pedelec skill/tool data 的 directory。Workspace 缺失或無法開啟時 reject WORKSPACE_OPEN_FAILED;Core 找不到 thread 時 reject THREAD_NOT_FOUNDresume() 不會從 workspace contents 重建 thread,也不會接觸 provider runtime。

Active、非 detached handle 呼叫會直接 resolve;同一個 ended handle 的 concurrent calls 共用一個 request。Transport-detached handle 會 reject,請改用 Pedelec.resumeSession(sessionId) 建立新的 reattachment handle。

await session.end();
await session.resume();
console.log(session.getStatus()); // "idle"
await session.sendText("繼續工作");
end(): Promise<void>

結束 Core session 並 mark SDK handle ended。

成功後:

  • onEnded() 針對這次進入 ended 的 transition 執行一次;
  • pending turn/preparation reject SESSION_ENDED
  • session 從 client registry 移除;
  • 後續 sendText()prepare() 在同一個 handle 成功 resume() 前 reject。

呼叫 end() 會結束 session,但不會立即刪除其 workspace。未指定 workspace.path 時,session 使用 Desktop 管理的暫存資料:Desktop App 正常退出時清除 managed workspace,異常終止遺留的內容會在下次啟動時嘗試清除。指定 absolute workspace.path 時,workspace 由 application 管理,Pedelec 在 session 結束、App 退出或 stale cleanup 時都不會刪除它,多個 active session 可以共用。關閉主視窗只會隱藏 App,不會觸發 managed cleanup。即使 workspace 仍存在,ended session 也不能使用 uploadAsset()listAssets()readAsset();shared workspace 的 filesystem conflict 由 application 負責,explicit path 不得與 managed workspace root overlap。

Already-ended handle 呼叫 end() 立即 resolve。

End request 失敗時 emit SDK error 並 reject,不會假裝 Core 已成功 ended。

所有 event registration 都回傳 () => void disposer。Owning component/state destroy 時呼叫。

Unsubscribe 不會 cancel 已執行中的 provider turn 或 handler promise。

type PedelecSessionStatus =
| "idle"
| "running"
| "waiting_tool_result"
| "ended"
| "error";

Transitions 與 context fields 詳見 Session 狀態與 Events