@kaoruisaac/pedelec 0.2.12 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -121,10 +121,15 @@ const session = await pedelec.createSession({
121
121
  });
122
122
 
123
123
  session.onChat((text) => {
124
- // Incremental text stream from the agent.
124
+ // One completed logical assistant message.
125
125
  console.log(text);
126
126
  });
127
127
 
128
+ session.onChatDelta((delta) => {
129
+ // Optional best-effort incremental text for live UI.
130
+ console.log(delta);
131
+ });
132
+
128
133
  session.onStatus((status) => {
129
134
  // idle | running | waiting_tool_result | ended | error
130
135
  console.log("status", status);
@@ -137,7 +142,19 @@ session.onError((error) => {
137
142
  await session.sendText("Please help me analyze the current page state");
138
143
  ```
139
144
 
140
- `sendText()` resolves after the current agent response completes. If the session is already handling a previous prompt, the new `sendText()` call is rejected to prevent multiple concurrent requests from running in the same session.
145
+ `sendText()` resolves after Core reports the matching semantic operation as completed. An `idle` status or bridge request response alone does not complete it. If the session is already handling a previous prompt, the new `sendText()` call is rejected to prevent multiple concurrent requests from running in the same session.
146
+
147
+ ## End and resume a session
148
+
149
+ ```ts
150
+ await session.end();
151
+ await session.resume();
152
+
153
+ console.log(session.getStatus()); // "idle"
154
+ await session.sendText("Continue");
155
+ ```
156
+
157
+ `session.resume()` reactivates the same non-detached handle and Core thread when Core still knows the thread and its recorded workspace still exists. It preserves handlers and session state, does not contact the provider runtime, and leaves provider startup/resume lazy until the next operation. A missing workspace returns `WORKSPACE_OPEN_FAILED`; a thread lost after a Desktop/Core restart returns `THREAD_NOT_FOUND`. `pedelec.resumeSession(sessionId)` is a separate reattachment API: it returns a new handle and does not implicitly revive an ended thread. A transport-detached old handle must use that API.
141
158
 
142
159
  ## Session assets
143
160
 
package/dist/index.d.ts CHANGED
@@ -142,6 +142,9 @@ export type PedelecError = {
142
142
  details?: unknown;
143
143
  };
144
144
  export type PedelecSessionStatus = "idle" | "running" | "waiting_tool_result" | "ended" | "error";
145
+ export type PedelecSessionUsage = {
146
+ totalTokens?: number;
147
+ };
145
148
  export type PedelecEventContext = {
146
149
  sessionId: string;
147
150
  provider: string;
@@ -155,6 +158,12 @@ export type PedelecEventContext = {
155
158
  source: "core" | "sdk";
156
159
  };
157
160
  export type ChatEventContext = PedelecEventContext & {
161
+ type: "chat_message";
162
+ turnId: string;
163
+ turnStartedAt: number;
164
+ eventReceivedAt: number;
165
+ };
166
+ export type ChatDeltaEventContext = PedelecEventContext & {
158
167
  type: "chat_delta";
159
168
  turnId: string;
160
169
  turnStartedAt: number;
@@ -184,31 +193,52 @@ type SessionEvent = {
184
193
  channelId: string;
185
194
  sessionId: string;
186
195
  seq?: number;
196
+ operationId?: string;
197
+ text: string;
198
+ } | {
199
+ type: "chat_message";
200
+ channelId: string;
201
+ sessionId: string;
202
+ seq?: number;
203
+ operationId?: string;
187
204
  text: string;
188
205
  } | {
189
206
  type: "status_changed";
190
207
  channelId: string;
191
208
  sessionId: string;
192
209
  seq?: number;
210
+ operationId?: string;
193
211
  status: PedelecSessionStatus;
194
212
  } | {
195
213
  type: "tool_call";
196
214
  channelId: string;
197
215
  sessionId: string;
198
216
  seq?: number;
217
+ operationId: string;
199
218
  toolRequestId: string;
200
219
  tool: string;
201
220
  args: unknown;
202
221
  } | {
203
- type: "done";
222
+ type: "operation_completed";
223
+ channelId: string;
224
+ sessionId: string;
225
+ seq?: number;
226
+ operationId: string;
227
+ operationKind: "user" | "prepare";
228
+ success: boolean;
229
+ error?: PedelecError;
230
+ } | {
231
+ type: "usage_updated";
204
232
  channelId: string;
205
233
  sessionId: string;
206
234
  seq?: number;
235
+ totalTokens: number;
207
236
  } | {
208
237
  type: "error";
209
238
  channelId?: string;
210
239
  sessionId?: string;
211
240
  seq?: number;
241
+ operationId?: string;
212
242
  error?: PedelecError;
213
243
  } | {
214
244
  type: "ended";
@@ -216,11 +246,41 @@ type SessionEvent = {
216
246
  sessionId: string;
217
247
  seq?: number;
218
248
  };
249
+ type SessionSnapshot = {
250
+ threadId: string;
251
+ status: string;
252
+ latestSeq: number;
253
+ usage?: {
254
+ totalTokens: number;
255
+ };
256
+ activeOperation?: {
257
+ operationId: string;
258
+ operationKind: "user" | "prepare";
259
+ startedAt: string;
260
+ };
261
+ lastCompletedOperation?: {
262
+ operationId: string;
263
+ operationKind: "user" | "prepare";
264
+ success: boolean;
265
+ error?: PedelecError;
266
+ completedAt: string;
267
+ };
268
+ pendingToolRequest?: {
269
+ requestId: string;
270
+ threadId: string;
271
+ operationId: string;
272
+ toolName: string;
273
+ args: unknown;
274
+ createdAt: string;
275
+ timeoutMs: number;
276
+ };
277
+ };
219
278
  type EventDispatchMeta = {
220
279
  source: "core" | "sdk";
221
280
  eventReceivedAt?: number;
222
281
  };
223
282
  type ChatHandler = (text: string, ctx: ChatEventContext) => void;
283
+ type ChatDeltaHandler = (text: string, ctx: ChatDeltaEventContext) => void;
224
284
  type GenericToolHandler<TToolName extends string = string> = (tool: TToolName, args: unknown, ctx: ToolCallContext) => unknown | Promise<unknown>;
225
285
  type ErrorHandler = (error: PedelecError, ctx: ErrorEventContext) => void;
226
286
  type StatusHandler = (status: PedelecSessionStatus, ctx: StatusEventContext) => void;
@@ -235,6 +295,8 @@ export declare class Pedelec {
235
295
  private readonly lastSeqBySession;
236
296
  private nextRequestNumber;
237
297
  private port;
298
+ private pageActivityListenersAttached;
299
+ private lastReportedPageActive;
238
300
  constructor(options?: PedelecOptions);
239
301
  createSession(): Promise<PedelecSession<string>>;
240
302
  createSession<const TTools extends readonly ToolDefinition[]>(input: CreateSessionInput<TTools>): Promise<PedelecSession<ToolNameOf<TTools>>>;
@@ -246,6 +308,16 @@ export declare class Pedelec {
246
308
  request<T>(type: string, payload?: Record<string, unknown>): Promise<T>;
247
309
  private requestWithOptions;
248
310
  unregisterSession(sessionId: string): void;
311
+ /** @internal */
312
+ registerExistingSession(session: PedelecSession<any>): void;
313
+ /** @internal */
314
+ isSessionRegistered(sessionId: string, session: PedelecSession<any>): boolean;
315
+ private attachPageActivityListeners;
316
+ private isDocumentHidden;
317
+ private isDocumentFocused;
318
+ private isPageForegroundActive;
319
+ private postSdkHello;
320
+ private reportPageActivity;
249
321
  private resolveCreateSessionInput;
250
322
  checkAvailability(): Promise<PedelecAvailability>;
251
323
  private resolveDefaultCreateSessionInput;
@@ -266,26 +338,33 @@ export declare class PedelecSession<TToolName extends string = string> {
266
338
  readonly provider: string;
267
339
  readonly effortLevel?: EffortLevel;
268
340
  readonly sessionCreatedAt: number;
341
+ readonly usage: PedelecSessionUsage;
269
342
  private status;
270
- private pendingSend;
271
- private pendingPrepare;
343
+ private pendingOperation;
272
344
  private preparePromise;
273
345
  private uploadPromise;
346
+ private reactivationPromise;
274
347
  private ending;
275
348
  private prepared;
349
+ private autoEndOnDisconnect;
276
350
  private activeTurn;
277
351
  private transportDetached;
278
352
  private genericToolHandler;
279
353
  private inlineToolHandlers;
280
354
  private readonly namedToolHandlers;
281
355
  private readonly chatHandlers;
356
+ private readonly chatDeltaHandlers;
282
357
  private readonly errorHandlers;
283
358
  private readonly statusHandlers;
284
359
  private readonly endedHandlers;
285
- constructor(client: Pedelec, sessionId: string, provider: string, effortLevel?: EffortLevel, inlineToolHandlers?: Map<string, ToolSpecificHandler>);
360
+ private readonly recoveredToolCalls;
361
+ private readonly handledToolRequestIds;
362
+ private authoritativePendingToolRequest;
363
+ constructor(client: Pedelec, sessionId: string, provider: string, effortLevel?: EffortLevel, inlineToolHandlers?: Map<string, ToolSpecificHandler>, autoEndOnDisconnect?: boolean);
286
364
  prepare(): Promise<void>;
287
365
  sendText(text: string): Promise<void>;
288
366
  onChat(handler: ChatHandler): () => void;
367
+ onChatDelta(handler: ChatDeltaHandler): () => void;
289
368
  onTool(handler: GenericToolHandler<TToolName>): () => void;
290
369
  onTool<TArgs = unknown, TResult = unknown>(toolName: TToolName, handler: ToolSpecificHandler<TArgs, TResult>): () => void;
291
370
  uploadAsset(file: File): Promise<AssetPath>;
@@ -299,28 +378,39 @@ export declare class PedelecSession<TToolName extends string = string> {
299
378
  onEnded(handler: EndedHandler): () => void;
300
379
  getStatus(): PedelecSessionStatus;
301
380
  end(): Promise<void>;
381
+ resume(): Promise<void>;
382
+ /** @internal */
383
+ setAutoEndOnDisconnect(autoEndOnDisconnect: boolean): void;
302
384
  handleEvent(event: SessionEvent, meta?: EventDispatchMeta): void;
385
+ /** @internal */
386
+ handleSnapshot(snapshot: SessionSnapshot, meta?: EventDispatchMeta): void;
303
387
  replaceInlineToolHandlers(handlers: Map<string, ToolSpecificHandler>): void;
304
388
  /** @internal */
305
389
  isTransportDetached(): boolean;
306
390
  /** @internal */
307
391
  handleTransportDisconnect(error: PedelecError): void;
308
- private handleToolCall;
309
- private resolvePendingSend;
310
- private resolvePendingPrepare;
311
- private resolveActivePending;
312
- private rejectPendingSend;
313
- private rejectPendingPrepare;
392
+ private handleToolCallOnce;
314
393
  private rejectActivePending;
315
- private clearPendingSend;
316
- private clearPendingPrepare;
317
394
  private markEnded;
318
395
  private emitError;
319
396
  private setStatus;
320
- private requireActiveTurn;
397
+ private requireOperationTurn;
398
+ private updateUsage;
399
+ private matchesActiveOperation;
400
+ private markOperationRequestSettled;
401
+ private failOperationFromRequest;
402
+ private completeOperation;
403
+ private maybeSettleOperation;
404
+ private rejectOperation;
405
+ private queueSnapshotTool;
406
+ private reconcileRecoveredToolCalls;
407
+ private isAuthoritativeToolRequestCurrent;
408
+ private hasToolHandler;
409
+ private drainRecoveredToolCalls;
321
410
  private finishActiveTurn;
322
411
  private createBaseContext;
323
412
  private createChatContext;
413
+ private createChatDeltaContext;
324
414
  private createToolCallContext;
325
415
  private createStatusContext;
326
416
  private createErrorContext;
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { PEDELEC_EXTENSION_ID } from "./extension-id.js";
2
2
  import { SDK_VERSION } from "./version.generated.js";
3
3
  const SDK_EXTERNAL_PORT_NAME = "pedelec-sdk-external";
4
+ const PAGE_ACTIVITY_MESSAGE_TYPE = "page_activity";
5
+ const SDK_HELLO_MESSAGE_TYPE = "sdk_hello";
4
6
  const DEFAULT_BRIDGE_TIMEOUT_MS = 30_000;
5
7
  function normalizeListAssetsResponse(response) {
6
8
  if (!response || typeof response !== "object" || Array.isArray(response))
@@ -124,6 +126,8 @@ export class Pedelec {
124
126
  lastSeqBySession = new Map();
125
127
  nextRequestNumber = 1;
126
128
  port = null;
129
+ pageActivityListenersAttached = false;
130
+ lastReportedPageActive = null;
127
131
  constructor(options = {}) {
128
132
  this.pageWindow = typeof window === "undefined" ? null : window;
129
133
  this.channelId = createChannelId();
@@ -131,6 +135,7 @@ export class Pedelec {
131
135
  if (!this.pageWindow) {
132
136
  return;
133
137
  }
138
+ this.attachPageActivityListeners();
134
139
  this.connectExtension();
135
140
  }
136
141
  async createSession(input = {}) {
@@ -148,7 +153,7 @@ export class Pedelec {
148
153
  if (!result.sessionId) {
149
154
  throw makeError("SDK_PROTOCOL_ERROR", "create_session response did not include sessionId");
150
155
  }
151
- return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers);
156
+ return this.registerSession(result.sessionId, resolvedInput.provider, resolvedInput.effortLevel, resolvedInput.inlineToolHandlers, resolvedInput.autoEndOnDisconnect);
152
157
  }
153
158
  async workspaceFolderPicker() {
154
159
  const result = await this.requestWithOptions("pick_workspace_folder", {}, { timeoutMs: null });
@@ -182,13 +187,27 @@ export class Pedelec {
182
187
  if (!sessionId?.trim()) {
183
188
  throw makeError("INVALID_INPUT", "sessionId is required");
184
189
  }
185
- const result = await this.request("resume_session", {
186
- sessionId,
187
- });
188
- if (!result.sessionId) {
189
- throw makeError("SDK_PROTOCOL_ERROR", "resume_session response did not include sessionId");
190
+ const existing = this.sessions.get(sessionId);
191
+ const session = existing && !existing.isTransportDetached()
192
+ ? existing
193
+ : this.registerSession(sessionId, "", undefined, new Map(), false);
194
+ try {
195
+ const result = await this.request("resume_session", {
196
+ sessionId,
197
+ });
198
+ if (!result.sessionId) {
199
+ throw makeError("SDK_PROTOCOL_ERROR", "resume_session response did not include sessionId");
200
+ }
201
+ if (typeof result.autoEndOnDisconnect === "boolean") {
202
+ session.setAutoEndOnDisconnect(result.autoEndOnDisconnect);
203
+ }
204
+ return session;
205
+ }
206
+ catch (error) {
207
+ if (session !== existing)
208
+ this.unregisterSession(sessionId);
209
+ throw error;
190
210
  }
191
- return this.registerSession(result.sessionId, "", undefined);
192
211
  }
193
212
  request(type, payload = {}) {
194
213
  return this.requestWithOptions(type, payload);
@@ -245,6 +264,76 @@ export class Pedelec {
245
264
  this.sessions.delete(sessionId);
246
265
  this.lastSeqBySession.delete(sessionId);
247
266
  }
267
+ /** @internal */
268
+ registerExistingSession(session) {
269
+ this.sessions.set(session.sessionId, session);
270
+ }
271
+ /** @internal */
272
+ isSessionRegistered(sessionId, session) {
273
+ return this.sessions.get(sessionId) === session;
274
+ }
275
+ attachPageActivityListeners() {
276
+ if (this.pageActivityListenersAttached || !this.pageWindow)
277
+ return;
278
+ this.pageActivityListenersAttached = true;
279
+ this.pageWindow.addEventListener("focus", () => {
280
+ if (!this.isDocumentHidden())
281
+ this.reportPageActivity(true, { force: true });
282
+ });
283
+ this.pageWindow.document?.addEventListener("visibilitychange", () => {
284
+ if (this.isDocumentHidden()) {
285
+ this.reportPageActivity(false);
286
+ return;
287
+ }
288
+ if (this.isDocumentFocused())
289
+ this.reportPageActivity(true);
290
+ });
291
+ }
292
+ isDocumentHidden() {
293
+ const doc = this.pageWindow?.document;
294
+ if (!doc)
295
+ return true;
296
+ if (typeof doc.hidden === "boolean")
297
+ return doc.hidden;
298
+ return doc.visibilityState === "hidden";
299
+ }
300
+ isDocumentFocused() {
301
+ const doc = this.pageWindow?.document;
302
+ if (!doc || typeof doc.hasFocus !== "function")
303
+ return false;
304
+ try {
305
+ return doc.hasFocus();
306
+ }
307
+ catch (_err) {
308
+ return false;
309
+ }
310
+ }
311
+ isPageForegroundActive() {
312
+ return !this.isDocumentHidden() && this.isDocumentFocused();
313
+ }
314
+ postSdkHello(port) {
315
+ try {
316
+ port.postMessage({ type: SDK_HELLO_MESSAGE_TYPE, sdkVersion: SDK_VERSION });
317
+ }
318
+ catch (err) {
319
+ const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
320
+ this.handleDisconnect(port, error);
321
+ }
322
+ }
323
+ reportPageActivity(active, options = {}) {
324
+ if (!this.port)
325
+ return;
326
+ if (!options.force && this.lastReportedPageActive === active)
327
+ return;
328
+ try {
329
+ this.port.postMessage({ type: PAGE_ACTIVITY_MESSAGE_TYPE, active });
330
+ this.lastReportedPageActive = active;
331
+ }
332
+ catch (err) {
333
+ const error = normalizeError(err, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
334
+ this.handleDisconnect(this.port, error);
335
+ }
336
+ }
248
337
  resolveCreateSessionInput(input) {
249
338
  const raw = (input ?? {});
250
339
  const provider = typeof raw.provider === "string" ? raw.provider.trim() : "";
@@ -345,13 +434,13 @@ export class Pedelec {
345
434
  throw makeError("DEFAULT_PROVIDER_UNAVAILABLE", "Default provider is not currently available. Open the Pedelec desktop app Settings page and choose an available provider.", { provider });
346
435
  }
347
436
  }
348
- registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
437
+ registerSession(sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
349
438
  const existing = this.sessions.get(sessionId);
350
439
  if (existing && !existing.isTransportDetached()) {
351
440
  existing.replaceInlineToolHandlers(inlineToolHandlers);
352
441
  return existing;
353
442
  }
354
- const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers);
443
+ const session = new PedelecSession(this, sessionId, provider, effortLevel, inlineToolHandlers, autoEndOnDisconnect);
355
444
  this.sessions.set(sessionId, session);
356
445
  return session;
357
446
  }
@@ -370,10 +459,13 @@ export class Pedelec {
370
459
  port.onMessage.addListener((message) => this.handlePortMessage(port, message));
371
460
  port.onDisconnect.addListener(() => this.handleDisconnect(port));
372
461
  this.port = port;
462
+ this.postSdkHello(port);
463
+ this.reportPageActivity(this.isPageForegroundActive(), { force: true });
373
464
  return port;
374
465
  }
375
466
  catch (err) {
376
467
  this.port = null;
468
+ this.lastReportedPageActive = null;
377
469
  throw normalizeError(err, "EXTENSION_UNAVAILABLE", "Pedelec extension is unavailable.");
378
470
  }
379
471
  }
@@ -398,6 +490,22 @@ export class Pedelec {
398
490
  this.handleResponse(message);
399
491
  return;
400
492
  }
493
+ if (message.type === "session_snapshot") {
494
+ if (message.sessionId) {
495
+ const latestSeq = message.snapshot?.latestSeq;
496
+ if (typeof latestSeq === "number") {
497
+ const current = this.lastSeqBySession.get(message.sessionId);
498
+ if (current === undefined || latestSeq > current) {
499
+ this.lastSeqBySession.set(message.sessionId, latestSeq);
500
+ }
501
+ }
502
+ this.sessions.get(message.sessionId)?.handleSnapshot(message.snapshot, {
503
+ source: "core",
504
+ eventReceivedAt: Date.now(),
505
+ });
506
+ }
507
+ return;
508
+ }
401
509
  if (isSessionEvent(message) && this.isNewEvent(message)) {
402
510
  const eventReceivedAt = Date.now();
403
511
  if (message.sessionId) {
@@ -441,6 +549,7 @@ export class Pedelec {
441
549
  const runtime = globalThis.chrome?.runtime;
442
550
  const error = normalizeError(disconnectError ?? runtime?.lastError, "EXTENSION_DISCONNECTED", "Pedelec extension disconnected.");
443
551
  this.port = null;
552
+ this.lastReportedPageActive = null;
444
553
  this.invalidateSessions(error);
445
554
  for (const [requestId, pending] of this.pendingRequests) {
446
555
  if (pending.port !== port)
@@ -483,34 +592,43 @@ export class PedelecSession {
483
592
  provider;
484
593
  effortLevel;
485
594
  sessionCreatedAt = Date.now();
595
+ usage = { totalTokens: undefined };
486
596
  status = "idle";
487
- pendingSend = null;
488
- pendingPrepare = null;
597
+ pendingOperation = null;
489
598
  preparePromise = null;
490
599
  uploadPromise = null;
600
+ reactivationPromise = null;
491
601
  ending = false;
492
602
  prepared = false;
603
+ autoEndOnDisconnect;
493
604
  activeTurn = null;
494
605
  transportDetached = false;
495
606
  genericToolHandler = null;
496
607
  inlineToolHandlers = new Map();
497
608
  namedToolHandlers = new Map();
498
609
  chatHandlers = new Set();
610
+ chatDeltaHandlers = new Set();
499
611
  errorHandlers = new Set();
500
612
  statusHandlers = new Set();
501
613
  endedHandlers = new Set();
502
- constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map()) {
614
+ recoveredToolCalls = new Map();
615
+ handledToolRequestIds = new Set();
616
+ authoritativePendingToolRequest = undefined;
617
+ constructor(client, sessionId, provider, effortLevel, inlineToolHandlers = new Map(), autoEndOnDisconnect = false) {
503
618
  this.client = client;
504
619
  this.sessionId = sessionId;
505
620
  this.provider = provider;
506
621
  this.effortLevel = effortLevel;
507
622
  this.inlineToolHandlers = new Map(inlineToolHandlers);
623
+ this.autoEndOnDisconnect = autoEndOnDisconnect;
508
624
  }
509
625
  prepare() {
510
626
  if (this.transportDetached || this.status === "ended") {
511
627
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
512
628
  }
513
- if (this.pendingSend) {
629
+ if (this.pendingOperation?.turn.kind === "user" ||
630
+ (this.activeTurn && this.activeTurn.kind !== "prepare") ||
631
+ this.activeTurn?.external) {
514
632
  return Promise.reject(makeError("SESSION_BUSY", "session is already running", { sessionId: this.sessionId }));
515
633
  }
516
634
  if (this.prepared) {
@@ -520,24 +638,32 @@ export class PedelecSession {
520
638
  return this.preparePromise;
521
639
  }
522
640
  const turn = createTurn("prepare");
641
+ const priorStatus = this.status;
523
642
  this.activeTurn = turn;
524
643
  this.setStatus("running", { source: "sdk" });
525
- const donePromise = new Promise((resolve, reject) => {
526
- this.pendingPrepare = { resolve, reject };
644
+ const operationPromise = new Promise((resolve, reject) => {
645
+ this.pendingOperation = {
646
+ turn,
647
+ resolve,
648
+ reject,
649
+ priorStatus,
650
+ requestSettled: false,
651
+ };
527
652
  });
528
- const requestPromise = this.client
653
+ void this.client
529
654
  .request("prepare_session", {
530
655
  sessionId: this.sessionId,
656
+ operationId: turn.turnId,
531
657
  })
532
- .catch((err) => {
658
+ .then(() => {
659
+ this.markOperationRequestSettled(turn);
660
+ }, (err) => {
533
661
  const error = normalizeError(err, "PREPARE_FAILED", "prepare failed");
534
662
  if (!this.transportDetached)
535
663
  this.emitError(error, { source: "sdk" });
536
- this.clearPendingPrepare();
537
- this.finishActiveTurn(turn);
538
- throw error;
664
+ this.failOperationFromRequest(turn, error);
539
665
  });
540
- this.preparePromise = Promise.all([requestPromise, donePromise])
666
+ this.preparePromise = operationPromise
541
667
  .then(() => {
542
668
  this.prepared = true;
543
669
  })
@@ -558,34 +684,46 @@ export class PedelecSession {
558
684
  if (this.transportDetached || this.status === "ended") {
559
685
  return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
560
686
  }
561
- if (this.pendingSend || this.pendingPrepare) {
687
+ if (this.pendingOperation || this.activeTurn) {
562
688
  return Promise.reject(makeError("SESSION_BUSY", "session is already running", { sessionId: this.sessionId }));
563
689
  }
564
690
  const turn = createTurn("user");
691
+ const priorStatus = this.status;
565
692
  this.activeTurn = turn;
566
693
  this.setStatus("running", { source: "sdk" });
567
- const donePromise = new Promise((resolve, reject) => {
568
- this.pendingSend = { resolve, reject };
694
+ const operationPromise = new Promise((resolve, reject) => {
695
+ this.pendingOperation = {
696
+ turn,
697
+ resolve,
698
+ reject,
699
+ priorStatus,
700
+ requestSettled: false,
701
+ };
569
702
  });
570
- const requestPromise = this.client
703
+ void this.client
571
704
  .request("send_text", {
572
705
  sessionId: this.sessionId,
573
706
  text,
707
+ operationId: turn.turnId,
574
708
  })
575
- .catch((err) => {
709
+ .then(() => {
710
+ this.markOperationRequestSettled(turn);
711
+ }, (err) => {
576
712
  const error = normalizeError(err, "SEND_TEXT_FAILED", "sendText failed");
577
713
  if (!this.transportDetached)
578
714
  this.emitError(error, { source: "sdk" });
579
- this.clearPendingSend();
580
- this.finishActiveTurn(turn);
581
- throw error;
715
+ this.failOperationFromRequest(turn, error);
582
716
  });
583
- return Promise.all([requestPromise, donePromise]).then(() => undefined);
717
+ return operationPromise;
584
718
  }
585
719
  onChat(handler) {
586
720
  this.chatHandlers.add(handler);
587
721
  return () => this.chatHandlers.delete(handler);
588
722
  }
723
+ onChatDelta(handler) {
724
+ this.chatDeltaHandlers.add(handler);
725
+ return () => this.chatDeltaHandlers.delete(handler);
726
+ }
589
727
  onTool(toolNameOrHandler, maybeHandler) {
590
728
  if (typeof toolNameOrHandler === "string") {
591
729
  const toolName = toolNameOrHandler;
@@ -596,6 +734,7 @@ export class PedelecSession {
596
734
  throw makeError("INVALID_INPUT", "tool handler must be a function", { toolName });
597
735
  }
598
736
  this.namedToolHandlers.set(toolName, maybeHandler);
737
+ void this.drainRecoveredToolCalls();
599
738
  return () => {
600
739
  if (this.namedToolHandlers.get(toolName) === maybeHandler) {
601
740
  this.namedToolHandlers.delete(toolName);
@@ -604,6 +743,7 @@ export class PedelecSession {
604
743
  }
605
744
  const handler = toolNameOrHandler;
606
745
  this.genericToolHandler = handler;
746
+ void this.drainRecoveredToolCalls();
607
747
  return () => {
608
748
  if (this.genericToolHandler === handler) {
609
749
  this.genericToolHandler = null;
@@ -743,9 +883,73 @@ export class PedelecSession {
743
883
  this.markEnded({ source: "sdk" });
744
884
  this.client.unregisterSession(this.sessionId);
745
885
  }
886
+ resume() {
887
+ if (this.transportDetached) {
888
+ return Promise.reject(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
889
+ }
890
+ if (this.reactivationPromise)
891
+ return this.reactivationPromise;
892
+ if (this.status !== "ended")
893
+ return Promise.resolve();
894
+ const wasRegistered = this.client.isSessionRegistered(this.sessionId, this);
895
+ const promise = (async () => {
896
+ if (!wasRegistered) {
897
+ // end() intentionally removed the handle from the registry. Put the
898
+ // exact same object back before the ended snapshot can arrive.
899
+ this.client.registerExistingSession(this);
900
+ }
901
+ try {
902
+ const result = await this.client.request("reactivate_session", {
903
+ sessionId: this.sessionId,
904
+ autoEndOnDisconnect: this.autoEndOnDisconnect,
905
+ });
906
+ if (result?.sessionId !== this.sessionId) {
907
+ throw makeError("SDK_PROTOCOL_ERROR", "reactivate_session response did not include sessionId");
908
+ }
909
+ if (this.status !== "idle") {
910
+ throw makeError("SDK_PROTOCOL_ERROR", "reactivate_session resolved before the authoritative idle snapshot", { sessionId: this.sessionId });
911
+ }
912
+ this.ending = false;
913
+ }
914
+ catch (err) {
915
+ const error = normalizeError(err, "SESSION_RESUME_FAILED", "session resume failed");
916
+ if (!this.transportDetached)
917
+ this.emitError(error, { source: "sdk" });
918
+ if (!wasRegistered && isDeterministicResumeError(error) && this.status !== "idle") {
919
+ this.client.unregisterSession(this.sessionId);
920
+ }
921
+ throw error;
922
+ }
923
+ })();
924
+ const guardedPromise = promise.finally(() => {
925
+ if (this.reactivationPromise === guardedPromise)
926
+ this.reactivationPromise = null;
927
+ });
928
+ this.reactivationPromise = guardedPromise;
929
+ return guardedPromise;
930
+ }
931
+ /** @internal */
932
+ setAutoEndOnDisconnect(autoEndOnDisconnect) {
933
+ this.autoEndOnDisconnect = autoEndOnDisconnect;
934
+ }
746
935
  handleEvent(event, meta = { source: "sdk" }) {
936
+ if (event.type === "usage_updated") {
937
+ this.updateUsage(event.totalTokens);
938
+ return;
939
+ }
747
940
  if (event.type === "chat_delta") {
748
- const turn = this.requireActiveTurn("chat_delta", meta);
941
+ const turn = this.requireOperationTurn(event.operationId, "chat_delta", meta);
942
+ if (!turn)
943
+ return;
944
+ if (turn.kind === "prepare")
945
+ return;
946
+ for (const handler of this.chatDeltaHandlers) {
947
+ handler(event.text, this.createChatDeltaContext(meta, turn));
948
+ }
949
+ return;
950
+ }
951
+ if (event.type === "chat_message") {
952
+ const turn = this.requireOperationTurn(event.operationId, "chat_message", meta);
749
953
  if (!turn)
750
954
  return;
751
955
  if (turn.kind === "prepare")
@@ -756,44 +960,109 @@ export class PedelecSession {
756
960
  return;
757
961
  }
758
962
  if (event.type === "status_changed") {
963
+ if (event.operationId && !this.matchesActiveOperation(event.operationId))
964
+ return;
965
+ if (!event.operationId && this.activeTurn)
966
+ return;
759
967
  this.setStatus(event.status, meta);
760
- if (event.status === "idle") {
761
- this.resolveActivePending();
762
- }
763
- else if (event.status === "ended") {
968
+ if (event.status === "idle")
969
+ this.ending = false;
970
+ if (event.status === "ended") {
764
971
  this.markEnded(meta);
765
972
  }
766
973
  else if (event.status === "error") {
767
974
  const error = makeError("SESSION_ERROR", "session entered error status", { sessionId: this.sessionId });
768
975
  this.emitError(error, meta);
769
- this.rejectActivePending(error);
770
976
  }
771
977
  return;
772
978
  }
773
979
  if (event.type === "tool_call") {
774
- const turn = this.requireActiveTurn("tool_call", meta);
980
+ const turn = this.requireOperationTurn(event.operationId, "tool_call", meta);
775
981
  if (!turn)
776
982
  return;
983
+ this.authoritativePendingToolRequest = {
984
+ operationId: event.operationId,
985
+ requestId: event.toolRequestId,
986
+ };
777
987
  this.setStatus("waiting_tool_result", meta);
778
- this.handleToolCall(event, meta, turn);
988
+ if (this.recoveredToolCalls.has(event.toolRequestId)) {
989
+ this.recoveredToolCalls.delete(event.toolRequestId);
990
+ }
991
+ if (turn.external && !this.hasToolHandler(event.tool)) {
992
+ this.recoveredToolCalls.set(event.toolRequestId, event);
993
+ return;
994
+ }
995
+ void this.handleToolCallOnce(event, meta, turn);
779
996
  return;
780
997
  }
781
- if (event.type === "done") {
782
- this.setStatus("idle", meta);
783
- this.resolveActivePending();
998
+ if (event.type === "operation_completed") {
999
+ if (!this.matchesActiveOperation(event.operationId))
1000
+ return;
1001
+ if (this.activeTurn?.kind !== event.operationKind)
1002
+ return;
1003
+ this.authoritativePendingToolRequest = null;
1004
+ this.setStatus(event.success ? "idle" : event.operationKind === "prepare" ? "idle" : "error", meta);
1005
+ this.completeOperation(this.activeTurn, event.success, event.error);
784
1006
  return;
785
1007
  }
786
1008
  if (event.type === "error") {
787
- this.setStatus("error", meta);
1009
+ if (event.operationId && !this.matchesActiveOperation(event.operationId))
1010
+ return;
788
1011
  const error = normalizeError(event.error, "SESSION_ERROR", "session error");
789
1012
  this.emitError(error, meta);
790
- this.rejectActivePending(error);
1013
+ if (!event.operationId)
1014
+ this.setStatus("error", meta);
791
1015
  return;
792
1016
  }
793
1017
  if (event.type === "ended") {
794
1018
  this.markEnded(meta);
795
1019
  }
796
1020
  }
1021
+ /** @internal */
1022
+ handleSnapshot(snapshot, meta = { source: "core" }) {
1023
+ if (!snapshot || snapshot.threadId !== this.sessionId)
1024
+ return;
1025
+ if (snapshot.usage)
1026
+ this.updateUsage(snapshot.usage.totalTokens);
1027
+ if (snapshot.status === "idle")
1028
+ this.ending = false;
1029
+ const active = snapshot.activeOperation;
1030
+ const pending = this.pendingOperation;
1031
+ this.reconcileRecoveredToolCalls(snapshot);
1032
+ if (!pending) {
1033
+ this.setStatus(coreStatusToSdkStatus(snapshot.status), meta);
1034
+ if (active) {
1035
+ this.activeTurn = {
1036
+ turnId: active.operationId,
1037
+ turnStartedAt: parseSnapshotTime(active.startedAt),
1038
+ kind: active.operationKind,
1039
+ external: true,
1040
+ };
1041
+ this.queueSnapshotTool(snapshot, this.activeTurn);
1042
+ }
1043
+ else if (this.activeTurn?.external) {
1044
+ this.activeTurn = null;
1045
+ }
1046
+ if (snapshot.status === "ended")
1047
+ this.markEnded(meta);
1048
+ return;
1049
+ }
1050
+ if (active?.operationId === pending.turn.turnId && active.operationKind === pending.turn.kind) {
1051
+ this.setStatus(coreStatusToSdkStatus(snapshot.status), meta);
1052
+ this.queueSnapshotTool(snapshot, pending.turn);
1053
+ return;
1054
+ }
1055
+ const completed = snapshot.lastCompletedOperation;
1056
+ if (completed?.operationId === pending.turn.turnId && completed.operationKind === pending.turn.kind) {
1057
+ this.setStatus(completed.success ? "idle" : completed.operationKind === "prepare" ? "idle" : "error", meta);
1058
+ this.completeOperation(pending.turn, completed.success, completed.error);
1059
+ return;
1060
+ }
1061
+ const error = makeError("SDK_LIFECYCLE_SYNC_ERROR", "authoritative session snapshot did not match the active SDK operation", { sessionId: this.sessionId, operationId: pending.turn.turnId });
1062
+ this.emitError(error, { source: "sdk" });
1063
+ this.rejectOperation(pending.turn, error);
1064
+ this.setStatus(coreStatusToSdkStatus(snapshot.status), meta);
1065
+ }
797
1066
  replaceInlineToolHandlers(handlers) {
798
1067
  this.inlineToolHandlers = new Map(handlers);
799
1068
  }
@@ -810,7 +1079,10 @@ export class PedelecSession {
810
1079
  this.emitError(error, { source: "sdk" });
811
1080
  this.rejectActivePending(error);
812
1081
  }
813
- async handleToolCall(event, meta, turn) {
1082
+ async handleToolCallOnce(event, meta, turn) {
1083
+ if (this.handledToolRequestIds.has(event.toolRequestId))
1084
+ return;
1085
+ this.handledToolRequestIds.add(event.toolRequestId);
814
1086
  let result;
815
1087
  const namedHandler = this.namedToolHandlers.get(event.tool);
816
1088
  const inlineHandler = this.inlineToolHandlers.get(event.tool);
@@ -851,7 +1123,7 @@ export class PedelecSession {
851
1123
  }),
852
1124
  };
853
1125
  }
854
- if (this.transportDetached)
1126
+ if (this.transportDetached || !this.isAuthoritativeToolRequestCurrent(event))
855
1127
  return;
856
1128
  try {
857
1129
  await this.client.request("submit_tool_result", {
@@ -866,51 +1138,10 @@ export class PedelecSession {
866
1138
  });
867
1139
  }
868
1140
  }
869
- resolvePendingSend() {
870
- const pending = this.pendingSend;
871
- this.pendingSend = null;
872
- pending?.resolve();
873
- this.finishActiveTurn();
874
- }
875
- resolvePendingPrepare() {
876
- const pending = this.pendingPrepare;
877
- this.pendingPrepare = null;
878
- pending?.resolve();
879
- this.finishActiveTurn();
880
- }
881
- resolveActivePending() {
882
- if (this.activeTurn?.kind === "prepare") {
883
- this.resolvePendingPrepare();
884
- }
885
- else {
886
- this.resolvePendingSend();
887
- }
888
- }
889
- rejectPendingSend(error) {
890
- const pending = this.pendingSend;
891
- this.pendingSend = null;
892
- pending?.reject(error);
893
- this.finishActiveTurn();
894
- }
895
- rejectPendingPrepare(error) {
896
- const pending = this.pendingPrepare;
897
- this.pendingPrepare = null;
898
- pending?.reject(error);
899
- this.finishActiveTurn();
900
- }
901
1141
  rejectActivePending(error) {
902
- if (this.activeTurn?.kind === "prepare") {
903
- this.rejectPendingPrepare(error);
904
- }
905
- else {
906
- this.rejectPendingSend(error);
907
- }
908
- }
909
- clearPendingSend() {
910
- this.pendingSend = null;
911
- }
912
- clearPendingPrepare() {
913
- this.pendingPrepare = null;
1142
+ const turn = this.activeTurn;
1143
+ if (turn)
1144
+ this.rejectOperation(turn, error);
914
1145
  }
915
1146
  markEnded(meta = { source: "sdk" }) {
916
1147
  const wasEnded = this.status === "ended";
@@ -920,9 +1151,9 @@ export class PedelecSession {
920
1151
  handler(this.createEndedContext(meta));
921
1152
  }
922
1153
  }
923
- this.rejectPendingSend(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
924
- this.rejectPendingPrepare(makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId }));
925
- this.finishActiveTurn();
1154
+ const error = makeError("SESSION_ENDED", "session has ended", { sessionId: this.sessionId });
1155
+ if (this.activeTurn)
1156
+ this.rejectOperation(this.activeTurn, error);
926
1157
  }
927
1158
  emitError(error, meta = { source: "sdk" }) {
928
1159
  for (const handler of this.errorHandlers) {
@@ -938,14 +1169,140 @@ export class PedelecSession {
938
1169
  handler(status, this.createStatusContext(status, previousStatus, meta));
939
1170
  }
940
1171
  }
941
- requireActiveTurn(type, meta) {
942
- if (this.activeTurn)
1172
+ requireOperationTurn(operationId, type, meta) {
1173
+ if (operationId && this.activeTurn?.turnId === operationId) {
943
1174
  return this.activeTurn;
944
- this.emitError(makeError("SDK_PROTOCOL_ERROR", `${type} event was received without an active turn`, {
1175
+ }
1176
+ this.emitError(makeError("SDK_PROTOCOL_ERROR", `${type} event did not match the active operation`, {
945
1177
  sessionId: this.sessionId,
1178
+ operationId,
946
1179
  }), { source: "sdk", eventReceivedAt: meta.eventReceivedAt });
947
1180
  return null;
948
1181
  }
1182
+ updateUsage(totalTokens) {
1183
+ if (!isValidSessionUsageTotalTokens(totalTokens))
1184
+ return;
1185
+ if (this.usage.totalTokens === undefined || totalTokens >= this.usage.totalTokens) {
1186
+ this.usage.totalTokens = totalTokens;
1187
+ }
1188
+ }
1189
+ matchesActiveOperation(operationId) {
1190
+ return this.activeTurn?.turnId === operationId;
1191
+ }
1192
+ markOperationRequestSettled(turn) {
1193
+ const pending = this.pendingOperation;
1194
+ if (!pending || pending.turn.turnId !== turn.turnId)
1195
+ return;
1196
+ pending.requestSettled = true;
1197
+ this.maybeSettleOperation(pending);
1198
+ }
1199
+ failOperationFromRequest(turn, error) {
1200
+ const pending = this.pendingOperation;
1201
+ if (!pending || pending.turn.turnId !== turn.turnId)
1202
+ return;
1203
+ // A semantic terminal may have arrived before the bridge response. Keep the
1204
+ // operation reserved until that response boundary is observed, but never let
1205
+ // a delayed request failure overwrite its matching terminal result.
1206
+ if (pending.completed) {
1207
+ pending.requestSettled = true;
1208
+ this.maybeSettleOperation(pending);
1209
+ return;
1210
+ }
1211
+ this.setStatus(turn.kind === "prepare" || isAdmissionRejection(error) ? pending.priorStatus : "error", { source: "sdk" });
1212
+ this.rejectOperation(turn, error);
1213
+ }
1214
+ completeOperation(turn, success, error) {
1215
+ const pending = this.pendingOperation;
1216
+ if (!pending || pending.turn.turnId !== turn.turnId) {
1217
+ if (this.activeTurn?.turnId === turn.turnId && turn.external)
1218
+ this.finishActiveTurn(turn);
1219
+ return;
1220
+ }
1221
+ pending.completed = { success, error };
1222
+ this.maybeSettleOperation(pending);
1223
+ }
1224
+ maybeSettleOperation(pending) {
1225
+ if (!pending.requestSettled || !pending.completed)
1226
+ return;
1227
+ const turn = pending.turn;
1228
+ this.pendingOperation = null;
1229
+ this.finishActiveTurn(turn);
1230
+ if (pending.completed.success)
1231
+ pending.resolve();
1232
+ else
1233
+ pending.reject(pending.completed.error ?? makeError("SESSION_ERROR", "session operation failed"));
1234
+ }
1235
+ rejectOperation(turn, error) {
1236
+ const pending = this.pendingOperation;
1237
+ if (pending && pending.turn.turnId === turn.turnId) {
1238
+ this.pendingOperation = null;
1239
+ this.finishActiveTurn(turn);
1240
+ pending.reject(error);
1241
+ return;
1242
+ }
1243
+ this.finishActiveTurn(turn);
1244
+ }
1245
+ queueSnapshotTool(snapshot, turn) {
1246
+ const request = snapshot.pendingToolRequest;
1247
+ if (!request || request.operationId !== turn.turnId)
1248
+ return;
1249
+ if (this.handledToolRequestIds.has(request.requestId))
1250
+ return;
1251
+ this.recoveredToolCalls.set(request.requestId, {
1252
+ type: "tool_call",
1253
+ channelId: "",
1254
+ sessionId: this.sessionId,
1255
+ seq: snapshot.latestSeq,
1256
+ operationId: request.operationId,
1257
+ toolRequestId: request.requestId,
1258
+ tool: request.toolName,
1259
+ args: request.args,
1260
+ });
1261
+ void this.drainRecoveredToolCalls();
1262
+ }
1263
+ reconcileRecoveredToolCalls(snapshot) {
1264
+ const active = snapshot.activeOperation;
1265
+ const pending = active && snapshot.pendingToolRequest?.operationId === active.operationId
1266
+ ? snapshot.pendingToolRequest
1267
+ : undefined;
1268
+ this.authoritativePendingToolRequest = pending
1269
+ ? { operationId: pending.operationId, requestId: pending.requestId }
1270
+ : null;
1271
+ for (const [requestId, event] of this.recoveredToolCalls) {
1272
+ if (!active ||
1273
+ !pending ||
1274
+ event.operationId !== active.operationId ||
1275
+ event.toolRequestId !== pending.requestId) {
1276
+ this.recoveredToolCalls.delete(requestId);
1277
+ }
1278
+ }
1279
+ }
1280
+ isAuthoritativeToolRequestCurrent(event) {
1281
+ const pending = this.authoritativePendingToolRequest;
1282
+ if (pending === undefined)
1283
+ return true;
1284
+ if (pending === null)
1285
+ return false;
1286
+ return (pending.operationId === event.operationId &&
1287
+ pending.requestId === event.toolRequestId);
1288
+ }
1289
+ hasToolHandler(toolName) {
1290
+ return this.namedToolHandlers.has(toolName) ||
1291
+ this.inlineToolHandlers.has(toolName) ||
1292
+ this.genericToolHandler !== null;
1293
+ }
1294
+ async drainRecoveredToolCalls() {
1295
+ const turn = this.activeTurn;
1296
+ if (!turn)
1297
+ return;
1298
+ for (const [requestId, event] of Array.from(this.recoveredToolCalls.entries())) {
1299
+ if (event.operationId !== turn.turnId || !this.hasToolHandler(event.tool))
1300
+ continue;
1301
+ this.recoveredToolCalls.delete(requestId);
1302
+ this.setStatus("waiting_tool_result", { source: "core" });
1303
+ await this.handleToolCallOnce(event, { source: "core", eventReceivedAt: Date.now() }, turn);
1304
+ }
1305
+ }
949
1306
  finishActiveTurn(turn = this.activeTurn) {
950
1307
  if (!turn || !this.activeTurn)
951
1308
  return;
@@ -966,6 +1323,16 @@ export class PedelecSession {
966
1323
  };
967
1324
  }
968
1325
  createChatContext(meta, turn) {
1326
+ return {
1327
+ ...this.createBaseContext(meta, turn),
1328
+ type: "chat_message",
1329
+ turnId: turn.turnId,
1330
+ turnStartedAt: turn.turnStartedAt,
1331
+ eventReceivedAt: meta.eventReceivedAt ?? Date.now(),
1332
+ source: "core",
1333
+ };
1334
+ }
1335
+ createChatDeltaContext(meta, turn) {
969
1336
  return {
970
1337
  ...this.createBaseContext(meta, turn),
971
1338
  type: "chat_delta",
@@ -1010,9 +1377,11 @@ export class PedelecSession {
1010
1377
  }
1011
1378
  function isSessionEvent(message) {
1012
1379
  return (message.type === "chat_delta" ||
1380
+ message.type === "chat_message" ||
1013
1381
  message.type === "status_changed" ||
1014
1382
  message.type === "tool_call" ||
1015
- message.type === "done" ||
1383
+ message.type === "operation_completed" ||
1384
+ message.type === "usage_updated" ||
1016
1385
  message.type === "error" ||
1017
1386
  message.type === "ended");
1018
1387
  }
@@ -1081,6 +1450,23 @@ function createTurn(kind = "user") {
1081
1450
  kind,
1082
1451
  };
1083
1452
  }
1453
+ function isValidSessionUsageTotalTokens(value) {
1454
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= 0;
1455
+ }
1456
+ function coreStatusToSdkStatus(status) {
1457
+ if (status === "waitingToolResult")
1458
+ return "waiting_tool_result";
1459
+ if (status === "starting" || status === "stopping")
1460
+ return "running";
1461
+ if (status === "idle" || status === "running" || status === "ended" || status === "error") {
1462
+ return status;
1463
+ }
1464
+ return "error";
1465
+ }
1466
+ function parseSnapshotTime(value) {
1467
+ const parsed = Date.parse(value);
1468
+ return Number.isFinite(parsed) ? parsed : Date.now();
1469
+ }
1084
1470
  function getCurrentOrigin(pageWindow) {
1085
1471
  const origin = pageWindow?.location?.origin;
1086
1472
  return typeof origin === "string" && origin ? origin : null;
@@ -1088,6 +1474,22 @@ function getCurrentOrigin(pageWindow) {
1088
1474
  function isExtensionUnavailableError(error) {
1089
1475
  return error.code === "EXTENSION_UNAVAILABLE" || error.code === "EXTENSION_DISCONNECTED";
1090
1476
  }
1477
+ function isAdmissionRejection(error) {
1478
+ return error.code === "THREAD_BUSY" ||
1479
+ error.code === "THREAD_ENDED" ||
1480
+ error.code === "THREAD_ACCESS_DENIED" ||
1481
+ error.code === "THREAD_NOT_FOUND" ||
1482
+ error.code === "INVALID_INPUT";
1483
+ }
1484
+ function isDeterministicResumeError(error) {
1485
+ return error.code === "THREAD_BUSY" ||
1486
+ error.code === "THREAD_ENDED" ||
1487
+ error.code === "THREAD_ACCESS_DENIED" ||
1488
+ error.code === "THREAD_NOT_FOUND" ||
1489
+ error.code === "WORKSPACE_OPEN_FAILED" ||
1490
+ error.code === "PROVIDER_COMMAND_FAILED" ||
1491
+ error.code === "INVALID_INPUT";
1492
+ }
1091
1493
  function normalizeError(err, fallbackCode, fallbackMessage) {
1092
1494
  if (!err)
1093
1495
  return makeError(fallbackCode, fallbackMessage);
@@ -50,10 +50,20 @@ async function typedOnToolNameFromCreateSession() {
50
50
  });
51
51
  session.onChat((_text, ctx) => {
52
52
  const chatCtx = ctx;
53
+ const messageType = ctx.type;
53
54
  const receivedAt = ctx.eventReceivedAt;
54
55
  void receivedAt;
56
+ void messageType;
55
57
  void chatCtx;
56
58
  });
59
+ session.onChatDelta((_text, ctx) => {
60
+ const chatDeltaCtx = ctx;
61
+ const deltaType = ctx.type;
62
+ const receivedAt = ctx.eventReceivedAt;
63
+ void receivedAt;
64
+ void deltaType;
65
+ void chatDeltaCtx;
66
+ });
57
67
  session.onStatus((_status, ctx) => {
58
68
  const statusCtx = ctx;
59
69
  const previous = ctx.previousStatus;
@@ -68,6 +78,7 @@ async function typedOnToolNameFromCreateSession() {
68
78
  const endedCtx = ctx;
69
79
  return endedCtx;
70
80
  });
81
+ await session.resume();
71
82
  }
72
83
  async function resumedSessionFallsBackToString() {
73
84
  const pedelec = new Pedelec();
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "0.2.12";
1
+ export declare const SDK_VERSION = "0.3.1";
@@ -1,2 +1,2 @@
1
1
  // Generated from sdk/package.json. Do not edit manually.
2
- export const SDK_VERSION = "0.2.12";
2
+ export const SDK_VERSION = "0.3.1";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaoruisaac/pedelec",
3
- "version": "0.2.12",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",