Session Status and Events
A PedelecSession keeps a browser-side status and exposes lifecycle callbacks for status, errors, and end events.
Status values
Section titled “Status values”type PedelecSessionStatus = | "idle" | "running" | "waiting_tool_result" | "ended" | "error";| Status | Meaning | Can call sendText()? |
|---|---|---|
idle |
No active turn; ready for another prompt | Yes |
running |
Preparation or a user turn is active | No |
waiting_tool_result |
The agent requested a frontend tool and is waiting | No |
ended |
The session has ended; the same attached handle may explicitly call resume() if Core and its recorded workspace are still recoverable |
No until resume() succeeds |
error |
Runtime reported a session error | No; recover by app policy, often a new/resumed session |
New PedelecSession objects begin in idle locally. A handle returned by resumeSession() is synchronized from Core before the promise resolves, so it can initially report running, waiting_tool_result, error, or ended instead.
Pedelec.resumeSession(sessionId) only reattaches to an existing thread, so an ended thread stays ended. PedelecSession.resume() is the explicit same-handle ended → idle transition. It preserves the handle’s handlers and session state, and provider runtime work remains lazy until the next normal operation.
Common transitions
Section titled “Common transitions”A turn without tools commonly follows:
idle → running → idleA turn with a frontend tool commonly follows:
idle → running → waiting_tool_result → running → idleThe runtime may end or error from an active state:
running → errorrunning → endedwaiting_tool_result → errorwaiting_tool_result → endedDo not build UI logic that assumes every theoretical intermediate status must appear. Events can be normalized, de-duplicated, or arrive close together. idle is observable thread state; it is not by itself proof that a particular prepare() or sendText() promise has completed.
Observe status changes
Section titled “Observe status changes”const offStatus = session.onStatus((status, ctx) => { console.log(ctx.previousStatus, "→", status); console.log(ctx.type, ctx.source);});The callback runs only when the value changes. Registering does not immediately emit the initial status.
renderStatus(session.getStatus());const offStatus = session.onStatus(renderStatus);Local versus Core-originated status
Section titled “Local versus Core-originated status”The SDK can emit a status before Core replies, for responsive UI:
sendText()andprepare()setrunninglocally;- a tool event sets
waiting_tool_resultwhile being handled; end()marks the session ended locally after its request succeeds.session.resume()does not optimistically setidle; the authoritative Core snapshot and operation-lessstatus_changedevent set that state.
Context identifies origin:
ctx.source // "sdk" | "core"ctx.type // "sdk_status_changed" | "status_changed"A Core event uses source: "core". A state transition initiated inside the SDK uses source: "sdk".
Status handlers are de-duplicated. If the SDK already set running and Core later reports running, no second callback occurs because the value did not change.
Error events
Section titled “Error events”const offError = session.onError((error, ctx) => { console.error(error.code, error.message, error.details); console.debug(ctx.type, ctx.source, ctx.sessionId);});type ErrorEventContext = PedelecEventContext & { type: "error" | "sdk_error";};Use onError() for session-wide observation, logging, and UI. Also catch promises from the action that initiated work.
An error callback does not always mean the session status becomes error. For example, a failed submission of a tool result emits SUBMIT_TOOL_RESULT_FAILED from the SDK, while the runtime may later determine the final session state. Core operation errors are diagnostics; the matching operation completion carries semantic rejection.
Ended events
Section titled “Ended events”const offEnded = session.onEnded((ctx) => { console.log("ended by", ctx.source);});type EndedEventContext = PedelecEventContext & { type: "ended" | "sdk_ended";};onEnded() runs once for each transition into ended, whether caused by a runtime event or a successful local end() call. Duplicate ended notifications while the handle is already ended do not run it again. After a successful same-handle ended -> idle resume(), a later transition back to ended runs the callback once for that new lifecycle transition.
Pending sendText() or prepare() work rejects with SESSION_ENDED when the session ends.
Common context fields
Section titled “Common context fields”type PedelecEventContext = { sessionId: string; provider: string; effortLevel?: "default" | "low" | "high"; sessionCreatedAt: number; eventReceivedAt?: number; eventEmittedAt: number; turnId?: string; turnStartedAt?: number; turnKind?: "user" | "prepare"; source: "core" | "sdk";};Not every field exists for every event:
- Core chat/tool callbacks include received time and active turn metadata.
- Local status changes can occur before a Core event and may not have
eventReceivedAt. - End/error events outside an active turn may not have turn fields.
- A newly resumed session handle may have blank provider metadata in the current SDK if the application did not retain it separately.
Tool-call context
Section titled “Tool-call context”Tool handlers receive extra fields:
ctx.type; // "tool_call"ctx.toolRequestId; // Core tool request identifierctx.tool; // tool namectx.turnId;Use toolRequestId for diagnostics and application bookkeeping, not for manually submitting results. The SDK submits the handler’s return value automatically.
Event order and duplicate protection
Section titled “Event order and duplicate protection”Core events can include a per-session sequence number. The client ignores an event whose sequence is not newer than the last one seen for that session, and operation-scoped events are accepted only for the active matching operation ID. Application code should still be resilient to rapid transitions and late asynchronous work inside its own handlers.
When a subscription is recreated, the SDK receives an authoritative lifecycle snapshot before live events resume. A resumed or recovered handle uses that snapshot to adopt the Core status and active operation. If Core reports a pending frontend tool, register its handler after resumeSession(); the SDK keeps the recovered request queued and submits it once a matching handler is available. Same-handle session.resume() also reconciles its post-reactivation snapshot before the promise resolves, so getStatus() is idle at the await boundary.
UI mapping
Section titled “UI mapping”A simple mapping:
session.onStatus((status) => { setSendEnabled(status === "idle"); setBusy(status === "running" || status === "waiting_tool_result"); setToolWaiting(status === "waiting_tool_result"); setSessionClosed(status === "ended");});Do not map waiting_tool_result to a generic frozen state when an interactive tool is intentionally waiting for user input. Show the relevant modal or instruction.
Continue with Session lifecycle and resume.